dotnet-cli-distribution
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.
Scope
- Distribution strategy decision matrix (AOT, framework-dependent, self-contained, dotnet tool)
- Runtime Identifier (RID) matrix planning for cross-platform targets
- Single-file publish configuration
- Binary size optimization for CLI tools
- Publishing workflow (local and release artifacts)
Out of scope
- Native AOT MSBuild configuration (PublishAot, ILLink descriptors) -- see [skill:dotnet-native-aot]
- AOT-first application design patterns -- 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.
Distribution Strategy Decision Matrix
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 |
~120-200ms |
60-80 MB |
None |
Simple distribution without AOT complexity |
dotnet tool (global/local) |
~200ms |
< 1 MB (NuGet) |
.NET SDK |
Developer tools, .NET ecosystem users |
Note: Startup timings are approximate for small console apps on modern hardware and vary by workload and
environment. Self-contained single-file builds are often slightly slower to start than framework-dependent single-file
builds due to larger artifacts and extraction overhead.
When to Choose Each Strategy
Native AOT single-file -- the gold standard for CLI distribution:
- Zero dependencies on target machine (no .NET runtime needed)
- Fastest startup (~10ms vs ~100ms+ for JIT)
- Smallest binary when combined with trimming
- Trade-off: longer build times, no reflection unless preserved
- See [skill:dotnet-native-aot] for PublishAot MSBuild configuration
Framework-dependent deployment:
- Smallest artifact size (only app code, no runtime)
- Users must have .NET runtime installed
- Best for internal/enterprise tools where runtime is managed
- Can still use single-file publish for convenience
Self-contained (non-AOT):
- Includes .NET runtime in the artifact
- Larger binary than AOT but simpler build process
- Full reflection and dynamic code support
- Good compromise when AOT compat is difficult
dotnet tool packaging:
- Distributed via NuGet -- simplest publishing workflow
- Users install with
dotnet tool install -g mytool
- Requires .NET SDK on target (not just runtime)
- Best for developer-facing tools in the .NET ecosystem
- See [skill:dotnet-cli-packaging] for NuGet distribution details
Runtime Identifier (RID) Matrix
Standard CLI RID Targets
Target 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 |
Optional Extended Targets
| 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) |
RID Configuration in .csproj
<!-- 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>
```text
Publish per RID from the command line:
```bash
# 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
```text
---
## Single-File Publish
Single-file publish bundles the application and its dependencies into one executable.
### Configuration
```xml
<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>
```text
### Single-File with Native AOT
When combined with Native AOT, single-file is implicit -- AOT always produces a single native binary:
```xml
<PropertyGroup>
<PublishAot>true</PublishAot>
<!-- PublishSingleFile is not needed -- AOT output is inherently single-file -->
<!-- SelfContained is implied by PublishAot -->
</PropertyGroup>
```text
See [skill:dotnet-native-aot] for the full AOT publish configuration including ILLink, type preservation, and analyzer
setup.
### Publish Command
```bash
# 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)
```text
---
## Size Optimization for CLI Binaries
### Trimming (Non-AOT)
Trimming removes unused code from the published output. For self-contained non-AOT builds:
```xml
<PropertyGroup>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>link</TrimMode>
<!-- Suppress known trim warnings for CLI scenarios -->
<SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>
</PropertyGroup>
```text
### AOT Size Optimization
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`.
### Size Comparison (Typical CLI Tool)
| 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 |
### Practical Size Reduction Checklist
1. **Enable invariant globalization** if the tool does not need locale-specific formatting
(`InvariantGlobalization=true`)
2. **Strip symbols** on Linux/macOS (`StripSymbols=true`) -- keep separate symbol files for crash analysis
3. **Optimize for size** (`OptimizationPreference=Size`) -- minimal runtime performance impact for I/O-bound CLI tools
4. **Disable reflection** where possible -- use source generators for JSON serialization
([skill:dotnet-aot-architecture])
5. **Audit NuGet dependencies** -- each dependency adds to the binary; remove unused packages
---
## Framework-Dependent vs Self-Contained Trade-offs
### Framework-Dependent
```bash
dotnet publish -c Release -r linux-x64 --self-contained false
```bash
**Advantages:**
- Smallest artifact (1-5 MB)
- Serviced by runtime updates (security patches applied by runtime, not app rebuild)
- Faster publish times
**Disadvantages:**
- Requires matching .NET runtime on target
- Runtime version mismatch causes startup failures
- Users must manage runtime installation
### Self-Contained
```bash
dotnet publish -c Release -r linux-x64 --self-contained true
```bash
**Advantages:**
- No runtime dependency on target
- App controls exact runtime version
- Side-by-side deployment (multiple apps, different runtimes)
**Disadvantages:**
- Larger artifact (60-80 MB without trimming)
- Must rebuild and redistribute for runtime security patches
- One artifact per target RID
---
## Publishing Workflow
### Local Development
```bash
# Quick local publish for testing
dotnet publish -c Release -r osx-arm64
# Verify the binary
./bin/Release/net8.0/osx-arm64/publish/mytool --version
```text
### Producing Release Artifacts
```bash
#!/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/"
```text
### Checksum Generation
Always produce checksums for release artifacts:
```bash
# Generate SHA-256 checksums
cd artifacts
shasum -a 256 *.tar.gz *.zip > checksums-sha256.txt
```bash
See [skill:dotnet-cli-release-pipeline] for automating this in GitHub Actions.
---
## Agent Gotchas
1. **Do not set RuntimeIdentifier in the .csproj for multi-platform CLI tools.** Hardcoding a RID in the project file
prevents building for other platforms. Pass `-r <rid>` at publish time instead.
2. **Do not use PublishSingleFile with PublishAot.** Native AOT output is inherently single-file. Setting both is
redundant and may cause confusing build warnings.
3. **Do not skip InvariantGlobalization for size-sensitive CLI tools.** Globalization data adds ~25 MB to AOT binaries.
Most CLI tools that do not format locale-specific dates/currencies should enable `InvariantGlobalization=true`.
4. **Do not distribute self-contained non-trimmed binaries.** A 60-80 MB CLI tool is unacceptable for end users. Either
trim (PublishTrimmed), use AOT, or distribute as framework-dependent.
5. **Do not forget to produce checksums for release artifacts.** Users and package managers need SHA-256 checksums to
verify download integrity. See [skill:dotnet-cli-release-pipeline] for automated checksum generation.
6. **Do not hardcode secrets in publish scripts.** Use environment variable placeholders (`${SIGNING_KEY}`) with a
comment about CI secret storage for any signing or upload credentials.
---
## Code Navigation (Serena MCP)
**Primary approach:** Use Serena symbol operations for efficient code navigation:
1. **Find definitions**: `serena_find_symbol` instead of text search
2. **Understand structure**: `serena_get_symbols_overview` for file organization
3. **Track references**: `serena_find_referencing_symbols` for impact analysis
4. **Precise edits**: `serena_replace_symbol_body` for clean modifications
**When to use Serena vs traditional tools:**
- ✅ **Use Serena**: Navigation, refactoring, dependency analysis, precise edits
- ✅ **Use Read/Grep**: Reading full files, pattern matching, simple text operations
- ✅ **Fallback**: If Serena unavailable, traditional tools work fine
**Example workflow:**
```text
# Instead of:
Read: src/Services/OrderService.cs
Grep: "public void ProcessOrder"
# Use:
serena_find_symbol: "OrderService/ProcessOrder"
serena_get_symbols_overview: "src/Services/OrderService.cs"
```
## References
- [.NET application publishing overview](https://learn.microsoft.com/en-us/dotnet/core/deploying/)
- [Single-file deployment](https://learn.microsoft.com/en-us/dotnet/core/deploying/single-file/overview)
- [Native AOT deployment](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/)
- [Runtime Identifier (RID) catalog](https://learn.microsoft.com/en-us/dotnet/core/rid-catalog)
- [Trimming options](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trimming-options)
1---2name: dotnet-cli-distribution3category: operations4subcategory: ci-cd5description: Chooses CLI output format. AOT vs framework-dependent, RID matrix, single-file, dotnet tool.6license: MIT7targets: ['*']8tags: [csharp, dotnet, skill]9version: '0.0.1'10author: 'dotnet-agent-harness'11invocable: true12claudecode:13 allowed-tools: ['Read', 'Grep', 'Glob', 'Bash', 'Write', 'Edit']14codexcli:15 short-description: '.NET skill guidance for csharp tasks'16opencode:17 allowed-tools: ['Read', 'Grep', 'Glob', 'Bash', 'Write', 'Edit']18copilot: {}19geminicli: {}20antigravity: {}21---2223# dotnet-cli-distribution2425CLI distribution strategy for .NET tools: choosing between Native AOT single-file publish, framework-dependent26deployment, and `dotnet tool` packaging. Runtime Identifier (RID) matrix planning for cross-platform targets (linux-x64,27osx-arm64, win-x64, linux-arm64), single-file publish configuration, and binary size optimization techniques for CLI28applications.2930**Version assumptions:** .NET 8.0+ baseline. Native AOT for console apps is fully supported since .NET 8. Single-file31publish has been mature since .NET 6.3233## Scope3435- Distribution strategy decision matrix (AOT, framework-dependent, self-contained, dotnet tool)36- Runtime Identifier (RID) matrix planning for cross-platform targets37- Single-file publish configuration38- Binary size optimization for CLI tools39- Publishing workflow (local and release artifacts)4041## Out of scope4243- Native AOT MSBuild configuration (PublishAot, ILLink descriptors) -- see [skill:dotnet-native-aot]44- AOT-first application design patterns -- see [skill:dotnet-aot-architecture]45- Multi-platform packaging formats (Homebrew, apt/deb, winget, Scoop) -- see [skill:dotnet-cli-packaging]46- Release CI/CD pipeline -- see [skill:dotnet-cli-release-pipeline]47- Container-based distribution -- see [skill:dotnet-containers]48- General CI/CD patterns -- see [skill:dotnet-gha-patterns] and [skill:dotnet-ado-patterns]4950Cross-references: [skill:dotnet-native-aot] for AOT compilation pipeline, [skill:dotnet-aot-architecture] for AOT-safe51design patterns, [skill:dotnet-cli-architecture] for CLI layered architecture, [skill:dotnet-cli-packaging] for52platform-specific package formats, [skill:dotnet-cli-release-pipeline] for automated release workflows,53[skill:dotnet-containers] for container-based distribution, [skill:dotnet-tool-management] for consumer-side tool54installation and manifest management.5556---5758## Distribution Strategy Decision Matrix5960Choose the distribution model based on target audience and deployment constraints.6162| Strategy | Startup Time | Binary Size | Runtime Required | Best For |63| ------------------------------- | ------------ | -------------- | ---------------- | -------------------------------------------------- |64| Native AOT single-file | ~10ms | 10-30 MB | None | Performance-critical CLI tools, broad distribution |65| Framework-dependent single-file | ~100ms | 1-5 MB | .NET runtime | Internal tools where runtime is guaranteed |66| Self-contained single-file | ~120-200ms | 60-80 MB | None | Simple distribution without AOT complexity |67| `dotnet tool` (global/local) | ~200ms | < 1 MB (NuGet) | .NET SDK | Developer tools, .NET ecosystem users |6869**Note:** Startup timings are approximate for small console apps on modern hardware and vary by workload and70environment. Self-contained single-file builds are often slightly slower to start than framework-dependent single-file71builds due to larger artifacts and extraction overhead.7273### When to Choose Each Strategy7475**Native AOT single-file** -- the gold standard for CLI distribution:7677- Zero dependencies on target machine (no .NET runtime needed)78- Fastest startup (~10ms vs ~100ms+ for JIT)79- Smallest binary when combined with trimming80- Trade-off: longer build times, no reflection unless preserved81- See [skill:dotnet-native-aot] for PublishAot MSBuild configuration8283**Framework-dependent deployment:**8485- Smallest artifact size (only app code, no runtime)86- Users must have .NET runtime installed87- Best for internal/enterprise tools where runtime is managed88- Can still use single-file publish for convenience8990**Self-contained (non-AOT):**9192- Includes .NET runtime in the artifact93- Larger binary than AOT but simpler build process94- Full reflection and dynamic code support95- Good compromise when AOT compat is difficult9697**`dotnet tool` packaging:**9899- Distributed via NuGet -- simplest publishing workflow100- Users install with `dotnet tool install -g mytool`101- Requires .NET SDK on target (not just runtime)102- Best for developer-facing tools in the .NET ecosystem103- See [skill:dotnet-cli-packaging] for NuGet distribution details104105---106107## Runtime Identifier (RID) Matrix108109### Standard CLI RID Targets110111Target the four primary RIDs for broad coverage:112113| RID | Platform | Notes |114| ------------- | ------------------- | ------------------------------------------------ |115| `linux-x64` | Linux x86_64 | Most Linux servers, CI runners, WSL |116| `linux-arm64` | Linux ARM64 | AWS Graviton, Raspberry Pi 4+, Apple Silicon VMs |117| `osx-arm64` | macOS Apple Silicon | M1/M2/M3+ Macs (primary macOS target) |118| `win-x64` | Windows x86_64 | Windows 10+, Windows Server |119120### Optional Extended Targets121122| RID | When to Include |123| ------------------ | -------------------------------------------------- |124| `osx-x64` | Legacy Intel Mac support (declining market share) |125| `linux-musl-x64` | Alpine Linux / Docker scratch images |126| `linux-musl-arm64` | Alpine on ARM64 |127| `win-arm64` | Windows on ARM (Surface Pro X, Snapdragon laptops) |128129### RID Configuration in .csproj130131````xml132133<!-- Set per publish, not in csproj (avoids accidental RID lock-in) -->134<!-- Use dotnet publish -r <rid> instead -->135136<!-- If you must set a default for local development -->137<PropertyGroup Condition="'$(RuntimeIdentifier)' == ''">138 <RuntimeIdentifier>osx-arm64</RuntimeIdentifier>139</PropertyGroup>140141```text142143Publish per RID from the command line:144145```bash146147# Publish for each target RID148dotnet publish -c Release -r linux-x64149dotnet publish -c Release -r linux-arm64150dotnet publish -c Release -r osx-arm64151dotnet publish -c Release -r win-x64152153```text154155---156157## Single-File Publish158159Single-file publish bundles the application and its dependencies into one executable.160161### Configuration162163```xml164165<PropertyGroup>166 <PublishSingleFile>true</PublishSingleFile>167 <!-- Required for single-file -->168 <SelfContained>true</SelfContained>169 <!-- Embed PDB for stack traces (optional, adds ~2-5 MB) -->170 <DebugType>embedded</DebugType>171 <!-- Include native libraries in the single file -->172 <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>173</PropertyGroup>174175```text176177### Single-File with Native AOT178179When combined with Native AOT, single-file is implicit -- AOT always produces a single native binary:180181```xml182183<PropertyGroup>184 <PublishAot>true</PublishAot>185 <!-- PublishSingleFile is not needed -- AOT output is inherently single-file -->186 <!-- SelfContained is implied by PublishAot -->187</PropertyGroup>188189```text190191See [skill:dotnet-native-aot] for the full AOT publish configuration including ILLink, type preservation, and analyzer192setup.193194### Publish Command195196```bash197198# Framework-dependent single-file (requires .NET runtime on target)199dotnet publish -c Release -r linux-x64 /p:PublishSingleFile=true --self-contained false200201# Self-contained single-file (includes runtime, no AOT)202dotnet publish -c Release -r linux-x64 /p:PublishSingleFile=true --self-contained true203204# Native AOT (inherently single-file, smallest and fastest)205dotnet publish -c Release -r linux-x64206# (when PublishAot=true is in csproj)207208```text209210---211212## Size Optimization for CLI Binaries213214### Trimming (Non-AOT)215216Trimming removes unused code from the published output. For self-contained non-AOT builds:217218```xml219220<PropertyGroup>221 <PublishTrimmed>true</PublishTrimmed>222 <TrimMode>link</TrimMode>223 <!-- Suppress known trim warnings for CLI scenarios -->224 <SuppressTrimAnalysisWarnings>false</SuppressTrimAnalysisWarnings>225</PropertyGroup>226227```text228229### AOT Size Optimization230231For Native AOT builds, size is controlled by AOT-specific MSBuild properties. See [skill:dotnet-native-aot] for the full232configuration. Key CLI-relevant properties include `StripSymbols`, `OptimizationPreference`, `InvariantGlobalization`,233and `StackTraceSupport`.234235### Size Comparison (Typical CLI Tool)236237| Configuration | Approximate Size |238| ----------------------------------------------- | ---------------- |239| Self-contained (no trim) | 60-80 MB |240| Self-contained + trimmed | 15-30 MB |241| Native AOT (default) | 15-25 MB |242| Native AOT + size optimized | 8-15 MB |243| Native AOT + invariant globalization + stripped | 5-10 MB |244| Framework-dependent | 1-5 MB |245246### Practical Size Reduction Checklist2472481. **Enable invariant globalization** if the tool does not need locale-specific formatting249 (`InvariantGlobalization=true`)2502. **Strip symbols** on Linux/macOS (`StripSymbols=true`) -- keep separate symbol files for crash analysis2513. **Optimize for size** (`OptimizationPreference=Size`) -- minimal runtime performance impact for I/O-bound CLI tools2524. **Disable reflection** where possible -- use source generators for JSON serialization253 ([skill:dotnet-aot-architecture])2545. **Audit NuGet dependencies** -- each dependency adds to the binary; remove unused packages255256---257258## Framework-Dependent vs Self-Contained Trade-offs259260### Framework-Dependent261262```bash263264dotnet publish -c Release -r linux-x64 --self-contained false265266```bash267268**Advantages:**269270- Smallest artifact (1-5 MB)271- Serviced by runtime updates (security patches applied by runtime, not app rebuild)272- Faster publish times273274**Disadvantages:**275276- Requires matching .NET runtime on target277- Runtime version mismatch causes startup failures278- Users must manage runtime installation279280### Self-Contained281282```bash283284dotnet publish -c Release -r linux-x64 --self-contained true285286```bash287288**Advantages:**289290- No runtime dependency on target291- App controls exact runtime version292- Side-by-side deployment (multiple apps, different runtimes)293294**Disadvantages:**295296- Larger artifact (60-80 MB without trimming)297- Must rebuild and redistribute for runtime security patches298- One artifact per target RID299300---301302## Publishing Workflow303304### Local Development305306```bash307308# Quick local publish for testing309dotnet publish -c Release -r osx-arm64310311# Verify the binary312./bin/Release/net8.0/osx-arm64/publish/mytool --version313314```text315316### Producing Release Artifacts317318```bash319320#!/bin/bash321# build-all.sh -- Produce artifacts for all target RIDs322set -euo pipefail323324VERSION="${1:?Usage: build-all.sh <version>}"325PROJECT="src/MyCli/MyCli.csproj"326OUTPUT_DIR="artifacts"327328RIDS=("linux-x64" "linux-arm64" "osx-arm64" "win-x64")329# Note: Native AOT cross-compilation for ARM64 on x64 requires platform toolchain330# See [skill:dotnet-cli-release-pipeline] for CI-based cross-compilation setup331332for rid in "${RIDS[@]}"; do333 echo "Publishing for $rid..."334 dotnet publish "$PROJECT" \335 -c Release \336 -r "$rid" \337 -o "$OUTPUT_DIR/$rid" \338 /p:Version="$VERSION"339done340341# Create archives342for rid in "${RIDS[@]}"; do343 if [[ "$rid" == win-* ]]; then344 (cd "$OUTPUT_DIR/$rid" && zip -q "../mytool-$VERSION-$rid.zip" *)345 else346 tar -czf "$OUTPUT_DIR/mytool-$VERSION-$rid.tar.gz" -C "$OUTPUT_DIR/$rid" .347 fi348done349350echo "Artifacts in $OUTPUT_DIR/"351352```text353354### Checksum Generation355356Always produce checksums for release artifacts:357358```bash359360# Generate SHA-256 checksums361cd artifacts362shasum -a 256 *.tar.gz *.zip > checksums-sha256.txt363364```bash365366See [skill:dotnet-cli-release-pipeline] for automating this in GitHub Actions.367368---369370## Agent Gotchas3713721. **Do not set RuntimeIdentifier in the .csproj for multi-platform CLI tools.** Hardcoding a RID in the project file373 prevents building for other platforms. Pass `-r <rid>` at publish time instead.3742. **Do not use PublishSingleFile with PublishAot.** Native AOT output is inherently single-file. Setting both is375 redundant and may cause confusing build warnings.3763. **Do not skip InvariantGlobalization for size-sensitive CLI tools.** Globalization data adds ~25 MB to AOT binaries.377 Most CLI tools that do not format locale-specific dates/currencies should enable `InvariantGlobalization=true`.3784. **Do not distribute self-contained non-trimmed binaries.** A 60-80 MB CLI tool is unacceptable for end users. Either379 trim (PublishTrimmed), use AOT, or distribute as framework-dependent.3805. **Do not forget to produce checksums for release artifacts.** Users and package managers need SHA-256 checksums to381 verify download integrity. See [skill:dotnet-cli-release-pipeline] for automated checksum generation.3826. **Do not hardcode secrets in publish scripts.** Use environment variable placeholders (`${SIGNING_KEY}`) with a383 comment about CI secret storage for any signing or upload credentials.384385---386387388389## Code Navigation (Serena MCP)390391**Primary approach:** Use Serena symbol operations for efficient code navigation:3923931. **Find definitions**: `serena_find_symbol` instead of text search3942. **Understand structure**: `serena_get_symbols_overview` for file organization3953. **Track references**: `serena_find_referencing_symbols` for impact analysis3964. **Precise edits**: `serena_replace_symbol_body` for clean modifications397398**When to use Serena vs traditional tools:**399- ✅ **Use Serena**: Navigation, refactoring, dependency analysis, precise edits400- ✅ **Use Read/Grep**: Reading full files, pattern matching, simple text operations401- ✅ **Fallback**: If Serena unavailable, traditional tools work fine402403**Example workflow:**404```text405# Instead of:406Read: src/Services/OrderService.cs407Grep: "public void ProcessOrder"408409# Use:410serena_find_symbol: "OrderService/ProcessOrder"411serena_get_symbols_overview: "src/Services/OrderService.cs"412```413## References414415- [.NET application publishing overview](https://learn.microsoft.com/en-us/dotnet/core/deploying/)416- [Single-file deployment](https://learn.microsoft.com/en-us/dotnet/core/deploying/single-file/overview)417- [Native AOT deployment](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/)418- [Runtime Identifier (RID) catalog](https://learn.microsoft.com/en-us/dotnet/core/rid-catalog)419- [Trimming options](https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trimming-options)420````