You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
**This article applies to:** ✔️ .NET SDK 10 and later versions
12
12
13
-
Package .NET tools for specific platforms and architectures so you can distribute native, fast, and trimmed applications. This capability makes it easier to distribute native, fast, trimmed .NET applications for command-line tools like MCP servers or other platform-specific utilities.
13
+
Package .NET tools for specific platforms and architectures so you can distribute native, fast, and trimmed applications. This capability makes it easier to distribute optimized applications for command-line tools like MCP servers or other platform-specific utilities.
14
14
15
15
## Overview
16
16
17
-
Starting with .NET SDK 10, you can create .NET tools that target specific Runtime Identifiers (RIDs). These tools can be:
17
+
Starting with .NET SDK 10, you can create .NET tools that target specific operating system environments (represented by Runtime Identifiers (RIDs)). These tools can be:
18
18
19
19
-**RID-specific**: Compiled for particular operating systems and architectures.
20
20
-**Self-contained**: Include the .NET runtime and don't require a separate .NET installation.
21
21
-**Native AOT**: Use Ahead-of-Time compilation for faster startup and smaller memory footprint.
22
22
23
-
When users install a RID-specific tool, the .NET CLI automatically selects and installs the appropriate package for their platform.
23
+
Users don't notice a difference when they install the tool. The .NET CLI automatically selects and installs the best package for their platform.
24
24
25
25
## Opt in to RID-specific packaging
26
26
@@ -52,6 +52,7 @@ Alternatively, use `ToolPackageRuntimeIdentifiers` for tool-specific RID configu
@@ -60,13 +61,36 @@ Alternatively, use `ToolPackageRuntimeIdentifiers` for tool-specific RID configu
60
61
61
62
Use a semicolon-delimited list of RID values. For a list of Runtime Identifiers, see the [RID catalog](../rid-catalog.md).
62
63
64
+
### When to use `RuntimeIdentifiers` vs `ToolPackageRuntimeIdentifiers`
65
+
66
+
Both `RuntimeIdentifiers` and `ToolPackageRuntimeIdentifiers` opt your tool into RID-specific packaging, but they serve slightly different purposes:
67
+
68
+
Use **`RuntimeIdentifiers`** when:
69
+
70
+
- You want the project to **build and publish RID-specific apps in general** (not just as a tool).
71
+
- You're primarily targeting **CoreCLR** (non-AOT) or you want the standard SDK behavior where a single `dotnet pack` produces multiple RID-specific packages.
72
+
- You may conditionalize `PublishAot` for a subset of RIDs, but you still want a CoreCLR-based package for every RID in `RuntimeIdentifiers`.
73
+
74
+
Use **`ToolPackageRuntimeIdentifiers`** when:
75
+
76
+
- You want to define **RID-specific behavior only for the tool packaging**, without changing how the project builds for other deployment scenarios.
77
+
- You're using **Native AOT** and plan to **manually build** AOT binaries per RID with `dotnet pack -r <RID>`.
78
+
- You want a **hybrid model** where some RIDs get Native AOT and others fall back to a portable CoreCLR implementation.
79
+
80
+
Notes:
81
+
82
+
- The top-level pointer package specifies the available RID-specific packages. If you specify `ToolPackageRuntimeIdentifiers`, it determines the tool RIDs; otherwise, `RuntimeIdentifiers` is used.
83
+
-`ToolPackageRuntimeIdentifiers` should be equal to or a subset of the RIDs in `RuntimeIdentifiers`
84
+
- When `PublishAot=true`, RID-specific packages are generated only when you pack for a specific RID (for example, `dotnet pack -r linux-x64`).
85
+
- Native AOT builds (`PublishAot=true`) is only supported when the build OS and target OS match.
86
+
63
87
## Package your tool
64
88
65
89
The packaging process differs depending on whether you're using AOT compilation. To build a NuGet package, or *.nupkg* file from the project, run the [dotnet pack](dotnet-pack.md) command.
66
90
67
91
### RID-specific and self-contained tools
68
92
69
-
For tools without AOT compilation, run`dotnet pack` once:
93
+
Run`dotnet pack` once:
70
94
71
95
```dotnetcli
72
96
dotnet pack
@@ -83,124 +107,189 @@ This command creates multiple NuGet packages:
83
107
84
108
### AOT tools
85
109
86
-
For tools with AOT compilation (`<PublishAot>true</PublishAot>`), you must pack separately for each platform:
110
+
For tools with AOT compilation (`<PublishAot>true</PublishAot>`), you must pack separately for each platform.
87
111
88
-
- Pack the top-level package once (on any platform):
112
+
#### Platform requirements for Native AOT
89
113
90
-
```dotnetcli
91
-
dotnet pack
114
+
Native AOT compilation requires the operating system (OS) part of the SDK RID to match the target RID's OS. The SDK can cross-compile for different architectures (for example, x64 to ARM64) but not across operating systems (for example, Windows to Linux).
115
+
116
+
This means you have several options for building Native AOT packages:
117
+
118
+
-**Build only for your development machine**: Support Native AOT only for the OS you're developing on.
119
+
-**Use containers for Linux builds**: If you're on macOS or Windows, use containers to cross-compile for Linux. For example, use `mcr.microsoft.com/dotnet/sdk:10.0-noble-aot` container images.
120
+
-**Federate your build across machines**: Use CI/CD systems like GitHub Actions or Azure DevOps Pipelines to build on different operating systems.
121
+
122
+
You don't need to build all RID-specific packages on the same machine or at the same time. You just need to build and publish them before you publish the top-level package.
123
+
124
+
#### Packing Native AOT tools
125
+
126
+
Pack the top-level package once (on any platform):
127
+
128
+
```dotnetcli
129
+
dotnet pack
130
+
```
131
+
132
+
Pack for each specific RID on the corresponding platform, for example:
133
+
134
+
```dotnetcli
135
+
dotnet pack -r linux-x64
136
+
```
137
+
138
+
You must run each RID-specific pack command on a platform where the OS matches the target RID's OS. For more information about the prerequisites for Native AOT compilation, see [Native AOT deployment](../deploying/native-aot/index.md).
139
+
140
+
When you set `PublishAot` to `true`, the packing behavior changes:
141
+
142
+
-`dotnet pack` produces the **top-level pointer package** (package type `DotnetTool`).
143
+
- RID-specific AOT packages are produced only when you explicitly pass `-r <RID>`, for example, `dotnet pack -r linux-x64` or `dotnet pack -r osx-arm64`.
- Pack for each specific RID on the corresponding platform:
165
+
1.**Create the pointer package.**
166
+
167
+
Run `dotnet pack` once (on any platform) to build the top-level package that points to the RID-specific packages:
95
168
96
169
```dotnetcli
97
-
dotnet pack -r win-x64
98
-
dotnet pack -r linux-x64
99
-
dotnet pack -r osx-arm64
170
+
dotnet pack
100
171
```
101
172
102
-
You must run each RID-specific pack command on the matching platform because AOT compilation produces native binaries. For more information about the prerequisites for Native AOT compilation, see [Native AOT deployment](../deploying/native-aot/index.md).
173
+
1.**Build Native AOT packages for selected RIDs.**
103
174
104
-
## Package structure
175
+
Native AOT compilation requires building on the target platform. Build each AOT-enabled RID package on the matching platform using `dotnet pack -r <RID>`.
105
176
106
-
### Package types
177
+
For example:
107
178
108
-
RID-specific tool packages use two package types:
179
+
```
180
+
dotnet pack -r linux-x64
181
+
```
109
182
110
-
-**DotnetTool**: The top-level package that contains metadata.
111
-
-**DotnetToolRidPackage**: The RID-specific packages that contain the actual tool binaries.
183
+
1.**Build a CoreCLR fallback package.**
112
184
113
-
### Package metadata
185
+
To provide a universal fallback, pack the `any` RID without AOT:
114
186
115
-
The top-level package includes metadata that signals it's a RID-specific tool and lists the RID-specific packages. When you run `dotnet tool install`, the CLI reads this metadata to determine which RID-specific package to install for the current platform.
187
+
```dotnetcli
188
+
dotnet pack -r any -p:PublishAot=false
189
+
```
116
190
117
-
## Publish your tool
191
+
This produces a portable CoreCLR package (for example, `yourtool.any.<version>.nupkg`) that can run on platforms that don't have a dedicated AOT build.
192
+
193
+
> [!NOTE]
194
+
> You can also use the `.NET SDK 10.0-noble-aot` container images to build and package Linux Native AOT tools from any host that supports Linux containers. For example:
195
+
>
196
+
> -`mcr.microsoft.com/dotnet/sdk:10.0-noble-aot`
197
+
>
198
+
> This is useful when your development machine isn't running Linux natively.
199
+
200
+
In this hybrid setup:
201
+
202
+
- The pointer package (`yourtool.<version>.nupkg`) references both:
203
+
- RID-specific Native AOT packages (for example, `yourtool.osx-arm64`, `yourtool.linux-x64`).
204
+
- The `any` CoreCLR package as a fallback.
205
+
- The .NET CLI automatically picks the most appropriate package for the user's platform when they run `dotnet tool install` or `dnx`.
206
+
207
+
#### Example: `dotnet10-hybrid-tool`
208
+
209
+
The [`dotnet10-hybrid-tool` repository](https://github.com/richlander/dotnet10-hybrid-tool) demonstrates this hybrid packaging pattern with Native AOT packages for `osx-arm64`, `linux-arm64`, and `linux-x64`, plus a CoreCLR fallback package for the `any` RID (used, for example, on Windows when no AOT build is available).
118
210
119
-
Publish all packages to NuGet.org or your package feed by using [dotnet nuget push](dotnet-nuget-push.md):
211
+
You can install and try the tool yourself:
120
212
121
213
```dotnetcli
122
-
dotnet nuget push path/to/package/root/*.nupkg
214
+
dotnet tool install -g dotnet10-hybrid-tool
215
+
dotnet10-hybrid-tool
123
216
```
124
217
125
-
## Run a RID-specific tool
218
+
The tool reports its runtime framework description, runtime identifier (RID), and compilation mode (Native AOT or CoreCLR).
126
219
127
-
Users run RID-specific tools the same way as platform-agnostic tools:
220
+
Example output on a platform with Native AOT:
128
221
129
-
```dotnetcli
130
-
dnx mytool
222
+
```output
223
+
Hi, I'm a 'DotNetCliTool v2' tool!
224
+
Yes, I'm quite fancy.
225
+
226
+
Version: .NET 10.0.2
227
+
RID: osx-arm64
228
+
Mode: Native AOT
131
229
```
132
230
133
-
The CLI automatically:
231
+
Example output on a platform using the CoreCLR fallback:
134
232
135
-
1. Downloads the top-level package.
136
-
1. Reads the RID-specific metadata.
137
-
1. Identifies the most appropriate package for the current platform.
138
-
1. Downloads and runs the RID-specific package.
233
+
```output
234
+
Hi, I'm a 'DotNetCliTool v2' tool!
235
+
Yes, I'm quite fancy.
139
236
140
-
## Example: Create an AOT tool
237
+
Version: .NET 10.0.2
238
+
RID: win-x64
239
+
Mode: CoreCLR
240
+
```
141
241
142
-
Here's a complete example of creating an AOT-compiled RID-specific tool:
242
+
This makes it a useful way to experiment with RID-specific, AOT-compiled tools and the CoreCLR fallback behavior.
143
243
144
-
1. Create a new console application:
244
+
## Publish your tool
145
245
146
-
```dotnetcli
147
-
dotnet new console -n MyFastTool
148
-
cd MyFastTool
149
-
```
246
+
When publishing RID-specific tool packages, the .NET CLI uses the version number of the top-level package to select the matching RID-specific packages. This means:
150
247
151
-
1. Update the project file to enable AOT and RID-specific packaging:
248
+
- All RID-specific packages must have the exact same version as the top-level package.
249
+
- All packages must be published to your feed before the top-level package becomes available.
<Description>A fast AOT-compiled tool</Description>
166
-
</PropertyGroup>
167
-
</Project>
168
-
```
251
+
To ensure a smooth publishing process:
169
252
170
-
1.Add your application code in `Program.cs`:
253
+
1.Publish all RID-specific packages first:
171
254
172
-
```csharp
173
-
Console.WriteLine("Hello from MyFastTool!");
174
-
Console.WriteLine($"Running on {Environment.OSVersion}");
255
+
```dotnetcli
256
+
dotnet nuget push yourtool.win-x64.1.0.0.nupkg
257
+
dotnet nuget push yourtool.linux-x64.1.0.0.nupkg
258
+
dotnet nuget push yourtool.osx-arm64.1.0.0.nupkg
259
+
dotnet nuget push yourtool.any.1.0.0.nupkg
175
260
```
176
261
177
-
1.Pack the top-level package:
262
+
1.Publish the top-level package last:
178
263
179
264
```dotnetcli
180
-
dotnet pack
265
+
dotnet nuget push yourtool.1.0.0.nupkg
181
266
```
182
267
183
-
1. Pack for each specific RID (on the corresponding platform):
268
+
Publishing the top-level package last ensures that all referenced RID-specific packages are available when users install your tool. If a user installs your tool before all RID packages are published, the installation will fail.
184
269
185
-
On Windows:
270
+
## Install and run tools
186
271
187
-
```dotnetcli
188
-
dotnet pack -r win-x64
189
-
```
272
+
Whether a tool uses RID-specific packaging is an implementation detail that's transparent to users. You install and run tools the same way, regardless of whether the tool developer opted into RID-specific packaging.
190
273
191
-
On Linux:
274
+
To install a tool globally:
192
275
193
-
```dotnetcli
194
-
dotnet pack -r linux-x64
195
-
```
276
+
```dotnetcli
277
+
dotnet tool install -g mytool
278
+
```
196
279
197
-
On macOS:
280
+
Once installed, you can invoke it directly:
198
281
199
-
```dotnetcli
200
-
dotnet pack -r osx-arm64
201
-
```
282
+
```dotnetcli
283
+
mytool
284
+
```
285
+
286
+
You can also use the `dnx` helper, which behaves similarly to `npx` in the Node.js ecosystem: it downloads and launches a tool in a single gesture if it isn't already present:
287
+
288
+
```dotnetcli
289
+
dnx mytool
290
+
```
202
291
203
-
1. Publish all packages to NuGet.org by using the [dotnet nuget push](dotnet-nuget-push.md) command.
292
+
When a tool uses RID-specific packaging, the .NET CLI automatically selects the correct package for your platform. You don't need to specify a RID—the CLI infers it from your system and downloads the appropriate RID-specific package.
0 commit comments