惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Jina AI
Jina AI
S
SegmentFault 最新的问题
D
DataBreaches.Net
H
Help Net Security
有赞技术团队
有赞技术团队
M
MIT News - Artificial intelligence
Martin Fowler
Martin Fowler
IT之家
IT之家
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
腾讯CDC
罗磊的独立博客
Y
Y Combinator Blog
阮一峰的网络日志
阮一峰的网络日志
云风的 BLOG
云风的 BLOG
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
WordPress大学
WordPress大学
Microsoft Security Blog
Microsoft Security Blog
J
Java Code Geeks
Vercel News
Vercel News
Hugging Face - Blog
Hugging Face - Blog
aimingoo的专栏
aimingoo的专栏
Stack Overflow Blog
Stack Overflow Blog
Recent Announcements
Recent Announcements
博客园 - 三生石上(FineUI控件)

博客园 - 冠军

Keep alive 分析 SSRS Reporting Service 日志 [翻译] 感谢 Avalonia, .NET MAUI 即将来到 Linux 和浏览器端 多区域一致性:鱼与熊掌兼得! Microsoft Agent Framework (预览) 入门:让所有的开发者轻松创建 AI Agents 使用 AI app 模板扩展来创建基于订制数据进行聊天的 .NET AI 应用 Microsoft.Extensions.AI 库 OfficeRuntime.storage 适配 oidc-client.js 中的 WebStorageStateStore 翻译:基于 OAuth2 在 Web 服务中使用 MailKit Microsoft.Extensions.VectorData.Abstractions 入门 基于本地 Llama 生成 .NET AI 矢量搜索应用 使用 PdfPig 处理 PDF 文档 使用 Any CPU构建 .NET 8 应用会支持 32 位吗? 使用 NATS CLI NATS: 使用 work-queue Stream NATS: Pull Consumers in JetStream .NET 单文件执行程序拆解器 SingleFileExtractor .NET Aspire Apps 集成测试 ASCII 与 Unicode 中的引号
真正的 .NET 单文件发布与构建
冠军 · 2025-08-29 · via 博客园 - 冠军

涉及到的项目属性与 MS Build 配置

通过在项目文件 .csproj 中的 PropertyGroup 中配置 MSBuild 的属性,或者,在 dotnet publish 的命令行中使用 -p 参数来传递构建的配置参数。

例如配置 TargetFramework 和 RuntimeIdentifier,在命令行中:

dotnet publish \
  -p:TargetFramework=net6.0 \
  -p:RuntimeIdentifier=osx-x64 \
  ...

在项目文件中

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net6.0</TargetFramework>
    <RuntimeIdentifier>osx-x64</RuntimeIdentifier>
  </PropertyGroup>
</Project>

必要的配置

  1. TargetFramework : string
    • Target version of dotnet; will be auto filled into your .(fs|cs)proj file if created via dotnet new
    • eg: net6.0
  2. RuntimeIdentifier : string
    • Host OS/arch identifier.
    • eg: osx-x64
  3. PublishSingleFile : boolean

    注意:在 .NET Core 3.x 中,PublishSingleFile 选项也捆绑了原生二进制文件。

  4. SelfContained : boolean
    • Bundle dotnet runtime with the published build (instead of depending on the host to have it).
  5. IncludeNativeLibrariesForSelfExtract : boolean
    • Include native libraries in the single file bundle that normally get written to the binary output directory even with PublishSingleFile enabled.

    IncludeNativeLibrariesForSelfExtract 选项是 .NET 5 中的新增选项,默认情况下不捆绑原生二进制文件。

    • Example native files that will show up beside your binary (from macOS):

      libSystem.IO.Compression.Native.dylib
      libSystem.Native.dylib
      libSystem.Net.Security.Native.dylib
      libSystem.Security.Cryptography.Native.Apple.dylib
      libSystem.Security.Cryptography.Native.OpenSsl.dylib
      libclrjit.dylib
      libcoreclr.dylib

  6. DebugType : string

可选的配置

  1. PublishTrimmed : boolean
    • Tree-shake the binary.
    • Makes binary smaller.
  2. PublishReadyToRun : boolean
    • AOT compile
    • Makes start-time faster.
    • Makes binary larger.
  3. EnableCompressionInSingleFile : boolean (added in .NET 6)
    • Compress the binary.
    • Makes binary smaller.
  4. ServerGarbageCollection : boolean

完整配置示例

针对 dotnet publish 命令。

dotnet publish \
  -p:TargetFramework=net6.0 \
  -p:RuntimeIdentifier=osx-x64 \
  -p:SelfContained=true \
  -p:PublishSingleFile=true \
  -p:IncludeNativeLibrariesForSelfExtract=true \
  -p:PublishTrimmed=true \
  -p:PublishReadyToRun=true \
  -p:EnableCompressionInSingleFile=true \
  -p:DebugType=embedded \
  -p:ServerGarbageCollection=true \
  --output dist

一些选项可以作为顶级 cli 标志传递,而不是属性注入:

dotnet publish \
  --framework net6.0 \
  --runtime osx-x64 \
  --self-contained true \
  -p:PublishSingleFile=true \
  -p:IncludeNativeLibrariesForSelfExtract=true \
  -p:PublishTrimmed=true \
  -p:PublishReadyToRun=true \
  -p:EnableCompressionInSingleFile=true \
  -p:DebugType=embedded \
  -p:ServerGarbageCollection=true \
  --output dist

我是怎么使用的

dotnet publish 中提供所有这些选项很烦人,我通常将除 RuntimeTarget 之外的所有内容都放在我的项目文件中:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <SelfContained>true</SelfContained>
    <PublishSingleFile>true</PublishSingleFile>
    <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
    <PublishTrimmed>true</PublishTrimmed>
    <PublishReadyToRun>true</PublishReadyToRun>
    <DebugType>embedded</DebugType>
    <EnableCompressionInSingleFile>true</EnableCompressionInSingleFile>
    <ServerGarbageCollection>true</ServerGarbageCollection>
  </PropertyGroup>
</Project>

然后,我就可以针对所有的平台,使用如下的命令来进行构建

dotnet publish --runtime osx-x64 --output dist/darwin-amd64
dotnet publish --runtime win10-x64 --output dist/win-amd64
dotnet publish --runtime linux-x64 --output dist/linux-amd64

# Equivalent to:
# dotnet publish -p:RuntimeIdentifier osx-x64 --output dist/darwin-amd64
# dotnet publish -p:RuntimeIdentifier win10-x64 --output dist/win-amd64
# dotnet publish -p:RuntimeIdentifier linux-x64 --output dist/linux-amd64

万能的 Make file

这是我在大多数项目中使用的一体化 Makefile。有些功能我不太会用到,比如 ServerGarbageCollection 和 PublishReadyToRun,因为它们在我的日常构建中用不到,或者我更喜欢更小的可执行文件,而不是为了缩短启动时间。

.PHONY: all build clean publish compress

# ==============================================================================
# Variables
# ==============================================================================

override BUILD_OUTPUT_LIN_DIR = $(DIST_DIR)/$(DOTNET_RUNTIME_LIN)
override BUILD_OUTPUT_LIN_EXE = $(BUILD_OUTPUT_LIN_DIR)/$(EXECUTABLE_NAME)
override BUILD_OUTPUT_MAC_DIR = $(DIST_DIR)/$(DOTNET_RUNTIME_MAC)
override BUILD_OUTPUT_MAC_EXE = $(BUILD_OUTPUT_MAC_DIR)/$(EXECUTABLE_NAME)
override BUILD_OUTPUT_WIN_DIR = $(DIST_DIR)/$(DOTNET_RUNTIME_WIN)
override BUILD_OUTPUT_WIN_EXE = $(BUILD_OUTPUT_WIN_DIR)/$(EXECUTABLE_NAME).exe
override COMPRESSED_LIN_PATH  = $(DIST_DIR)/$(EXECUTABLE_NAME)-$(DOTNET_RUNTIME_LIN).tar.gz
override COMPRESSED_MAC_PATH  = $(DIST_DIR)/$(EXECUTABLE_NAME)-$(DOTNET_RUNTIME_MAC).tar.gz
override COMPRESSED_WIN_PATH  = $(DIST_DIR)/$(EXECUTABLE_NAME)-$(DOTNET_RUNTIME_WIN).zip
override DIST_DIR             = dist
override DOTNET_RUNTIME_LIN   = linux-x64
override DOTNET_RUNTIME_MAC   = osx-x64
override DOTNET_RUNTIME_WIN   = win10-x64
override DOTNET_VERSION       = net6.0
override EXECUTABLE_NAME      = $(error "Please set EXECUTABLE_NAME to the name of your executable")
override TESTS_PATH           = $(error "Please set TESTS_PATH to relative path to test project")

# ==============================================================================
# Targets
# ==============================================================================

all: clean test publish
build: $(BUILD_OUTPUT_MAC_EXE) $(BUILD_OUTPUT_LIN_EXE) $(BUILD_OUTPUT_WIN_EXE)
compress: $(COMPRESSED_MAC_PATH) $(COMPRESSED_LIN_PATH) $(COMPRESSED_WIN_PATH)
publish: build compress
clean:
    dotnet clean
    -rm -rf $(DIST_DIR)

test:
    dotnet run --project $(TESTS_PATH)

test_watch:
    dotnet watch --project $(TESTS_PATH)

# Build
$(BUILD_OUTPUT_MAC_EXE):
    dotnet publish \
        -p:TargetFramework=$(DOTNET_VERSION) \
        -p:RuntimeIdentifier=$(DOTNET_RUNTIME_MAC) \
        -p:SelfContained=true \
        -p:PublishSingleFile=true \
        -p:IncludeNativeLibrariesForSelfExtract=true \
        -p:PublishTrimmed=true \
        -p:DebugType=embedded \
        -p:EnableCompressionInSingleFile=true \
        --output $(BUILD_OUTPUT_MAC_DIR)

$(BUILD_OUTPUT_LIN_EXE):
    dotnet publish \
        -p:TargetFramework=$(DOTNET_VERSION) \
        -p:RuntimeIdentifier=$(DOTNET_RUNTIME_LIN) \
        -p:SelfContained=true \
        -p:PublishSingleFile=true \
        -p:IncludeNativeLibrariesForSelfExtract=true \
        -p:PublishTrimmed=true \
        -p:DebugType=embedded \
        -p:EnableCompressionInSingleFile=true \
        --output $(BUILD_OUTPUT_LIN_DIR)

$(BUILD_OUTPUT_WIN_EXE):
    dotnet publish \
        -p:TargetFramework=$(DOTNET_VERSION) \
        -p:RuntimeIdentifier=$(DOTNET_RUNTIME_WIN) \
        -p:SelfContained=true \
        -p:PublishSingleFile=true \
        -p:IncludeNativeLibrariesForSelfExtract=true \
        -p:PublishTrimmed=true \
        -p:DebugType=embedded \
        -p:EnableCompressionInSingleFile=true \
        --output $(BUILD_OUTPUT_WIN_DIR)

# Compress
$(COMPRESSED_MAC_PATH): $(BUILD_OUTPUT_MAC_EXE)
    tar -zcvf $(COMPRESSED_MAC_PATH) \
        --directory=$(BUILD_OUTPUT_MAC_DIR) $(EXECUTABLE_NAME)

$(COMPRESSED_LIN_PATH): $(BUILD_OUTPUT_LIN_EXE)
    tar -zcvf $(COMPRESSED_LIN_PATH) \
        --directory=$(BUILD_OUTPUT_LIN_DIR) $(EXECUTABLE_NAME)

$(COMPRESSED_WIN_PATH): $(BUILD_OUTPUT_WIN_EXE)
    cd $(BUILD_OUTPUT_WIN_DIR) \
        && zip ../$(EXECUTABLE_NAME)-$(DOTNET_RUNTIME_WIN).zip $(EXECUTABLE_NAME).exe

参考资料