nodejs源碼解析之一 (windows 編譯腳本 vcbuild.bat)

repo: GitHub - nodejs/node: Node.js JavaScript runtime

vcbuild.bat 是一個 Windows 平臺下的批處理腳本,用于構建 Node.js 源代碼。它使用 Visual Studio 的命令行工具 msbuild 來編譯和鏈接 Node.js 的 C++ 模塊瓷胧,生成可執(zhí)行文件和庫文件等葡公。

在 Windows 環(huán)境下俊马,如果要自己編譯 Node.js 的源代碼呆瞻,需要先安裝 Visual Studio峭范,并設置好環(huán)境變量萝嘁。然后梆掸,可以運行 vcbuild.bat 腳本來編譯源代碼,生成 Node.js 可執(zhí)行文件和庫文件等牙言。這個腳本會自動檢測操作系統(tǒng)版本和 Visual Studio 的版本沥潭,并選擇合適的編譯選項。

1.試運行

為了追蹤 vcbuild.bat 的執(zhí)行流程嬉挡,而不實際進行編譯(實際編譯太費時間)钝鸽,我們需要對腳本做一個修改

首先找到下面這行,在它前面加上 echo庞钢,這樣就不會進行實際的編譯了

msbuild node.sln xxx -> echo msbuild node.sln xxx

執(zhí)行 vcbuild.bat拔恰,觀察輸出結果

.\vcbuild.bat
Looking for Python
Python found in D:\dev\lang\python\Python310\\python.exe
Python 3.10.2
Looking for NASM
Looking for Visual Studio 2022
calling: "D:\dev\IDE\VisualStudio\2022\Community\VC\\Auxiliary\Build\vcvarsall.bat" amd64
**********************************************************************
** Visual Studio 2022 Developer Command Prompt v17.5.4
** Copyright (c) 2022 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x64'
Found MSVS version 17.0
configure  --dest-cpu=x64
Node.js configure: Found Python 3.10.2...
Warning: Missing input files:
D:\workspace\github\node\tools\v8_gypfiles\..\..\deps\v8\tools\testrunner\utils\dump_build_config_gyp.py
INFO: configure completed successfully
Project files generated.
msbuild node.sln /m:2 /t:node /p:Configuration=Release /p:Platform=x64 /clp:NoItemAndPropertyList;Verbosity=minimal /nologo 
為 Release <<===>> out\Release 創(chuàng)建的聯(lián)接

可以看到,vcbuild.bat 的執(zhí)行過程大概分為下面幾步:

  1. 查找 python基括,找到3.10.2版本
  2. 查找 NASM颜懊,用于匯編編譯
  3. 查找 Visual Studio 2022,使用 msbuild 編譯c++
  4. 調用 Visual Studio 的命令提示符腳本,它初始化了x64環(huán)境變量
  5. 執(zhí)行 python configure 腳本河爹,傳入各種 flags
  6. 最后對 node.sln 執(zhí)行 msbuild

其實腳本還有個初始化過程匠璧,不過沒有任何輸出。下面我們來弄清楚每一步都發(fā)生了什么

2.初始化

2.1 help命令

檢測第一個參數(shù)咸这,如果是 help夷恍,跳轉到幫助內容

if /i "%1"=="help" goto help
if /i "%1"=="--help" goto help
if /i "%1"=="-help" goto help
if /i "%1"=="/help" goto help
if /i "%1"=="?" goto help
if /i "%1"=="-?" goto help
if /i "%1"=="--?" goto help
if /i "%1"=="/?" goto help

:help
echo vcbuild.bat [debug/release] [msi] [doc] [test/test-all/test-addons/test-doc/test-js-native-api/test-node-api/test-internet/test-tick-processor/test-known-issues/test-node-inspect/test-check-deopts/test-npm/test-v8/test-v8-intl/test-v8-benchmarks/test-v8-all] [ignore-flaky] [static/dll] [noprojgen] [projgen] [small-icu/full-icu/without-intl] [nobuild] [nosnapshot] [nonpm] [nocorepack] [ltcg] [licensetf] [sign] [ia32/x86/x64/arm64] [vs2019/vs2022] [download-all] [enable-vtune] [lint/lint-ci/lint-js/lint-md] [lint-md-build] [package] [build-release] [upload] [no-NODE-OPTIONS] [link-module path-to-module] [debug-http2] [debug-nghttp2] [clean] [cctest] [no-cctest] [openssl-no-asm]
echo Examples:
echo   vcbuild.bat                          : builds release build
echo   vcbuild.bat debug                    : builds debug build
echo   vcbuild.bat release msi              : builds release build and MSI installer package
echo   vcbuild.bat test                     : builds debug build and runs tests
echo   vcbuild.bat build-release            : builds the release distribution as used by nodejs.org
echo   vcbuild.bat enable-vtune             : builds Node.js with Intel VTune profiling support to profile JavaScript
echo   vcbuild.bat link-module my_module.js : bundles my_module as built-in module
echo   vcbuild.bat lint                     : runs the C++, documentation and JavaScript linter
echo   vcbuild.bat no-cctest                : skip building cctest.exe
goto exit

2.2 測試參數(shù)

cd到當前文件目錄,設置傳遞給 tools[test.py](http://test.py/ "test.py") 的測試參數(shù)

cd %~dp0

set JS_SUITES=default
set NATIVE_SUITES=addons js-native-api node-api
@rem CI_* variables should be kept synchronized with the ones in Makefile
set "CI_NATIVE_SUITES=%NATIVE_SUITES% benchmark"
set "CI_JS_SUITES=%JS_SUITES% pummel"
set CI_DOC=doctool
@rem Same as the test-ci target in Makefile
set "common_test_suites=%JS_SUITES% %NATIVE_SUITES%&set build_addons=1&set build_js_native_api_tests=1&set build_node_api_tests=1"

2.3 遞歸處理參數(shù)

遞歸處理腳本參數(shù)媳维,設置各種flags

:next-arg
if "%1"=="" goto args-done
if /i "%1"=="debug"         set config=Debug&goto arg-ok
if /i "%1"=="release"       set config=Release&set ltcg=1&set cctest=1&goto arg-ok
if /i "%1"=="clean"         set target=Clean&goto arg-ok
if /i "%1"=="testclean"     set target=TestClean&goto arg-ok
if /i "%1"=="ia32"          set target_arch=x86&goto arg-ok
if /i "%1"=="x86"           set target_arch=x86&goto arg-ok
if /i "%1"=="x64"           set target_arch=x64&goto arg-ok
if /i "%1"=="arm64"         set target_arch=arm64&goto arg-ok
if /i "%1"=="vs2019"        set target_env=vs2019&goto arg-ok
if /i "%1"=="vs2022"        set target_env=vs2022&goto arg-ok
if /i "%1"=="noprojgen"     set noprojgen=1&goto arg-ok
if /i "%1"=="projgen"       set projgen=1&goto arg-ok
if /i "%1"=="nobuild"       set nobuild=1&goto arg-ok
if /i "%1"=="nosign"        set "sign="echo Note: vcbuild no longer signs by default. "nosign" is redundant.&goto arg-ok
if /i "%1"=="sign"          set sign=1&goto arg-ok
if /i "%1"=="nosnapshot"    set nosnapshot=1&goto arg-ok
if /i "%1"=="nonpm"         set nonpm=1&goto arg-ok
if /i "%1"=="nocorepack"    set nocorepack=1&goto arg-ok
if /i "%1"=="ltcg"          set ltcg=1&goto arg-ok
if /i "%1"=="licensertf"    set licensertf=1&goto arg-ok
if /i "%1"=="test"          set test_args=%test_args% %common_test_suites%&set lint_cpp=1&set lint_js=1&set lint_md=1&goto arg-ok
if /i "%1"=="test-ci-native" set test_args=%test_args% %test_ci_args% -p tap --logfile test.tap %CI_NATIVE_SUITES% %CI_DOC%&set build_addons=1&set build_js_native_api_tests=1&set build_node_api_tests=1&set cctest_args=%cctest_args% --gtest_output=xml:cctest.junit.xml&goto arg-ok
if /i "%1"=="test-ci-js"    set test_args=%test_args% %test_ci_args% -p tap --logfile test.tap %CI_JS_SUITES%&set no_cctest=1&goto arg-ok
if /i "%1"=="build-addons"   set build_addons=1&goto arg-ok
if /i "%1"=="build-js-native-api-tests"   set build_js_native_api_tests=1&goto arg-ok
if /i "%1"=="build-node-api-tests"   set build_node_api_tests=1&goto arg-ok
if /i "%1"=="test-addons"   set test_args=%test_args% addons&set build_addons=1&goto arg-ok
if /i "%1"=="test-doc"      set test_args=%test_args% %CI_DOC%&set doc=1&&set lint_js=1&set lint_md=1&goto arg-ok
if /i "%1"=="test-js-native-api"   set test_args=%test_args% js-native-api&set build_js_native_api_tests=1&goto arg-ok
if /i "%1"=="test-node-api"   set test_args=%test_args% node-api&set build_node_api_tests=1&goto arg-ok
if /i "%1"=="test-tick-processor" set test_args=%test_args% tick-processor&goto arg-ok
if /i "%1"=="test-internet" set test_args=%test_args% internet&goto arg-ok
if /i "%1"=="test-known-issues" set test_args=%test_args% known_issues&goto arg-ok
if /i "%1"=="test-all"      set test_args=%test_args% gc internet pummel %common_test_suites%&set lint_cpp=1&set lint_js=1&goto arg-ok
if /i "%1"=="test-node-inspect" set test_node_inspect=1&goto arg-ok
if /i "%1"=="test-check-deopts" set test_check_deopts=1&goto arg-ok
if /i "%1"=="test-npm"      set test_npm=1&goto arg-ok
if /i "%1"=="test-v8"       set test_v8=1&set custom_v8_test=1&goto arg-ok
if /i "%1"=="test-v8-intl"  set test_v8_intl=1&set custom_v8_test=1&goto arg-ok
if /i "%1"=="test-v8-benchmarks" set test_v8_benchmarks=1&set custom_v8_test=1&goto arg-ok
if /i "%1"=="test-v8-all"       set test_v8=1&set test_v8_intl=1&set test_v8_benchmarks=1&set custom_v8_test=1&goto arg-ok
if /i "%1"=="lint-cpp"      set lint_cpp=1&goto arg-ok
if /i "%1"=="lint-js"       set lint_js=1&goto arg-ok
if /i "%1"=="jslint"        set lint_js=1& Please use lint-js instead of jslint&goto arg-ok
if /i "%1"=="lint-md"       set lint_md=1&goto arg-ok
if /i "%1"=="lint-md-build" set lint_md_build=1&goto arg-ok
if /i "%1"=="lint"          set lint_cpp=1&set lint_js=1&set lint_md=1&goto arg-ok
if /i "%1"=="lint-ci"       set lint_cpp=1&set lint_js_ci=1&goto arg-ok
if /i "%1"=="package"       set package=1&goto arg-ok
if /i "%1"=="msi"           set msi=1&set licensertf=1&set download_arg="--download=all"&set i18n_arg=full-icu&goto arg-ok
if /i "%1"=="build-release" set build_release=1&set sign=1&goto arg-ok
if /i "%1"=="upload"        set upload=1&goto arg-ok
if /i "%1"=="small-icu"     set i18n_arg=%1&goto arg-ok
if /i "%1"=="full-icu"      set i18n_arg=%1&goto arg-ok
if /i "%1"=="intl-none"     set i18n_arg=none&goto arg-ok
if /i "%1"=="without-intl"  set i18n_arg=none&goto arg-ok
if /i "%1"=="download-all"  set download_arg="--download=all"&goto arg-ok
if /i "%1"=="ignore-flaky"  set test_args=%test_args% --flaky-tests=dontcare&goto arg-ok
if /i "%1"=="dll"           set dll=1&goto arg-ok
if /i "%1"=="enable-vtune" set enable_vtune_arg=1&goto arg-ok
if /i "%1"=="static"           set enable_static=1&goto arg-ok
if /i "%1"=="no-NODE-OPTIONS"   set no_NODE_OPTIONS=1&goto arg-ok
if /i "%1"=="debug-nghttp2" set debug_nghttp2=1&goto arg-ok
if /i "%1"=="link-module"   set "link_module= --link-module=%2%link_module%"&goto arg-ok-2
if /i "%1"=="no-cctest"     set no_cctest=1&goto arg-ok
if /i "%1"=="cctest"        set cctest=1&goto arg-ok
if /i "%1"=="openssl-no-asm"   set openssl_no_asm=1&goto arg-ok
if /i "%1"=="no-shared-roheap" set no_shared_roheap=1&goto arg-ok
if /i "%1"=="doc"           set doc=1&goto arg-ok
if /i "%1"=="binlog"        set extra_msbuild_args=/binaryLogger:%config%\node.binlog&goto arg-ok

echo Error: invalid command line option `%1`.
exit /b 1

:arg-ok-2
shift
:arg-ok
shift
goto next-arg

:args-done

  • :next-arg:處理下一個參數(shù)酿雪,即參數(shù)移位之后的 %1,如果不存在則跳轉到 args-done
  • :arg-ok:當前參數(shù)%1對應的 flag 設置成功侄刽,使用 shift 進行參數(shù)移位指黎,將 %2 移動到 %1
  • :arg-ok-2:它的不同在于,%1 和 %2 都被使用之后州丹,需要調用兩次 shift醋安,進行兩次參數(shù)移位
  • :args-done:當 %1 為空時,參數(shù)處理完畢
  • 如果 %1 不正確墓毒,拋出無效參數(shù)錯誤

2.4 參數(shù)后處理

執(zhí)行參數(shù)后處理茬故,設置 configure_flags

if defined build_release (
  set config=Release
  set package=1
  set msi=1
  set licensertf=1
  set download_arg="--download=all"
  set i18n_arg=full-icu
  set projgen=1
  set cctest=1
  set ltcg=1
)

if defined msi     set stage_package=1
if defined package set stage_package=1

:: assign path to node_exe
set "node_exe=%config%\node.exe"
set "node_gyp_exe="%node_exe%" deps\npm\node_modules\node-gyp\bin\node-gyp"
set "npm_exe="%~dp0%node_exe%" %~dp0deps\npm\bin\npm-cli.js"
if "%target_env%"=="vs2019" set "node_gyp_exe=%node_gyp_exe% --msvs_version=2019"
if "%target_env%"=="vs2022" set "node_gyp_exe=%node_gyp_exe% --msvs_version=2022"

:: skip building if the only argument received was lint
if "%*"=="lint" if exist "%node_exe%" goto lint-cpp

if "%config%"=="Debug"      set configure_flags=%configure_flags% --debug
if defined nosnapshot       set configure_flags=%configure_flags% --without-snapshot
if defined nonpm            set configure_flags=%configure_flags% --without-npm
if defined nocorepack       set configure_flags=%configure_flags% --without-corepack
if defined ltcg             set configure_flags=%configure_flags% --with-ltcg
if defined release_urlbase  set configure_flags=%configure_flags% --release-urlbase=%release_urlbase%
if defined download_arg     set configure_flags=%configure_flags% %download_arg%
if defined enable_vtune_arg set configure_flags=%configure_flags% --enable-vtune-profiling
if defined dll              set configure_flags=%configure_flags% --shared
if defined enable_static    set configure_flags=%configure_flags% --enable-static
if defined no_NODE_OPTIONS  set configure_flags=%configure_flags% --without-node-options
if defined link_module      set configure_flags=%configure_flags% %link_module%
if defined i18n_arg         set configure_flags=%configure_flags% --with-intl=%i18n_arg%
if defined config_flags     set configure_flags=%configure_flags% %config_flags%
if defined target_arch      set configure_flags=%configure_flags% --dest-cpu=%target_arch%
if defined debug_nghttp2    set configure_flags=%configure_flags% --debug-nghttp2
if defined openssl_no_asm   set configure_flags=%configure_flags% --openssl-no-asm
if defined no_shared_roheap set configure_flags=%configure_flags% --disable-shared-readonly-heap
if defined DEBUG_HELPER     set configure_flags=%configure_flags% --verbose
if "%target_arch%"=="x86" if "%PROCESSOR_ARCHITECTURE%"=="AMD64" set configure_flags=%configure_flags% --no-cross-compiling

if not exist "%~dp0deps\icu" goto no-depsicu
if "%target%"=="Clean" echo deleting %~dp0deps\icu
if "%target%"=="Clean" rmdir /S /Q %~dp0deps\icu
:no-depsicu

if "%target%"=="TestClean" (
  echo deleting test/.tmp*
  if exist "test\.tmp*" for /f %%i in ('dir /a:d /s /b test\.tmp*') do rmdir /S /Q "%%i"
  goto exit
)
  • build_release:構建發(fā)布版本
  • msi:使用 msbuild 構建 .msi 安裝包
  • package:將編譯結果打包為 zip 文件
  • node_exe:設置編譯結果的 node / npm / node_gyp 的執(zhí)行路徑
  • lint:執(zhí)行 js 和 cpp 代碼lint
  • configure_flags:傳遞給 configure python 腳本的 flags
  • deps\icu:如果 icu 庫已經(jīng)存在,則需要先刪除它蚁鳖,后面會下載 icu 庫
  • TestClean:清理臨時測試文件

3.查找python

調用查找 python 的腳本磺芭,如果出錯則離開

call tools\msvs\find_python.cmd if errorlevel 1 goto :exit

下面查看 tools\msvs\find_python.cmd 的內容

設置腳本

@IF NOT DEFINED DEBUG_HELPER @ECHO OFF

echo Looking for Python
setlocal enabledelayedexpansion
  • DEBUG_HELPER:如果沒有開啟 debug崖瞭,則不輸出執(zhí)行的命令
  • enabledelayedexpansion:啟用延遲擴展迅诬,變量將在每次執(zhí)行該行時擴展兜叨,默認情況下擴展只發(fā)生一次

在 PATH 環(huán)境變量里查找 python

:: Use python.exe if in %PATH%
set need_path=0
for /f "delims=" %%a in ('where python.exe 2^> nul') do (
  set p=%%~dpa
  goto :found-python
)
  • need_path:如果python不在 PATH 環(huán)境變量里理逊,則為1樊展,代表需要將其加入 PATH 環(huán)境變量
  • for /f:for代表循環(huán)跑筝,/f 指對結果進行文件解析章蚣,將其分解為單獨的文本行并將每一行解析為零或多個標記
  • "delims=":指定分隔符兜辞,此處分隔符為空
  • %%a:變量名己英,代表每一行的文本
  • ('where python.exe 2^> nul'):使用 where 命令查找 python 的位置间螟,2^> nul 指重定向錯誤輸出
  • set p=%%~dpa:設置 p 為當前行中的 python 路徑
  • :found-python:已經(jīng)找到 python,跳轉到指定 label

在注冊表里查找 python 安裝路徑

:: Query the 3 locations mentioned in PEP 514 for a Python InstallPath
set need_path=1
for %%k in ( "HKCU\Software", "HKLM\SOFTWARE", "HKLM\Software\Wow6432Node") do (
  call :find-versions %%k
  if not errorlevel 1 goto :found-python
)

goto :no-python
  • need_path:python 不在 PATH 環(huán)境變量里损肛,如果找到了厢破,需要將其加入 PATH
  • %%k:代表指定的注冊表路徑
  • :find-versions:調用標簽,在指定的注冊表路徑中查找 python 版本
  • :found-python:如果錯誤碼不是1治拿,則找到 python
:: Find Python installations in a registry location
:find-versions
for /f "delims=" %%a in ('reg query "%~1\Python\PythonCore" /f * /k 2^> nul ^| findstr /r ^^HK') do (
  call :read-installpath %%a
  if not errorlevel 1 exit /b 0
)
exit /b 1
  • for /f "delims=" %%a in:遍歷命令執(zhí)行結果的每一行摩泪,每行內容保存在 %%a 中
  • %~1\Python\PythonCore:使用第一個參數(shù)組成注冊表的鍵,也就是上面?zhèn)鬟f的注冊表路徑
  • reg query:查詢注冊表的命令
  • /f *:查找指定鍵下與通配符匹配的項
  • /k:只查找鍵劫谅,不包括值數(shù)據(jù)
  • 2^> nul:錯誤輸出重定向到空設備
  • ^|:管道见坑,將前面命令的輸出作為后面命令的輸入
  • findstr /r ^HK:在命令結果中查找包含指定字符串的行嚷掠,代表的轉義
  • :read-installpath:將讀取到的完整注冊表路徑傳遞給標簽,用于獲取安裝路徑
  • exit /b 0:如果錯誤碼不是1荞驴,則查找成功
  • exit /b 1:如果查找失敗不皆,向上級返回錯誤碼 1
for /f "skip=2 tokens=1* delims=)" %%a in ('reg query "%1\InstallPath" /ve /t REG_SZ 2^> nul') do (
  for /f "tokens=1*" %%c in ("%%b") do (
    if not "%%c"=="REG_SZ" exit /b 1
    set "p=%%d"
    exit /b 0
  )
)
exit /b 1
  • "skip=2 tokens=1* delims=)":跳過命令結果前兩行,使用')'作為分隔符熊楼,只獲取第一部分
  • /ve:對值為空的進行查詢霹娄,即查詢默認值
  • /t REG_SZ:值類型為字符串
  • %%b:在 %%a 后面的第二部分,')'是分隔符
  • %%c:將 %%b 按空格分割后的字符串孙蒙,
  • "%%c"=="REG_SZ":如果類型不是字符串项棠,則報錯
  • %%d:在 %%c 后面的第二部分悲雳,空格是分隔符

已經(jīng)找到 python

:found-python
echo Python found in %p%\python.exe
call :check-python "%p%\python.exe"
if errorlevel 1 goto :no-python
endlocal ^
  & set "pt=%p%" ^
  & set "need_path_ext=%need_path%"
if %need_path_ext%==1 set "PATH=%pt%;%PATH%"
set "pt="
set "need_path_ext="
exit /b 0
  • :check-python:檢查 python 版本
  • pt=%p%:設置 python 的路徑
  • need_path_ext=%need_path%:是否需要把 python 路徑加入 PATH 環(huán)境變量里

檢查 python 版本

:check-python
%1 -V
:: 9009 means error file not found
if %errorlevel% equ 9009 (
  echo Not an executable Python program
  exit /b 1
)
exit /b 0

4.查找NASM

nasm用于編譯 openssl 里的匯編語言挎峦,在arm64架構下需要

REM NASM is only needed on IA32 and x86_64.
if not defined openssl_no_asm if "%target_arch%" NEQ "arm64" call tools\msvs\find_nasm.cmd
if errorlevel 1 echo Could not find NASM, install it or build with openssl-no-asm. See BUILDING.md.

查找方式和 python 基本一樣

@IF NOT DEFINED DEBUG_HELPER @ECHO OFF

ECHO Looking for NASM

FOR /F "delims=" %%a IN ('where nasm 2^> NUL') DO (
  EXIT /B 0
)

IF EXIST "%ProgramFiles%\NASM\nasm.exe" (
  SET "Path=%Path%;%ProgramFiles%\NASM"
  EXIT /B 0
)

IF EXIST "%ProgramFiles(x86)%\NASM\nasm.exe" (
  SET "Path=%Path%;%ProgramFiles(x86)%\NASM"
  EXIT /B 0
)

if EXIST "%LOCALAPPDATA%\bin\NASM\nasm.exe" (
  SET "Path=%Path%;%LOCALAPPDATA%\bin\NASM"
  EXIT /B 0
)

EXIT /B 1

5.查找node版本

調用獲取node版本的label

call :getnodeversion || exit /b 1

![nshu.io/upload_images/28865460-ccfaf87fedfc4a57.gif?imageMogr2/auto-orient/strip)

  • || exit /b 1:如果標簽執(zhí)行成功,則后面的exit不會執(zhí)行合瓢,如果失敗則退出

調用獲取 node 版本的python腳本坦胶,輸出類似 21.0.0

:getnodeversion
set NODE_VERSION=
set TAG=
set FULLVERSION=

for /F "usebackq tokens=*" %%i in (`python "%~dp0tools\getnodeversion.py"`) do set NODE_VERSION=%%i
if not defined NODE_VERSION (
  echo Cannot determine current version of Node.js
  exit /b 1
)
  • for /F:逐行讀取腳本輸出
  • usebackq:開啟反引號包含的命令行模式
  • tokens=*:讀取整行并存儲在 %%i 中
  • 如果 NODE_VERSION 不存在則返回錯誤碼 1

下面看下 getnodeversion.py ,它主要從 node_version.h 源碼中讀取當前源碼的版本

from __future__ import print_function
import os

def get_major_minor_patch(text):
  for line in text.splitlines():
    if line.startswith('#define NODE_MAJOR_VERSION'):
      major = line.split()[2]
    elif line.startswith('#define NODE_MINOR_VERSION'):
      minor = line.split()[2]
    elif line.startswith('#define NODE_PATCH_VERSION'):
      patch = line.split()[2]
  return major, minor, patch

node_version_h = os.path.join(os.path.dirname(__file__),
                              '..',
                              'src',
                              'node_version.h')
with open(node_version_h) as in_file:
  print('.'.join(get_major_minor_patch(in_file.read())))

然后需要決定打包名稱 TARGET_NAME晴楔,里面包含了 node 版本和系統(tǒng)架構

if not defined DISTTYPE set DISTTYPE=release
if "%DISTTYPE%"=="release" (
  set FULLVERSION=%NODE_VERSION%
  goto distexit
)

:distexit
if not defined DISTTYPEDIR set DISTTYPEDIR=%DISTTYPE%
set TARGET_NAME=node-v%FULLVERSION%-win-%target_arch%
goto :EOF

6.查找 visual studio 工具鏈

6.1 msvs架構

首先設置 msbuild 環(huán)境變量

@rem Set environment for msbuild

set msvs_host_arch=x86
if _%PROCESSOR_ARCHITECTURE%_==_AMD64_ set msvs_host_arch=amd64
if _%PROCESSOR_ARCHITEW6432%_==_AMD64_ set msvs_host_arch=amd64
if _%PROCESSOR_ARCHITECTURE%_==_ARM64_ set msvs_host_arch=arm64
@rem usually vcvarsall takes an argument: host + '_' + target
set vcvarsall_arg=%msvs_host_arch%_%target_arch%
@rem unless both the host and the target are the same
if %target_arch%==x64 if %msvs_host_arch%==amd64 set vcvarsall_arg=amd64
if %target_arch%==%msvs_host_arch% set vcvarsall_arg=%target_arch%
  • msvs_host_arch:msvs宿主架構顿苇,x86 或者 amd64
  • vcvarsall_arg:傳遞給 vcvarsall.bat 腳本的架構參數(shù),在我的64位系統(tǒng)上是 amd64

6.2 查找 vs2022

首先查找 visual studio 2022

@rem Look for Visual Studio 2022

:vs-set-2022
if defined target_env if "%target_env%" NEQ "vs2022"
 goto vs-set-2019
echo Looking for Visual Studio 2022
  • target_env:可以設置為 vs2022 或 vs2019
@rem VCINSTALLDIR may be set if run from a VS Command Prompt and needs to
@rem cleared first as vswhere_usability_wrapper.cmd doesn't when it fails to
@rem detect the version searched for
if not defined target_env set "VCINSTALLDIR="
call tools\msvs\vswhere_usability_wrapper.cmd "[17.0,18.0)" %target_arch% "prerelease"
if "_%VCINSTALLDIR%_" == "__" goto vs-set-2019
  • VCINSTALLDIR:visual studio安裝目錄
  • vswhere_usability_wrapper.cmd:查找 vs installer 目錄税弃,目的是使用 vswhere 程序

下面查看 vswhere_usability_wrapper.cmd 腳本

@if not defined DEBUG_HELPER @ECHO OFF
setlocal
if "%~3"=="prerelease" set VSWHERE_WITH_PRERELEASE=1
set "InstallerPath=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer"
if not exist "%InstallerPath%" set "InstallerPath=%ProgramFiles%\Microsoft Visual Studio\Installer"
if not exist "%InstallerPath%" goto :no-vswhere
:: Manipulate %Path% for easier " handeling
set "Path=%Path%;%InstallerPath%"
where vswhere 2> nul > nul
if errorlevel 1 goto :no-vswhere
if "%2"=="arm64" (
    set VSWHERE_REQ=-requires Microsoft.VisualStudio.Component.VC.Tools.ARM64    
) else (
    set VSWHERE_REQ=-requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64
)
set VSWHERE_PRP=-property installationPath
set VSWHERE_LMT=-version %1
vswhere -prerelease > nul
if not errorlevel 1 if "%VSWHERE_WITH_PRERELEASE%"=="1" set "VSWHERE_LMT=%VSWHERE_LMT% -prerelease"
SET VSWHERE_ARGS=-latest -products * %VSWHERE_REQ% %VSWHERE_PRP% %VSWHERE_LMT%
for /f "usebackq tokens=*" %%i in (`vswhere %VSWHERE_ARGS%`) do (
    endlocal
    set "VCINSTALLDIR=%%i\VC\"
    set "VS150COMNTOOLS=%%i\Common7\Tools\"
    exit /B 0
)

:no-vswhere
endlocal
exit /B 1
  • 首先在 %ProgramFiles(x86)% 中查找 visual studio installer 目錄
  • 然后在 %ProgramFiles% 中查找
  • 將 installer 目錄加入 PATH 環(huán)境變量
  • where vswhere:在 PATH 環(huán)境變量中查找 vswhere 纪岁,如果沒找到返回錯誤碼1
  • vswhere %VSWHERE_ARGS%:執(zhí)行 vswhere 查找,返回值只有一行vs的安裝目錄
  • VCINSTALLDIR:設置 VC 安裝目錄
  • VS150COMNTOOLS:設置工具目錄
@rem need to clear VSINSTALLDIR for vcvarsall to work as expected
set "VSINSTALLDIR="
@rem prevent VsDevCmd.bat from changing the current working directory
set "VSCMD_START_DIR=%CD%"
set vcvars_call="%VCINSTALLDIR%\Auxiliary\Build\vcvarsall.bat" %vcvarsall_arg%
echo calling: %vcvars_call%
call %vcvars_call%
if errorlevel 1 goto vs-set-2019
if defined DEBUG_HELPER @ECHO ON
  • VSINSTALLDIR:清理它的值则果,讓叫不能按照預期工作
  • VSCMD_START_DIR:防止更改當前工作目錄
  • vcvarsall.bat:配置 Visual C++ 環(huán)境的批處理腳本幔翰,初始化了 x64 架構下的環(huán)境變量

6.3 查找 vs2019

基本過程和查找 vs2022 相同

7.執(zhí)行 configure

7.1 處理configure參數(shù)

:msbuild-found

set project_generated=
:project-gen
@rem Skip project generation if requested.
if defined noprojgen goto msbuild
if defined projgen goto run-configure
if not exist node.sln goto run-configure
if not exist .gyp_configure_stamp goto run-configure
echo %configure_flags% > .tmp_gyp_configure_stamp
where /R . /T *.gyp* >> .tmp_gyp_configure_stamp
fc .gyp_configure_stamp .tmp_gyp_configure_stamp >NUL 2>&1
if errorlevel 1 goto run-configure
  • noprojgen:跳過項目生成,直接構建
  • projgen:項目生成西壮,run configure
  • .tmp_gyp_configure_stamp:將 configure_flags 和 .gyp 文件的路徑寫入臨時文件中
  • .gyp_configure_stamp:如果與臨時文件不一致遗增,則 run configure

7.2 運行 configure

:run-configure
del .tmp_gyp_configure_stamp 2> NUL
del .gyp_configure_stamp 2> NUL
@rem Generate the VS project.
echo configure %configure_flags%
echo %configure_flags%> .used_configure_flags
python configure %configure_flags%
if errorlevel 1 goto create-msvs-files-failed
if not exist node.sln goto create-msvs-files-failed
set project_generated=1
echo Project files generated.
echo %configure_flags% > .gyp_configure_stamp
where /R . /T *.gyp* >> .gyp_configure_stamp
  • .tmp_gyp_configure_stamp:刪除臨時文件
  • configure:運行 configure python 腳本
  • create-msvs-files-failed:項目生成失敗
  • project_generated:項目生成成功

8.msbuild 構建

8.1 執(zhí)行構建

:msbuild
@rem Skip build if requested.
if defined nobuild goto :after-build

@rem Build the sln with msbuild.
set "msbcpu=/m:2"
if "%NUMBER_OF_PROCESSORS%"=="1" set "msbcpu=/m:1"
set "msbplatform=Win32"
if "%target_arch%"=="x64" set "msbplatform=x64"
if "%target_arch%"=="arm64" set "msbplatform=ARM64"
if "%target%"=="Build" (
  if defined no_cctest set target=node
  if "%test_args%"=="" set target=node
  if defined cctest set target="Build"
)
if "%target%"=="node" if exist "%config%\cctest.exe" del "%config%\cctest.exe"
if defined msbuild_args set "extra_msbuild_args=%extra_msbuild_args% %msbuild_args%"
@rem Setup env variables to use multiprocessor build
set UseMultiToolTask=True
set EnforceProcessCountAcrossBuilds=True
set MultiProcMaxCount=%NUMBER_OF_PROCESSORS%
msbuild node.sln %msbcpu% /t:%target% /p:Configuration=%config% /p:Platform=%msbplatform% /clp:NoItemAndPropertyList;Verbosity=minimal /nologo %extra_msbuild_args%
if errorlevel 1 (
  if not defined project_generated echo Building Node with reused solution failed. To regenerate project files use "vcbuild projgen"
  exit /B 1
)
if "%target%" == "Clean" goto exit
  • nobuild:如果不需要構建,直接跳過
  • msbcpu:設置msbuild 構建使用的 cpu 數(shù)量
  • msbplatform:msbuild 構建的目標平臺
  • msbuild node.sln:對解決方案執(zhí)行構建

8.2 構建后處理

:after-build
rd %config%
if errorlevel 1 echo "Old build output exists at 'out\%config%'. Please remove." & exit /B
:: Use /J because /D (symlink) requires special permissions.
if EXIST out\%config% mklink /J %config% out\%config%
if errorlevel 1 echo "Could not create junction to 'out\%config%'." & exit /B
  • rd %config%:刪除配置目錄
  • mklink /J:創(chuàng)建符號鏈接款青, out%config% 到 %config%

9.簽名

簽名是對可執(zhí)行文件進行數(shù)字簽名做修,以證明文件的來源和完整性,確保文件未被篡改抡草,從而提高文件的安全性饰及。

:sign
@rem Skip signing unless the `sign` option was specified.
if not defined sign goto licensertf

call tools\sign.bat Release\node.exe
if errorlevel 1 echo Failed to sign exe, got error code %errorlevel%&goto exit

下面查看 tools/sign.bat 腳本

@echo off

set timeservers=(http://timestamp.globalsign.com/scripts/timestamp.dll http://timestamp.comodoca.com/authenticode http://timestamp.verisign.com/scripts/timestamp.dll http://tsa.starfieldtech.com)

for %%s in %timeservers% do (
    signtool sign /a /d "Node.js" /du "https://nodejs.org" /fd SHA256 /t %%s %1
    if not ERRORLEVEL 1 (
        echo Successfully signed %1 using timeserver %%s
        exit /b 0
    )
    echo Signing %1 failed using %%s
)

echo Could not sign %1 using any available timeserver
exit /b 1
  • timeservers:多個時間戳服務器的地址
  • signtool sign:使用簽名工具對輸入文件進行數(shù)字簽名

10.小結

大致的構建過程就是這樣,vcbuild.bat 里還包含了其他的功能康震,可以被各種參數(shù)打開

后面有機會再進行梳理

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末旋炒,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子签杈,更是在濱河造成了極大的恐慌瘫镇,老刑警劉巖鼎兽,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異铣除,居然都是意外死亡谚咬,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門尚粘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來择卦,“玉大人,你說我怎么就攤上這事郎嫁”蹋” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵泽铛,是天一觀的道長尚辑。 經(jīng)常有香客問我,道長盔腔,這世上最難降的妖魔是什么杠茬? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮弛随,結果婚禮上瓢喉,老公的妹妹穿的比我還像新娘。我一直安慰自己舀透,他們只是感情好栓票,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著愕够,像睡著了一般走贪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上链烈,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天厉斟,我揣著相機與錄音,去河邊找鬼强衡。 笑死擦秽,一個胖子當著我的面吹牛,可吹牛的內容都是我干的漩勤。 我是一名探鬼主播感挥,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼越败!你這毒婦竟也來了触幼?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤究飞,失蹤者是張志新(化名)和其女友劉穎置谦,沒想到半個月后堂鲤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡媒峡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年瘟栖,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谅阿。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡半哟,死狀恐怖,靈堂內的尸體忽然破棺而出签餐,到底是詐尸還是另有隱情寓涨,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布氯檐,位于F島的核電站戒良,受9級特大地震影響,放射性物質發(fā)生泄漏男摧。R本人自食惡果不足惜蔬墩,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一译打、第九天 我趴在偏房一處隱蔽的房頂上張望耗拓。 院中可真熱鬧,春花似錦奏司、人聲如沸乔询。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽竿刁。三九已至,卻和暖如春搪缨,著一層夾襖步出監(jiān)牢的瞬間食拜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工副编, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留负甸,地道東北人。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓痹届,卻偏偏與公主長得像呻待,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子队腐,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內容