Яка найкраща практика для "Копіювання місцевих" та посилань на проекти?


154

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

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

За замовчуванням Visual Studio позначає ці посилання на проект як "Copy Local", в результаті чого кожен "C" копіюється в $ (SolutionDir) / Debug один раз для кожного "B", який створюється. Це здається марнотратним. Що може піти не так, якщо я просто відключу "Copy Local"? Що роблять інші люди з великими системами?

СЛІДУВАТИ:

Дуже багато відповідей пропонують розділити збірку на менші .sln файли ... У наведеному вище прикладі я спочатку побудую основні класи "C", а потім основну частину модулів "B", а потім декілька додатків " А ". У цій моделі мені потрібно мати непроектні посилання на C від B. Проблема, з якою я стикаюся, полягає в тому, що "Налагодження" або "Випуск" потрапляє в підказковий шлях, і я закінчую будівництво моїх версій "B" проти налагодження складання "C".

Для тих, хто розділяє збірку на кілька .sln файлів, як ви керуєте цією проблемою?


9
Ви можете зробити посилання на підказку посилання на каталог налагодження чи випуску, відредагувавши файл проекту безпосередньо. Використовуйте $ (конфігурація) замість налагодження або випуску. Напр., <HintPath> .. \ output \ $ (Конфігурація) \ test.dll </HintPath> Це біль, коли у вас є багато посилань (хоча комусь не повинно бути важким, щоб написати надбудову в керуйте цим).
ultravelocity

4
Чи "Copy Local" у Visual Studio таке ж, як <Private>True</Private>у csproj?
Полковник Паніка

Але розділення .slnна більш дрібні розбиває автоматичний розрахунок взаємозалежності VS <ProjectReference/>s. Я перейшов від декількох менших .slns більше до однієї великої .slnсам лише тому, що VS таким чином спричиняє менше проблем ... Отже, можливо, подальший припущення передбачає не обов'язково найкраще рішення оригінального питання? ;-)
binki

Тільки з цікавості. Навіщо ускладнювати речі і мати на першому місці 100+ проектів? Це поганий дизайн чи що?
корисноБіти

@ColonelPanic Так. Принаймні, це те, що змінюється на диску, коли я змінюю цей перемикач у графічному інтерфейсі.
Zero3

Відповіді:


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>
    

Це скоротило наш час збирання за певний час від пари годин (в основному через обмеження пам’яті), до пари хвилин.

Redefined 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, а не робити це вручну, розбиваючи рішення .


Чи можете ви описати зміни, які ви внесли і чому? Мої очні яблука занадто втомилися після довгого дня кодування, щоб спробувати його інженером змінити :)
Charlie Flowers

Як щодо спроби скопіювати та вставити це знову - ТАК переплутав, як 99% тегів.
ZXX

@Charlie Flowers, @ZXX відредагував текст, щоб він опису не зміг чітко розставити XML.
Бас Боссінк

1
Від Microsoft.Common.targets: GetCopyToOutputDirectoryItems Отримайте всі елементи проекту, які можуть знадобитися перенести у вихідний каталог. Сюди входять предмети багажу з проектів, що стосуються транзиту. Здається, що ця мета обчислює повне транзитивне закриття елементів вмісту для всіх посилаються проектів; однак це не так.
Brans Ds

2
Він збирає лише елементи вмісту від своїх безпосередніх дітей, а не дітей дітей. Причина цього відбувається в тому, що список ProjectReferenceWithConfiguration, який споживається _SplitProjectReferencesByFileExistence, заповнюється лише в поточному проекті і порожній для дітей. Порожній список призводить до того, що _MSBuildProjectReferenceExistent є порожнім і припиняє рекурсію. Тож це видається не корисним.
Brans Ds

32

Я пропоную вам прочитати статті Патрика Смакіа на цю тему:

Проекти CC.Net VS покладаються на параметр складання локальної довідкової копії, встановлений на true. [...] Це не тільки значно збільшить час компіляції (x3 у випадку з NUnit), але й зіпсує ваше робоче середовище. І останнє, але не менш важливе, це вводить ризик для версії потенційних проблем. Btw, NDepend надсилатиме попередження, якщо він знайде 2 збірки у двох різних каталогах з однаковою назвою, але не однакового змісту чи версії.

Правильно зробити, це визначити 2 каталоги $ RootDir $ \ bin \ Debug та $ RootDir $ \ bin \ Release та налаштувати ваші проекти VisualStudio для випромінювання збірок у цих каталогах. Усі посилання на проекти повинні посилатися на збірки в каталозі налагодження.

Ви також можете прочитати цю статтю, яка допоможе вам зменшити кількість проектів та покращити час складання.


1
Я б хотів, щоб я міг порекомендувати практики Smacchia з більш ніж одним результатом! Зменшення кількості проектів є ключовим фактором, не розбиваючи рішення.
Ентоні Мастрен

23

Я пропоную скопіювати локальний = false для майже всіх проектів, крім того, який знаходиться у верхній частині дерева залежності. І для всіх посилань у тому, що знаходиться зверху, копіюйте local = true. Я бачу багато людей, які пропонують поділитися вихідним каталогом; Я думаю, що це жахлива ідея, заснована на досвіді. Якщо ваш стартовий проект містить посилання на dll, що будь-який інший проект містить посилання на вас, у якийсь момент ви відчуєте порушення доступу / спільного використання, навіть якщо копіювати локальне = false у всьому, і ваша збірка не вдасться. Це питання дуже дратує і важко відстежити. Я повністю пропоную триматися подалі від каталогу вихідних фрагментів і замість того, щоб мати проект у верхній частині ланцюга залежностей, записати необхідні збірки у відповідну папку. Якщо у вас немає проекту вгорі, то я б запропонував копію після складання, щоб все було в потрібному місці. Крім того, я б намагався мати на увазі простоту налагодження. Будь-які проекти EXE, я все ще залишаю копію local = true, щоб досвід налагодження F5 працював.


2
Я мав цю саму ідею і сподівався знайти когось іншого, хто думав би так само; однак мені цікаво, чому ця публікація не має більше результатів. Люди, які не згодні: чому ви не згодні?
бурбаки

Ні, цього не може статися, якщо один і той же проект будується двічі, чому він може перезаписати \ доступ \ порушення порушення, якщо він створений один раз і не копіює жодних файлів?
паульм

Це. Якщо робочий процес розробки потребує побудови одного проекту sln, тоді як інший проект-виконуваний проект рішення працює, маючи все в одному вихідному каталозі буде безлад. Набагато краще в цьому випадку відокремити виконавчі папки.
Мартін Ба

10

Ви праві. CopyLocal абсолютно вбиває ваші строки збирання. Якщо у вас є велике дерево джерела, вам слід відключити CopyLocal. На жаль, це не так просто, як це повинно бути чисто відключити. Я відповів на це точне запитання щодо відключення функції CopyLocal на сторінці Як перекрити налаштування CopyLocal (Private) для посилань у .NET від MSBUILD . Перевір. А також кращі практики для великих рішень у Visual Studio (2008).

Ось додаткова інформація про CopyLocal, як я бачу.

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

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


8

На мою думку, мати рішення зі 100 проектами - велика помилка. Можливо, ви могли б розділити своє рішення на дійсні логічні невеликі одиниці, тим самим спрощуючи технічне обслуговування та збірки.


1
Бруно, будь ласка, дивіться моє наступне запитання вище - якщо ми розбиваємося на менші файли .sln, як ти керуєш аспектом Debug vs.
Дейв Мур

1
Я погоджуюся з цим пунктом, рішення, з яким я працюю, має ~ 100 проектів, лише декілька з яких мають більше 3 класів, час складання є шокуючим, і в результаті мої попередники розділили рішення на 3, що повністю розбиває пошук всі посилання 'та рефакторинг. Вся ця річ могла б вписатись у кілька проектів, які б склалися за лічені секунди!
Джон М

Дейв, Добре запитання. Там, де я працюю, у нас є сценарії побудови, які роблять такі речі, як побудова залежностей для даного рішення, і розміщують двійкові файли кудись там, де запит на рішення може їх отримати. Ці сценарії параметризовані як для налагодження, так і для версій версій. Мінусом є додатковий час наперед для створення зазначених сценаріїв, але їх можна повторно використовувати в додатках. Це рішення добре працювало за моїми мірками.
jyoungdev

7

Я здивований, що ніхто не згадав про використання жорстких посилань. Замість того, щоб копіювати файли, він створює жорстке посилання на вихідний файл. Це економить простір на диску, а також значно прискорює збірку. Це може бути включено в командному рядку з такими властивостями:

/ p: CreateHardLinksForAdditionalFilesIfPossible = true; CreateHardLinksForCopyAdditionalFilesIfPossible = true; CreateHardLinksForCopyFilesToOutputDirectoryIfPossible = true; CreateHardLinksForCopyLocalIfPossible = true; CreateHardLinksForPPlive = True true; CreateHardLinksForPPlified

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


5

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


4

наша "найкраща практика" - уникати рішень з багатьма проектами. У нас є каталог з назвою "матриця" з поточними версіями збірок, і всі посилання з цього каталогу. Якщо ви змінили якийсь проект і зможете сказати "зараз зміна завершена", ви можете скопіювати збірку в каталог "матриці". Тож усі проекти, що залежать від цієї збірки, матимуть поточну (= останню) версію.

Якщо у вас мало проектів на вирішення, процес збирання відбувається набагато швидше.

Ви можете автоматизувати крок "копіювання збірки в матричний каталог" за допомогою візуальних макросів студії або за допомогою "меню -> інструменти -> зовнішні інструменти ...".


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

Існує багато сценаріїв, коли потрібно, щоб Copy Local 'залишилося в True, наприклад,

  • Проекти вищого рівня,
  • Залежності другого рівня,
  • DLL, викликані відображенням

Можливі проблеми, описані в питаннях SO:
" Коли слід встановити локальну копію для true та коли її немає? ",
" Повідомлення про помилку" Неможливо завантажити один або декілька запитуваних типів. Для отримання додаткової інформації отримайте властивість LoaderExceptions. " "
І   Арон-stainback « s відповідь  на це питання.

Мій досвід із встановленням CopyLocal = false НЕ вдався. Дивіться мою публікацію в блозі "НЕ міняйте посилання проекту" Копіювати місцеві "на помилкові, якщо не розумієте подальших змін".

Час вирішення проблем надмірно важить переваги встановлення copyLocal = false.


Налаштування CopyLocal=False, безумовно, спричинить деякі проблеми, але для них є рішення. Крім того, вам слід виправити форматування свого блогу, воно ледве читабельне, і сказати там, що «мене попереджав <випадковий консультант> від <випадкової компанії> про можливі помилки під час розгортання» - це не аргумент. Вам потрібно розвиватися.
Сюзанна Дуперон

@ GeorgesDupéron, Час вирішення проблем із зайвою вагою переваги встановлення copyLocal = false. Посилання на консультанта - це не аргумент, а заслуга, і мій блог пояснює, у чому проблеми. Дякуємо за Ваш відгук. форматування, я це виправлю.
Майкл Фрейджім

1

Я схильний створювати загальний каталог (наприклад, .. \ bin), тому можу створити невеликі тестові рішення.


1

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

<developmentMode developerInstallation="true" />

у файл machine.config на робочій станції кожного розробника. Єдине, що вам потрібно зробити - це скопіювати будь-яку нову версію у свою папку, де точки DEVPATH змінні.

Також розділіть своє рішення на кілька менших рішень, якщо можливо.


Цікаво ... Як би це працювало з налагодженнями та релізами версій?
Дейв Мур

Я не впевнений, чи існує якесь підходяще рішення для завантаження збоїв налагодження / випуску через DEVPATH, воно призначене для використання лише для спільних збірок, я б не рекомендував це робити для регулярних збірок. Також майте на увазі, що версія монтажу та GAC змінюються під час використання цієї методики.
Олександр

1

Це може бути не найкращим способом, але я працюю так.

Я помітив, що керований C ++ скидає всі свої двійкові файли в $ (SolutionDir) / 'DebugOrRelease'. Тому я також скинув усі свої C # проекти. Я також вимкнув "Copy Local" всіх посилань на проекти в рішенні. У своєму маленькому 10 проектному рішенні я помітно покращив час побудови. Це рішення являє собою суміш C #, керованого C ++, нативного C ++, C # веб-сервісу та інсталяторських проектів.

Можливо, щось порушено, але оскільки це єдиний спосіб роботи, я цього не помічаю.

Було б цікаво дізнатися, що я ламаю.


0

Зазвичай вам потрібно скопіювати локальне, лише якщо ви хочете, щоб ваш проект використовувався DLL, який знаходиться у вашій скриньці порівняно з тим, що є десь іншим (GAC, інші проекти тощо).

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

Ви також можете скористатися програмою Configuration Manager, щоб створити собі різні конфігурації побудови в рамках одного рішення, яке будуватиме лише задані набори проектів.

Здавалося б дивним, якби всі 100 проектів покладалися один на одного, тож вам слід мати змогу або розбити його, або скористатися програмою Configuration Manager, щоб допомогти собі.


0

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


1
Бруно - так, це працює з посиланнями на проект, що є однією з причин того, що ми в першу чергу закінчилися проектним рішенням. Це не працює над посиланнями, де я переглядаю попередньо вбудовані версії налагодження - я закінчую програму Release, побудовану проти збоїв налагодження, що є проблемою
Дейв Мур

4
Відредагуйте файл проекту у текстовому редакторі та використовуйте $ (Configuration) у своєму HintPath, наприклад <HintPath> .. \ output \ $ (Configuration) \ test.dll </HintPath>.
ultravelocity


0

Якщо посилання не міститься в GAC, ми повинні встановити місце локального копіювання на true, щоб програма працювала, якщо ми впевнені, що посилання буде попередньо встановлено в GAC, то воно може бути встановлено на false.


0

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

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

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

Насправді це було не все, що він робив. Але це дійсно вразило моє розуміння продуктивності.
100% використання процесорів та величезний проект за 3 хвилини з усіма залежностями.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.