Какова наилучшая практика для «Копировать локальный» и со ссылками на проект? - PullRequest
153 голосов
/ 11 ноября 2008

У меня большой файл решения c # (~ 100 проектов), и я пытаюсь сократить время сборки. Я думаю, что «Копировать Локальный» во многих случаях для нас бесполезен, но меня интересуют лучшие практики.

В нашем .sln у нас есть приложение A, зависящее от сборки B, которое зависит от сборки C. В нашем случае есть десятки «B» и несколько «C». Поскольку все они включены в .sln, мы используем ссылки на проекты. Все сборки в настоящее время встроены в $ (SolutionDir) / Debug (или Release).

По умолчанию Visual Studio помечает эти ссылки на проекты как «Копировать локально», что приводит к тому, что каждый «C» копируется в $ (SolutionDir) / Debug один раз для каждого «B», который собирается. Это кажется расточительным. Что может пойти не так, если я просто отключу «Копировать локальный»? Что делают другие люди с большими системами?

Followup:

Множество ответов предлагают разбить сборку на более мелкие файлы .sln ... В приведенном выше примере я сначала собрал бы базовые классы "C", а затем большую часть модулей "B", а затем несколько приложения, «А». В этой модели мне нужно иметь не-проектные ссылки на C из B. Проблема, с которой я сталкиваюсь, заключается в том, что «Debug» или «Release» запекаются на пути подсказки, и я заканчиваю сборку выпусков сборки «B» против отладочных сборок "C".

Как справиться с этой проблемой для тех из вас, кто разбил сборку на несколько файлов .sln?

Ответы [ 18 ]

84 голосов
/ 16 марта 2009

В предыдущем проекте я работал с одним большим решением со ссылками на проекты, а также столкнулся с проблемой производительности. Решение было три раза:

  1. Всегда устанавливайте для свойства Copy Local значение false и применяйте его с помощью пользовательского шага msbuild

  2. Установите выходной каталог для каждого проекта в один и тот же каталог (предпочтительно относительно $ (SolutionDir)

  3. Цели cs по умолчанию, поставляемые с платформой, рассчитывают набор ссылок, которые необходимо скопировать в выходной каталог проекта, который в настоящее время создается. Поскольку для этого требуется вычислить транзитивное замыкание по отношению «Ссылки», это может стать ОЧЕНЬ дорогостоящим. Мой обходной путь для этого состоял в том, чтобы переопределить цель GetCopyToOutputDirectoryItems в общем файле целей (например, Common.targets), который импортируется в каждый проект после импорта Microsoft.CSharp.targets. В результате каждый файл проекта будет выглядеть следующим образом:

    <Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <PropertyGroup>
        ... snip ...
      </ItemGroup>
      <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
      <Import Project="[relative path to Common.targets]" />
      <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
           Other similar extension points exist, see Microsoft.Common.targets.
      <Target Name="BeforeBuild">
      </Target>
      <Target Name="AfterBuild">
      </Target>
      -->
    </Project>
    

Это сократило наше время сборки в данный момент времени с пары часов (в основном из-за ограничений памяти) до пары минут.

Переопределенный GetCopyToOutputDirectoryItems может быть создан путем копирования строк 2438 - 2,450 и 2,474 - 2,524 из C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Microsoft.Common.targets в Common.targets.

Для полноты итоговое определение цели становится следующим:

<!-- This is a modified version of the Microsoft.Common.targets
     version of this target it does not include transitively
     referenced projects. Since this leads to enormous memory
     consumption and is not needed since we use the single
     output directory strategy.
============================================================
                    GetCopyToOutputDirectoryItems

Get all project items that may need to be transferred to the
output directory.
============================================================ -->
<Target
    Name="GetCopyToOutputDirectoryItems"
    Outputs="@(AllItemsFullPathWithTargetPath)"
    DependsOnTargets="AssignTargetPaths;_SplitProjectReferencesByFileExistence">

    <!-- Get items from this project last so that they will be copied last. -->
    <CreateItem
        Include="@(ContentWithTargetPath->'%(FullPath)')"
        Condition="'%(ContentWithTargetPath.CopyToOutputDirectory)'=='Always' or '%(ContentWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"
            >
        <Output TaskParameter="Include" ItemName="AllItemsFullPathWithTargetPath"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectoryAlways"
                Condition="'%(ContentWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectory"
                Condition="'%(ContentWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
    </CreateItem>

    <CreateItem
        Include="@(_EmbeddedResourceWithTargetPath->'%(FullPath)')"
        Condition="'%(_EmbeddedResourceWithTargetPath.CopyToOutputDirectory)'=='Always' or '%(_EmbeddedResourceWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"
            >
        <Output TaskParameter="Include" ItemName="AllItemsFullPathWithTargetPath"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectoryAlways"
                Condition="'%(_EmbeddedResourceWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectory"
                Condition="'%(_EmbeddedResourceWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
    </CreateItem>

    <CreateItem
        Include="@(Compile->'%(FullPath)')"
        Condition="'%(Compile.CopyToOutputDirectory)'=='Always' or '%(Compile.CopyToOutputDirectory)'=='PreserveNewest'">
        <Output TaskParameter="Include" ItemName="_CompileItemsToCopy"/>
    </CreateItem>
    <AssignTargetPath Files="@(_CompileItemsToCopy)" RootFolder="$(MSBuildProjectDirectory)">
        <Output TaskParameter="AssignedFiles" ItemName="_CompileItemsToCopyWithTargetPath" />
    </AssignTargetPath>
    <CreateItem Include="@(_CompileItemsToCopyWithTargetPath)">
        <Output TaskParameter="Include" ItemName="AllItemsFullPathWithTargetPath"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectoryAlways"
                Condition="'%(_CompileItemsToCopyWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectory"
                Condition="'%(_CompileItemsToCopyWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
    </CreateItem>

    <CreateItem
        Include="@(_NoneWithTargetPath->'%(FullPath)')"
        Condition="'%(_NoneWithTargetPath.CopyToOutputDirectory)'=='Always' or '%(_NoneWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"
            >
        <Output TaskParameter="Include" ItemName="AllItemsFullPathWithTargetPath"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectoryAlways"
                Condition="'%(_NoneWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
        <Output TaskParameter="Include" ItemName="_SourceItemsToCopyToOutputDirectory"
                Condition="'%(_NoneWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
    </CreateItem>
</Target>

Имея этот обходной путь, я нашел возможным иметь до 120 проектов в одном решении, и это дает основное преимущество, заключающееся в том, что порядок сборки проектов все еще может быть определен VS вместо того, чтобы делать это вручную путем разделения ваше решение.

32 голосов
/ 16 марта 2009

Я предлагаю вам прочитать статьи Патрика Смаккья на эту тему:

Проекты CC.Net VS используют опцию копирования локальной эталонной сборки, установленную в значение true. [...] Это не только значительно увеличивает время компиляции (x3 в случае NUnit), но также портит вашу рабочую среду. И последнее, но не менее важное: это создает риск для потенциальных версий. Кстати, NDepend выдаст предупреждение, если найдет 2 сборки в 2 разных каталогах с одинаковым именем, но не с одинаковым содержимым или версией.

Правильно сделать, это определить 2 каталога $ RootDir $ \ bin \ Debug и $ RootDir $ \ bin \ Release и настроить ваши проекты VisualStudio для генерации сборок в этих каталогах. Все ссылки на проекты должны ссылаться на сборки в каталоге Debug.

Вы также можете прочитать эту статью , чтобы помочь уменьшить количество проектов и сократить время компиляции.

23 голосов
/ 30 июня 2011

Я предлагаю использовать копию local = false почти для всех проектов, кроме того, который находится вверху дерева зависимостей. И для всех ссылок в одной сверху установите копию local = true. Я вижу много людей, предлагающих поделиться выходным каталогом; Я думаю, что это ужасная идея, основанная на опыте. Если ваш стартовый проект содержит ссылки на dll, на который ссылается любой другой проект, вы в какой-то момент столкнетесь с нарушением прав доступа \ общего доступа, даже если копировать local = false на все, и ваша сборка не удастся. Эта проблема очень раздражает и ее трудно отследить. Я полностью рекомендую держаться подальше от выходного каталога сегмента и вместо того, чтобы проект находился наверху цепочки зависимостей, записывать необходимые сборки в соответствующую папку. Если у вас нет проекта на «вершине», то я бы предложил копию после сборки, чтобы получить все в нужном месте. Кроме того, я хотел бы иметь в виду легкость отладки. Любые exe-проекты, которые я по-прежнему оставляю copy local = true, чтобы отладка F5 работала.

10 голосов
/ 08 января 2010

Вы правы. CopyLocal абсолютно убьет ваше время сборки. Если у вас большое дерево исходников, вам следует отключить CopyLocal. К сожалению, это не так просто, как следует, чтобы отключить его чисто. Я ответил на этот точный вопрос об отключении CopyLocal на Как переопределить параметр CopyLocal (Private) для ссылок в .NET из MSBUILD . Проверьте это. А также Лучшие практики для больших решений в Visual Studio (2008).

Вот еще немного информации о CopyLocal, как я ее вижу.

CopyLocal был действительно реализован для поддержки локальной отладки. Когда вы готовите свое приложение для упаковки и развертывания, вы должны собрать свои проекты в одну и ту же выходную папку и убедиться, что у вас есть все необходимые ссылки.

Я написал о том, как работать с большими деревьями исходников, в статье MSBuild: Лучшие практики для создания надежных сборок, часть 2 .

8 голосов
/ 11 ноября 2008

По моему мнению, иметь решение с 100 проектами - БОЛЬШАЯ ошибка. Возможно, вы могли бы разделить свое решение на допустимые логические небольшие блоки, что упростит как обслуживание, так и сборки.

7 голосов
/ 17 апреля 2015

Я удивлен, что никто не упомянул использование жестких ссылок. Вместо копирования файлов создается жесткая ссылка на исходный файл. Это экономит дисковое пространство, а также значительно ускоряет сборку. Это можно включить в командной строке со следующими свойствами:

/ р: CreateHardLinksForAdditionalFilesIfPossible = истина; CreateHardLinksForCopyAdditionalFilesIfPossible = истина; CreateHardLinksForCopyFilesToOutputDirectoryIfPossible = истина; CreateHardLinksForCopyLocalIfPossible = истина; CreateHardLinksForPublishFilesIfPossible = истина

Вы также можете добавить это в центральный файл импорта, чтобы все ваши проекты также могли получить это преимущество.

5 голосов
/ 16 марта 2009

Если вы получили структуру зависимостей, определенную с помощью ссылок на проекты или зависимостей уровня решения, можно безопасно отключить «Копировать локально». Я бы даже сказал, что это лучший метод, так как это позволит вам использовать MSBuild 3.5 для запуска сборки. параллельно (через / maxcpucount) без различных процессов, спотыкающихся друг на друга при попытке скопировать ссылочные сборки.

4 голосов
/ 11 ноября 2008

наша «лучшая практика» - избегать решений по многим проектам. У нас есть каталог с именем "matrix" с текущими версиями сборок, и все ссылки из этого каталога. Если вы изменили какой-либо проект и можете сказать «теперь изменение завершено», вы можете скопировать сборку в каталог «matrix». Поэтому все проекты, зависящие от этой сборки, будут иметь текущую (= последнюю) версию.

Если в решении мало проектов, процесс сборки будет намного быстрее.

Вы можете автоматизировать этап «Копировать сборку в каталог матрицы», используя макросы Visual Studio или «меню -> Инструменты -> Внешние инструменты ...».

3 голосов
/ 16 июня 2015

Вам не нужно изменять значения CopyLocal. Все, что вам нужно сделать, - это предварительно задать общий $ (OutputPath) для всех проектов в решении и установить для $ (UseCommonOutputDirectory) значение true. Видеть это: http://blogs.msdn.com/b/kirillosenkov/archive/2015/04/04/using-a-common-intermediate-and-output-directory-for-your-solution.aspx

2 голосов
/ 09 декабря 2012

Set CopyLocal = false сократит время сборки, но может вызвать различные проблемы во время развертывания.

Существует много сценариев, когда вам нужно, чтобы Copy Local ’оставалось равным True, например,

  • проектов верхнего уровня,
  • Зависимости второго уровня,
  • DLL, вызванные отражением

Возможные проблемы, описанные в SO вопросах
" Когда для copy-local должно быть установлено значение true, а когда - нет? ",
" Сообщение об ошибке" Невозможно загрузить один или несколько запрошенных типов. Для получения дополнительной информации получите свойство LoaderExceptions. ' "
и aaron-Stainback ответ на этот вопрос.

Мой опыт установки CopyLocal = false НЕ был успешным. См. Мое сообщение в блоге Ссылки «Не копировать« Копировать локальные »проекта» на false, если не понимают подпоследовательности. "

Время для решения проблем перевешивает преимущества установки copyLocal = false.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...