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库都没有。