How to run dotnet tool in prebuild phase of an sdk project

751 Views Asked by At

As part of my build process I want to run a dotnet tool before the compile.

I can add this section to my sdk project file:

  <ItemGroup>
    <PackageDownload Include="MyTool" Version="[1.0.1]" />
  </ItemGroup>

Then the tool is downloaded and is available inside:

  \Users\me\.nuget\packages\MyTool\1.0.1\tools\netcoreapp3.1\any\

I can then add a prebuild target like this:

  <Target Name="PreBuild" BeforeTargets="CoreCompile">
    <Exec Command="dotnet C:\Users\me\.nuget\packages\MyTool\1.0.1\tools\netcoreapp3.1\any\MyTool.dll <MyOptions> />
  </Target>

This works, but obviously I do not want absolute references to my user profile (or version) in the path.

Is there a way to substitute path with an environment variable?

I have tried adding GeneratePathProperty="true" to the PackageDownload but $(PkgMyTool) is undefined.

I also tried referencing the tool with <PackageReference> but this fails due to SDK incompatibility. My Tool is netcore3.1 and this project is netstandard2.0.

3

There are 3 best solutions below

1
On BEST ANSWER

As you learned, PackageDownload doesn’t yet support GeneratePathProperty. Here are a couple of workarounds you could try, though:

  • Declare your build-time dependency using PackageReference (not PackageDownload) and use PrivateAssets/ExcludeAssets to control what happens with the package’s contents. Example:
    <ItemGroup>
        <PackageReference Include="MyTool" Version="1.2.3">
            <PrivateAssets>all</PrivateAssets>
            <ExcludeAssets>all</ExcludeAssets>
            <GeneratePathProperty>true</GeneratePathProperty>
        </PackageReference>
    </ItemGroup>
    
    <Target Name="RunMyTool">
        <!-- PkgMyTool should be available here -->
        <Exec Command="$(PkgMyTool)\tools\MyTool.exe" />
    </Target>
    
    This does restrict you to using one version of the package per project.
  • Use PackageDownload and address the tool relative to the NugetPackageRoot:
    <ItemGroup>
        <PackageDownload Include="MyTool" Version="1.2.3" />
    </ItemGroup>
    
    <Target Name="RunMyTool">
        <Exec Command="$(NugetPackageRoot)\mytool\1.2.3\tools\MyTool.exe" />
    </Target>
    
    ($(NugetPackageRoot) should resolve to C:\Users\me\.nuget\packages on your machine - it should be defined in the generated nuget.g.props file if you wanna confirm that.) Off the top of my head I think there may be certain niche configurations in which PackageDownloads get installed somewhere other than the NugetPackageRoot location? Not sure. It’s a little tedious to mention the package version twice, of course.
  • If your tool was packaged as a dotnet tool, you can declare the dependency in a tool manifest. (Tool manifests are designed to be checked in to your repository.) Then it should get installed during restore, after which you can run it via dotnet.
    // dotnet-tools.json
    {
        "tools": {
            "myTool": {
                "version": "1.2.3",
                "commands": ["myTool"]
            }
        }
    }
    
    <!-- MyProject.csproj -->
    <Target Name="RunMyTool">
        <Exec Command="dotnet tool run myTool" />
    </Target>
    
    This requires the package to have been authored as a tool package, of course. (Seems like in your case it was, so this is probably the best option for you.)

All three of these have worked for me in the past.

1
On

You can use only the macros provided by the framework. You can find them here. Almost all of them are referring to the relative path of your project. I suggest you to copy your tool inside a project folder and you can make use of these macros.

0
On

The best solution I had success with thus far is this:

  <Target Name="PreBuild" BeforeTargets="CoreCompile">
    <Exec Command="dotnet tool update MyTool --tool-path=$(TargetDir)\tools --version=1.0.1" />
    <Exec Command="$(TargetDir)\tools\MyTool <MyOptions> />
  </Target>

And it DOES save me from the nitty gritty details like \netcoreapp3.1\any but it does not reuse the tool from NuGet cache, meaning it has to be downloaded on every build.

I still hope someone will provide a better answer.