Choosing CLI output format. AOT vs framework-dependent, RID matrix, single-file, dotnet tool.
CLI distribution strategy for .NET tools: choosing between Native AOT single-file publish, framework-dependent deployment, and dotnet tool packaging. Runtime Identifier (RID) matrix planning for cross-platform targets (linux-x64, osx-arm64, win-x64, linux-arm64), single-file publish configuration, and binary size optimization techniques for CLI applications.
Version assumptions: .NET 8.0+ baseline. Native AOT for console apps is fully supported since .NET 8. Single-file publish has been mature since .NET 6.
Out of scope: Native AOT MSBuild configuration (PublishAot, ILLink descriptors, EnableAotAnalyzer, trimming) -- see [skill:dotnet-native-aot]. AOT-first application design patterns (source gen over reflection, DI choices) -- see [skill:dotnet-aot-architecture]. Multi-platform packaging formats (Homebrew, apt/deb, winget, Scoop) -- see [skill:dotnet-cli-packaging]. Release CI/CD pipeline -- see [skill:dotnet-cli-release-pipeline]. Container-based distribution -- see [skill:dotnet-containers]. General CI/CD patterns -- see [skill:dotnet-gha-patterns] and [skill:dotnet-ado-patterns].
Cross-references: [skill:dotnet-native-aot] for AOT compilation pipeline, [skill:dotnet-aot-architecture] for AOT-safe design patterns, [skill:dotnet-cli-architecture] for CLI layered architecture, [skill:dotnet-cli-packaging] for platform-specific package formats, [skill:dotnet-cli-release-pipeline] for automated release workflows, [skill:dotnet-containers] for container-based distribution, [skill:dotnet-tool-management] for consumer-side tool installation and manifest management.
Choose the distribution model based on target audience and deployment constraints.
| Strategy | Startup Time | Binary Size | Runtime Required | Best For |
|----------|-------------|-------------|-----------------|----------|
| Native AOT single-file | ~10ms | 10-30 MB | None | Performance-critical CLI tools, broad distribution |
| Framework-dependent single-file | ~100ms | 1-5 MB | .NET runtime | Internal tools where runtime is guaranteed |
| Self-contained single-file | ~100ms | 60-80 MB | None | Simple distribution without AOT complexity |
| dotnet tool (global/local) | ~200ms | < 1 MB (NuGet) | .NET SDK | Developer tools, .NET ecosystem users |
Native AOT single-file -- the gold standard for CLI distribution:
Framework-dependent deployment:
Self-contained (non-AOT):
dotnet tool packaging:
dotnet tool install -g mytoolTarget the four primary RIDs for broad coverage:
| RID | Platform | Notes |
|-----|----------|-------|
| linux-x64 | Linux x86_64 | Most Linux servers, CI runners, WSL |
| linux-arm64 | Linux ARM64 | AWS Graviton, Raspberry Pi 4+, Apple Silicon VMs |
| osx-arm64 | macOS Apple Silicon | M1/M2/M3+ Macs (primary macOS target) |
| win-x64 | Windows x86_64 | Windows 10+, Windows Server |
| RID | When to Include |
|-----|----------------|
| osx-x64 | Legacy Intel Mac support (declining market share) |
| linux-musl-x64 | Alpine Linux / Docker scratch images |
| linux-musl-arm64 | Alpine on ARM64 |
| win-arm64 | Windows on ARM (Surface Pro X, Snapdragon laptops) |
<!-- Set per publish, not in csproj (avoids accidental RID lock-in) -->
<!-- Use dotnet publish -r <rid> instead -->
<!-- If you must set a default for local development -->
<PropertyGroup Condition="'$(RuntimeIdentifier)' == ''">
<RuntimeIdentifier>osx-arm64</RuntimeIdentifier>
</PropertyGroup>
Publish per RID from the command line:
# Publish for each target RID
dotnet publish -c Release -r linux-x64
dotnet publish -c Release -r linux-arm64
dotnet publish -c Release -r osx-arm64
dotnet publish -c Release -r win-x64
Single-file publish bundles the application and its dependencies into one executable.
<PropertyGroup>
<PublishSingleFile>true</PublishSingleFile>
<!-- Required for single-file -->
<SelfContained>true</SelfContained>
<!-- Embed PDB for stack traces (optional, adds ~2-5 MB) -->
<DebugType>embedded</DebugType>
<!-- Include native libraries in the single file -->
<IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
</PropertyGroup>
When combined with Native AOT, single-file is implicit -- AOT always produces a single native binary:
<PropertyGroup>
<PublishAot>true</PublishAot>
<!-- PublishSingleFile is not needed -- AOT output is inherently single-file -->
<!-- SelfContained is implied by PublishAot -->
</PropertyGroup>
See [skill:dotnet-native-aot] for the full AOT publish configuration including ILLink, type preservation, and analyzer setup.
# Framework-dependent single-file (requires .NET runtime on target)
dotnet publish -c Release -r linux-x64 /p:PublishSingleFile=true --self-contained false
# Self-contained single-file (includes runtime, no AOT)
dotnet publish -c Release -r linux-x64 /p:PublishSingleFile=true --self-contained true
# Native AOT (inherently single-file, smallest and fastest)
dotnet publish -c Release -r linux-x64
# (when PublishAot=true is in csproj)
Trimming removes unused code from the published output. For self-contained non-AOT builds:
<PropertyGroup>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>link</TrimMode>
<!-- Suppress known trim warnings for CLI scenarios -->
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
</PropertyGroup>
For Native AOT builds, size is controlled by AOT-specific MSBuild properties. See [skill:dotnet-native-aot] for the full configuration. Key CLI-relevant properties include StripSymbols, OptimizationPreference, InvariantGlobalization, and StackTraceSupport.
| Configuration | Approximate Size | |---------------|-----------------| | Self-contained (no trim) | 60-80 MB | | Self-contained + trimmed | 15-30 MB | | Native AOT (default) | 15-25 MB | | Native AOT + size optimized | 8-15 MB | | Native AOT + invariant globalization + stripped | 5-10 MB | | Framework-dependent | 1-5 MB |
InvariantGlobalization=true)StripSymbols=true) -- keep separate symbol files for crash analysisOptimizationPreference=Size) -- minimal runtime performance impact for I/O-bound CLI toolsdotnet publish -c Release -r linux-x64 --self-contained false
Advantages:
Disadvantages:
dotnet publish -c Release -r linux-x64 --self-contained true
Advantages:
Disadvantages:
# Quick local publish for testing
dotnet publish -c Release -r osx-arm64
# Verify the binary
./bin/Release/net8.0/osx-arm64/publish/mytool --version
#!/bin/bash
# build-all.sh -- Produce artifacts for all target RIDs
set -euo pipefail
VERSION="${1:?Usage: build-all.sh <version>}"
PROJECT="src/MyCli/MyCli.csproj"
OUTPUT_DIR="artifacts"
RIDS=("linux-x64" "linux-arm64" "osx-arm64" "win-x64")
# Note: Native AOT cross-compilation for ARM64 on x64 requires platform toolchain
# See [skill:dotnet-cli-release-pipeline] for CI-based cross-compilation setup
for rid in "${RIDS[@]}"; do
echo "Publishing for $rid..."
dotnet publish "$PROJECT" \
-c Release \
-r "$rid" \
-o "$OUTPUT_DIR/$rid" \
/p:Version="$VERSION"
done
# Create archives
for rid in "${RIDS[@]}"; do
if [[ "$rid" == win-* ]]; then
(cd "$OUTPUT_DIR/$rid" && zip -q "../mytool-$VERSION-$rid.zip" *)
else
tar -czf "$OUTPUT_DIR/mytool-$VERSION-$rid.tar.gz" -C "$OUTPUT_DIR/$rid" .
fi
done
echo "Artifacts in $OUTPUT_DIR/"
Always produce checksums for release artifacts:
# Generate SHA-256 checksums
cd artifacts
shasum -a 256 *.tar.gz *.zip > checksums-sha256.txt
See [skill:dotnet-cli-release-pipeline] for automating this in GitHub Actions.
-r <rid> at publish time instead.InvariantGlobalization=true.${SIGNING_KEY}) with a comment about CI secret storage for any signing or upload credentials.npx skills add wshaddix/dotnet-cli-distribution下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer