開發者就 Make 與現代構建系統展開辯論,C 語言編譯指南引發討論

BigGo 編輯部
開發者就 Make 與現代構建系統展開辯論,C 語言編譯指南引發討論

最近一份關於使用 Make 編譯 C 程式的指南在開發者中引發了激烈討論,涉及構建系統、依賴管理以及幾十年歷史的工具在現代軟體開發中的持續相關性。

環境變數和編譯器標誌造成困惑

社群強調了圍繞 Make 環境變數的重大困惑,特別是 CPPFLAGS 和 CXXFLAGS。許多開發者錯誤地認為這些變數用途相似,但實際上它們完全不同。CPPFLAGS 處理 C 預處理器選項(無論什麼語言),而 CXXFLAGS 專門針對 C++ 編譯。這種區別在排查編譯問題時變得至關重要,特別是在 macOS 上,依賴路徑通常需要手動指定。

討論還揭示了在靜態連結場景中連結器標誌順序非常重要,儘管這個要求在動態連結時會消失。這個技術細節讓許多開發者措手不及,導致神秘的構建失敗,似乎在某些系統上能工作但在其他系統上不行。

注:C 預處理器(cpp)是一個在編譯前處理原始碼的工具,處理包含標頭檔案和展開宏等任務。

關鍵 Make 環境變數:

  • CPPFLAGS:C 預處理器標誌(用於標頭檔案位置)
  • CXXFLAGS:C++ 編譯器標誌
  • LDFLAGS:位於目標檔案之前的連結器標誌
  • LDLIBS:位於目標檔案之後的庫標誌(如 -lssl -ldl)

Make 作為通用介面獲得關注

一個有趣的趨勢是開發者將 Make 用作跨不同程式語言和專案的標準化介面。許多團隊現在實現一致的 Make 目標,如 make formatmake lintmake build,無論他們使用的是 Go、Rust、Python 還是其他語言,而不是記住各種特定語言的構建命令。

這種方法解決了專案隨時間在不同構建系統間遷移的常見困擾。雖然底層工具可能從一個包管理器變為另一個,但高階 Make 命令保持不變,減少了開發者在專案間切換時的認知負擔。

跨程式語言的常見 Make 目標:

  • make format:程式碼格式化
  • make lint:程式碼檢查/分析
  • make build:構建專案
  • make docker_build:構建 Docker 容器
  • make docker_run:執行 Docker 容器
  • make install_deps:安裝依賴項

依賴管理仍是 C 語言的致命弱點

社群討論強化了 C 語言缺乏內建依賴管理器仍然是一個主要痛點。雖然一些人認為像 apt 或 homebrew 這樣的系統包管理器承擔了這個角色,但其他人指出像 Conan 和 vcpkg 這樣的新興解決方案是新興替代品。

然而,許多經驗豐富的 C 開發者實際上將此視為特性而非缺陷。複雜專案通常涉及多種程式語言,使得特定語言的依賴管理器可能對整體構建過程有害。手動依賴管理方法雖然前期工作更多,但提供了更大的控制和透明度。

C 依賴管理解決方案:

  • 系統包管理器: apt 、 homebrew 、 dnf 、 pacman
  • C 專用工具: Conan 、 vcpkg
  • 手動管理:傳統的手動庫安裝方法
  • 容器化構建:基於 Docker 的依賴打包

替代構建系統挑戰 Make 的主導地位

討論揭示了對傳統 Make 替代品日益增長的興趣。一些開發者倡導 Plan 9 的 mk 系統,讚揚其更清潔的語法和更具描述性的自動變數。其他人建議像 CMake 這樣的現代工具,儘管有其自身的複雜性,但提供了更好的跨平臺支援和依賴處理。

「沒有其他人知道它是如何工作的,這就是為什麼每個 configure 指令碼都檢查可工作的 fortran 編譯器。」

這種對 autotools 的情緒反映了對積累了幾十年複雜性和邊緣情況的遺留構建系統的更廣泛挫敗感。

Docker 成為構建環境解決方案

一個越來越受歡迎的實用解決方案涉及使用 Docker 容器提供一致的構建環境。這種方法將編譯器和依賴項打包到可重現的容器中,消除了困擾不同系統間 C 編譯的在我的機器上能工作問題。

然而,這種策略在靜態連結方面面臨限制,特別是在使用 glibc 的系統上,它不完全支援靜態編譯。這個技術約束意味著容器化構建並不總是產生真正可移植的二進位制檔案。

持續的辯論反映了軟體開發中更廣泛的緊張關係:在維護與既定工具的相容性和擁抱承諾更好開發者體驗的現代替代品之間。雖然 Make 即將迎來其50週年,但社群對其長壽性是代表經過驗證的可靠性還是累積的技術債務仍存在分歧。

參考:Using make to compile C programs (for non-C-programmers)