Search code examples
.net-coresdkmsbuildnugetpackagereference

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


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.


Solution

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