Max Toro
Max Toro

Reputation: 28608

Include compile file (incremental build)

I have an incremental build setup as follows:

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

The target produces a single .cs file $(MyOutput) that needs to be included and compiled in the current project. Note in the target I'm first removing then including this file.

  1. On first build, the target runs as expected.
  2. On second build, the target does not run, as expected. But MSBuild still compiles because $(MyOutput) "has been modified since the last up-to-date check".
  3. On third build, everything is up-to-date.

How can I get an "everything is up-to-date" result on the second build?

Upvotes: 6

Views: 695

Answers (1)

stijn
stijn

Reputation: 35901

Including the file unconditionally should work. For example with these lines in a default C# project template right after the Microsoft.CSharp.targets Import:

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

The results of running msbuild /v:d on the commandline are (showing only output for MyCodeGen Target):

Output of first run (Generated.cs does not exist yet):

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

Output of second/third/... run:

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

Output of build after modifying 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".

Output of runs after this:

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

If this doesn't work for you, it could be for instance that the process you use to generate the file doesn't play well with msbuild in that it doesn't update the timestamp correctly. E.g. just copying an existing file which is older than the project file would lead to Generated.cs having a timestamp older than the $(MSBuildThisFileFullPath) input so the target would just always run.

Upvotes: 1

Related Questions