MSBuild复制动态生成的文件作为项目依赖的一部分

22

我有一个自定义的 msbuild 任务,正在将一些输出文件生成到 ProjectA 的输出目录 ($(TargetDir)) 中。当前的代码大致如下:

<MyCustomTask ...>
   <Output TaskParameter="OutputFiles" ItemName="FileWrites"/>
</MyCustomTask>

一个项目B引用了项目A,但问题是在构建项目B时,由MyCustomTask生成的文件未被复制到项目B的输出目录中。

我们如何使用MSBuild将动态生成的附加文件作为项目依赖项一起复制?


这里有一个类似的问题,结果采用了稍微不同的方法 https://stackoverflow.com/q/44752139/165500 ,对于不涉及自定义任务的情况可能会很方便。 - Andrew Russell
5个回答

15
我终于成功地实现了从项目B自动复制,而不必修改它。IIya离解决方案并不远,但事实上,使用MyCustomTask生成的文件列表是动态的,因此无法静态生成。在更深入地研究Microsoft.Common.targets后,我发现ProjectB将通过调用目标GetCopyToOutputDirectoryItems项目A获取输出列表。该目标依赖于AssignTargetPaths,后者又依赖于目标列表属性AssignTargetPathsDependsOn
因此,为了动态生成内容并使其通过标准项目依赖关系自动复制,我们需要在两个不同的位置挂钩项目A
1. 在AssignTargetPathsDependsOn中,因为它被项目B通过GetCopyToOutputDirectoryItems间接调用项目A。当调用PrepareResource时,项目A也会间接调用它。在这里,我们只输出将要生成(由项目A)或由项目B消耗的文件列表。AssignTargetPathsDependsOn将调用一个名为MyCustomTaskList的自定义任务,该任务仅负责输出文件列表(但不生成文件),这个文件列表将使用CopyOutputDirectory创建动态的“内容”。
2. 在BuildDependsOn中,以便在项目A中实际生成内容。这将调用MyCustomTask来生成内容。
所有这些都是在ProjectA中设置的。
<!-- In Project A -->

<!-- Task to generate the files -->
<UsingTask TaskName="MyCustomTask" AssemblyFile="$(PathToMyCustomTaskAssembly)"/>

<!-- Task to output the list of generated of files - It doesn't generate the file -->
<UsingTask TaskName="MyCustomTaskList" AssemblyFile="$(PathToMyCustomTaskAssembly)"/>

<!-- 1st PART : When Project A is built, It will generate effectively the files -->
<PropertyGroup>
  <BuildDependsOn>
    MyCustomTaskTarget;
    $(BuildDependsOn);
  </BuildDependsOn>
</PropertyGroup>

<Target Name="MyCustomTaskTarget">
  <!-- Call MyCustomTask generate the files files that will be generated by MyCustomTask -->
  <MyCustomTask
      ProjectDirectory="$(ProjectDir)"
      IntermediateDirectory="$(IntermediateOutputPath)"
      Files="@(MyCustomFiles)"
      RootNamespace="$(RootNamespace)"
      >
  </MyCustomTask>
</Target>

<!-- 2nd PART : When Project B is built, It will call GetCopyToOutputDirectoryItems on ProjectA so we need to generate this list when it is called  -->
<!-- For this we need to override AssignTargetPathsDependsOn in order to generate the list of files -->
<!-- as GetCopyToOutputDirectoryItems  ultimately depends on AssignTargetPathsDependsOn -->
<!-- Content need to be generated before AssignTargets, because AssignTargets will prepare all files to be copied later by GetCopyToOutputDirectoryItems -->
<!-- This part is also called from ProjectA when target 'PrepareResources' is called -->
<PropertyGroup>
  <AssignTargetPathsDependsOn>
    $(AssignTargetPathsDependsOn);
    MyCustomTaskListTarget;
  </AssignTargetPathsDependsOn>
</PropertyGroup>

<Target Name="MyCustomTaskListTarget">

  <!-- Call MyCustomTaskList generating the list of files that will be generated by MyCustomTask -->
  <MyCustomTaskList
      ProjectDirectory="$(ProjectDir)"
      IntermediateDirectory="$(IntermediateOutputPath)"
      Files="@(MyCustomFiles)"
      RootNamespace="$(RootNamespace)"
      >
      <Output TaskParameter="ContentFiles" ItemName="MyCustomContent"/>
  </MyCustomTaskList>

  <ItemGroup>
    <!--Generate the lsit of content generated by MyCustomTask -->
    <Content Include="@(MyCustomContent)" KeepMetadata="Link;CopyToOutputDirectory"/>
  </ItemGroup>
</Target>

这种方法适用于使用Common.Targets的任何C#项目(因此它适用于纯桌面、WinRT XAML应用程序或Windows Phone 8项目)。


1
请注意,AssignTargetPathsDependsOn技巧已被.NET SDK破坏,请参见此问题以获取帮助。 - ForNeVeR

4

似乎这样可以工作,要么手动将其包含到ProjectA的.csproj文件中(请注意,VS偶尔会将通配符解析为绝对路径并覆盖.csproj文件),要么由自定义任务动态注入。此外,因为VS在打开时缓存项目组,所以如果已经删除了某些文件,则可能不会复制这些文件或构建失败。在这种情况下,需要重新加载项目或重启VS才能重新评估项目组。MSBuild、TFS等应该总是可以正常工作。

<ItemGroup>
  <Content Include="$(TargetDir)\*.txt">
    <Link>%(Filename)%(Extension)</Link>
    <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
  </Content>
</ItemGroup>

不是很远,但我不能像你那样静态生成这个列表。请看我的答案。 - xoofx

1

如果您已经使用MSBuild自己构建,请添加Copy Task以便自己传输文件?


我猜问题是如何配置任务,使其实际将文件复制到ProjectB的目标中。或者更可能的是以某种方式标记ProjectA文件夹中的文件,以便ProjectB类似于引用和其他文件一样使用“始终复制到输出目录”。 - Alexei Levenkov
@Wolfwyrd,正如Alexei所说,您的解决方案意味着我必须修改ProjectB才能执行复制,但我想避免这种情况。 "FileWrites"似乎仅用于在使用“Clean”目标时跟踪文件,但不用于注册输出。 Microsoft.Common.targets文件似乎执行一些特定操作以复制依赖项,并且不清楚它是否可扩展以处理此操作。 - xoofx
@xoofx - 所以ProjectB在某个时候包含了你对MyCustomTask的调用?为什么不能在MyCustomTask之后的同一个目标中放置一个<Copy>任务调用呢?或者为什么不直接在MyCustomTask的代码中通过你喜欢的.NET复制API进行复制呢? - Jason Malinowski
1
项目B包含对项目A的常规ProjectReference依赖项。但是,项目B并不知道项目A实际在做什么(是否为MyCustomTask)。解决方案需要在msbuild中进行更多的挂钩,请参见我的答案。 - xoofx

1
如果我在目标中放置了<None Link="..." />,我就能够在我的基于SDK的项目中处理输出,而不会出现在其中。此外,引用该项目的其他项目也会将其作为输出的一部分。
例如:
    <ItemGroup>
        <WebPackBuildOutput Include="..\..\WebPackOutput\dist\**\*" />
    </ItemGroup>

    <Target Name="WebPackOutputContentTarget" BeforeTargets="BeforeBuild">
        <Message Text="Output dynamic content: @(WebPackBuildOutput)" Importance="high"/>
        <ItemGroup>
            <!-- Manually constructing Link metadata, works in classic projects as well -->
            <None Include="@(WebPackBuildOutput)" Link="dist\%(RecursiveDir)%(Filename)%(Extension)" CopyToOutputDirectory="PreserveNewest" />
        </ItemGroup>        
    </Target>

0

据我理解,您想通过在 ProjectB.msbuild 中仅编写此行来添加其他步骤:

<Import Project="ProjectA.msbuild" />

要实现它,您可以在ProjectA中编写类似以下的代码:

<PropertyGroup>
  <BuildDependsOn>$(BuildDependsOn);MyCustomTask</BuildDependsOn>
</PropertyGroup>

这将把你的任务添加到构建任务的依赖列表中。

有关详细信息,请参阅以下问题: StyleCop MS Build magic? Who is calling the StyleCop target?


4
不是很想这样做。我想在项目A中动态生成一些内容,然后由项目B复制,而无需修改项目B。项目B只有一个ProjectReference依赖项,但它不必知道项目A正在动态生成某些内容。 - xoofx

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接