.NET5.0 单文件发布打包操作深度剖析
.NET5.0 單文件發(fā)布打包操作深度剖析
前言
隨著 .NET5.0 Preview 8 的發(fā)布,許多新功能正在被社區(qū)成員一一探索;這其中就包含了“單文件發(fā)布”這個(gè)炫酷的功能,實(shí)際上,這也是社區(qū)一直以來(lái)的呼聲,從 WinForm 的 msi 開始,我們就希望有這樣一個(gè)功能,雖然在 docker 時(shí)代,單文件發(fā)布的功能顯得“不那么重要”,但正是從這一點(diǎn)可以看出,.NET 的團(tuán)隊(duì)成員一直在致力于實(shí)用功能的完善。
在 Java 的世界里,單文件發(fā)布一直伴隨著他們的成長(zhǎng),War 文件可以直接上傳到 Tomcat 上運(yùn)行,話說我們還是有那么一丟丟的羨慕的,不過凡事有利就有弊,單文件發(fā)布對(duì)于細(xì)分模塊的熱更新來(lái)說,還有有一點(diǎn)點(diǎn)的不方便。
不過瑕不掩瑜,在微服務(wù)概念越來(lái)越火熱的今天,相信單文件發(fā)布的功能帶給大家更多的是興奮。
什么是單文件發(fā)布
首先,我們要清楚的了解,什么是單文件發(fā)布。
官方的目標(biāo)定義:
.Net 5.0單個(gè)文件解決方案應(yīng)為:
廣泛兼容:可以將包含IL程序集,隨時(shí)運(yùn)行的程序集,復(fù)合程序集,本機(jī)二進(jìn)制文件,配置文件等的應(yīng)用程序打包為一個(gè)可執(zhí)行文件。
可以直接從打包軟件直接運(yùn)行應(yīng)用程序的托管組件,而無(wú)需提取到磁盤。
可與調(diào)試器和工具一起使用。
從上面的目標(biāo)可以看出,和以往版本最大的不同在于:將所有依賴打包到一個(gè)可執(zhí)行文件中,可直接運(yùn)行,不影響調(diào)試操作。
注意上面的這句話“將所有依賴打包到一個(gè)可執(zhí)行文件中”,而在以往,我們使用 dotnet publish 將應(yīng)用程序進(jìn)行發(fā)布之后,我們會(huì)看到,在 publish 下有許多項(xiàng)目依賴的 dll 文件,在 .NET5.0 到來(lái)之后,這些依賴文件可收納到一個(gè)文件中,瞬間讓人感受到了清涼。
發(fā)布操作指令相關(guān)
命令
可選參數(shù)
配置文件設(shè)置參數(shù)
除了可以使用命令行參數(shù)的形式,還可以通過配置文件的形式設(shè)置發(fā)布參數(shù),編輯項(xiàng)目文件,添加配置節(jié)點(diǎn)到文件中并保存即可。
關(guān)于 RID 說明見:https://docs.microsoft.com/en-us/dotnet/core/rid-catalog
這是截止本文發(fā)布前的 RID 版本,不排除 .NET5.0 有新的發(fā)布
其它參數(shù)
除了上面的三個(gè)可選參數(shù),我在查詢文檔的過程中還發(fā)現(xiàn),官方還提到了其它參數(shù)的使用,目前不確定是否有效
還可以通過設(shè)置 ExcludeFromSingleFile 元素,該設(shè)置將指定某些文件不嵌入單個(gè)文件之中。
編寫待打包的應(yīng)用程序
為了更直觀的看出正常發(fā)布和單文件發(fā)布的區(qū)別,我們特別準(zhǔn)備了一個(gè) Web 應(yīng)用程序,并對(duì)兩個(gè)程序集進(jìn)行依賴引用。
準(zhǔn)備好項(xiàng)目,編譯成功,嘗試發(fā)布,打開 PowerShel 控制臺(tái),分別輸入以下命令
linux-x64 和 win-x64 兩個(gè)目錄下,分別有 publish 目錄,由于平臺(tái)的不同,所引用的依賴也不一樣,這是我們?cè)缇土私膺^的,我們看看打包前后的區(qū)別
以上執(zhí)行的兩條命令語(yǔ)句,會(huì)為我們生成 Linux 和 Windows 兩個(gè)平臺(tái)的程序包,從上圖中可以看出,在打包之前,項(xiàng)目的各種引用依賴都被復(fù)制到了發(fā)布目錄下,這也是我們之前的程序發(fā)布方式,在經(jīng)過打包后,所有依賴文件都被裝入了一個(gè)可執(zhí)行文件中,在 Linux 平臺(tái)下表現(xiàn)為:PreviewWebApplication ,Windows 平臺(tái)下則為:PreviewWebApplication.exe。從打包效果來(lái)看,遷移將變得更加方便了。
運(yùn)行打包程序
打包后的程序和未打包的發(fā)布程序在運(yùn)行方式上沒有太多的差異性,在 Windows 平臺(tái)上,只需要雙擊 PreviewWebApplication.exe 就可以運(yùn)行該打包程序了,本示例創(chuàng)建的是一個(gè) WebApi 的程序,直接訪問程序偵聽的地址后得到接口返回的結(jié)果,如果您創(chuàng)建的是帶有 Razor 視圖或者攜帶其它資源文件的,可能無(wú)法訪問指定的 url。
在程序成功運(yùn)行起來(lái)后,我們發(fā)現(xiàn),打包程序并沒有解壓縮文件到磁盤,而是直接從包中加載文件到內(nèi)存中運(yùn)行;這是巨大的進(jìn)步,也是和 War 文件根本的區(qū)別。
需要注意的是,該 .exe 文件并不能單獨(dú)復(fù)制到別的地方運(yùn)行,你必須把 .exe 當(dāng)前目錄完整的復(fù)制才能運(yùn)行,這涉及到主機(jī)探測(cè)的問題,下面我們將會(huì)一一提到。
跨平臺(tái)的打包文件
通過上面的示例我們了解到,打包程序總是為不同的平臺(tái)生成獨(dú)立的包程序,這是為什么呢?這里就涉及到一個(gè)概念,也就是 Tool Interface Standard (TIS)
Executable and Linking Format(ELF)
Common Object File Format(COFF)于1983年引入,最初使用在 AT&T 的 UNIX 系統(tǒng)上。由于 COFF 的各種局限性,比如:節(jié)的最大數(shù)量受到限制,節(jié)名稱,所包含的源文件的長(zhǎng)度受到限制,并且符號(hào)調(diào)試信息無(wú)法支持實(shí)際的語(yǔ)言。最后,在 System V Release 4 (SVR4) 發(fā)布后,AT&T 使用 ELF 替代了 COFF。
工具接口標(biāo)準(zhǔn)委員會(huì) 援引委員會(huì)規(guī)范文件的說明:可執(zhí)行文件和鏈接格式最初由 UNIX 系統(tǒng)開發(fā)和發(fā)布實(shí)驗(yàn)室(USL)作為應(yīng)用程序二進(jìn)制接口(API)的一部分。工具接口標(biāo)準(zhǔn)委員會(huì) (TIS) 選擇將不斷發(fā)展的 ELF 標(biāo)準(zhǔn)作為便攜式對(duì)象文件。該標(biāo)準(zhǔn)適用于各種操作系統(tǒng)的 32 位英特爾架構(gòu)環(huán)境的格式。ELF 標(biāo)準(zhǔn)旨在通過向開發(fā)人員提供具有一組跨多個(gè)操作環(huán)境的二進(jìn)制接口定義。這將減少不同接口實(shí)現(xiàn)的數(shù)量,從而減少需要重新編寫和編譯的代碼。
ELF 文件結(jié)構(gòu)又分為三種類型,分別是:
Portable Executable (PE)
在 Windows 陣營(yíng),微軟在此 COFF 標(biāo)準(zhǔn)的基礎(chǔ)上,又進(jìn)行了創(chuàng)新和發(fā)展出了 PE 文件標(biāo)準(zhǔn)
PE Format 該規(guī)范描述了Windows操作系統(tǒng)家族下的可執(zhí)行文件(圖像)和目標(biāo)文件的結(jié)構(gòu)。這些文件分別稱為可移植可執(zhí)行(PE)和公用對(duì)象文件格式(COFF)文件。
從上面的兩種規(guī)范中可以看出,LinuX 和 Windows 都有各自的文件格式規(guī)范,而這種規(guī)范在一定程度上是不兼容的,不論是從文件結(jié)構(gòu)還是解析方式;所以 .NET5.0 中的打包程序必須為不同的平臺(tái)實(shí)現(xiàn)獨(dú)立的打包器。打包器的實(shí)現(xiàn)在 runtime 中的 Microsoft.NET.HostModel 庫(kù)中。
認(rèn)識(shí)了 ELF 和 PE 文件結(jié)構(gòu)之后,我們就可以對(duì)打包器代碼進(jìn)行閱讀理解。
Microsoft.NET.HostModel
你可以從 github 上下載 .NET 5.0 的源代碼, 轉(zhuǎn)到目錄:
runtime/src/installer/managed/Microsoft.NET.HostModel
源碼不太多,可直接進(jìn)行閱讀,主要理解層次關(guān)系即可。
打包器主要包含了三大部分的內(nèi)容,分別是 AppHost、Boundler、ComHost
在文件 Boundle/Manifest.cs 的頭部,我們看到了“單文件程序”的文件結(jié)構(gòu)定義
從上面的文件結(jié)構(gòu)中,我們可以非常清晰的看到,單文件程序的結(jié)構(gòu)一共分為三大部分,分別是:
文件頭信息的查看
我們可以通過一些工具去查看已經(jīng)打包好的文件,在 Linux 下,可以使用 readelf/objdump 等程序來(lái)獲取 PreviewWebApplication 文件的信息。在 Windows 下,可以使用 PE Tools 等工具
Linux 下 readelf 讀取文件頭信息
從圖中我們可以看到 Type:DYN(Sharedobjectfile) 這是一個(gè)標(biāo)準(zhǔn)的共享對(duì)象文件,關(guān)于 ELF 頭部信息的內(nèi)容不再展開,有興趣的同學(xué)可以自行學(xué)習(xí)相關(guān)內(nèi)容。
Windows下 PE Tools 讀取文件頭信息
已經(jīng)打包好的程序內(nèi)部包含了 319(Linux)、Windows(359) 個(gè)文件,Windows 版本在未打包前是 84.3MB,打包后是 69.8MB,最重要的是在運(yùn)行時(shí)無(wú)需解壓縮,直接從 Boundle 中運(yùn)行文件。
文件中的第三部分,也就是 “實(shí)體清單(Manifest Entries)的寫入代碼在 Boundle\Boundler.cs\AddToBundle
在成員方法 GenerateBundle(IReadOnlyListfileSpecs) 內(nèi)部迭代調(diào)用了 AddToBundle 方法,完成了實(shí)體清單文件的寫入。
因?yàn)榻鈮浩鞯膶?shí)現(xiàn)已經(jīng)轉(zhuǎn)移到了 HostFxr 和 HostPolicy 中,以靜態(tài)鏈接庫(kù)的方式鏈接到打包器中,且該部分代碼由 C++ 進(jìn)行編寫,鑒于 C++ 水平有限,在這里不作介紹。
結(jié)束語(yǔ)
編寫這篇文章耗費(fèi)了我大量的時(shí)間,期間大量閱讀海量的參考資料、文獻(xiàn)、標(biāo)準(zhǔn)文檔、制作文章配圖等等,寫干貨文章真的需要投入巨大的精力和時(shí)間,希望你們喜歡。
文章進(jìn)行到這里,我知道肯定還有很多同學(xué)沒看過癮,但是我們可以通過回顧打包器的開發(fā)進(jìn)度表來(lái)體驗(yàn)一下 .NET 團(tuán)隊(duì)的開發(fā)熱情。
主要參考資料
.NET團(tuán)隊(duì)計(jì)劃經(jīng)理 Richard Lander 的博客:https://devblogs.microsoft.com/dotnet/announcing-net-5-0-preview-8/
Boundler 進(jìn)度表:https://github.com/dotnet/runtime/issues/36590
single-file:https://github.com/dotnet/designs/tree/master/accepted/2020/single-file
ELF文檔:https://refspecs.linuxbase.org/elf/elf.pdf
ELF維基百科:https://en.wikipedia.org/wiki/ExecutableandLinkable_Format
Readelf:https://sourceware.org/binutils/docs/binutils/readelf.html
PE文檔:https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
PE Tools:https://github.com/petoolse/petools
總結(jié)
以上是生活随笔為你收集整理的.NET5.0 单文件发布打包操作深度剖析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 程序员过关斩将--应对高并发系统有没有通
- 下一篇: 不喜欢 merge 分叉,那就用 reb