
Dotnet10 Pack Tool
- 122 installs
- 70 repo stars
- Updated July 26, 2026
- rysweet/amplihack
Package and publish .NET 10 libraries or tools from amplihack with correct manifests, versioning, and NuGet-ready outputs for downstream consumers.
About
The dotnet10-pack-tool skill automates .NET 10 packaging for amplihack—metadata, versioning, and NuGet-ready outputs—so libraries and CLI tools can be published reliably. It targets integration work where correct package manifests and repeatable pack pipelines prevent broken downstream installs.
- Builds NuGet-ready .NET 10 packages
- Manages versioning and package metadata
- Validates manifest and dependency declarations
- Prepares libraries for internal or public feeds
- Automates repeatable pack-and-publish steps
Dotnet10 Pack Tool by the numbers
- 122 all-time installs (skills.sh)
- +1 installs in the week ending Jul 26, 2026 (Skillselion tracking)
- Ranked #76 of 153 .NET & C# skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/rysweet/amplihack --skill dotnet10-pack-toolAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 122 |
|---|---|
| repo stars | ★ 70 |
| Last updated | July 26, 2026 |
| Repository | rysweet/amplihack ↗ |
What it does
Package and publish .NET 10 libraries or tools from amplihack with correct manifests, versioning, and NuGet-ready outputs for downstream consumers.
Files
.NET 10 Hybrid Pack Tool
Purpose
Guides you through creating hybrid .NET 10 tool packages that combine Native AOT for maximum performance on select platforms with CoreCLR fallback for universal compatibility.
When I Activate
I automatically load when you mention:
- "pack .NET tool" or "dotnet pack AOT"
- "Native AOT tool" or "hybrid .NET tool"
- "ToolPackageRuntimeIdentifiers"
- ".NET 10 tool packaging"
- "cross-platform .NET tool with AOT"
What I Do
1. Configure your .csproj with ToolPackageRuntimeIdentifiers and PublishAot=true 2. Generate the pointer package (metapackage) 3. Build Native AOT packages for each target RID 4. Create CoreCLR fallback with -r any 5. Validate package structure
Quick Start
Step 1: Configure .csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<!-- Package as .NET Tool -->
<PackAsTool>true</PackAsTool>
<ToolCommandName>your-tool-name</ToolCommandName>
<!-- RIDs: CoreCLR fallback + Native AOT targets -->
<ToolPackageRuntimeIdentifiers>any;osx-arm64;linux-arm64;linux-x64</ToolPackageRuntimeIdentifiers>
<!-- Enable Native AOT -->
<PublishAot>true</PublishAot>
</PropertyGroup>
<!-- Native AOT optimizations -->
<PropertyGroup Condition="'$(PublishAot)' == 'true'">
<InvariantGlobalization>true</InvariantGlobalization>
<OptimizationPreference>Size</OptimizationPreference>
<StripSymbols>true</StripSymbols>
</PropertyGroup>
</Project>Step 2: Build Packages
# 1. Create pointer package (no binaries, just metadata)
dotnet pack -o ./packages
# 2. Build Native AOT for each target platform
dotnet pack -r osx-arm64 -o ./packages # On macOS
dotnet pack -r linux-arm64 -o ./packages # On Linux ARM or container
dotnet pack -r linux-x64 -o ./packages # On Linux x64 or container
# 3. Create CoreCLR fallback for all other platforms
dotnet pack -r any -p:PublishAot=false -o ./packagesStep 3: Install & Run
dotnet tool install -g your-tool-name
your-tool-name # Auto-selects best package for platformKey Concepts
| Concept | Description |
|---|---|
| Pointer Package | Metapackage that references RID-specific packages |
| ToolPackageRuntimeIdentifiers | Lists RIDs, creates pointer structure (no auto-build) |
| `-r any` | CoreCLR fallback for unlisted platforms |
| `-p:PublishAot=false` | Disables AOT for CoreCLR fallback |
Why This Pattern Works
PublishAot=truedisables automatic RID package generation (AOT can't cross-compile OSes)ToolPackageRuntimeIdentifierscreates the pointer package structure- Manual
-r <RID>builds produce AOT binaries per platform -r any -p:PublishAot=falsecreates portable CoreCLR fallback
Documentation
- reference.md: Complete build script, container builds, CI/CD patterns
- examples.md: Real-world examples and troubleshooting
Requirements
- .NET 10 SDK installed
- Docker (for cross-platform Linux builds from macOS/Windows)
- AOT-compatible container:
mcr.microsoft.com/dotnet/sdk:10.0-noble-aot
.NET 10 Hybrid Pack Tool - Examples
Real-world examples and usage patterns for building hybrid .NET tools.
Example 1: Simple CLI Tool
Project Setup
dotnet new console -n my-hybrid-tool
cd my-hybrid-toolProgram.cs
using System.Runtime.InteropServices;
Console.WriteLine("Hello from my-hybrid-tool!");
Console.WriteLine($"Runtime: {RuntimeInformation.FrameworkDescription}");
Console.WriteLine($"RID: {RuntimeInformation.RuntimeIdentifier}");
#if NATIVE_AOT
Console.WriteLine("Mode: Native AOT 🚀");
#else
Console.WriteLine("Mode: CoreCLR");
#endifmy-hybrid-tool.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<PackAsTool>true</PackAsTool>
<ToolCommandName>my-hybrid-tool</ToolCommandName>
<ToolPackageRuntimeIdentifiers>any;osx-arm64;linux-x64</ToolPackageRuntimeIdentifiers>
<PublishAot>true</PublishAot>
<PackageId>my-hybrid-tool</PackageId>
<Version>1.0.0</Version>
</PropertyGroup>
<PropertyGroup Condition="'$(PublishAot)' == 'true'">
<DefineConstants>$(DefineConstants);NATIVE_AOT</DefineConstants>
<InvariantGlobalization>true</InvariantGlobalization>
<OptimizationPreference>Size</OptimizationPreference>
<StripSymbols>true</StripSymbols>
</PropertyGroup>
</Project>Build
# Pointer package
dotnet pack -o ./packages
# macOS ARM64 (if on Mac)
dotnet pack -r osx-arm64 -o ./packages
# Linux x64 (via container)
docker run --rm -v "$(pwd):/src" -w /src \
mcr.microsoft.com/dotnet/sdk:10.0-noble-aot \
dotnet pack -r linux-x64 -o /src/packages
# CoreCLR fallback
dotnet pack -r any -p:PublishAot=false -o ./packagesInstall & Test
# Install from local packages
dotnet tool install -g my-hybrid-tool --add-source ./packages
# Run
my-hybrid-tool---
Example 2: Using dnx (Quick Test)
The .NET 10 SDK includes dnx for running tools without installation:
# Run directly from NuGet
dnx dotnet10-hybrid-tool
# Output (on macOS ARM64):
# Hi, I'm a 'DotNetCliTool v2' tool!
# Yes, I'm quite fancy.
#
# Version: .NET 10.0.2
# RID: osx-arm64
# Mode: Native AOT---
Example 3: Minimal Build Script
For simple projects without CI/CD:
#!/bin/bash
set -e
PACKAGES_DIR="./packages"
rm -rf "$PACKAGES_DIR" bin obj
mkdir -p "$PACKAGES_DIR"
# Build all packages
dotnet pack -o "$PACKAGES_DIR" # Pointer
dotnet pack -r osx-arm64 -o "$PACKAGES_DIR" # macOS
dotnet pack -r any -p:PublishAot=false -o "$PACKAGES_DIR" # Fallback
echo "Packages built:"
ls -la "$PACKAGES_DIR"---
Example 4: Adding Windows Support
To add Windows Native AOT (requires Windows machine or CI):
Update .csproj
<ToolPackageRuntimeIdentifiers>any;osx-arm64;linux-x64;win-x64</ToolPackageRuntimeIdentifiers>Build on Windows
# On Windows machine or GitHub Actions windows-latest
dotnet pack -r win-x64 -o ./packagesOr Let Windows Use Fallback
If you don't need Windows AOT performance, the any package works:
<!-- Windows users get CoreCLR automatically -->
<ToolPackageRuntimeIdentifiers>any;osx-arm64;linux-x64</ToolPackageRuntimeIdentifiers>---
Example 5: Testing Local Packages
# Create a local NuGet source
mkdir -p ~/.nuget/local-packages
cp packages/*.nupkg ~/.nuget/local-packages/
# Add local source
dotnet nuget add source ~/.nuget/local-packages --name local-packages
# Install from local
dotnet tool install -g my-hybrid-tool --version 1.0.0
# Or use dnx for quick test
dnx my-hybrid-tool --add-source ./packages---
Example 6: Version Bumping
# Update version in .csproj
<VersionPrefix>1.1.0</VersionPrefix>
# Rebuild all packages
./build-packages.sh
# Upgrade installed tool
dotnet tool update -g my-hybrid-tool---
Example 7: Conditional AOT Features
Some features aren't AOT-compatible. Use conditional compilation:
public class MyTool
{
public void Run()
{
#if NATIVE_AOT
// AOT-safe implementation
RunOptimized();
#else
// Full CoreCLR features (reflection, dynamic code, etc.)
RunWithReflection();
#endif
}
private void RunOptimized()
{
// Source-generated serialization, no reflection
Console.WriteLine("Running optimized path");
}
private void RunWithReflection()
{
// Can use reflection, Activator.CreateInstance, etc.
Console.WriteLine("Running with full CLR features");
}
}---
Common Mistakes
❌ Wrong: Using RuntimeIdentifiers
<!-- This auto-generates ALL RID packages with CoreCLR -->
<RuntimeIdentifiers>osx-arm64;linux-x64;win-x64</RuntimeIdentifiers>✅ Correct: Using ToolPackageRuntimeIdentifiers
<!-- This creates pointer package only, manual RID builds for AOT -->
<ToolPackageRuntimeIdentifiers>any;osx-arm64;linux-x64</ToolPackageRuntimeIdentifiers>❌ Wrong: Forgetting -p:PublishAot=false for any
# This tries to build AOT for "any" which fails
dotnet pack -r any -o ./packages✅ Correct: Disable AOT for fallback
dotnet pack -r any -p:PublishAot=false -o ./packages❌ Wrong: Cross-OS AOT compilation
# Can't build Windows AOT from macOS
dotnet pack -r win-x64 -o ./packages # Fails!✅ Correct: Use containers for cross-arch, native machines for cross-OS
# On macOS: build macOS + Linux via containers
dotnet pack -r osx-arm64 -o ./packages
docker run ... dotnet pack -r linux-x64 -o /src/packages
# Windows: build on Windows machine/runner
# Or: let Windows use CoreCLR fallback (any)---
Troubleshooting
"Tool not found after install"
# Check installation path
dotnet tool list -g
# Ensure ~/.dotnet/tools is in PATH
export PATH="$HOME/.dotnet/tools:$PATH""Wrong package installed"
# Verify which package was installed
dotnet tool list -g | grep my-tool
# Reinstall to get latest
dotnet tool uninstall -g my-tool
dotnet tool install -g my-tool"Build fails with trimming warnings"
Add to .csproj:
<PropertyGroup>
<SuppressTrimAnalysisWarnings>true</SuppressTrimAnalysisWarnings>
<!-- Or fix the warnings properly -->
</PropertyGroup>"Container can't access packages directory"
# Use absolute paths
docker run --rm -v "$(pwd):/src" -w /src \
mcr.microsoft.com/dotnet/sdk:10.0-noble-aot \
dotnet pack -r linux-x64 -o /src/packages.NET 10 Hybrid Pack Tool - Reference
Complete reference for building hybrid Native AOT + CoreCLR .NET tool packages.
Architecture
Package Structure
When you build with this pattern, you get:
packages/
├── your-tool.1.0.0.nupkg # Pointer package (metapackage)
├── your-tool.any.1.0.0.nupkg # CoreCLR fallback (works everywhere)
├── your-tool.osx-arm64.1.0.0.nupkg # Native AOT for macOS ARM64
├── your-tool.linux-arm64.1.0.0.nupkg # Native AOT for Linux ARM64
└── your-tool.linux-x64.1.0.0.nupkg # Native AOT for Linux x64How It Works
User runs: dotnet tool install -g your-tool
.NET CLI:
1. Downloads pointer package (your-tool.1.0.0.nupkg)
2. Reads ToolPackageRuntimeIdentifiers metadata
3. Matches user's RID to available packages:
- osx-arm64 → downloads your-tool.osx-arm64.nupkg (Native AOT)
- linux-x64 → downloads your-tool.linux-x64.nupkg (Native AOT)
- win-x64 → downloads your-tool.any.nupkg (CoreCLR fallback)
4. Installs only the matching packageKey Benefit: Users only download what they need. No wasted bandwidth on unused platform binaries.
Complete .csproj Configuration
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<!-- Package as .NET Tool -->
<PackAsTool>true</PackAsTool>
<ToolCommandName>your-tool-name</ToolCommandName>
<!-- RIDs: CoreCLR fallback + Native AOT targets -->
<ToolPackageRuntimeIdentifiers>any;osx-arm64;linux-arm64;linux-x64</ToolPackageRuntimeIdentifiers>
<!-- Package metadata -->
<PackageId>your-tool-name</PackageId>
<VersionPrefix>1.0.0</VersionPrefix>
<Authors>Your Name</Authors>
<Description>Your tool description</Description>
<!-- Enable Native AOT by default -->
<PublishAot>true</PublishAot>
</PropertyGroup>
<!-- SourceLink and reproducible builds (for CI) -->
<PropertyGroup Condition="'$(OfficialBuild)' == 'true'">
<DebugType>embedded</DebugType>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<Deterministic>true</Deterministic>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup Condition="'$(OfficialBuild)' == 'true'">
<None Include="README.md" Pack="true" PackagePath="\" />
</ItemGroup>
<!-- Native AOT optimizations -->
<PropertyGroup Condition="'$(PublishAot)' == 'true'">
<DefineConstants>$(DefineConstants);NATIVE_AOT</DefineConstants>
<InvariantGlobalization>true</InvariantGlobalization>
<OptimizationPreference>Size</OptimizationPreference>
<StripSymbols>true</StripSymbols>
</PropertyGroup>
</Project>Complete Build Script
Reference implementation from richlander/dotnet10-hybrid-tool:
#!/bin/bash
set -euo pipefail
# Build script for hybrid .NET tool packages
# Creates Native AOT packages for supported platforms + CoreCLR fallback
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PACKAGES_DIR="$SCRIPT_DIR/packages"
AOT_IMAGE="mcr.microsoft.com/dotnet/sdk:10.0-noble-aot"
# Get git commit for SourceLink
GIT_COMMIT=$(git rev-parse HEAD 2>/dev/null || echo "")
# Build args for official builds
PACK_ARGS="-p:OfficialBuild=true"
if [ -n "$GIT_COMMIT" ]; then
PACK_ARGS="$PACK_ARGS -p:SourceRevisionId=$GIT_COMMIT"
fi
# Clean function that handles root-owned files from docker
clean_build() {
docker run --rm -v "$SCRIPT_DIR:/src" -w /src $AOT_IMAGE \
bash -c 'find bin obj -mindepth 1 -delete 2>/dev/null || true; rm -rf bin obj'
}
# Clean previous builds
docker run --rm -v "$SCRIPT_DIR:/src" -w /src $AOT_IMAGE \
bash -c 'find bin obj packages -mindepth 1 -delete 2>/dev/null || true; rm -rf bin obj packages'
mkdir -p "$PACKAGES_DIR"
echo "=== Step 1: Create pointer package ==="
dotnet pack $PACK_ARGS -o "$PACKAGES_DIR"
echo "=== Step 2: Build osx-arm64 with Native AOT ==="
clean_build
dotnet pack $PACK_ARGS -r osx-arm64 -o "$PACKAGES_DIR"
echo "=== Step 3: Build linux-arm64 with Native AOT (container) ==="
clean_build
docker run --rm \
-v "$SCRIPT_DIR:/src" \
-w /src \
$AOT_IMAGE \
dotnet pack $PACK_ARGS -r linux-arm64 -o /src/packages
echo "=== Step 4: Build linux-x64 with Native AOT (container + emulation) ==="
clean_build
docker run --rm --platform linux/amd64 \
-v "$SCRIPT_DIR:/src" \
-w /src \
$AOT_IMAGE \
dotnet pack $PACK_ARGS -r linux-x64 -o /src/packages
echo "=== Step 5: Build any runtime with CoreCLR ==="
clean_build
dotnet pack $PACK_ARGS -r any -p:PublishAot=false -o "$PACKAGES_DIR"
echo "=== Build complete ==="
ls -lh "$PACKAGES_DIR"Container-Based Builds
Why Containers?
Native AOT cannot cross-compile across operating systems (only architectures). To build Linux binaries from macOS:
# Linux ARM64 (native on Apple Silicon via Rosetta-free container)
docker run --rm \
-v "$(pwd):/src" \
-w /src \
mcr.microsoft.com/dotnet/sdk:10.0-noble-aot \
dotnet pack -r linux-arm64 -o /src/packages
# Linux x64 (via emulation on Apple Silicon)
docker run --rm --platform linux/amd64 \
-v "$(pwd):/src" \
-w /src \
mcr.microsoft.com/dotnet/sdk:10.0-noble-aot \
dotnet pack -r linux-x64 -o /src/packagesAOT-Compatible Container Image
Use mcr.microsoft.com/dotnet/sdk:10.0-noble-aot which includes:
- .NET 10 SDK
- Native AOT toolchain (clang, lld)
- All required build dependencies
CI/CD Integration
GitHub Actions Example
name: Build Hybrid Tool
on:
push:
tags: ["v*"]
jobs:
build-pointer:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"
- run: dotnet pack -o ./packages
- uses: actions/upload-artifact@v4
with:
name: pointer-package
path: ./packages/*.nupkg
build-linux-x64:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"
- run: dotnet pack -r linux-x64 -o ./packages
- uses: actions/upload-artifact@v4
with:
name: linux-x64-package
path: ./packages/*.nupkg
build-linux-arm64:
runs-on: ubuntu-24.04-arm
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"
- run: dotnet pack -r linux-arm64 -o ./packages
- uses: actions/upload-artifact@v4
with:
name: linux-arm64-package
path: ./packages/*.nupkg
build-macos-arm64:
runs-on: macos-14 # M1/M2 runner
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"
- run: dotnet pack -r osx-arm64 -o ./packages
- uses: actions/upload-artifact@v4
with:
name: osx-arm64-package
path: ./packages/*.nupkg
build-coreclr-fallback:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"
- run: dotnet pack -r any -p:PublishAot=false -o ./packages
- uses: actions/upload-artifact@v4
with:
name: any-package
path: ./packages/*.nupkg
publish:
needs:
[build-pointer, build-linux-x64, build-linux-arm64, build-macos-arm64, build-coreclr-fallback]
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
pattern: "*-package"
path: packages
merge-multiple: true
- run: dotnet nuget push packages/*.nupkg --source nuget.org --api-key ${{ secrets.NUGET_API_KEY }}ToolPackageRuntimeIdentifiers vs RuntimeIdentifiers
| Property | Behavior | Use Case |
|---|---|---|
RuntimeIdentifiers | Auto-generates RID packages during pack | CoreCLR-only tools |
ToolPackageRuntimeIdentifiers | Creates pointer package only; manual RID builds | Hybrid AOT + CoreCLR |
Why ToolPackageRuntimeIdentifiers for AOT?
- AOT cannot cross-compile across OSes
- Manual builds give you control over which platforms get AOT
- CoreCLR fallback ensures universal compatibility
Runtime Detection in Code
Check if running AOT vs CoreCLR:
#if NATIVE_AOT
Console.WriteLine("Mode: Native AOT");
#else
Console.WriteLine("Mode: CoreCLR");
#endif
// Or at runtime:
var isAot = !System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported;Performance Comparison
From the reference implementation:
# Native AOT (osx-arm64)
$ time dotnet10-hybrid-tool
Hi, I'm a 'DotNetCliTool v2' tool!
dotnet10-hybrid-tool 0.00s user 0.01s system 60% cpu 0.015 total
# CoreCLR (any)
$ time dotnet10-hybrid-tool
Hi, I'm a 'DotNetCliTool v2' tool!
dotnet10-hybrid-tool 0.15s user 0.05s system 85% cpu 0.235 total~15x faster startup with Native AOT.
Troubleshooting
"AOT build fails with missing symbols"
Ensure you're using the AOT-compatible SDK image:
docker pull mcr.microsoft.com/dotnet/sdk:10.0-noble-aot"Can't build Windows AOT from macOS"
Correct - AOT doesn't cross-compile OSes. Options:
1. Use GitHub Actions with Windows runner 2. Use Azure DevOps Windows agent 3. Windows users build locally 4. Windows falls back to CoreCLR (any package)
"Package too large"
Enable size optimizations:
<PropertyGroup Condition="'$(PublishAot)' == 'true'">
<OptimizationPreference>Size</OptimizationPreference>
<StripSymbols>true</StripSymbols>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>"Container build permission denied"
Files created by Docker may be root-owned:
# Clean with Docker to handle permissions
docker run --rm -v "$(pwd):/src" -w /src mcr.microsoft.com/dotnet/sdk:10.0-noble-aot \
bash -c 'rm -rf bin obj'