想像一下,你正在維護一個原生專案。你在 Windows 上使用 Visual Studio 進行建置,因此你做了負責任的事,並將其列為依賴項。
建置需求:安裝 Visual Studio
如果你夠幸運還不知道這件事,我羨慕你。不幸的是,此時連 Boromir 都知道了……
你可能沒意識到,你實際上已經簽約成為微軟「Visual Studio 安裝程式」的無薪技術支援。你可能會注意到 GitHub Issues 越來越少是關於你的程式碼,而是更多關於建置失敗,特別是在 Windows 上。你發現自己要向貢獻者解釋,他們沒有勾選「使用 C++ 的桌面開發」工作負載,但特別是 v143 建置工具和 10.0.22621.0 SDK。不,不是那個,是另一個。你花在專案上的時間越來越少,因為你忙著成為一個由人類驅動的依賴解析器,為一個 50GB 的 IDE 服務。
說「安裝 Visual Studio」就像是遞給貢獻者一本充滿壞結局的「選擇你自己的冒險」書,其中一些結局甚至不讓你回頭。多年來,我不得不重新安裝我的整個作業系統不止一次。
在 Linux 上,工具鏈通常只需一個套件管理員指令即可。另一方面,「Visual Studio」卻有數千個元件。它如此龐大,以至於微軟透過一個複雜的圖形化安裝程式來分發它,你在其中導覽一個核取方塊的迷宮,尋找哪些「工作負載」或「個別元件」包含了實際的編譯器。選錯了,你可能會花費數小時安裝你不需要的東西。錯過了一個,例如「Windows 10 SDK (10.0.17763.0)」或「Spectre 緩解程式庫」,你的建置就會在三個小時後以一個難懂的錯誤,如 MSB8101 失敗。如果你需要降級到舊版的建置工具來處理舊專案,那更是難上加難。
Visual Studio 生態系統建立在「一體式」巨型架構的基礎上。它將編輯器、編譯器和 SDK 混為一談,形成一個單一、糾纏不清的網絡。當我們將「Visual Studio」列為依賴項時,我們未能區分我們用來編寫程式碼的工具和編譯它所需的環境。
即使安裝完成後,從命令列編譯單一 C 檔案也需要找到開發人員命令提示字元。在底層,這個捷徑會調用 vcvarsall.bat,一個脆弱的批次腳本,它會全局修改你的環境變數,只是為了找到編譯器這個星期藏在哪裡。
最終,你會得到看起來像法律免責聲明的建置說明:
「在我的機器上,使用 VS 17.4.2 (Build 33027.167) 和 SDK 10.0.22621.0 可正常運作。如果你有 17.5,請參閱 Issue #412。如果你在 ARM64 上,祝你好運。」
在 Windows 上,這已經成為「做生意的成本」。我們告訴使用者等待三個小時安裝 20GB 的軟體,只是為了讓他們能夠編譯一個 5MB 的可執行檔。這已經成為原生開發的實際阻礙。
我不想成為別人安裝程式的人類除錯器。我希望 MSVC 工具鏈能像現代依賴項一樣運作:版本化、隔離、聲明式。
我花費了幾週時間開發了一個開源工具來改善這種狀況。它叫做 msvcup。這是一個小型 CLI 程式。在網路/硬體良好的情況下,它可以在幾分鐘內安裝工具鏈/SDK,包括交叉編譯到/從 ARM 的所有內容。每個版本的工具鏈/SDK 都有自己的隔離目錄。它是冪等的,並且足夠快,可以每次建置時調用。讓我們來試試看。
信不信由你,這個 build.bat 腳本取代了「安裝 Visual Studio」的需要。這個腳本應該可以在任何 Windows 10 或更新的系統上執行(假設它有 curl/tar,這兩者自 2018 年以來就已內建)。它會安裝 MSVC 工具鏈、Windows SDK,然後編譯我們的程式。
對於我的 Windows 開發者同胞們,請花點時間。Visual Studio 再也無法傷害你了。上面的 build.bat 不僅僅是一個輔助腳本;它是一份獨立於 Visual Studio 安裝程式的聲明。我們的依賴項已完全指定,使得建置在不同機器上具有可重複性。當這些依賴項安裝時,它們不會污染你的登錄檔,也不會將你鎖定在單一的全局版本中。
另外請注意,第一次執行後,msvcup 指令只需毫秒即可完成,這意味著我們可以將這些指令保留在我們的建置腳本中,現在我們就有了一個完全獨立的腳本,幾乎可以在任何現代 Windows 機器上建置我們的專案。
msvcup 的靈感來自 Mārtiņš Možeiko 編寫的一個小型 Python 腳本。關鍵的洞察是,微軟發布了描述 Visual Studio 中每個元件的 JSON 清單,這與官方安裝程式使用的清單相同。msvcup 解析這些清單,僅識別編譯所需的套件(編譯器、連結器、標頭檔和函式庫),並直接從微軟的 CDN 下載它們。所有內容都位於 C:\msvcup\ 下的版本化目錄中。有關鎖定檔案、交叉編譯和其他功能的詳細資訊,請參閱 msvcup README.md。
敏銳的讀者也會注意到,我們的 build.bat 腳本從未來源任何批次檔案來設定「開發人員環境」。該腳本包含兩個 msvcup 指令。第一個安裝工具鏈/SDK,就像正常的安裝一樣,它包含設定開發人員環境的「vcvars」腳本。相反,我們的 build.bat 利用 msvcup 的 autoenv 指令來建立一個「自動環境」。這會建立一個目錄,其中包含包裝器可執行檔,在你轉發給底層工具之前,會代表你設定環境變數。它甚至包含一個 toolchain.cmake 檔案,該檔案會將你的 CMake 專案指向這些工具,讓你可以在特殊環境之外建置你的 CMake 專案。
在 Tuple(一個配對程式設計應用程式),我將 msvcup 整合到我們的建置系統和 CI 中,這使我們能夠移除使用者/CI 預先安裝 Visual Studio 的要求。Tuple 編譯了數百個 C/C++ 專案,包括 WebRTC。這使得 CI 能夠進行 x86_64 和 ARM 的建置,同時讓 CI 和每個人都使用相同的工具鏈/SDK。
不再有「在我機器上可以運作,因為我有 2019 建置工具」的情況。不再需要搜尋登錄檔來找出 cl.exe 這週藏在哪裡。有了 msvcup,你的環境由你的程式碼定義,可在不同機器之間移植,並能在毫秒內準備好進行編譯。
msvcup 專注於核心編譯工具鏈。如果你需要完整的 Visual Studio IDE,你仍然需要官方安裝程式。然而,對於大多數原生開發工作流程,它涵蓋了你實際需要的內容。
讓我們在一個真實的專案上試試這個。這是一個在乾淨的 Windows 系統上從頭開始建置 raylib 的腳本。在此情況下,我們將僅使用 SDK 而不使用 autoenv:
無需安裝 Visual Studio。無需 GUI。無需祈禱。只有一個腳本,能精確執行它所說的。
P.S. 這裡有一個頁面,展示了如何使用 msvcup 在 Windows 上從頭開始建置 LLVM 和 Zig。