真正的 .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>
必要的配置
- TargetFramework : string
- Target version of dotnet; will be auto filled into your .(fs|cs)proj file if created via dotnet new
- eg: net6.0
- RuntimeIdentifier : string
- Host OS/arch identifier.
- eg: osx-x64
- PublishSingleFile : boolean
- Publish to a single binary file instead of a folder with a bunch of .dll.
- Requires SelfContained.
- 此选项仅发布少量文件,而不是数十个(或数百个)文件,其余文件在应用程序启动时解压缩到内存中
- 只有托管的 DLL 文件会被打包到单个可执行文件中,而原生的二进制文件则保留为单独的文件。
- https://docs.microsoft.com/en-us/dotnet/core/deploying/single-file#publish-a-single-file-app---sample-project-file
注意:在 .NET Core 3.x 中,PublishSingleFile 选项也捆绑了原生二进制文件。
- SelfContained : boolean
- Bundle dotnet runtime with the published build (instead of depending on the host to have it).
- 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
- DebugType : string
- Bundle the .pdb into the outputted executable
- https://docs.microsoft.com/en-us/dotnet/core/deploying/single-file#include-pdb-files-inside-the-bundle
可选的配置
- PublishTrimmed : boolean
- Tree-shake the binary.
- Makes binary smaller.
- PublishReadyToRun : boolean
- AOT compile
- Makes start-time faster.
- Makes binary larger.
- EnableCompressionInSingleFile : boolean (added in .NET 6)
- Compress the binary.
- Makes binary smaller.
- ServerGarbageCollection : boolean
- https://docs.microsoft.com/en-us/dotnet/core/run-time-config/garbage-collector
- Set to true if you know you have beefy hardware and know that your app is going to be abusing the GC.
- Can drastically improve performance if you have a lot of GC pressure. For example if doing work with tree data structures.
完整配置示例
针对 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
浙公网安备 33010602011771号