【问题标题】:Include compile file (incremental build)包含编译文件(增量构建)
【发布时间】:2021-11-22 14:01:45
【问题描述】:

我有如下增量构建设置:

  <ItemGroup>
    <MyInput Include="$(MyInput)" />
    <UpToDateCheckInput Include="@(MyInput)" />
    <UpToDateCheckBuilt Include="$(MyOutput)" />
  </ItemGroup>

  <Target Name="MyCodeGen"
          BeforeTargets="PreBuildEvent"
          DependsOnTargets="ResolveReferences"
          Inputs="@(MyInput);$(MSBuildThisFileFullPath);$(MyCodegenExe)"
          Outputs="$(MyOutput)">
    <Exec Command="$(MyCodegenExe) $(MyCodegenParams)" />
    <ItemGroup>
      <Compile Remove="$(MyOutput)" />
      <Compile Include="$(MyOutput)" />
    </ItemGroup>
  </Target>

目标生成单个 .cs 文件 $(MyOutput),需要在当前项目中包含和编译该文件。请注意我首先删除的目标,然后包含此文件。

  1. 首次构建时,目标按预期运行。
  2. 在第二次构建时,目标未按预期运行。但是 MSBuild 仍然可以编译,因为 $(MyOutput)“自上次最新检查以来已被修改”。
  3. 在第三次构建时,一切都是最新的。

如何在第二次构建时获得“一切都是最新的”结果?

【问题讨论】:

  • 您是否有必要删除然后包含 $(MyOutput) 文件?如果你删除这个“Remove-Include”项目组,它会起作用吗?
  • @Tianyu 如果我不包含,那么 $(MyOutput) 不会在第一次构建时编译。如果我不删除,那么我会收到关于 $(MyOutput) 在第二个构建中包含两次的警告。
  • 如果您的目标始终运行,您可以将 ItemGroup 移出目标并始终包含输出。
  • @stijn 它并不总是运行,只有在相关文件发生变化时才会运行。
  • @MaxToro 您是否将文件生成到$(IntermediateOutputDirectory) 或 glob 模式会选择 .cs 文件的地方(并在 VS 解决方案资源管理器中显示它们)?如果是这样,几乎没有什么可以做的(有一个 _GenerateCompileDependencyCache 私有目标捕获项目中的所有编译项,这将在第一次和第二次构建期间发生变化)

标签: visual-studio msbuild csproj


【解决方案1】:

无条件包含该文件应该可以工作。例如,在 Microsoft.CSharp.targets 导入之后的默认 C# 项目模板中使用这些行:

<ItemGroup>
  <MyInput Include="in.txt" />
</ItemGroup>
<PropertyGroup>
  <MyOutput>Generated.cs</MyOutput>
</PropertyGroup>
<Target Name="MyCodeGen" BeforeTargets="PreBuildEvent" DependsOnTargets="ResolveReferences"
        Inputs="@(MyInput);$(MSBuildThisFileFullPath)"
        Outputs="$(MyOutput)">
  <ReadLinesFromFile File="@(MyInput)">
    <Output TaskParameter="Lines" ItemName="MyInputlines"/>
  </ReadLinesFromFile>
  <Message Text="CODEGEN lines @(MyInputlines)" />
  <WriteLinesToFile File="$(MyOutput)" Lines="@(MyInputlines)" Overwrite="True" />
</Target>
<ItemGroup>
  <Compile Include="$(MyOutput)"/>
</ItemGroup>

在命令行上运行 msbuild /v:d 的结果是(仅显示 MyCodeGen Target 的输出):

第一次运行的输出(Generated.cs 还不存在):

Target "MyCodeGen" in project "my.csproj" (target "PreBuildEvent" depends on it):
Building target "MyCodeGen" completely.
Output file "Generated.cs" does not exist.
Using "ReadLinesFromFile" task from assembly "Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".
Task "ReadLinesFromFile"
Done executing task "ReadLinesFromFile".
Task "Message"
  CODEGEN lines using System;
Done executing task "Message".
Using "WriteLinesToFile" task from assembly "Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".
Task "WriteLinesToFile"
Done executing task "WriteLinesToFile".
Done building target "MyCodeGen" in project "my.csproj".

第二次/第三次/...运行的输出:

Target "MyCodeGen" in project "my.csproj" (target "PreBuildEvent" depends on it):
Skipping target "MyCodeGen" because all output files are up-to-date with respect to the input files.
Input files: in.txt;my.csproj
Output files: Generated.cs
Done building target "MyCodeGen" in project "my.csproj".

修改in.txt后生成的输出:

Target "MyCodeGen" in project "my.csproj" (target "PreBuildEvent" depends on it):
Building target "MyCodeGen" completely.
Input file "in.txt" is newer than output file "Generated.cs".
Using "ReadLinesFromFile" task from assembly "Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".
Task "ReadLinesFromFile"
Done executing task "ReadLinesFromFile".
Task "Message"
  CODEGEN lines using System.Linq;
Done executing task "Message".
Using "WriteLinesToFile" task from assembly "Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a".
Task "WriteLinesToFile"
Done executing task "WriteLinesToFile".
Done building target "MyCodeGen" in project "my.csproj".

在此之后运行的输出:

Target "MyCodeGen" in project "my.csproj" (target "PreBuildEvent" depends on it):
Skipping target "MyCodeGen" because all output files are up-to-date with respect to the input files.
Input files: in.txt;my.csproj
Output files: Generated.cs
Done building target "MyCodeGen" in project "my.csproj".

如果这对您不起作用,例如,您用于生成文件的过程可能无法与 msbuild 配合使用,因为它没有正确更新时间戳。例如。仅复制比项目文件更早的现有文件会导致 Generated.cs 的时间戳早于 $(MSBuildThisFileFullPath) 输入,因此目标将始终运行。

【讨论】:

  • 建议将MyOutput 添加到DefaultItemExcludes,这样就不会有重复的项目——当有人使用属性页手动编辑项目系统时,这可能会在一些奇怪的情况下混淆项目系统。
  • 在第二次构建时跳过 MyCodeGen 没有问题。问题是 CoreCompile 没有被跳过。但是我发现这个问题只发生在 VS 中,而不是从命令行调用 MSBuild 时发生。
  • @MartinUllrich DefaultItemExcludes 作为删除输出文件的替代方法。
  • 我无法重现;当 Generated.cs 不存在时在 VS 中构建调用 MyCodeGen/CoreCompile/... VS 中的第二次构建仅输出 ========== Build: 0 succeeded, 0 failed, 1 up-to-date, 0 skipped ========== 即实际构建没有启动。您是否使用了上述代码?您的代码生成与所示的虚拟实现有什么不同吗?
  • 你在用&lt;Project Sdk="Microsoft.NET.Sdk"&gt;吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多