TL;DR:
Nate McMaster’s post provides a really nice and detailed explanation on how to configure your csproj
to build your app as a tool.
1 | <PropertyGroup> |
Build with dotnet pack
(creates NuGet package to be published)
Nice to test by setting up your own NuGet.Server
Configuration
The above is quite simple and works as expected. However, some projects might require more configuration.
I wanted to package an existing console app as a .NET global tool, whilst still keeping the current console apps (multi-target) capabilities. So my original configuration was the standard multi-target specification:
1 | <PropertyGroup> |
After adding the <PackAsTool>true</PackAsTool>
to this configuration the NuGet package failed to build, stating that …Microsoft.NET.PackTool.targets(32,5): error NETSDK1054: only supports .NET Core…
I really didn’t want to create a new project, so I eventually got the NuGet package building by splitting out the PackAsTool
into a specific .NET Core 2.1 PropertyGroup
:1
2
3
4
5
6
7<PropertyGroup>
<TargetFrameworks>netcoreapp2.1;net472</TargetFrameworks>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<PackAsTool>true</PackAsTool>
</PropertyGroup>
The package now contained the console in their respective framework’s lib
folder, as well as the netcoreapp2.1
in the tools
folder. This package was hosted successfully, but could NOT be installed.
The final result was to build separate NuGet packages. This also meant giving them different names. So I just used a random made-up condition (GlobalTool
) and kept the original PropertyGroup
with its default name, and specifying my tool with an explicit netcoreapp.1
framework a new PackageId
:1
2
3
4
5
6
7
8
9<PropertyGroup Condition="'$(GlobalTool)' != true">
<TargetFrameworks>netcoreapp2.1;net472</TargetFrameworks>
</PropertyGroup>
<PropertyGroup Condition="'$(GlobalTool)' == true">
<PackAsTool>true</PackAsTool>
<TargetFramework>netcoreapp2.1</TargetFramework>
<PackageId>Foo-Tool</PackageId>
</PropertyGroup>
Note the use of
Condition="'$(GlobalTool)' != true"
instead ofCondition="'$(GlobalTool)' == false"
made my default build & pack use thatPropertyGroup
.
It’s also possible to add (override) any other NuGet metadata properties (i.e. Description) in these sections.
Now my pack process looks like this:
dotnet pack
dotnet pack /p:GlobalTool=true
This results in 2 NuGet packages, which are both uploaded and can be installed independently. You can then install the global tool with dotnet tool install -g foo-tool
Because the ToolCommandName
defaults to the name of the assembly name, you are still able to use it with foo
.
Conclusion
I was pleasantly surprised with the flexibility of the csproj
configuration. Making this possible took some digging and experimentation, but good exercise to step outside of the standard tooling.
It’s still very early for the .NET Core global tools, but still awesome and can see this being widely adopted soon.