7

作为包含许多项目的解决方案的一部分,我有一个引用的项目(通过<ProjectReference>解决方案中的其他三个项目,以及其他一些项目)。在 中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文件存放在 ProjectC 的obj/Debug文件夹中,然后触发 ProjectC 重新构建AfterBuild,而如果从构建中排除此项目,则 [正确] 不会触发 ProjectC 的重建(关键是 ProjectC 的所有依赖项的重建)。在这种情况下,这可能是特定于 VS 的诡计,不会在 TeamBuild 或其他命令行 MSBuild 调用的上下文中发生(但最常见的用法是通过 VS,因此我需要以任何一种方式解决此问题)

依赖项目(以及一般的解决方案的其余部分)都是用 VS 交互创建的,因此ProjectRefences 包含相对路径等。我已经看到提到这可能会导致问题 - 但没有完整解释原因,或者何时修复或如何解决它。换句话说,我对例如ProjectReference通过手动编辑.csproj 将路径转换为绝对路径并不感兴趣。

虽然完全有可能我在做一些愚蠢的事情,并且有人会立即指出它是什么(这会很棒),但请放心,我已经花了很多时间研究/v:diag输出等(尽管我没有尝试从从头开始 - 这是在相对复杂的整体构建的背景下)

4

4 回答 4

8

正如我在评论中所指出的,在引用的项目上调用 GetTargetPath 只会返回该项目的主输出程序集。要获取引用项目的所有引用的复制本地程序集,它有点混乱。

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

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

我的特殊情况是我需要在我的顶级主机项目的 bin 文件夹中为 System.AddIn 重新创建 Pipeline 文件夹结构。这有点混乱,我对 MSDN 建议的使用 OutputPath 的解决方案不满意 - 因为这会在我们的构建服务器上中断并阻止在不同的项目(例如 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>
于 2014-01-08T16:01:56.203 回答
6

您的原始解决方案应该只需更改即可

Targets="Build"

Targets="GetTargetPath"

GetTargetPath目标只是返回属性并且TargetPath不需要构建。

于 2010-09-27T05:28:05.957 回答
2

如果您首先调用这样的目标,则可以保护您在 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>
于 2010-09-16T16:12:45.947 回答
1

目前的解决方法是基于这个 SO question,即我有:

    <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>

和:

    <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>
于 2010-02-24T11:41:16.153 回答