chromium中的GN構建系統

閱讀最新的chromium源碼,發現項目的構建系統已經從GYP全面切換到GN了。在軟件開發中,經常有人忠告:不要重複造輪子。但谷歌可不管這個,造的輪子一個接一個,誰叫人家牛呢?chromiumi項目爲啥要折騰構建系統呢?因爲谷歌chrome瀏覽器追求一個字:快。不僅瀏覽器的速度要快,構建系統也要追求快。

構建系統簡介

在探討chromium的最新GN構建系統之前,回顧一下軟件開發中的構建系統。構建系統的需求是隨着軟件規模的增大而提出的。如果只是做軟件編程訓練,通常代碼量比較小,編寫的源代碼只有幾個文件。比如你編寫了一段代碼放入helloworld.c文件中,要編譯這段代碼,只需要執行以下命令:

gcc helloworld.c
  • 1

當軟件規模逐漸增加,這時可能有幾十個源代碼文件,而且有了模塊劃分,有的要編譯成靜態庫,有的要編譯成動態庫,最後鏈接成可執行代碼,這時命令行方式就捉襟見肘,需要一個構建系統。常見的構建系統有GNU Make。需要注意的是,構建系統並不是取代gcc這樣的工具鏈,而是定義編譯規則,最終還是會調用工具鏈編譯代碼。

當軟件規模進一步擴大,特別是有多平臺支持需求的時候,編寫GNU Makefile將是一件繁瑣和乏味的事情,而且極容易出錯。這時就出現了生成Makefile的工具,比如cmake、AutoMake等等,這種構建系統稱作元構建系統(meta build system)。在Linux上軟件倉庫的概念還沒有普及的時候,通常我們安裝軟件的步驟是:

./configure
make
make install
  • 1
  • 2
  • 3

第一步就是調用AutoTool工具,根據系統環境(Linux的版本衆多,軟件安裝情況也不一樣),生成GNU Makefile。

Chromium中的構建系統

在我幾年前接觸chromium開源項目的時候,chromium採用的是GYP(Generate Your Projects)構建系統,這也是一種元構建系統。軟件工程師根據GYP規則編寫構建工程文件(通常以gyp, gypi爲後綴),GYP工具根據gyp文件生成GNU Makefile。接着chromium項目又整出了Ninja構建系統,但這個Ninja並不是用來取代GYP的,而是取代GNU make的,據谷歌官方的說法是速度有了好幾倍的提升。對於我們開發者而言,不需要去深入瞭解Ninja或GNU Makefile這樣構建系統,因爲這只是一種中間輸出,所以ninja的出現,與我們關係不大,原來怎麼寫gyp,現在還是怎麼寫,只是構建命令稍微做了改變。

最近再看chromium的源碼,發現裏面熟悉的GYP文件都不見了,取而代之的是GN文件(以gn和gni爲文件名後綴),瞬時感覺一夜回到解放前。然而稍微研究了一個GN文件,還是那些熟悉的模塊、依賴、條件等等元素,和GYP差別不大,而且總體上比原來的GYP文件更清晰。

GN構建系統

GN是一種元構建系統,生成Ninja構建文件(Ninja build files),相較GYP而言,具有如下優點:

  • 可讀性更好,更容易編寫和維護。
  • 速度更快,谷歌官方給的數據是20倍的速度提升。
  • 修改GN文件後,執行ninja構建時會自動更新Ninja構建文件。以前用GYP的時候就有過修改了GYP,而忘記使用gyp命令重新生成Ninja構建文件的尷尬。
  • 更簡單的模塊依賴,提供了public_deps, data_deps等,在GYP中,只有一種目標依賴,導致依賴關係錯綜複雜,容易引入不必要的模塊依賴。
  • 提供了更好的工具查詢模塊依賴圖譜。這在GYP構建系統中是一個噩夢,要查一個目標依賴哪些模塊或者一個模塊被哪些目標依賴幾乎是不可能的。
  • 更好的調試支持。爲了打印GYP中的變量值,我以前還專門寫過一篇博文<<如何打印gyp構建系統中的變量值>>,在GN中,只需要一條print語句就可以解決。

快速入門

運行GN

從命令行運行gn,這實際上是depot_tools下的一個腳本,所以需要確保depot_tools路徑包含在環境變量$PATH中。

配置一個構建

在GYP中,有兩個特定的目錄Debug和Release目錄,分別用於生成Debug版本和Release版本。在GN中,採用了更靈活的方式,你隨便指定一個目錄,比如爲了測試,定義一個test輸出目錄,可以採用如下的命令:

gn gen out/test
  • 1

那要是我要分別構建Debug版本和Release版本怎麼辦?GN通過傳遞參數來解決。也就是說,現在光通過輸出目錄是無法確定到底是Debug版本和Release版本,而要取決於傳遞的構建參數。

傳遞構建參數

將上面的命令稍微修改一下,即可設置構建參數:

gn args out/test
  • 1

您可以使用下面的命令列出可用的構建參數和它們的缺省值:

gn args --list out/test
  • 1

我在chromium源碼下運行,參數如此之多,要翻好幾屏,所以不需要記住所有的參數,只需知道幾個比較常用的參數:

is_component_build = true
is_debug = false
  • 1
  • 2

前面一個參數決定是否分動態庫build,現在chrome for android包含了build出了幾十個so,就是這麼來的,好處是節約修改代碼後的構建時間。後面一個參數決定是build Debug版本還是Release版本。

從編寫代碼到build

  1. 編寫代碼,比如代碼文件爲test/hello_world.cc
  2. 編寫GN文件,比如放在和上面代碼同一目錄下。

    executable("hello_world") {
    sources = [
     "hello_world.cc",
    ]
    }
    • 1
    • 2
    • 3
    • 4
    • 5
  3. 打開源碼根目錄下的BUILD.gn,加入對上述目標的依賴:

    group("root") {
    deps = [
     ...
     "//url",
     "//test:hello_world",
    ]
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    這裏//代表源碼根目錄。

  4. 構建

    gn gen out/Default
    ninja -C out/Default hello_world
    out/Default/hello_world
    • 1
    • 2
    • 3

調試

  1. 打印

    static_library("hello") {
    ...
    print(configs)
    }
    • 1
    • 2
    • 3
    • 4

    可以打印這個目標的配置信息。

  2. 查看目標信息

    gn desc out/Default //test:hello_world
    • 1

    這會列出很多詳細的信息,包括配置參數、目標依賴,通過更復雜的命令參數,你還可以查看某個宏定義是在哪個目標定義,目標依賴的樹結構,比如:

    gn desc out/Default //base:base_i18n deps --tree
    • 1

    更多參數的說明可以使用gn help desc查看。

總結

因爲還沒有編寫複雜的GN文件,所以對GN的優缺點還體會不深,不過對於GYP的幾個深有感受的痛點: 
- 多層嵌套,導致GYP文件難以閱讀和修改,想想chromium下的build/common.gypi這個文件有多恐怖 
- 打印支持 
- 對於複雜的依賴缺少有效的手段去定位和排查

這在GN上得到了完美的解決,就衝這這一點,也要爲谷歌點贊。雖然是重複發明輪子,但輪子比原來的好用啊!

參考文檔

  1. What is GN?
  2. GN Quick Start guide
版權聲明:本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接http://blog.csdn.net/mogoweb
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章