在不触发冗余重建的情况下确定MSBuild中ProjectReference的输出

在不触发冗余重建的情况下确定MSBuild中ProjectReference的输出,msbuild,msbuild-task,project-reference,Msbuild,Msbuild Task,Project Reference,作为包含许多项目的解决方案的一部分,我有一个项目可以引用(通过解决方案中的其他三个项目,以及其他一些项目)。在AfterBuild中,我需要将3个特定依赖项目的输出复制到另一个位置 通过各种SO答案等,我决定实现这一目标的方式是: <MSBuild Projects="@(ProjectReference)" Targets="Build" BuildInParallel="true" Condition="'

作为包含许多项目的解决方案的一部分,我有一个项目可以引用(通过
解决方案中的其他三个项目,以及其他一些项目)。在
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" />

然而,我遇到了这个问题。
我的,即我有:

    <ItemGroup>
        <DependentAssemblies Include="
            ..\ProjectA\bin\$(Configuration)\ProjectA.dll;
            ..\ProjectB\bin\$(Configuration)\ProjectB.dll;
            ..\ProjectC\bin\$(Configuration)\ProjectC.dll">
        </DependentAssemblies>
    </ItemGroup>

然而,这将在TeamBuild下中断(所有输出都在一个目录中结束),并且如果依赖项目的任何输出的名称发生更改

编辑:还可以查看关于如何使硬编码稍微比以下内容更简洁的答案是否更简洁的评论:

    <PropertyGroup>
        <_TeamBuildingToSingleOutDir Condition="'$(TeamBuildOutDir)'!='' AND '$(CustomizableOutDir)'!='true'">true</_TeamBuildingToSingleOutDir>
    </PropertyGroup>

真的
以及:


如果您首先调用这样的目标,则可以在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>

您的原始解决方案只需更改

Targets="Build"


GetTargetPath
目标只返回
TargetPath
属性,不需要构建。

如我在评论中所述,对引用的项目调用GetTargetPath只返回该项目的主输出程序集。要获取引用项目的所有引用副本本地程序集,这有点混乱

将以下内容添加到要获取其副本的引用的每个项目中:

    <Target
    Name="ComputeCopyLocalAssemblies"
    DependsOnTargets="ResolveProjectReferences;ResolveAssemblyReferences"
    Returns="@(ReferenceCopyLocalPaths)" /> 

我的特殊情况是,我需要在顶级主机项目的bin文件夹中为System.AddIn重新创建管道文件夹结构。这有点混乱,我对MSDN建议的解决方案不满意,因为这会破坏我们的构建服务器,并阻止在不同的项目中创建文件夹结构(例如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>

%(ProjectReference.PipelineFolder)
%(ProjectReference.PipelineFolder)
我还需要将所需的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>

{FFCD0BFC-5A7B-4E13-9E1B-8D01E86975EA}
Dogs.Pipeline.AddInSideAdapter
假的
管道\AddInSidAdapter\

首先,感谢/祝贺您选择这个谜题作为您的第一个答案!我个人对于引入对这种性质的MSBuild内部的依赖性持保留态度,但是是的,这肯定会允许我以编程方式实现“它写了什么文件”!这里还有一些其他的“计算输出”问题,以便更好地回答这些问题。对我来说,这里的主要问题是,
+1谢谢,无法验证它是否确实有效(从那时起,桥下有很多水!),但似乎是一种非常可行和干净的方法。YMMV:GetTargetPath只返回项目的主输出程序集(.DLL/.EXE)如果您希望该项目的所有引用程序集都需要不同的技术,那么任何其他人都需要为C++项目做这个操作,正确的目标调用是目标=“GETNATIVETARGETPATH”。
<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>
    <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>