NuGet Packaging: CLI Tool Packages

How to package and install the CLI tools

Dec 8, 2023

Engineering

Nuget Packaging: CLI Tool Packages
Nuget Packaging: CLI Tool Packages

NuGet Packaging Series

This article is part of the Creating NuGet Packages series. You can find the complete list of articles in the series at the end.


Introduction

So, you have developed a command line tool, and now you want to distribute it in a way that is convenient for your end users. You also want to ensure the installation process is smooth, whether done manually or through an installation script in a CI/CD pipeline. Luckily, .NET provides a great solution for both of these challenges.

I've developed a basic CLI tool for this exercise that computes the nth zero-based Fibonacci number. The final outcome in the terminal will be a straightforward command fibonacci with the input argument --iterations, which takes the number of iterations to calculate.

Once installed, one can open up the terminal and call it: fibonacci --iterations 3 which would produce:

PS C:\Users\User> fibonacci --iterations 3 
The Fibonacci number for 3 iterations is 2

Said that, let's dive into producing such a tool...


Setting Up the Project for Tool Packaging

The 99% of packaging is done the same way for regular projects, as described in the first article. There are only three extra properties needed to pack a tool:

PackAsTool

This boolean property tells the packager that this project is a CLI tool and to package it that way. Simple as that.

ToolCommandName

The <ToolCommandName> property is an optional component that allows you to specify the command that will be used to invoke the tool after it has been installed.

If this element is not provided, the default command name for the tool will be the assembly name. The assembly name is typically the project file name without the .csproj extension.

PackageOutputPath

The <PackageOutputPath> is an optional property that specifies the location where the NuGet package will be generated. In our case, the package will be created in the ./nupkgs directory.


All Together

Putting it all together, your .csproj file should look something like this (note that for this example, I used the CommandLineParser library for easier parsing of passed arguments):

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net7.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>

        <!-- NUGET PACKAGE SETTINGS -->
        <IsPackable>true</IsPackable>
        <PackageId>Packaging.Tools</PackageId>
        <Title>Example Library for NuGet Packaging of dotnet tools.</Title>
        <Version>1.0.0</Version>
        <Authors>Dinko Pavicic</Authors>
        <Description>Example NuGet package for NuGet packaging dotnet tool projects.</Description>
        <Copyright>Copyright (c) Dinko Pavicic 2024</Copyright>
        <PackageProjectUrl>https://dinkopavicic.com</PackageProjectUrl>
        <RepositoryUrl>https://github.com/dpavicic/Research-Nuget-Packaging/tree/master/Packaging-Generators</RepositoryUrl>
        <RepositoryType>git</RepositoryType>
        <PackageReadmeFile>README.md</PackageReadmeFile>
        <PackageLicenseExpression>MIT</PackageLicenseExpression>
        <PackageTags>nuget, packaging, tool, example</PackageTags>
        <PackageReleaseNotes>Version 1.0.0: First version of example library for NuGet packaging dotnet tools.</PackageReleaseNotes>
        <PackageIcon>package_icon.png</PackageIcon>
        
        <!-- IMPORTANT PART FOR TOOL PACKAGING -->
        
        <!-- Set as tool -->
        <PackAsTool>true</PackAsTool>
        
        <!-- 
            The <ToolCommandName> property is an optional component that allows you to specify the command 
            that will be used to invoke the tool after it has been installed.
            If this element is not provided, the default command name for the tool will be the assembly name. 
            The assembly name is typically the project file name without the .csproj extension. 
         -->
        <ToolCommandName>fibonacci</ToolCommandName>
        
        <!--
            An Optional element that determines where the NuGet package will be produced.
        -->
        <PackageOutputPath>./nupkg</PackageOutputPath>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="CommandLineParser" Version="2.8.0" />
    </ItemGroup>

    <!-- Include package assets -->
    <ItemGroup>
        <None Include="README.md" Pack="true" PackagePath="\"/>
        <None Include="images\package_icon.png" Pack="true" PackagePath="\"/>
    </ItemGroup>

</Project>


Tool Installation and Uninstallation

Installation

Once the NuGet package tool is created, it can be installed. .NET offers two methods for installing tools: local and global. In this example, we will install the tool globally, automatically adding it to the PATH environment variable and making it accessible globally.

This can be achieved with a simple command used in the project root:

dotnet tool install --global --add-source ./nupkg Packaging.Tools

After this, your tool will be available to use globally. In our case, and as previously demonstrated, like this:

PS C:\Users\User> fibonacci --iterations 3 

The Fibonacci number for 3 iterations is 2

The Parameters

global

--global parameter tells the installer to install the CLI tool package globally

add-source

The --add-source parameter tells the .NET CLI to use the ./nupkg directory as the source feed for NuGet packages where to find the Packaging.Tools library and install the tool from it.

If omitted, it will try to search the Nuget.org site for the tool library with that ID.


Uninstallation

To uninstall the installed global tool, write this line providing the package ID of the tool (NOT the tool name):

dotnet tool uninstall -g Packaging.Tools


Additional

I have included in the solution's root directory both .bat and .sh scripts for packaging, installing, and uninstalling the example CLI tool. These scripts will help streamline the process and make interacting with the example tool easier.

You can run the package.bat script on Windows or package.sh on Unix-based systems. These scripts will generate the NuGet package that can be distributed and installed on other machines.

Once the package is created, you can use the install.bat script on Windows or the install.sh script on Unix-based systems to install the CLI tool. This script will handle the installation process, including any dependencies or configurations required.

If you need to remove the CLI tool from your system, you can use the uninstall.bat script on Windows or the uninstall.sh script on Unix-based systems. Running these scripts will cleanly remove all traces of the tool from your machine.


Other Things to Consider

RID

RID, short for runtime identifier, is used to identify the target platforms an application runs on. These identifiers are crucial for .NET packages as they help represent platform-specific assets in NuGet packages. Examples of RIDs include linux-x64, win-x64, and osx-x64.

In the case of packages with native dependencies, the RID specifies the platforms on which the package can be restored.

.NET Runtime Identifier (RID) catalog - .NET | Microsoft Learn


What Have We Learned?

As you see, creating CLI tools and packaging them as NuGets is really easy and straightforward in .NET. In this article, we learned:

  • How to prepare the CLI project to be packed as a NuGet package

  • Packing, Installing, and Uninstalling

  • Using the globally installed .NET tool

Example Project on GitHub

An example project for this article can be found here:

Workshop-Nuget-Packaging

Creating NuGet Packages Series

This article is part of the Creating NuGet Packages series. If you enjoyed this one and want more, here is the complete list in the series:

Happy Coding!

More Articles

Thanks
for Visiting

.. and now that you've scrolled down here, maybe I can invite you to explore other sections of this site

Thanks
for Visiting

.. and now that you've scrolled down here, maybe I can invite you to explore other sections of this site