Какова лучшая практика для "Копировать локальный" и со ссылками на проект?
У меня большой файл решения C# (~100 проектов), и я пытаюсь улучшить время сборки. Я думаю, что "Копировать Локальный" во многих случаях является для нас расточительным, но я задаюсь вопросом о лучших методах.
В нашем.sln у нас есть приложение A в зависимости от сборки B, которая зависит от сборки C. В нашем случае, есть десятки "B" и несколько "C". Поскольку все они включены в.sln, мы используем ссылки на проекты. Все сборки в настоящее время встраиваются в $(SolutionDir)/Debug (или Release).
По умолчанию Visual Studio помечает эти ссылки на проекты как "Копировать локально", что приводит к тому, что каждый "C" копируется в $ (SolutionDir) / Debug один раз для каждого "B", который собирается. Это кажется расточительным. Что может пойти не так, если я просто отключу "Копировать локально"? Что делают другие люди с большими системами?
СЛЕДОВАТЬ ЗА:
Множество ответов предлагают разбить сборку на более мелкие файлы.sln... В приведенном выше примере я бы сначала собрал базовые классы "C", а затем основную часть модулей "B", а затем несколько приложений ". А". В этой модели мне нужно иметь не-проектные ссылки на C из B. Проблема, с которой я сталкиваюсь, заключается в том, что "Debug" или "Release" запекаются на пути подсказки, и я заканчиваю сборку своих выпусков сборки "B" против отладочных сборок "C".
Для тех из вас, кто разбил сборку на несколько файлов.sln, как вы справляетесь с этой проблемой?
18 ответов
В предыдущем проекте я работал с одним большим решением со ссылками на проект, а также столкнулся с проблемой производительности. Решение было три раза:
Всегда устанавливайте для свойства Copy Local значение false и применяйте это с помощью пользовательского шага msbuild.
Установите выходной каталог для каждого проекта в один и тот же каталог (предпочтительно относительно $(SolutionDir)
Цели 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
можно создать, скопировав строки 2,438–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, вместо того чтобы делать это вручную путем разделения вашего решения.,
Я предлагаю вам прочитать статьи Патрика Смаккья на эту тему:
- Разбиение базы кода с помощью сборок.NET и проектов Visual Studio -> Должен ли каждый проект Visual Studio быть в своей собственной сборке? И что на самом деле означает "Копировать локально =True"?
- Уроки, извлеченные из базы кода NUnit -> Ссылка на проект VisualStudio + опция Копировать локальный true - это зло!)
- Анализ базы кода CruiseControl.NET -> Неправильное использование параметра Копировать локальную ссылочную сборку, установленного в True)
Проекты CC.Net VS используют опцию копирования локальной эталонной сборки, установленную в значение true. [...] Это не только значительно увеличивает время компиляции (x3 в случае NUnit), но также портит вашу рабочую среду. Последнее, но не менее важное, это создает риск для потенциальных версий. Кстати, NDepend выдаст предупреждение, если найдет 2 сборки в 2 разных каталогах с одинаковым именем, но не с одинаковым содержимым или версией.
Правильнее всего сделать так, чтобы определить 2 каталога $RootDir$\bin\Debug и $RootDir$\bin\Release и настроить проекты VisualStudio для генерации сборок в этих каталогах. Все ссылки на проекты должны ссылаться на сборки в каталоге Debug.
Вы также можете прочитать эту статью, чтобы уменьшить количество проектов и сократить время компиляции.
Я предлагаю использовать копию local = false почти для всех проектов, кроме того, что находится в верхней части дерева зависимостей. И для всех ссылок в одной сверху установите копию local = true. Я вижу много людей, предлагающих поделиться выходным каталогом; Я думаю, что это ужасная идея, основанная на опыте. Если ваш стартовый проект содержит ссылки на dll, на который ссылается любой другой проект, вы в какой-то момент столкнетесь с нарушением прав доступа \ общего доступа, даже если copy local = false для всего и ваша сборка не удастся. Эта проблема очень раздражает и ее трудно отследить. Я полностью рекомендую держаться подальше от выходного каталога сегмента и вместо того, чтобы проект находился наверху цепочки зависимостей, записывать необходимые сборки в соответствующую папку. Если у вас нет проекта на "вершине", то я бы предложил копию после сборки, чтобы все было в нужном месте. Кроме того, я хотел бы иметь в виду легкость отладки. Любые exe-проекты я все еще оставляю copy local = true, чтобы отладка F5 работала.
Ты прав. CopyLocal абсолютно убьет ваше время сборки. Если у вас большое исходное дерево, вам следует отключить CopyLocal. К сожалению, это не так просто, как должно быть, чтобы отключить его чисто. Я ответил на этот точный вопрос об отключении CopyLocal в разделе Как переопределить параметр CopyLocal (Private) для ссылок в.NET из MSBUILD. Проверьте это. А также Лучшие практики для больших решений в Visual Studio (2008).
Вот еще немного информации о CopyLocal, как я ее вижу.
CopyLocal был реализован действительно для поддержки локальной отладки. Когда вы готовите свое приложение для упаковки и развертывания, вы должны собрать свои проекты в одной выходной папке и убедиться, что у вас есть все необходимые ссылки.
Я написал о том, как работать с большими деревьями исходников, в статье MSBuild: Лучшие практики для создания надежных сборок, часть 2.
По моему мнению, иметь решение с 100 проектами - БОЛЬШАЯ ошибка. Возможно, вы могли бы разделить свое решение на допустимые логические небольшие блоки, что упростит как обслуживание, так и сборку.
Я удивлен, что никто не упомянул использование жестких ссылок. Вместо копирования файлов создается жесткая ссылка на исходный файл. Это экономит дисковое пространство, а также значительно ускоряет сборку. Это можно включить в командной строке со следующими свойствами:
/ Р:CreateHardLinksForAdditionalFilesIfPossible= истина;CreateHardLinksForCopyAdditionalFilesIfPossible= истина;CreateHardLinksForCopyFilesToOutputDirectoryIfPossible= истина;CreateHardLinksForCopyLocalIfPossible= истина;CreateHardLinksForPublishFilesIfPossible= истина
Вы также можете добавить это в центральный файл импорта, чтобы все ваши проекты также могли получить это преимущество.
Если вы получили структуру зависимостей, определенную с помощью ссылок на проекты или зависимостей уровня решения, можно безопасно отключить "Копировать локально", я бы даже сказал, что это лучший способ, так как это позволит вам использовать MSBuild 3.5 для параллельного запуска сборки (via /maxcpucount) без различных процессов, спотыкающихся друг на друга при попытке скопировать ссылочные сборки.
Наша "лучшая практика" - избегать решений по многим проектам. У нас есть каталог с именем "matrix" с текущими версиями сборок, и все ссылки из этого каталога. Если вы изменили какой-либо проект и можете сказать "теперь изменение завершено", вы можете скопировать сборку в каталог "matrix". Таким образом, все проекты, которые зависят от этой сборки, будут иметь текущую (= последнюю) версию.
Если в решении мало проектов, процесс сборки будет намного быстрее.
Вы можете автоматизировать шаг "Скопировать сборку в каталог матрицы", используя макросы Visual Studio или "Меню -> Инструменты -> Внешние инструменты...".
Вам не нужно менять значения 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
Установите CopyLocal=false уменьшит время сборки, но может вызвать различные проблемы во время развертывания.
Существует много сценариев, когда вам нужно, чтобы параметр "Копировать локально" оставался равным True, например,
- Проекты на высшем уровне,
- Зависимости второго уровня,
- DLL, вызванные отражением
Возможные проблемы описаны в SO вопросах
" Когда copy-local должен быть установлен в true, а когда нет?",
" Сообщение об ошибке" Невозможно загрузить один или несколько запрошенных типов. Получите свойство LoaderExceptions для получения дополнительной информации ". "
и ответ Aaron Stainback на этот вопрос.
Мой опыт установки CopyLocal=false не был успешным. См. Мой пост в блоге "НЕ меняйте" Копировать локальные "ссылки на проект на false, если не понимаете подпоследовательности".
Время для решения проблем перевешивает преимущества установки copyLocal = false.
Я склонен создавать общий каталог (например, \bin), поэтому я могу создавать небольшие тестовые решения.
Вы можете попробовать использовать папку, в которую будут скопированы все сборки, которые совместно используются проектами, а затем создать переменную среды DEVPATH и установить <developmentMode developerInstallation="true" />
в файле machine.config на каждой рабочей станции разработчика. Единственное, что вам нужно сделать, это скопировать любую новую версию в вашу папку, куда указывает переменная DEVPATH.
Также разделите ваше решение на несколько небольших решений, если это возможно.
Это может быть не лучшей практикой, но так я работаю.
Я заметил, что Managed C++ выгружает все свои двоичные файлы в $(SolutionDir)/'DebugOrRelease'. Так что я выбросил все свои проекты на C# и там. Я также отключил "Копировать локальные" всех ссылок на проекты в решении. У меня было заметное улучшение времени сборки в моем маленьком 10 проектном решении. Это решение представляет собой смесь C#, управляемого C++, нативного C++, C# webservice и проектов установщика.
Может быть, что-то сломано, но так как я работаю только так, я этого не замечаю.
Было бы интересно узнать, что я нарушаю.
У вас могут быть ссылки на ваши проекты, указывающие на отладочные версии библиотек. Чем в вашем скрипте msbuild, вы можете установить /p:Configuration=Release
Таким образом, у вас будет выпускная версия вашего приложения и всех сателлитных сборок.
Если ссылка не содержится в GAC, мы должны установить для параметра Local Local значение true, чтобы приложение работало. Если мы уверены, что ссылка будет предварительно установлена в GAC, тогда для нее можно установить значение false.
Ну, я, конечно, не знаю, как решаются проблемы, но у меня был контакт с решением для сборки, которое помогло себе так, что все созданные файлы были помещены на виртуальный диск с помощью символических ссылок.
- c: \ папка решений \bin -> ramdisk r:\ папка решений \ bin \
c: \ папка решений \obj -> ramdisk r:\ папка решений \obj\
Вы также можете дополнительно указать Visual Studio, какой временный каталог он может использовать для сборки.
На самом деле это не все, что он сделал. Но это действительно поразило мое понимание производительности.
100% использование процессора и огромный проект за 3 минуты со всеми зависимостями.
Обычно вам нужно только копировать Local, если вы хотите, чтобы ваш проект использовал DLL, которая находится в вашей корзине, и то, что находится где-то еще (GAC, другие проекты и т. Д.)
Я хотел бы согласиться с другими людьми, что вы должны также попытаться, если это вообще возможно, сломать это решение.
Вы также можете использовать Configuration Manager для создания разных конфигураций сборки в рамках одного решения, которое будет создавать только определенные наборы проектов.
Было бы странно, если бы все 100 проектов опирались друг на друга, поэтому вы можете разбить его или использовать Configuration Manager, чтобы помочь себе.
Если вы хотите иметь центральное место для ссылки на библиотеку DLL, используя локальное копирование, произойдет сбой без GAC, если вы не сделаете этого.