「ローカルにコピー」のベストプラクティスは何ですか?


154

大きなc#ソリューションファイル(〜100プロジェクト)があり、ビルド時間を改善しようとしています。「ローカルコピー」は多くの場合無駄であると思いますが、ベストプラクティスについて疑問に思っています。

.slnには、アセンブリBに依存するアプリケーションAがあり、アセンブリBはアセンブリCに依存しています。この場合、数十の「B」と少数の「C」があります。これらはすべて.slnに含まれているため、プロジェクト参照を使用しています。現在、すべてのアセンブリは$(SolutionDir)/ Debug(またはRelease)に組み込まれています。

デフォルトでは、Visual Studioはこれらのプロジェクト参照を「ローカルにコピー」としてマークします。これにより、ビルドされる「B」ごとに「C」がすべて$(SolutionDir)/ Debugにコピーされます。これは無駄に思えます。「ローカルのコピー」をオフにしただけでは何がうまくいかないのですか?大規模なシステムを持つ他の人々は何をしますか?

ファローアップ:

多くの応答は、ビルドを小さな.slnファイルに分割することを提案しています...上記の例では、最初に基礎クラス「C」を構築し、次にモジュール「B」の大部分、次にいくつかのアプリケーションを構築します。 A "。このモデルでは、BからCへの非プロジェクト参照が必要です。そこで発生する問題は、「デバッグ」または「リリース」がヒントパスに焼き付けられ、「B」のリリースビルドのビルドに巻き込まれることです。 「C」のデバッグビルドに対して。

ビルドを複数の.slnファイルに分割した場合、この問題をどのように管理しますか?


9
プロジェクトファイルを直接編集して、ヒントパスがデバッグディレクトリまたはリリースディレクトリを参照するようにできます。デバッグまたはリリースの代わりに$(Configuration)を使用します。例:<HintPath> .. \ output \ $(Configuration)\ test.dll </ HintPath>これは、多くの参照がある場合の苦痛です(ただし、誰かがアドインを作成するのは難しいことではありません)これを管理します)。
超高速2009

4
Visual Studioの「ローカルコピー」は<Private>True</Private>csproj と同じですか?
大佐パニック2014

しかし、a .slnをより小さなものに分割すると、VSの<ProjectReference/>s のオートマジック相互依存計算が壊れます。VSによって問題が少なくなるという理由だけで.sln、複数の小さなsから1つの大きなsに移動しました.sln。それで、フォローアップは元の質問に対して必ずしも最善ではないソリューションを想定しているのでしょうか。;-)
binki 2014

単なる好奇心から。なぜ物事を複雑にし、最初から100以上のプロジェクトがあるのですか?これは悪いデザインですか?
有用なビー

@ColonelPanicはい。少なくとも、GUIでトグルを変更すると、ディスク上でそれが変更されます。
Zero3

回答:


85

以前のプロジェクトでは、プロジェクト参照を持つ1つの大きなソリューションで作業し、パフォーマンスの問題にもぶつかりました。解決策は3つありました。

  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>

この回避策を実行したところ、1つのソリューションで120を超えるプロジェクトを実行できることがわかりました。これには、ソリューションを分割して手動で行うのではなく、VSがプロジェクトのビルド順序を決定できるという主な利点があります。 。


加えた変更とその理由を教えてください。コーディングの長い1日の後、私の眼球は疲れすぎて自分でリバースエンジニアリングを試みることはできません:)
Charlie Flowers

これをもう一度コピーして貼り付けてみてください。タグの99%のように混乱しています。
ZXX、

@Charlie Flowers、@ ZXXがテキストが説明になるように編集し、xmlを適切にレイアウトできませんでした。
Bas Bossink 2010

1
Microsoft.Common.targetsから:GetCopyToOutputDirectoryItems出力ディレクトリに転送する必要があるすべてのプロジェクトアイテムを取得します。これには、推移的に参照されるプロジェクトからの手荷物アイテムが含まれます。このターゲットは、参照されているすべてのプロジェクトのコンテンツ項目の完全な推移的閉鎖を計算しているように見えます。しかし、そうではありません。
Brans Ds 2013

2
コンテンツのアイテムは、直接の子からのみ収集され、子の子からは収集されません。これが発生する理由は、_SplitProjectReferencesByFileExistenceによって使用されるProjectReferenceWithConfigurationリストが現在のプロジェクトにのみ入力され、子では空であるためです。空のリストにより、_MSBuildProjectReferenceExistentが空になり、再帰が終了します。そのため、役に立たないようです。
Brans Ds 2013

32

そのテーマに関するPatric Smacchiaの記事を読むことをお勧めします。

CC.Net VSプロジェクトは、ローカル参照アセンブリのコピーオプションをtrueに設定して依存します。[...]これにより、コンパイル時間が大幅に増加するだけでなく(NUnitの場合は3倍)、作業環境が混乱します。最後に重要なことですが、そうすることは潜在的な問題をバージョン管理するリスクをもたらします。ところで、NDependは、同じ名前で同じコンテンツまたはバージョンではない2つの異なるディレクトリで2つのアセンブリを検出すると警告を発します。

正しいことは、2つのディレクトリ$ RootDir $ \ bin \ Debugおよび$ RootDir $ \ bin \ Releaseを定義し、これらのディレクトリにアセンブリを生成するようにVisualStudioプロジェクトを構成することです。すべてのプロジェクト参照は、Debugディレクトリのアセンブリを参照する必要があります。

この記事を読んでプロジェクト数を減らし、コンパイル時間を改善することもできます。


1
私は複数の賛成投票でSmacchiaの慣行をお勧めしたいと思います!ソリューションを分割するのではなく、プロジェクトの数を減らすことが重要です。
Anthony Mastrean、

23

依存関係ツリーの最上位にあるプロジェクトを除いて、ほとんどすべてのプロジェクトでcopy local = falseにすることをお勧めします。そして、一番上の参照のすべての参照について、local = trueをコピーします。多くの人が出力ディレクトリの共有を提案しています。これは経験に基づく恐ろしい考えだと思います。スタートアッププロジェクトがDLLへの参照を保持している場合、他のプロジェクトがあなたへの参照を保持している場合でも、すべてのコピーでlocal = falseをコピーしても、アクセス\共有違反が発生し、ビルドが失敗します。この問題は非常に煩わしく、追跡するのが困難です。依存関係チェーンの最上位にあるプロジェクトに、必要なアセンブリを対応するフォルダーに書き込むのではなく、シャード出力ディレクトリに近づかないようにすることをお勧めします。「トップ」にプロジェクトがない場合は、次に、すべてを適切な場所に配置するために、ビルド後のコピーを提案します。また、デバッグのしやすさを心がけています。F5デバッグエクスペリエンスが機能するように、copy local = trueのままにしておくすべてのexeプロジェクト。


2
私も同じ考えを持っていて、ここで同じように考えている他の誰かを見つけたいと思っていました。ただし、なぜこの投稿にこれ以上の賛成票がないのか知りたいです。同意しない人:なぜ同意しないのですか?
bwerks 2013年

同じプロジェクトが2回ビルドされない限り、それは起こり得ません。なぜ、一度ビルドされてファイルをコピーしないと、overwrite \ access \ sharing違反になるのですか?
ポール、2015年

この。開発ワークフローでslnの1つのプロジェクトをビルドする必要があり、ソリューションの別のプロジェクト実行可能ファイルが実行されている場合、すべてを同じ出力ディレクトリに置くのは面倒です。この場合、実行可能な出力フォルダーを分離する方がはるかに優れています。
Martin Ba

10

あなたは正しいです。CopyLocalはビルド時間を完全に殺します。ソースツリーが大きい場合は、CopyLocalを無効にする必要があります。残念ながら、それを完全に無効にするのは簡単ではありません。MSBUILDからの.NETでの参照のCopyLocal(Private)設定をどのように上書きするかで、CopyLocalを無効にすることに関するこの正確な質問に答えました。見てみな。などのVisual Studio(2008年)で大規模なソリューションのためのベストプラクティス。

私が見ているように、ここにCopyLocalに関する詳細があります。

CopyLocalは、ローカルデバッグをサポートするために実装されました。アプリケーションをパッケージ化してデプロイする準備をするときは、プロジェクトを同じ出力フォルダーにビルドし、必要なすべての参照があることを確認する必要があります。

記事「MSBuild:信頼できるビルドを作成するためのベストプラクティス、パート2」で、大きなソースツリーの構築に対処する方法について説明しました。


8

私の意見では、100プロジェクトのソリューションを持つことは大きな間違いです。ソリューションを有効な論理的な小さなユニットに分割することで、メンテナンスとビルドの両方を簡素化できます。


1
ブルーノ、上記の私のフォローアップの質問を参照してください-より小さな.slnファイルに侵入した場合、デバッグとリリースの側面をどのように管理し、それを参照のヒントパスに焼き付けますか?
Dave Moore、

1
この点に同意します。私が使用しているソリューションには100個以下のプロジェクトがあり、そのうちのいくつかは3つを超えるクラスを含み、ビルド時間は衝撃的です。その結果、私の前任者はソリューションを3つに分割し、完全に解決しました。すべての参照」とリファクタリング。すべてが数秒で構築される少数のプロジェクトに適合します!
Jon M

デイブ、いい質問だ。私が作業している場所には、特定のソリューションの依存関係をビルドするようなビルドスクリプトがあり、問題のソリューションがそれらを取得できる場所にバイナリを配置しています。これらのスクリプトは、デバッグビルドとリリースビルドの両方でパラメーター化されています。欠点は、前述のスクリプトを作成するための追加の時間ですが、アプリ間で再利用できます。このソリューションは私の基準でうまく機能しています。
jyoungdev

7

誰もハードリンクの使用について言及していないことに驚いています。ファイルをコピーする代わりに、元のファイルへのハードリンクを作成します。これにより、ディスク領域が節約されるだけでなく、ビルドが大幅に高速化されます。これは、次のプロパティを使用してコマンドラインで有効にできます。

/ p:CreateHardLinksForAdditionalFilesIfPossible = true; CreateHardLinksForCopyAdditionalFilesIfPossible = true; CreateHardLinksForCopyFilesToOutputDirectoryIfPossible = true; CreateHardLinksForCopyLocalIfPossible = true; CreateHardLinksForPublishFilesIfPossible = true

これを中央のインポートファイルに追加して、すべてのプロジェクトでこの利点を活用することもできます。


5

プロジェクト参照またはソリューションレベルの依存関係を介して定義された依存関係構造を取得した場合、「ローカルにコピー」をオフにしても安全です。これにより、MSBuild 3.5を使用してビルドを並行して実行できるため、そうすることがベストプラクティスであるとさえ言えます( / maxcpucountを介して)参照されたアセンブリをコピーしようとするときに、異なるプロセスが互いにトリップすることなく


4

私たちの「ベストプラクティス」は、多くのプロジェクトでソリューションを回避することです。現在のバージョンのアセンブリを含む「matrix」という名前のディレクトリがあり、すべての参照はこのディレクトリからのものです。プロジェクトを変更して「変更が完了しました」と言うことができる場合は、アセンブリを「マトリックス」ディレクトリにコピーできます。したがって、このアセンブリに依存するすべてのプロジェクトは、現在の(=最新)バージョンになります。

ソリューションにプロジェクトが少ない場合、ビルドプロセスははるかに高速です。

Visual Studioマクロを使用するか、「メニュー->ツール->外部ツール...」を使用して、「アセンブリをマトリックスディレクトリにコピーする」ステップを自動化できます。


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年にはうまく機能しているようです。ありがとうございます。
Dave Moore

2

CopyLocal = falseを設定するとビルド時間が短縮されますが、展開中に別の問題が発生する可能性があります。

たとえば、「ローカルのコピー」をTrueにしておく必要がある場合、多くのシナリオがあります。

  • トップレベルのプロジェクト
  • 第2レベルの依存関係、
  • リフレクションによって呼び出されるDLL

SOの質問
copy-localをtrueに設定する必要がある場合とそうでない場合」に記載されている可能性のあるエラーメッセージ
要求されたタイプの1つ以上をロードできません。詳細については、LoaderExceptionsプロパティを取得してください。」"
と   アーロン・stainback答え  この質問について。

CopyLocal = falseを設定する私の経験は成功しませんでした。私のブログ記事「サブシーケンスを理解しない限り、「ローカルのコピー」プロジェクトの参照をfalseに変更しないでください」を参照してください。

問題を解決するための時間は、copyLocal = falseを設定することの利点よりも重要です。


設定CopyLocal=Falseは確かにいくつかの問題を引き起こしますが、それらの解決策があります。また、ブログのフォーマットを修正する必要があります。読みづらく、「展開中に発生する可能性のあるエラーについて、<ランダムな会社>の<ランダムなコンサルタント>から警告を受けました」とは言えません。開発する必要があります。
スザンヌデュペロン2013年

@GeorgesDupéron、問題を解決するための時間は、copyLocal = falseを設定することの利点を超えています。コンサルタントへの言及は議論ではなく、信用であり、私のブログは問題が何であるかを説明しています。フィードバックをお寄せいただきありがとうございます。フォーマット、修正します。
Michael Freidgeim 2013年

1

共通のディレクトリ(例.. \ bin)にビルドする傾向があるため、小さなテストソリューションを作成できます。


1

プロジェクト間で共有されるすべてのアセンブリがコピーされるフォルダーを使用してから、DEVPATH環境変数を作成し、

<developmentMode developerInstallation="true" />

各開発者のワークステーションのmachine.configファイルに設定できます。行う必要があるのは、DEVPATH変数が指すフォルダーに新しいバージョンをコピーすることだけです。

また、可能であれば、ソリューションをいくつかの小さなソリューションに分割します。


興味深い...これはデバッグビルドとリリースビルドでどのように機能しますか?
デイブ・ムーア

DEVPATHを介してデバッグ/リリースアセンブリをロードするための適切なソリューションが存在するかどうかはわかりません。これは、共有アセンブリのみに使用することを目的としています。通常のビルドを行う場合はお勧めしません。また、この手法を使用すると、アセンブリバージョンとGACが上書きされることに注意してください。
Aleksandar

1

これは最善の方法ではないかもしれませんが、これが私が働く方法です。

Managed C ++はそのすべてのバイナリを$(SolutionDir)/ 'DebugOrRelease'にダンプすることに気付きました。そこで、C#プロジェクトもすべてダンプしました。また、ソリューション内のプロジェクトへのすべての参照の「ローカルのコピー」をオフにしました。私の小さな10プロジェクトソリューションでは、ビルド時間が大幅に改善されました。このソリューションは、C#、マネージC ++、ネイティブC ++、C#Webサービス、およびインストーラープロジェクトの混合です。

何かが壊れているのかもしれませんが、これが唯一の仕事なので、気づきません。

私が何を壊しているかを見つけるのは興味深いでしょう。


0

通常、ビンにあるDLLを使用するプロジェクトと他の場所にあるもの(GAC、他のプロジェクトなど)を使用する場合にのみ、ローカルをコピーする必要があります。

私は他の人たちにも同意する傾向がありますが、可能であれば、その解決策を解体するために、あなたも試みるべきです。

構成マネージャーを使用して、特定のプロジェクトのセットのみをビルドする1つのソリューション内で異なるビルド構成を作成することもできます。

100のプロジェクトすべてが互いに依存している場合は奇妙に思えるので、プロジェクトを分割するか、Configuration Managerを使用して支援する必要があります。


0

dllのデバッグバージョンを指すプロジェクト参照を作成できます。msbuildスクリプトよりもを設定できるため/p:Configuration=Release、アプリケーションのリリースバージョンとすべてのサテライトアセンブリを使用できます。


1
ブルーノ-はい、これはプロジェクト参照で機能します。これが、最初に100プロジェクトソリューションを導入した理由の1つです。ビルド済みのデバッグリリースを参照する参照では機能しません-デバッグアセンブリに対してビルドされたリリースアプリで終了します。これは問題です
Dave Moore

4
テキストエディターでプロジェクトファイルを編集し、HintPathで$(Configuration)を使用します(例:<HintPath> .. \ output \ $(Configuration)\ test.dll </ HintPath>)。
超速度


0

参照がGAC内に含まれていない場合、アプリケーションが機能するようにローカルのコピーをtrueに設定する必要があります。参照がGACにプレインストールされることが確実な場合は、falseに設定できます。


0

まあ、問題がどのように機能するかは確かにわかりませんが、すべての作成されたファイルがシンボリックリンクの助けを借りてRAMディスクに置かれるように自分自身を助けるビルドソリューションに連絡しました。

  • c:\ solution folder \ bin-> ramdisk r:\ solution folder \ bin \
  • c:\ solution folder \ obj-> ramdisk r:\ solution folder \ obj \

  • さらに、ビルドに使用できる一時ディレクトリをVisual Studioに通知することもできます。

実際、それだけではありませんでした。しかし、それはパフォーマンスに対する私の理解に本当に影響を与えました。
100%プロセッサを使用し、すべての依存関係がある3分未満の巨大なプロジェクト。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.