.NET 5 時介紹過用 Visual Studio 發行 .NET 5 專案,.NET 6 這篇改整理 .NET CLI (dotnet 命令列工具) 發行技巧。

主要參考來源為微軟文件 - .NET application publishing overview

dotnet publish 產生的執行程式有兩種形式:可執行檔 (在 Windows 為 .exe,在 Linux/macOS 則是與專案同名的無附檔名檔案) 或跨平台程式檔 (.dll,用 dotnet appName.dll 執行)。

可執行檔種類取決執行 dotnet publish 所在的平台,加上 -r win-x64、-r linux-x64 則可產生特定平台用的可執行檔,win-x64 是所謂 RID ( .NET 6 支援的 RID 清單),不同平台又再有 Self-Contained 及 Framework-Dependent 兩種選擇。

  • Self-Contained
    部署結果包含 .NET Runtime,部署對象不需先安裝 .NET SDK 或 .NET Runtime,缺點是檔案較多,且每支程式重複自帶 Runtime 會多佔用磁碟空間
  • Framework-Dependent
    只包含程式及其參照程式庫,部署對象必須裝好 .NET SDK 或 .NET Runtime

由可執行檔 vs 跨平台程式檔,Self-Contained vs Framework-Dependent 兩個維度排列組合,dotnet publish 參數分別為:(適用 .NET Core 3.1、.NET 5/6,排除 .NET 2.1)

  • Framework-Dependent 跨平台程式檔
    dotnet publish -c Release -p:UseAppHost=false
  • Framework-Dependent 可執行檔
    dotnet publish -c Release -r <RID> --no-self-contained
    dotnet publish -c Release -r <RID> --self-contained false
    dotnet publish -c Release (未指定 -r 時,產出目前所在平台的可執行檔)
  • Self-Contained 可執行檔
    dotnet publish -c Release -r <RID> --self-contained
  • Self-Contained 跨平台程式檔
    無此組合

先列出主要可用參數,後面再補充說明

  • PublishSingleFile
  • UseAppHost
  • IncludeNativeLibrariesForSelfExtract
  • IncludeAllContentForSelfExtract
  • IncludeSymbolsInSingleFile
  • PublishReadyToRun
  • EnableCompressionInSingleFile

以上參數有兩種設定方式,直接寫在 .csproj:

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <RootNamespace>dotnet_publish_test</RootNamespace>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <PublishSingleFile>false</PublishSingleFile>
  </PropertyGroup>

或是 dotnet publish 加上 -p:PublishSingleFile=true 設定,它將覆寫 .csproj 裡的設定:

dotnet publish -c Release -r win-x64 --no-self-contained -p:PublishSingleFile=true

簡單介紹各參數用法。

如果覺得 Publish 結果包含一堆 DLL 太亂,有個 PublishSingleFile 參數可將所有 DLL 整併成單一執行檔,部署時一檔搞定。但 --self-contained 時會內含 .NET Runtime,執行檔大小常超過 70MB,有個 PublishTrimmed 參數可排除沒用到 DLL,執行檔有時可瘦身一半以上。但偵測未用 DLL 很難完全精準,瘦身版務必仔細測試再上線。

PublishSingleFile 預設只會整併 .NET 寫的 DLL (Managed DLL),DLL 依附的原生函式庫仍保留獨立檔案形式,IncludeNativeLibrariesForSelfExtract 參數能將這部分也合併進執行檔,執行時原生 DLL 檔將匯出到特定目錄(在 Windows 為 %TEMP%/.net、Linux 及 MacOS 為 $HOIME/.net,或可使用 DOTNET_BUNDLE_EXTRACT_BASE_DIR 環境變數指定)。依實測,沒用到原生 DLL,PublishSingleFile 併入的 Managed DLL 可直接載入記憶體使用,檔案不需落地;但如用到 IncludeNativeLibrariesForSelfExtract 併入原生 DLL,則需一併指定 IncludeAllContentForSelfExtract,將原生 DLL 跟參照 .NET DLL 都匯出到 %TEMP%/.net、$HOME/.net 或 DOTNET_BUNDLE_EXTRACT_BASE_DIR 指定目錄,才能正確載入原生 DLL。實地觀察可在 .net 目錄看到匯出檔案:

PublishSingleFile 合併成單一檔案後,仍會有 .pdb 檔,使用 IncludeSymbolsInSingleFile 則可合併 .pdb 內容,實現真正單一檔案。

若有特殊 DLL 檔不要併入執行檔但要放進發行目錄,可修改 csproj:

<ItemGroup>
  <Content Update="Plugin.dll">
    <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
    <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
  </Content>
</ItemGroup>

另外,PublishReadyToRun 參數可指定預先完成 JIT 編譯,程式碼體積會變大,但可節省程式啟動時間。

.NET 6 起,多了 EnableCompressionInSingleFile 參數可壓縮併入執行檔的 DLL,進一步縮小體積。但程式啟動時需先在記憶體中解壓縮,要付出效能上的代價,建議視應用情境取捨。

[2021-12-14 補充] dotnet publish 預設包含編譯程序,還有個 --no-build 參數可直接使用 obj 下的內容。若發行程序有使用混淆器處理 obj 目錄檔案,--no-build 可避免混淆過的檔案被覆寫,但此時不可用 PublishTrimmed。(感謝讀者 Peter Cheng 分享)

最後來個範例整理:

  1. dotnet publish -c Release
    不含 .NET Runtime,同時產出 .dll 及 .exe (或是所在平台的可執行檔),部署對象需安裝 .NET Runtime/SDK
  2. dotnet publish -c Release -p:UseAppHost=false
    不含 .NET Runtime,只產出 .dll,部署對象需安裝 .NET Runtime/SDK,使用 dotnet appName.dll 執行
  3. dotnet publish -c Release -r win-x64 --self-contained
    包含 Windows x64 .NET Runtime 以及 .exe 可執行檔
  4. dotnet publish -c Release -r linux-x64 --no-self-contained
    產生 64 位元 Linux 用的 .exe 可執行檔,部署對象需安裝 .NET Runtime/SDK
  5. dotnet publish -c Release -r linux-x64 --self-contained
    包含 64 位元 Linux .NET Runtime 以及 Linux 用的可執行檔
  6. dotnet publish -c Release -r win-x64 --self-contained -p:PublishSingleFile=true
    將 Windows x64 .NET Runtime、參照的 .NET .dll,以及程式本身打包成一個 .exe 可執行檔 (大小可能超過 70MB),但參照程式庫附屬的原生程式庫仍維持個別檔案
  7. dotnet publish -c Release -r win-x64 --self-contained -p:PublishSingleFile=true -p:IncludeNativeLibrariesForSelfExtract=true -p:IncludeAllContentForSelfExtract=true 同 6,但將參照程式庫附屬的原生程式庫也包進 .exe,實測 IncludeNativeLibrariesForSelfExtract 需配合 IncludeAllContentForSelfExtract 參數才能正確執行
  8. dotnet publish -c Release -r win-x64 --self-contained -p:PublishSingleFile=true -p:PublishTrimmed=true
    同 6,但進行程式瘦身,剔除沒用到的程式庫部分,.exe 甚至可縮小一半以上。但有可能出現誤刪,部署前請慎重測試

Summary of using .NET CLI to publish .NET 6 project.


Comments

# by 阿光

黑暗大哥你好, 如果我要同時publish到多台網站主機,而且我只要把bin更新就好,其他web.config、app.config都不要發布,要怎麼寫成自動化指令呢?

# by 阿光

忘了說,我的環境是.net 4.7.2,windows 2012 R2

# by Jeffrey

to 阿光,這篇介紹的是 .NET 6,與 .NET Framework (4.0~4.8) 發佈或複製檔案到主機關係不大。但你說的複製檔案到多台主機的需求我也有遇到,以前是用輔助工具產生指令腳本 https://blog.darkthread.net/blog/copy-script-generator/ ,現在的話應該會用 PowerShell 寫 for 迴圈處理,這裡有更複雜的範例 https://blog.darkthread.net/blog/sync-files-by-hash/

# by 阿光

真是抱歉發錯地方,謝謝黑暗大哥的提醒。 也謝謝你的建議,我會去拜讀您的文章研究看看。

# by Will

IncludeSymbolsInSingleFile 從 .NET 5 開始就不能用了,要改用 -p:DebugType=embedded

Post a comment