c# - Какова наилучшая практика для «Копировать локальную» и с рекомендациями по проекту?




.net visual-studio (12)

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

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

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

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

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

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

У меня есть большой файл решения 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», против отладочных сборников «С».

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


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

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

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

  3. Заданные по умолчанию cs-объекты, которые поставляются вместе с каркасом, вычисляют набор ссылок, которые нужно скопировать в выходной каталог проекта, который в настоящее время строится. Поскольку это требует вычисления транзитивного замыкания в соответствии с отношением «Ссылки», это может стать ОЧЕНЬ дорогостоящим. Моим обходным GetCopyToOutputDirectoryItems для этого было переопределить цель 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 вместо того, чтобы сделать это вручную, разделив ваше решение ,


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

<developmentMode developerInstallation="true" />

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

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


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


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


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


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

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

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

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


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

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

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

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


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

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

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

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


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

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

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

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


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


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

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

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







msbuild