安裝

GN 本來只是 Chromium 自己在用 (很久之前介紹過),隨著另一個大專案 Fuchsia 也開始採用了,Google 終於決定要給它個名份,所以它有自己的網址了: https://gn.googlesource.com

Google 也直接提供 binary 讓你下載使用了,雖然自己編譯安裝也是很簡單。

從源碼編譯的步驟:

git clone https://gn.googlesource.com/gn
cd gn
python build/gen.py
ninja -C out
# 編譯完成後,binary 會在 out/gn

怎麼用?

Brett Wilson 的 Using GN build 是我看過最好的入門介紹。

基本流程

  1. 建立專案根目錄的 .gn 檔案,指定 buildconfig:
# .gn
buildconfig = "//build/BUILDCONFIG.gn"
  1. 建立 BUILD.gn 定義 targets:
# BUILD.gn
executable("my_app") {
  sources = [
    "main.cc",
    "app.cc",
    "app.h",
  ]
  deps = [
    "//base",
    "//third_party/foo",
  ]
}
  1. 生成 ninja 建置檔:
gn gen out/Default
  1. 執行建置:
ninja -C out/Default

優點

簡單易用的 dependency tree 管理能力

GN 會根據 BUILD.gn 建立所謂的 dependency tree,確保建置的正確性與效率:

# 範例:建立 library 與其依賴關係
static_library("my_lib") {
  sources = [ "lib.cc", "lib.h" ]
  public_deps = [ "//base" ]  # 會傳遞給依賴此 library 的 target
  deps = [ "//internal/util" ]  # 只有自己會用到
}

executable("my_app") {
  sources = [ "main.cc" ]
  deps = [ ":my_lib" ]  # 會自動繼承 my_lib 的 public_deps
}

gn check 可以檢查 code (經由 #include 形成的)的相依關係是否符合 build structure:

# 檢查所有 targets
gn check out/Default

# 只檢查特定 target
gn check out/Default //my/target:name

如果你在 code 中 include 了某個 header,但沒有在 BUILD.gn 中宣告對應的 dependency,gn check 就會報錯。

豐富的文檔

GN 內建完整的文檔系統,可以直接在命令列查詢:

# 查看所有可用命令
gn help

# 查看特定命令的說明
gn help gen
gn help check

# 查看特定函數或變數的說明
gn help executable
gn help sources
gn help deps

所有的 BUILD.gn 語法、內建函數、變數都可以透過 gn help 查到,非常方便。

內建分析及除錯功能

GN 提供強大的分析工具,幫助你理解和除錯複雜的建置結構:

gn desc - 查看 target 的詳細資訊:

# 顯示 target 的所有屬性
gn desc out/Default //my/target:name

# 只顯示特定屬性
gn desc out/Default //my/target:name sources
gn desc out/Default //my/target:name deps --tree

gn path - 找出兩個 targets 之間的依賴路徑:

# 為什麼 target_a 會依賴到 target_b?
gn path out/Default //target_a //target_b

gn refs - 查看誰依賴了這個 target:

# 列出所有依賴 //base 的 targets
gn refs out/Default //base

# 以樹狀結構顯示
gn refs out/Default //base --tree

gn analyze - 分析哪些 targets 需要重新建置:

# 當某些檔案改變時,哪些 targets 會受影響
gn analyze out/Default input.json output.json

可客製化的彈性

GN 的 template 功能讓你可以建立可重複使用的建置模式:

# 定義一個 template
template("my_custom_library") {
  static_library(target_name) {
    forward_variables_from(invoker, "*")

    # 自動加入一些共通設定
    configs += [ "//build:common_config" ]

    if (!defined(invoker.sources)) {
      sources = []
    }
    sources += [ "//build/version.cc" ]
  }
}

# 使用 template
my_custom_library("foo") {
  sources = [ "foo.cc" ]
  deps = [ "//base" ]
}

這樣可以將專案中重複的建置邏輯抽象化,讓 BUILD.gn 檔案更簡潔易讀。

可能的坑

1. includes vs include_dirs

如果你從其他建置系統(如 CMake 或 Make)轉過來,可能會習慣性地寫 includes,但在 GN 中正確的變數名稱是 include_dirs:

# ❌ 錯誤
static_library("foo") {
  sources = [ "foo.cc" ]
  includes = [ "include" ]  # 這不會生效!
}

# ✅ 正確
static_library("foo") {
  sources = [ "foo.cc" ]
  include_dirs = [ "include" ]
}

2. public_deps vs deps

混淆這兩者會導致建置時找不到 header 或 linking 錯誤:

  • deps: 只有當前 target 自己會用到的依賴
  • public_deps: 會傳遞給依賴此 target 的其他 targets
# 如果你的 header 檔會 include 另一個 library 的 header
# 就要用 public_deps,否則使用你 library 的人會找不到 header
static_library("my_lib") {
  sources = [ "lib.h", "lib.cc" ]
  public_deps = [ "//base" ]  # lib.h 有 #include "base/base.h"
}

3. 路徑表示法

GN 使用特殊的路徑表示法,需要注意:

  • // 代表專案根目錄(有 .gn 檔案的地方)
  • : 用來指定同一個 BUILD.gn 內的 target
  • 相對路徑要用 ./ 或直接寫檔名
deps = [
  "//base",           # 根目錄下的 base 目錄
  "//third_party/foo",  # 根目錄下的 third_party/foo
  ":my_lib",          # 同一個 BUILD.gn 內的 my_lib target
]

4. gn format 自動格式化

養成使用 gn format 的習慣,可以避免很多格式上的問題:

# 格式化單一檔案
gn format BUILD.gn

# 格式化整個目錄
gn format --all