web-dev-qa-db-ja.com

「ローカルコピー」およびプロジェクト参照のベストプラクティスは何ですか?

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

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

既定では、Visual Studioはこれらのプロジェクト参照を「ローカルコピー」としてマークします。これにより、ビルドされる「B」ごとに1回ずつ「C」が$(SolutionDir)/ Debugにコピーされます。これはもったいないようです。 「ローカルコピー」をオフにするとどうなりますか?大規模システムを持つ他の人は何をしますか?

ファローアップ:

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

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

154
Dave Moore

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

  1. Copy Localプロパティを常にfalseに設定し、カスタムmsbuildステップを介してこれを強制します。

  2. 各プロジェクトの出力ディレクトリを同じディレクトリに設定します(できれば、$(SolutionDir)を基準にしてください)

  3. フレームワークに同梱されているデフォルトのcsターゲットは、現在ビルド中のプロジェクトの出力ディレクトリにコピーされる参照セットを計算します。これには、「参照」関係の下で推移閉包を計算する必要があるため、VERYのコストがかかる可能性があります。これに対する私の回避策は、Common.targetsのインポート後にすべてのプロジェクトにインポートされる共通ターゲットファイル(たとえばMicrosoft.CSharp.targets)でGetCopyToOutputDirectoryItemsターゲットを再定義することでした。すべてのプロジェクトファイルは次のようになります。

    <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がプロジェクトのビルド順序を決定できるという主な利点があります。

84
Bas Bossink

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

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

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

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

32
Julien Hoarau

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

23
Aaron Stainback

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

CopyLocalの詳細を次に示します。

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

私は記事で大規模なソースツリーの構築を扱う方法について書いています MSBuild:信頼性の高いビルドを作成するためのベストプラクティス、パート2

10

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

8
Bruno Shine

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

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

また、これを中央のインポートファイルに追加して、すべてのプロジェクトでこのメリットを得ることができます。

7
Matt Slagle

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

私たちの「ベストプラクティス」は、多くのプロジェクトで解決策を避けることです。現在のバージョンのアセンブリを含む「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を設定するとビルド時間が短縮されますが、展開中にさまざまな問題が発生する可能性があります。

[ローカルにコピー]をTrueのままにする必要がある場合、多くのシナリオがあります。

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

SOの質問で説明されている考えられる問題
" copy-localをtrueに設定する必要があるのはいつですか? "、
" エラーメッセージ '要求されたタイプの1つ以上をロードできません。詳細については、LoaderExceptionsプロパティを取得してください。' "
aaron-stainbackanswer この質問に対して。

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

問題を解決するための時間は、copyLocal = falseを設定する利点を圧倒します。

2

これは最良の方法ではないかもしれませんが、これは私が仕事をする方法です。

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

何かが壊れているかもしれませんが、これが私が働く唯一の方法なので、私はそれに気づきません。

私が何を壊しているのかを知るのは面白いでしょう。

1
jyoung

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

1
kenny

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

<developmentMode developerInstallation="true" />

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

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

1
Aleksandar

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

0
SharpGobi

Copy local falseを使用してDLLを参照する中心的な場所が必要な場合、これを行わない限り、GACなしで失敗します。

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

0
user306031

まあ、私は確かに問題がどのように機能するのかわかりませんが、ramdiskシンボリックリンクの助けを借りて。

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

  • Visual Studioに、ビルドに使用できる一時ディレクトリを追加で伝えることもできます。

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

0
user7179930

Dllのデバッグバージョンを指すプロジェクト参照を持つことができます。 msbuildスクリプトでは、/p:Configuration=Releaseを設定できます。したがって、アプリケーションのリリースバージョンとすべてのサテライトアセンブリがあります。

0
Bruno Shine

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

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

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

100のプロジェクトすべてが相互に依存している場合は奇妙に思えます。そのため、それを分割するか、Configuration Managerを使用して自分自身を助けることができるはずです。

0
CubanX