nodejs源碼解析之一 (windows 編譯腳本 vcbuild.bat)
vcbuild.bat 是一個(gè) Windows 平臺(tái)下的批處理腳本,用于構(gòu)建 Node.js 源代碼。它使用 Visual Studio 的命令行工具 msbuild 來編譯和鏈接 Node.js 的 C++ 模塊,生成可執(zhí)行文件和庫文件等。
在 Windows 環(huán)境下,如果要自己編譯 Node.js 的源代碼,需要先安裝 Visual Studio,并設(shè)置好環(huán)境變量。然后,可以運(yùn)行 vcbuild.bat 腳本來編譯源代碼,生成 Node.js 可執(zhí)行文件和庫文件等。這個(gè)腳本會(huì)自動(dòng)檢測(cè)操作系統(tǒng)版本和 Visual Studio 的版本,并選擇合適的編譯選項(xiàng)。
1.試運(yùn)行
為了追蹤 vcbuild.bat 的執(zhí)行流程,而不實(shí)際進(jìn)行編譯(實(shí)際編譯太費(fèi)時(shí)間),我們需要對(duì)腳本做一個(gè)修改
首先找到下面這行,在它前面加上 echo,這樣就不會(huì)進(jìn)行實(shí)際的編譯了
執(zhí)行 vcbuild.bat,觀察輸出結(jié)果
可以看到,vcbuild.bat 的執(zhí)行過程大概分為下面幾步:
查找 python,找到3.10.2版本
查找 NASM,用于匯編編譯
查找 Visual Studio 2022,使用 msbuild 編譯c++
調(diào)用 Visual Studio 的命令提示符腳本,它初始化了x64環(huán)境變量
執(zhí)行 python configure 腳本,傳入各種 flags
最后對(duì) node.sln 執(zhí)行 msbuild
其實(shí)腳本還有個(gè)初始化過程,不過沒有任何輸出。下面我們來弄清楚每一步都發(fā)生了什么
2.初始化
2.1 help命令
檢測(cè)第一個(gè)參數(shù),如果是 help,跳轉(zhuǎn)到幫助內(nèi)容
2.2 測(cè)試參數(shù)
cd到當(dāng)前文件目錄,設(shè)置傳遞給 tools\
的測(cè)試參數(shù)
2.3 遞歸處理參數(shù)
遞歸處理腳本參數(shù),設(shè)置各種flags
:next-arg:處理下一個(gè)參數(shù),即參數(shù)移位之后的 %1,如果不存在則跳轉(zhuǎn)到 args-done
:arg-ok:當(dāng)前參數(shù)%1對(duì)應(yīng)的 flag 設(shè)置成功,使用 shift 進(jìn)行參數(shù)移位,將 %2 移動(dòng)到 %1
:arg-ok-2:它的不同在于,%1 和 %2 都被使用之后,需要調(diào)用兩次 shift,進(jìn)行兩次參數(shù)移位
:args-done:當(dāng) %1 為空時(shí),參數(shù)處理完畢
如果 %1 不正確,拋出無效參數(shù)錯(cuò)誤
2.4 參數(shù)后處理
執(zhí)行參數(shù)后處理,設(shè)置 configure_flags
build_release:構(gòu)建發(fā)布版本
msi:使用 msbuild 構(gòu)建 .msi 安裝包
package:將編譯結(jié)果打包為 zip 文件
node_exe:設(shè)置編譯結(jié)果的 node / npm / node_gyp 的執(zhí)行路徑
lint:執(zhí)行 js 和 cpp 代碼lint
configure_flags:傳遞給 configure python 腳本的 flags
deps\icu:如果 icu 庫已經(jīng)存在,則需要先刪除它,后面會(huì)下載 icu 庫
TestClean:清理臨時(shí)測(cè)試文件
3.查找python
調(diào)用查找 python 的腳本,如果出錯(cuò)則離開
下面查看 tools\msvs\find_python.cmd 的內(nèi)容
設(shè)置腳本
DEBUG_HELPER:如果沒有開啟 debug,則不輸出執(zhí)行的命令
enabledelayedexpansion:?jiǎn)⒂醚舆t擴(kuò)展,變量將在每次執(zhí)行該行時(shí)擴(kuò)展,默認(rèn)情況下擴(kuò)展只發(fā)生一次
在 PATH 環(huán)境變量里查找 python
need_path:如果python不在 PATH 環(huán)境變量里,則為1,代表需要將其加入 PATH 環(huán)境變量
for /f:for代表循環(huán),/f 指對(duì)結(jié)果進(jìn)行文件解析,將其分解為單獨(dú)的文本行并將每一行解析為零或多個(gè)標(biāo)記
"delims=":指定分隔符,此處分隔符為空
%%a:變量名,代表每一行的文本
('where python.exe 2^> nul'):使用 where 命令查找 python 的位置,2^> nul 指重定向錯(cuò)誤輸出
set p=%%~dpa:設(shè)置 p 為當(dāng)前行中的 python 路徑
:found-python:已經(jīng)找到 python,跳轉(zhuǎn)到指定 label
在注冊(cè)表里查找 python 安裝路徑
need_path:python 不在 PATH 環(huán)境變量里,如果找到了,需要將其加入 PATH
%%k:代表指定的注冊(cè)表路徑
:find-versions:調(diào)用標(biāo)簽,在指定的注冊(cè)表路徑中查找 python 版本
:found-python:如果錯(cuò)誤碼不是1,則找到 python
for /f "delims=" %%a in:遍歷命令執(zhí)行結(jié)果的每一行,每行內(nèi)容保存在 %%a 中
%~1\Python\PythonCore:使用第一個(gè)參數(shù)組成注冊(cè)表的鍵,也就是上面?zhèn)鬟f的注冊(cè)表路徑
reg query:查詢注冊(cè)表的命令
/f *: 查找指定鍵下與通配符匹配的項(xiàng)
/k:只查找鍵,不包括值數(shù)據(jù)
2^> nul:錯(cuò)誤輸出重定向到空設(shè)備
^|:管道,將前面命令的輸出作為后面命令的輸入
findstr /r ^^HK:在命令結(jié)果中查找包含指定字符串的行,^^代表^的轉(zhuǎn)義
:read-installpath:將讀取到的完整注冊(cè)表路徑傳遞給標(biāo)簽,用于獲取安裝路徑
exit /b 0:如果錯(cuò)誤碼不是1,則查找成功
exit /b 1:如果查找失敗,向上級(jí)返回錯(cuò)誤碼 1
"skip=2 tokens=1* delims=)":跳過命令結(jié)果前兩行,使用')'作為分隔符,只獲取第一部分
/ve:對(duì)值為空的進(jìn)行查詢,即查詢默認(rèn)值
/t REG_SZ:值類型為字符串
%%b:在 %%a 后面的第二部分,')'是分隔符
%%c:將 %%b 按空格分割后的字符串,
"%%c"=="REG_SZ":如果類型不是字符串,則報(bào)錯(cuò)
%%d:在 %%c 后面的第二部分,空格是分隔符
已經(jīng)找到 python
:check-python:檢查 python 版本
pt=%p%:設(shè)置 python 的路徑
need_path_ext=%need_path%:是否需要把 python 路徑加入 PATH 環(huán)境變量里
檢查 python 版本
4.查找NASM
nasm用于編譯 openssl 里的匯編語言,在arm64架構(gòu)下需要
查找方式和 python 基本一樣
5.查找node版本
調(diào)用獲取node版本的label
|| exit /b 1:如果標(biāo)簽執(zhí)行成功,則后面的exit不會(huì)執(zhí)行,如果失敗則退出
調(diào)用獲取 node 版本的python腳本,輸出類似 21.0.0
for /F:逐行讀取腳本輸出
usebackq:開啟反引號(hào)包含的命令行模式
tokens=*:讀取整行并存儲(chǔ)在 %%i 中
如果 NODE_VERSION 不存在則返回錯(cuò)誤碼 1
下面看下
,它主要從 node_version.h 源碼中讀取當(dāng)前源碼的版本
然后需要決定打包名稱 TARGET_NAME,里面包含了 node 版本和系統(tǒng)架構(gòu)
6.查找 visual studio 工具鏈
6.1 msvs架構(gòu)
首先設(shè)置 msbuild 環(huán)境變量
msvs_host_arch:msvs宿主架構(gòu),x86 或者 amd64
vcvarsall_arg:傳遞給 vcvarsall.bat 腳本的架構(gòu)參數(shù),在我的64位系統(tǒng)上是 amd64
6.2 查找 vs2022
首先查找 visual studio 2022
target_env:可以設(shè)置為 vs2022 或 vs2019
VCINSTALLDIR:visual studio安裝目錄
vswhere_usability_wrapper.cmd:查找 vs installer 目錄,目的是使用 vswhere 程序
下面查看 vswhere_usability_wrapper.cmd 腳本
首先在 %ProgramFiles(x86)% 中查找 visual studio installer 目錄
然后在 %ProgramFiles% 中查找
將 installer 目錄加入 PATH 環(huán)境變量
where vswhere:在 PATH 環(huán)境變量中查找 vswhere ,如果沒找到返回錯(cuò)誤碼1
vswhere %VSWHERE_ARGS%:執(zhí)行 vswhere 查找,返回值只有一行vs的安裝目錄
VCINSTALLDIR:設(shè)置 VC 安裝目錄
VS150COMNTOOLS:設(shè)置工具目錄
VSINSTALLDIR:清理它的值,讓叫不能按照預(yù)期工作
VSCMD_START_DIR:防止更改當(dāng)前工作目錄
vcvarsall.bat:配置 Visual C++ 環(huán)境的批處理腳本,初始化了 x64 架構(gòu)下的環(huán)境變量
6.3 查找 vs2019
基本過程和查找 vs2022 相同
7.執(zhí)行 configure
7.1 處理configure參數(shù)
noprojgen:跳過項(xiàng)目生成,直接構(gòu)建
projgen:項(xiàng)目生成,run configure
.tmp_gyp_configure_stamp:將 configure_flags 和 .gyp 文件的路徑寫入臨時(shí)文件中
.gyp_configure_stamp:如果與臨時(shí)文件不一致,則 run configure
7.2 運(yùn)行 configure
.tmp_gyp_configure_stamp:刪除臨時(shí)文件
configure:運(yùn)行 configure python 腳本
create-msvs-files-failed:項(xiàng)目生成失敗
project_generated:項(xiàng)目生成成功
8.msbuild 構(gòu)建
8.1 執(zhí)行構(gòu)建
nobuild:如果不需要構(gòu)建,直接跳過
msbcpu:設(shè)置msbuild 構(gòu)建使用的 cpu 數(shù)量
msbplatform:msbuild 構(gòu)建的目標(biāo)平臺(tái)
msbuild node.sln:對(duì)解決方案執(zhí)行構(gòu)建
8.2 構(gòu)建后處理
rd %config%:刪除配置目錄
mklink /J:創(chuàng)建符號(hào)鏈接, out\%config% 到 %config%
9.簽名
簽名是對(duì)可執(zhí)行文件進(jìn)行數(shù)字簽名,以證明文件的來源和完整性,確保文件未被篡改,從而提高文件的安全性。
下面查看 tools/sign.bat 腳本
timeservers:多個(gè)時(shí)間戳服務(wù)器的地址
signtool sign:使用簽名工具對(duì)輸入文件進(jìn)行數(shù)字簽名
10.小結(jié)
大致的構(gòu)建過程就是這樣,vcbuild.bat 里還包含了其他的功能,可以被各種參數(shù)打開
后面有機(jī)會(huì)再進(jìn)行梳理