Chromium是用gn和ninja進行編譯的,即gn把.gn文件轉換成.ninja文件,然后ninja根據.ninja文件將源碼生成目標程序。gn和ninja的關系就與cmake和make的關系差不多。
1. 環境配置
在我們自己的項目中,也可以使用gn來進行編譯。
在windows上總是會遇到各種各樣的問題,還是直接下載二進制程序省心:
https://github.com/ninja-build/ninja/releases
https://chrome-infra-packages.appspot.com/p/gn/gn
然后設置環境變量,以便在命令行中直接使用。
2. 示例
這里寫個hello_word來演示下gn的基本使用。
首先,寫一個hello_word.cc源碼文件:
#include <iostream>
int main()
{
std::cout << "Hello world: gn build example" << std::endl;
return 0;
}
然后在同一目錄下創建BUILD.gn文件:
executable("hello_world") {
sources = [
"hello_world.cc",
]
}
同時,gn還需要在項目根目錄有一個.gn文件用於指定編譯工具鏈。這里我們直接拷貝gn官方的例子的配置,完整工程:hello_world.zip
之后就可以直接執行編譯:
gn gen out/Default
ninja -C out/Default
這樣就會在out/Default目錄生成可執行文件hello_world.exe。
這樣一個簡單的示例就完成了。
在自己的項目中使用gn,必須遵循以下要求:
- 在根目錄創建.gn文件,該文件用於指定BUILDCONFIG.gn文件的位置;
- 在BUILDCONFIG.gn中指定編譯時使用的編譯工具鏈;
- 在獨立的gn文件中定義編譯使用的工具鏈;
- 在項目根目錄下創建BUILD.gn文件,指定編譯的目標。
3. gn命令
gn gen out/dir [--args="..."]:創建新的編譯目錄,會自動創建args.gn文件作為編譯參數。
gn args --list out/dir:列出可選的編譯參數。
gn ls out/dir:列出所有的target;
gn ls out/dir "//:hello_word*":列出匹配的target;
gn desc out/dir "//:hello_word":查看指定target的描述信息,包括src源碼文件、依賴的lib、編譯選項等;
gn refs out/dir 文件:查看依賴該文件的target;
gn refs out/dir //:hello_word:查看依賴該target的target
。。。
注意//代表從項目根目錄開始。
4. BUILD.gn文件語法
gn語法很接近python,主要的官方文檔是以下兩篇:
https://chromium.googlesource.com/chromium/src/tools/gn/+/48062805e19b4697c5fbd926dc649c78b6aaa138/docs/language.md
https://gn.googlesource.com/gn/+/master/docs/reference.md
這里簡單介紹下一些關鍵用法:
4.1 新增編譯參數
declare_args() {
enable_test = true
}
這樣就新增了一個enable_test的gn編譯參數,默認值是true。在BUILD.gn文件中,你就可以根據這個編譯參數的值進行一些特殊化配置:
if(enable_test)
{
...
}
4.2 新增宏
defines = [ "AWESOME_FEATURE", "LOG_LEVEL=3" ]
這些宏可以直接在C++或C代碼中使用。
4.3 新增編譯單元
target就是一個最小的編譯單元,可以將它單獨傳遞給ninja進行編譯。
從google文檔上看有以下幾種target:
- action: Declare a target that runs a script a single time.(指定一段指定的腳本)
- action_foreach: Declare a target that runs a script over a set of files.(為一組輸入文件分別執行一次腳本)
- bundle_data: [iOS/macOS] Declare a target without output. (聲明一個無輸出文件的target)
- copy: Declare a target that copies files. (聲明一個只是拷貝文件的target)
- create_bundle: [iOS/macOS] Build an iOS or macOS bundle. (編譯MACOS/IOS包)
- executable: Declare an executable target. (生成可執行程序)
- generated_file: Declare a generated_file target.
- group: Declare a named group of targets. (執行一組target編譯)
- loadable_module: Declare a loadable module target. (創建運行時加載動態連接庫,和deps方式有一些區別)
- rust_library: Declare a Rust library target.
- shared_library: Declare a shared library target. (生成動態鏈接庫,.dll or .so)
- source_set: Declare a source set target. (生成靜態庫,比static_library要快)
- static_library: Declare a static library target. (生成靜態鏈接庫,.lib or .a)
- target: Declare an target with the given programmatic type.
因此,我們的hello示例其實也只是增加了一個executable target。
4.4 新增配置
使用config可以提供一個公共的配置對象,包括編譯flag、include、defines等,可被其他target包含。
config("myconfig") {
include_dirs = [ "include/common" ]
defines = [ "ENABLE_DOOM_MELON" ]
}
executable("mything") {
configs = [ ":myconfig" ]
}
4.5 新增模板
模板,顧名思義,可以用來定義可重用的代碼,比如添加新的target類型等。
通常可以將模板單獨定義成一個.gni文件,然后其他文件就可以通過import來引入實現共享。這部分就比較復雜,具體例子可參閱官方文檔。
4.6 新增依賴關系
平時我們在編譯的時候都會很小心地處理各種動態庫和靜態庫的鏈接引入,在gn中,我們需要使用deps來實現庫的依賴關系:
if (enable_nacl) {
deps += [ "//components/nacl/loader:nacl_loader_unittests" ]
if (is_linux) {
# TODO(dpranke): Figure out what platforms should actually have this.
deps += [ "//components/nacl/loader:nacl_helper" ]
if (enable_nacl_nonsfi) {
deps += [
"//components/nacl/loader:helper_nonsfi",
"//components/nacl/loader:nacl_helper_nonsfi_unittests",
]
}
}
}
5.通用toolchain配置
gn編譯的toolchain配置非常關鍵,決定了你編譯的方式和產物的用途,chromium自帶的toolchains也能實現跨平台,但是太過龐大,我們日常使用的話,可以使用:https://github.com/timniederhausen/gn-build
6.使用gn編譯mini_chromium庫
mini_chromium提供了一個mini版本的chromium base庫,里面提供很多有用的工具:日志庫、字符串處理、文件處理等,github地址:https://github.com/chromium/mini_chromium.git 。
默認它使用的是gyp編譯,但是其實它已經寫好了BUILD.gn文件,我們只需添加.gn文件指定編譯工具鏈即可,修改后的倉庫:https://github.com/243286065/mini_chromium.git。
最后是再windows上和ubuntu上測試通過。不過發現mini_chromium上的確實內容太少了,連timer和json庫都沒有。