Какова наилучшая практика для "Копировать локальную" и с рекомендациями по проекту?

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

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

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

Followup:

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

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

Ответ 1

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

  • Всегда устанавливайте для свойства 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 и 2450 и 2474 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 вместо того, чтобы делать это вручную путем разделения ваше решение.

Ответ 2

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

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

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

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

Ответ 3

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

Ответ 4

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

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

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

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

Ответ 5

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

Ответ 6

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

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

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

Ответ 7

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

Ответ 8

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

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

Вы можете автоматизировать шаг "копирование в матричный каталог" с помощью макросов визуальной студии или с помощью "menu → tools → внешних инструментов...".

Ответ 9

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

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

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

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

Мой опыт с настройкой CopyLocal = false не был успешным. См. Мой пост в блоге "НЕ ИЗМЕНЯЙТЕ" Копировать локальные "ссылки на проект на false, если не понимать подпоследовательности" .

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

Ответ 11

Я склоняюсь к созданию общего каталога (например,..\bin), поэтому я могу создавать небольшие тестовые решения.

Ответ 12

Вы можете попытаться использовать папку, в которой будут скопированы все сборки, которые совместно используются между проектами, затем создайте переменную среды DEVPATH и установите

<developmentMode developerInstallation="true" />

в файл machine.config на каждой рабочей станции разработчика. Единственное, что вам нужно сделать, это скопировать любую новую версию в вашу папку, в которой указаны значения переменной DEVPATH.

Также по возможности разделите свое решение на несколько меньших решений.

Ответ 13

Это может быть не лучшая оценка, но вот как я работаю.

Я заметил, что Managed С++ сбрасывает все свои двоичные файлы в $(SolutionDir)/'DebugOrRelease'. Поэтому я тоже бросил все мои проекты на С#. Я также отключил "Копировать локальную" все ссылки на проекты в решении. В моем небольшом 10 проектном решении у меня было замечательное улучшение времени на сборку. Это решение представляет собой смесь С#, управляемых С++, родных С++, С# webservice и проектов установщика.

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

Было бы интересно узнать, что я нарушаю.

Ответ 14

Обычно вам нужно только скопировать локальный, если вы хотите, чтобы ваш проект использовал DLL, находящуюся в вашем бине, или что-то в другом месте (GAC, другие проекты и т.д.).

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

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

Казалось бы странным, если бы все 100 проектов полагались друг на друга, поэтому вы можете либо разбить его, либо использовать Configuration Manager, чтобы помочь себе.

Ответ 15

У вас могут быть ссылки на проекты, указывающие на версии отладки DLL. Вместо вашего msbuild script вы можете установить /p:Configuration=Release, таким образом, у вас будет версия выпуска вашего приложения и все спутниковые сборки.

Ответ 16

Если вы хотите иметь центральное место для ссылки на DLL, используя локальную копию local, то сбой будет отсутствовать без GAC, если вы этого не сделаете.

http://nbaked.wordpress.com/2010/03/28/gac-alternative/

Ответ 17

Если ссылка не содержится в GAC, мы должны установить Copy Local в true, чтобы приложение работало, если мы уверены, что эта ссылка будет предварительно установлена ​​в GAC, тогда ее можно установить как false.

Ответ 18

Ну, я, конечно, не знаю, как проблемы возникают, но я столкнулся с решением сборки, которое помогло себе в том, что все созданные файлы, на которые помещается ramdisk, с помощью символические ссылки.

  • c:\папка решения \bin → ramdisk r:\папка решения \bin\
  • c:\папка решения \obj → ramdisk r:\папка решения \obj\

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

Собственно, это было не все, что он сделал. Но это действительно поразило мое понимание производительности.
100% процессор и огромный проект в возрасте до 3 минут со всеми зависимостями.