Is there a .NET Core CLI pre before build task?

2020-02-23 08:35发布

问题:

The question

I assume when msbuild is about to compile source code files, it produces a list of files to build, based on Include and Exclude rules of the project.

Is there a way to execute a task before the list of files to compile is evaluated ?

This is to be able to generate a source code file and get it to be taken in the build.

Current research and trials

I'm making a .NET Core CLI tool that has to be run before the build of the project that is using it, because it (the CLI tool) generates a file that has to be included in the build.

The project is created with the new .csproj system, not the old project.json one.

Along with my .NET Core CLI tool project, I created a library project for testing purpose.

If I add this in the .csproj of the testing library:

<ItemGroup>
    <DotNetCliToolReference Include="MyCliTool" Version="x.x.x" />
    <!-- here x.x.x is just a placeholder -->
</ItemGroup>

<Target Name="MyCliToolTarget" AfterTargets="Restore" BeforeTargets="BeforeBuild">
    <Exec Command="dotnet my-cli-tool" />
</Target>

then the file generated by the CLI tool is not taken into account in the compilation if it didn't exist before. That means when the file exists, it is OK but it also means the very first build (after a clone, cleanup or what) will always fail.

I tried several different targets for BeforeTargets but I couldn't find a way to make it work. I tried to set my target in the InitialTargets of the Project node, but it didn't work either. I tried to set the Outputs property of the Target node to the filename generated by the CLI tool, but same, it fails.

The only solution I found that worked is to manually add a Compile directive, as follow:

<ItemGroup>
    <Compile Include="MyGeneratedFile.cs" />
</ItemGroup>

This solution is fine for the moment, but the filename may change based on the CLI tool options, and this would make two places where a filename would have to be updated on change, like the following:

<ItemGroup>
    <Compile Include="PATH\TO\CUSTOM_FILENAME_HERE.CS" />
    <DotNetCliToolReference Include="MyCliTool" Version="x.x.x" />
</ItemGroup>

<Target Name="MyCliToolTarget" AfterTargets="Restore" BeforeTargets="BeforeBuild">
    <Exec Command="dotnet my-cli-tool --output PATH\TO\CUSTOM_FILENAME_HERE.CS" />
</Target>

(see CUSTOM_FILENAME_HERE.CS appears twice)

I know I could use a constant, as follow:

<PropertyGroup>
    <MyFilename>PATH\TO\CUSTOM_FILENAME_HERE.CS</MyFilename>
</PropertyGroup>

<ItemGroup>
    <Compile Condition="!Exists('$(MyFilename)')" Include="$(MyFilename)" />
    <DotNetCliToolReference Include="MyCliTool" Version="x.x.x" />
</ItemGroup>

<Target Name="MyCliToolTarget" AfterTargets="Restore" BeforeTargets="BeforeBuild">
    <Exec Command="dotnet my-cli-tool --output $(MyFilename)" />
</Target>

but I'm not satisfied with this approach, it makes things way too complicated for a lambda user to integrate, assuming that this is still a simplified version, because the CLI tool can take several other options, meaning having other variables, bla bla bla.

I'm using .NET Core SDK 1.0.1.

Sorry for the lengthy question and my noobness with msbuild.

Thanks in advance for your time and help.




Side note: calling dotnet my-cli-tool in pre-build event, as in:

<PropertyGroup>
    <PreBuildEvent>dotnet my-cli-tool</PreBuildEvent>
</PropertyGroup>

doesn't work at all, I get the following error:

Code: MSB3073 Description: The command "dotnet my-cli-tool" exited with code 1. Project: the testing library project, not the tool one File: C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\Microsoft.Common.CurrentVersion.targets Line: 4935

Though, the following works fine:

<PropertyGroup>
    <PreBuildEvent>echo meh</PreBuildEvent>
</PropertyGroup>

so this is not a bug with the pre build events.

Anyway this is another story which I don't care much about for the moment.

回答1:

The "default items" as the .NET SDK calls it are part of the static evaluation of the project file - before any target is run. So you'll need a target that is run before the @(Compile) items are needed.

The trick is to include files added to the filesystem after the custom tool is run. This can be done by re-scanning all files and excluding those already part of the project inside a target that is run before the build:

  <Target Name="GenerateSomeFiles" BeforeTargets="BeforeBuild">
    <Exec Command="dotnet my-tool" />
    <ItemGroup>
      <Compile Include="**/*$(DefaultLanguageSourceExtension)"
               Exclude="$(DefaultItemExcludes);$(DefaultExcludesInProjectFolder);$(BaseIntermediateOutputPath)**;$(BaseOutputPath)**;@(Compile)" />
    </ItemGroup>
  </Target>