冗長なリビルドをトリガーせずに MSBuild で ProjectReference の出力を決定する
-
22-09-2019 - |
質問
多くのプロジェクトを含むソリューションの一部として、( <ProjectReference>
ソリューション内の他の 3 つのプロジェクトとその他のプロジェクト)。の中に AfterBuild
, 3 つの特定の依存プロジェクトの出力を別の場所にコピーする必要があります。
さまざまなSO回答などを通じて。それを達成するために私が決めた方法は次のとおりです。
<MSBuild
Projects="@(ProjectReference)"
Targets="Build"
BuildInParallel="true"
Condition="'%(Name)'=='ProjectA' OR '%(Name)'=='ProjectB' OR '%(Name)'=='ProjectC'">
<Output TaskParameter="TargetOutputs" ItemName="DependentAssemblies" />
</MSBuild>
<Copy SourceFiles="@(DependentAssemblies)" DestinationFolder="XX" SkipUnchangedFiles="true" />
しかし、これでは問題が発生しました。の <MSBuild
ステップの IncrementalClean
タスクは最終的に多くの出力を削除することになります。 ProjectC
. 。これを VS2008 で実行すると、 build.force
ファイルが保存されています obj/Debug
ProjectC のフォルダー。プロジェクトにこれが含まれている場合、ソリューション全体でビルドを実行すると、ProjectC の再ビルドがトリガーされます。 AfterBuild
一方、このプロジェクトをビルドから除外すると、[正しく] ProjectC のリビルドがトリガーされません (そして 批判的に ProjectC のすべての依存関係の再構築)。この場合、これは VS 固有のトリックである可能性があり、TeamBuild またはその他のコマンドライン MSBuild 呼び出しのコンテキストでは発生しません (ただし、最も一般的な使用法は VS 経由であるため、いずれかの方法でこれを解決する必要があります)
依存プロジェクト (および一般的なソリューションの残りの部分) はすべて VS と対話的に作成されているため、 ProjectRefence
s には相対パスなどが含まれます。これが問題を引き起こす可能性があるという言及を見たことがありますが、その理由、いつ修正されるのか、どのように回避するのかについては十分な説明がありません。言い換えれば、私はあまり興味がありません。を変換する ProjectReference
.csproj を手動で編集して、パスを絶対パスに変更します。
私が何か愚かなことをしている可能性は十分にありますが、誰かがそれが何であるかをすぐに指摘してくれるでしょう(それは素晴らしいことですが)、私はじっくりと時間をかけて調べてきましたのでご安心ください。 /v:diag
出力など(ただし、私は再現物を一から構築しようとしたわけではありません。これは比較的複雑な全体的な構築のコンテキスト内でのことです)
解決
としては唯一のアセンブリ、そのプロジェクトのプライマリ出力を返す参照プロジェクトにGetTargetPathを呼び出して、私のコメントで指摘しました。それはビットメシエだ参照プロジェクトのすべての参照、コピー地方議会を取得する。
あなたはのCopyLocalsを取得したいことを参照しているプロジェクトごとに以下を追加します:
<Target
Name="ComputeCopyLocalAssemblies"
DependsOnTargets="ResolveProjectReferences;ResolveAssemblyReferences"
Returns="@(ReferenceCopyLocalPaths)" />
私の特定の状況は、私は私のトップレベルのホストプロジェクトのbinフォルダにSystem.AddInのためのパイプラインフォルダ構造を再作成するために必要なことです。これはちょっと厄介だと私はMSDNがOutputPathをいじくるのソリューションを提案して幸せではなかった - 私たちのビルドサーバーと別のプロジェクトにフォルダ構造を作成防止上の区切りとして(例えばA SystemTest)
したがって、上記の目標(.targetsのインポートを使用して)を追加するとともに、私が作成したパイプラインフォルダを必要とする各「ホスト」でインポート.targetsファイルに以下を追加します:
<Target
Name="ComputePipelineAssemblies"
BeforeTargets="_CopyFilesMarkedCopyLocal"
Outputs="%(ProjectReference.Identity)">
<ItemGroup>
<_PrimaryAssembly Remove="@(_PrimaryAssembly)" />
<_DependentAssemblies Remove="@(_DependentAssemblies)" />
</ItemGroup>
<!--The Primary Output of the Pipeline project-->
<MSBuild Projects="%(ProjectReference.Identity)"
Targets="GetTargetPath"
Properties="Configuration=$(Configuration)"
Condition=" '%(ProjectReference.PipelineFolder)' != '' ">
<Output TaskParameter="TargetOutputs"
ItemName="_PrimaryAssembly" />
</MSBuild>
<!--Output of any Referenced Projects-->
<MSBuild Projects="%(ProjectReference.Identity)"
Targets="ComputeCopyLocalAssemblies"
Properties="Configuration=$(Configuration)"
Condition=" '%(ProjectReference.PipelineFolder)' != '' ">
<Output TaskParameter="TargetOutputs"
ItemName="_DependentAssemblies" />
</MSBuild>
<ItemGroup>
<ReferenceCopyLocalPaths Include="@(_PrimaryAssembly)"
Condition=" '%(ProjectReference.PipelineFolder)' != '' ">
<DestinationSubDirectory>%(ProjectReference.PipelineFolder)</DestinationSubDirectory>
</ReferenceCopyLocalPaths>
<ReferenceCopyLocalPaths Include="@(_DependentAssemblies)"
Condition=" '%(ProjectReference.PipelineFolder)' != '' ">
<DestinationSubDirectory>%(ProjectReference.PipelineFolder)</DestinationSubDirectory>
</ReferenceCopyLocalPaths>
</ItemGroup>
</Target>
私はまた、実際のプロジェクトの参照に必要なPipelineFolderのメタデータを追加する必要がありました。たとえばます:
<ProjectReference Include="..\Dogs.Pipeline.AddInSideAdapter\Dogs.Pipeline.AddInSideAdapter.csproj">
<Project>{FFCD0BFC-5A7B-4E13-9E1B-8D01E86975EA}</Project>
<Name>Dogs.Pipeline.AddInSideAdapter</Name>
<Private>False</Private>
<PipelineFolder>Pipeline\AddInSideAdapter\</PipelineFolder>
</ProjectReference>
他のヒント
あなたのオリジナルのソリューションは、変更するだけで動作するはずです。
Targets="Build"
と
Targets="GetTargetPath"
GetTargetPath
目標は、単にTargetPath
プロパティを返し、構築する必要はありません。
あなたはProjectCでファイルを保護することができます
<Target Name="ProtectFiles">
<ReadLinesFromFile File="obj\ProjectC.csproj.FileListAbsolute.txt">
<Output TaskParameter="Lines" ItemName="_FileList"/>
</ReadLinesFromFile>
<CreateItem Include="@(_DllFileList)" Exclude="File1.sample; File2.sample">
<Output TaskParameter="Include" ItemName="_FileListWitoutProtectedFiles"/>
</CreateItem>
<WriteLinesToFile
File="obj\ProjectC.csproj.FileListAbsolute.txt"
Lines="@(_FileListWitoutProtectedFiles)"
Overwrite="true"/>
</Target>
私の 現在の回避策はこのSOの質問に基づいています, つまり、私は次のものを持っています:
<ItemGroup>
<DependentAssemblies Include="
..\ProjectA\bin\$(Configuration)\ProjectA.dll;
..\ProjectB\bin\$(Configuration)\ProjectB.dll;
..\ProjectC\bin\$(Configuration)\ProjectC.dll">
</DependentAssemblies>
</ItemGroup>
ただし、これは TeamBuild (すべての出力が 1 つのディレクトリにまとめられる) では中断され、依存プロジェクトのいずれかの出力の名前が変更された場合にも中断されます。
編集:また、ハードコーディングをもう少しクリーンにする方法について、次のようなよりクリーンな答えがあるかどうかについてのコメントも探しています。
<PropertyGroup>
<_TeamBuildingToSingleOutDir Condition="'$(TeamBuildOutDir)'!='' AND '$(CustomizableOutDir)'!='true'">true</_TeamBuildingToSingleOutDir>
</PropertyGroup>
そして:
<ItemGroup>
<DependentAssemblies
Condition="'$(_TeamBuildingToSingleOutDir)'!='true'"
Include="
..\ProjectA\bin\$(Configuration)\ProjectA.dll;
..\ProjectB\bin\$(Configuration)\ProjectB.dll;
..\ProjectC\bin\$(Configuration)\ProjectC.dll">
</DependentAssemblies>
<DependentAssemblies
Condition="'$(_TeamBuildingToSingleOutDir)'=='true'"
Include="
$(OutDir)\ProjectA.dll;
$(OutDir)\ProjectB.dll;
$(OutDir)\ProjectC.dll">
</DependentAssemblies>
</ItemGroup>