it-swarm.com.ru

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

У меня большой файл решения 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, как вы справляетесь с этой проблемой?

153
Dave Moore

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

  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 можно создать, скопировав строки 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, а не делать это вручную путем разделения вашего решения. ,.

83
Bas Bossink

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

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

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

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

31
Julien Hoarau

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

23
Aaron Stainback

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

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

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

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

10
Sayed Ibrahim Hashimi

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

9
Bruno Shine

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

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

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

6
Matt Slagle

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

5
Torbjörn Gyllebring

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

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

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

4
TcKs

Вам не нужно менять значения 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

3
Sheen

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

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

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

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

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

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

2
Michael Freidgeim

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

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

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

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

1
jyoung

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

1
kenny

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

<developmentMode developerInstallation="true" /> 

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

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

1
Aleksandar

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

0
SharpGobi

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

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

0
user306031

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

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

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

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

0
user7179930

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

0
Bruno Shine

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

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

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

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

0
CubanX