Tool and Template Packages
|
This section documents MSBuild 18.9, the .NET SDK 10.0.402 (with its This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The C# Reference bibliography lists the reference material consulted while preparing these pages. |
Two kinds of packages carry something other than a library: .NET tools (console programs installed and run by the
dotnet CLI) and template packages (dotnet new templates). Both are ordinary .nupkg files with a package type,
pushed to the same feeds. This page packs Ledgerline.Cli and Ledgerline.Templates, pushes the tool to the local
BaGetter feed and runs it, and tries the RID-specific tools that arrived in the .NET 10 SDK, with SDK 10.0.402.
Using tools is on .NET Tools, writing
templates on Authoring Templates, and
the wider choice of distribution channels for command-line programs on
Packaging and Distribution — dotnet tool.
A .NET tool
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<IsPackable>true</IsPackable>
<PackAsTool>true</PackAsTool>
<ToolCommandName>ledgerline</ToolCommandName>
<PackageId>Ledgerline.Cli</PackageId>
</PropertyGroup>
ToolCommandName is the command users type; it defaults to the assembly name, and must not end in .exe or .cmd
because the tool is installed behind an app host. PackageOutputPath is often set to ./nupkg in tool projects.
dotnet pack src/Ledgerline.Cli -o artifacts/tool -p:Version=1.0.1
unzip -l artifacts/tool/Ledgerline.Cli.1.0.1.nupkg
unzip -p artifacts/tool/Ledgerline.Cli.1.0.1.nupkg tools/net10.0/any/DotnetToolSettings.xml
Ledgerline.Cli.nuspec
README.md
icon.png
tools/net10.0/any/DotnetToolSettings.xml
tools/net10.0/any/Ledgerline.Cli.deps.json
tools/net10.0/any/Ledgerline.Cli.dll
tools/net10.0/any/Ledgerline.Cli.pdb
tools/net10.0/any/Ledgerline.Cli.runtimeconfig.json
tools/net10.0/any/Ledgerline.dll
tools/net10.0/any/Ledgerline.pdb
tools/net10.0/any/Ledgerline.xml
<DotNetCliTool Version="1">
<Commands>
<Command Name="ledgerline" EntryPoint="Ledgerline.Cli.dll" Runner="dotnet" />
</Commands>
</DotNetCliTool>
A tool package has no dependencies: the project reference to Ledgerline was bundled as Ledgerline.dll under
tools/, and the .nuspec declares <packageType name="DotnetTool" />. Pushed to BaGetter and run with dnx (new in
.NET 10: download, cache and run in one step), from a folder whose nuget.config maps Ledgerline.* to that feed:
dotnet nuget push artifacts/tool/Ledgerline.Cli.1.0.1.nupkg -s ledgerline-bagetter -k "$BAGETTER_API_KEY"
dnx Ledgerline.Cli@1.0.1 --yes -- check j.ledger.csv
Your package was pushed.
OK: 2 lines, every entry balances.
RID-specific tools (.NET 10)
A classic tool needs a matching .NET runtime on the user’s machine. Since the .NET 10 SDK a tool can ship per platform: framework-dependent but platform-specific, self-contained, trimmed or Native AOT. Two properties opt in:
-
RuntimeIdentifiers— the project’s RIDs in general; onedotnet packproduces every RID package. -
ToolPackageRuntimeIdentifiers— the RIDs of the tool packages only, a subset ofRuntimeIdentifiers; needed for Native AOT, where each RID is packed separately withdotnet pack -r <rid>on a matching OS.
Adding any to the list adds a framework-dependent, portable fallback package. Ledgerline.Cli with:
<ToolPackageRuntimeIdentifiers>linux-x64;win-x64;any</ToolPackageRuntimeIdentifiers>
<RuntimeIdentifiers>linux-x64;win-x64</RuntimeIdentifiers>
failed: restore had no net10.0/any target —
error NETSDK1047: Assets file '.../obj/project.assets.json' doesn't have a target for 'net10.0/any'. Ensure that restore has run and that you have included 'net10.0' in the TargetFrameworks for your project. You may also need to include 'any' in your project's RuntimeIdentifiers.
— so any belongs in RuntimeIdentifiers too (linux-x64;win-x64;any). Then one pack produced four packages:
3218 Ledgerline.Cli.1.1.0.nupkg
30123 Ledgerline.Cli.any.1.1.0.nupkg
64535 Ledgerline.Cli.linux-x64.1.1.0.nupkg
104051 Ledgerline.Cli.win-x64.1.1.0.nupkg
Ledgerline.Cli itself is a 3 KB pointer package whose tools/any/any/DotnetToolSettings.xml lists the others:
<DotNetCliTool Version="2">
<Commands>
<Command Name="ledgerline" />
</Commands>
<RuntimeIdentifierPackages>
<RuntimeIdentifierPackage RuntimeIdentifier="linux-x64" Id="Ledgerline.Cli.linux-x64" />
<RuntimeIdentifierPackage RuntimeIdentifier="win-x64" Id="Ledgerline.Cli.win-x64" />
<RuntimeIdentifierPackage RuntimeIdentifier="any" Id="Ledgerline.Cli.any" />
</RuntimeIdentifierPackages>
</DotNetCliTool>
The RID packages have the package type DotnetToolRidPackage, an app host (Runner="executable") and — here — a
runtimeconfig.json naming Microsoft.NETCore.App 10.0.0: they were framework-dependent, 64 KB and 104 KB. The
RID-specific tools page says the SDK "creates self-contained packages for each specified RID"; on 10.0.402 that took
-p:SelfContained=true, which made them 37 MB each. The .NET 10 "what’s new" page lists both forms. All four packages
must be published before users install; with them on BaGetter, dnx Ledgerline.Cli@1.1.0 on Linux x64 downloaded
Ledgerline.Cli and Ledgerline.Cli.linux-x64, and ran.
The hybrid pattern combines Native AOT for chosen RIDs with a CoreCLR fallback: PublishAot=true and
ToolPackageRuntimeIdentifiers=osx-arm64;linux-arm64;linux-x64;any; dotnet pack for the pointer, dotnet pack -r
linux-x64 (and so on, each on its own OS) for the AOT packages, and dotnet pack -r any -p:PublishAot=false for the
fallback. Native AOT can’t cross OS boundaries, so this needs a CI matrix
(CI Release Workflows).
The .NET 11 SDK (preview) adds a warning for a tool project that also sets NuspecFile:
warning NETSDK1235: .NET Tools do not support using a custom .nuspec file, but the nuspec file 'custom.nuspec' was provided. Remove the NuspecFile property from this project to enable packing it as a .NET Tool.
(quoted from the .NET 11 documentation; 10.0.402 does not emit it).
Source: RID-specific tools: Opt in, RuntimeIdentifiers property, ToolPackageRuntimeIdentifiers property, Provide a framework-dependent version, RuntimeIdentifiers vs ToolPackageRuntimeIdentifiers, Package your tool, RID-specific and self-contained tools, AOT tools, Packing Native AOT tools, Hybrid AOT + CoreCLR pattern, Publish your tool, Install and run tools, What’s new in .NET 10: Platform-specific .NET tools, Use the any RuntimeIdentifier, What’s new in .NET 11: NETSDK1235.
Template packages
A template package holds one or more templates under content/, with PackageType Template (required for dotnet
new search to find it on nuget.org). The templatepack template from Microsoft.TemplateEngine.Authoring.Templates
scaffolds such a project (dotnet new install Microsoft.TemplateEngine.Authoring.Templates, then dotnet new
templatepack -n Ledgerline.Templates); Ledgerline.Templates uses the same settings:
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<IsPackable>true</IsPackable>
<PackageType>Template</PackageType>
<PackageId>Ledgerline.Templates</PackageId>
<IncludeContentInPack>true</IncludeContentInPack>
<IncludeBuildOutput>false</IncludeBuildOutput>
<ContentTargetFolders>content</ContentTargetFolders>
<NoDefaultExcludes>true</NoDefaultExcludes>
<EnableDefaultItems>false</EnableDefaultItems>
<NoWarn>$(NoWarn);NU5128</NoWarn>
</PropertyGroup>
<ItemGroup>
<Content Include="templates\**\*" Exclude="templates\**\bin\**;templates\**\obj\**" />
<Compile Remove="**\*" />
</ItemGroup>
NoDefaultExcludes keeps .template.config/ (a dot-folder) in the package; Compile Remove stops the packing
project from compiling the templates' own code. Packed, installed and removed:
dotnet pack src/Ledgerline.Templates -o artifacts/tpl
dotnet new install artifacts/tpl/Ledgerline.Templates.1.0.0.nupkg
dotnet new uninstall Ledgerline.Templates
content/templates/ledgerline-app/.template.config/template.json
content/templates/ledgerline-app/LedgerlineApp.csproj
content/templates/ledgerline-app/Program.cs
Success: Ledgerline.Templates@1.0.0 installed the following templates:
Template Name Short Name Language Tags
---------------------- -------------- -------- ------------------
Ledgerline console app ledgerline-app [C#] Console/Accounting
Success: Ledgerline.Templates@1.0.0 was uninstalled.
Uninstalling uses the package ID, not the template name. From a feed, dotnet new install Ledgerline.Templates
(optionally ::1.0.0 and --add-source) works the same way.
Common mistakes
-
ToolCommandNamewith.exeor.cmd— the tool is installed behind an app host, and the guide says to avoid extensions. -
Publishing the pointer package before the RID packages — installs fail until every package is on the feed.
-
anyonly inToolPackageRuntimeIdentifiers— NETSDK1047. -
Expecting self-contained RID packages by default — set
SelfContainedexplicitly. -
Template packages without
PackageType=Template— invisible todotnet new search. -
Dot-folders excluded from a template package — set
NoDefaultExcludes.