冠军

导航

真正的 .NET 单文件发布与构建

涉及到的项目属性与 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

参考资料

posted on 2025-08-29 15:02  冠军  阅读(88)  评论(0)    收藏  举报