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

154

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

Дейв Мур
источник
9
Вы можете сделать ваш Hint Path ссылкой на каталог Debug или Release, отредактировав файл проекта напрямую. Используйте $ (Configuration) вместо Debug или Release. Например, <HintPath> .. \ output \ $ (Configuration) \ test.dll </ HintPath> Это боль, когда у вас много ссылок (хотя для кого-то должно быть нетрудно написать надстройку для справиться с этим).
ультраскорость
4
Является ли «Копировать локальный» в Visual Studio так же, как <Private>True</Private>в csproj?
полковник Паник
Но расщепление вверх .slnна более мелкие , ломает Automagic расчета Взаимозависимости VS по <ProjectReference/>с. Я сам перешел от нескольких меньших .slns к одному большому .slnтолько потому, что таким образом VS вызывает меньше проблем ... Итак, может быть, продолжение предполагает не обязательно лучшее решение исходного вопроса? ;-)
Бинки
Просто из любопытства. Зачем усложнять вещи и иметь более 100 проектов на первом месте? Это плохой дизайн или как?
полезноBee
@ColonelPanic Да. По крайней мере, это то, что меняется на диске, когда я меняю этот переключатель в графическом интерфейсе.
Ноль3

Ответы:

85

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

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

Bas Bossink
источник
Можете ли вы описать сделанные вами изменения и почему? Мои глазные яблоки слишком устали после долгого дня кодирования, чтобы попытаться самому их реконструировать :)
Чарли Флауэрс
Как насчет попытки скопировать и вставить это снова - ТАК испортили как 99% тегов.
ZXX
@Charlie Flowers, @ZXX отредактировал текст, чтобы он стал описанием, и не смог правильно оформить xml.
Bas Bossink
1
Из Microsoft.Common.targets: GetCopyToOutputDirectoryItems Получите все элементы проекта, которые, возможно, потребуется перенести в выходной каталог. Это включает в себя предметы багажа из транзитных проектов. Похоже, что эта цель вычисляет полное транзитивное закрытие элементов контента для всех проектов, на которые ссылаются; Однако это не так.
Brans Ds
2
Он собирает только элементы контента от своих непосредственных детей, а не детей от детей. Причина этого заключается в том, что список ProjectReferenceWithConfiguration, который используется _SplitProjectReferencesByFileExistence, заполняется только в текущем проекте и пуст в дочерних элементах. Пустой список приводит к тому, что _MSBuildProjectReferenceExistent будет пустым и завершает рекурсию. Так что это кажется бесполезным.
Brans Ds
32

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

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

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

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

Жюльен Хорау
источник
1
Я хотел бы порекомендовать практику Smacchia с более чем одним голосом! Сокращение числа проектов является ключевым фактором, а не разделением решения.
Энтони Мастреан
23

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

Аарон Стейнбэк
источник
2
У меня была такая же идея, и я надеялся найти кого-то еще, кто думал бы так же; Тем не менее, мне любопытно, почему в этом посте нет больше голосов. Люди, которые не согласны: почему вы не согласны?
bwerks
Нет, этого не может произойти, если один и тот же проект не собирается дважды, почему он будет перезаписан \ нарушен \ доступ \ совместное использование, если он собран один раз и не копирует файлы?
Пол
Это. Если рабочий процесс разработки требует построения одного проекта sln, в то время как выполняется другой проект-выполнимое решение, размещение всего в одном выходном каталоге будет беспорядочным. Гораздо лучше разделить исполняемые папки вывода в этом случае.
Мартин Ба
10

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

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

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

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

Сказал Ибрагим Хашими
источник
8

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

Бруно Шайн
источник
1
Бруно, пожалуйста, см. Мой вопрос для продолжения выше - если мы разбиваемся на более мелкие файлы .sln, как ты управляешь аспектом Отладка против выпуска, который затем включается в путь подсказок моих ссылок?
Дейв Мур
1
Я согласен с этим, у решения, с которым я работаю, ~ 100 проектов, только у немногих из которых более 3 классов, время сборки шокирует, и в результате мои предшественники разделили решение на 3, что полностью нарушает поиск все ссылки и рефакторинг. Все это может вписаться в несколько проектов, которые будут построены в считанные секунды!
Джон М
Дэйв, хороший вопрос. Там, где я работаю, у нас есть сценарии сборки, которые выполняют такие вещи, как построение зависимостей для данного решения, и помещают двоичные файлы туда, где их может получить рассматриваемое решение. Эти сценарии параметризованы как для отладочной, так и для релизной сборок. Недостатком является дополнительное время для создания указанных сценариев, но их можно использовать в разных приложениях. Это решение хорошо сработало по моим стандартам.
Jyoungdev
7

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

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

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

Мэтт Слэйгл
источник
5

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

Torbjörn Gyllebring
источник
4

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

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

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

TcKs
источник
3

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

блеск
источник
Я не уверен, что это было доступно еще в 2009 году, но, похоже, оно хорошо работает в 2015 году. Спасибо!
Дейв Мур
2

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

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

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

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

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

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

Майкл Фрейдгейм
источник
Настройка CopyLocal=Falseнаверняка вызовет некоторые проблемы, но есть решения для них. Кроме того, вы должны исправить форматирование вашего блога, он едва читаем, и утверждение «я был предупрежден <случайным консультантом> из <случайной компании> о возможных ошибках во время развертываний» не является аргументом. Вам нужно развиваться.
Сюзанна Дюперон,
@ GeorgesDupéron, время решения проблем перевешивает преимущества установки copyLocal = false. Ссылка на консультанта не аргумент, а кредит, и мой блог объясняет, в чем проблемы. Спасибо за ваш отзыв повторно. форматирование я исправлю.
Майкл Фрейдгейм
1

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

Kenny
источник
1

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

<developmentMode developerInstallation="true" />

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

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

Александар
источник
Интересно ... Как бы это работало со сборками отладки и релиза?
Дэйв Мур
Я не уверен, существует ли какое-либо подходящее решение для загрузки сборок отладки / выпуска через DEVPATH, оно предназначено для использования только для общих сборок, я бы не рекомендовал его для выполнения регулярных сборок. Также имейте в виду, что версия сборки и GAC переопределяются при использовании этого метода.
Александар
1

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

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

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

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

jyoung
источник
0

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

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

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

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

CubanX
источник
0

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

Бруно Шайн
источник
1
Бруно - да, это работает с Project References, и это одна из причин, по которой мы в итоге получили 100 проектных решений. Он не работает со ссылками, где я просматриваю предварительно собранные выпуски Debug - я столкнулся с приложением Release, созданным для сборок Debug, что является проблемой
Дэйв Мур
4
Отредактируйте файл проекта в текстовом редакторе и используйте $ (Configuration) в HintPath, например, <HintPath> .. \ output \ $ (Configuration) \ test.dll </ HintPath>.
ультраскорость
0

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

SharpGobi
источник
0

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

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

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

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


источник