Google Chrome GN 两月小结

@vrqq  November 16, 2020
什么Modern CMake,不存在的!

上手了一个多月的gn,感觉从玩具CMake终于到了Pro工具
官方文档很好查,就是说如果官方文档里没有,多半在其他地方也找不到用法。。

Part1 语法Intro

基础:手打gcc参数编译,手打ld链接(或了解其过程,或通过gcc调用linker)

入门照着这个做 https://gn.googlesource.com/gn/+/HEAD/docs/quick_start.md

  • Config 入口在://.gn (包括编译器配置等)
  • Target 入口在://BUILD.gn
    其他的一切BUILD.gn还有gni等等都是由这两个展开引出的.
    简单来说就是,先定义编译器和辅助工具例如时间戳stamp,再定义编译时候的默认参数,最后写一个自己的target: executable("hello") 就可以了

重点

  • config字段指的是给编译器的传参,例如-Wl,rpath=xx
  • config是 面向编译器的,最小的 参数集合,以单个config为单位可叠加/增删
  • deps字段常用于target之间的依赖

Part2 进阶 及跨平台经验

再讲下我的设计思路,把编译配置文件分为了几部分

  • 全局args:例如 build_type, is_llvm
  • 编译器:toolchain("xxx"){}
  • 编译器默认参数:config("default_ldconfig"){}
  • 插件:一些文件例如sanitizer.gni, imported_library.gni, protoc.gni,每个插件内部也有自己的控制变量
  • Target:包括自己写的executable(){}, shared_library(){}, static_library(){}
  • windows下的cp命令我用批处理cp2.bat将就了一下
  • 我把编译场景分为三块:linux, windows, 生产环境, 于是我根据这三个去写默认编译参数//gn_build/toolchain_args/xxx

gni我理解成插件

  • 这点chrome sourcecode里面很乱 一看就是历史包袱 改不得!
  • 但是新工程我们要分清职责,一种方法是:把划分依据讲给自己听,到底是不是个模块划分,低耦合。
  • 既然是插件就是可有可没的东西,依赖关系要清晰,如果不能(就在封装一层 《别)
  • 例如sanitizer要加参数,按我的理解就是:sanitizer.gni里写了所有编译器的参数

第三方库 为了和别人协同开发方便,我都放在third文件夹下了,然后通过group()暴露给用户

  • chrome 是模块都自带源码,通过重新编译做跨平台。。
  • 我想简易跨平台,所以把每个平台的依赖编译好,分别放进平台文件夹例如linux_release, windows_release,合作者按需下载,每个跨平台的“第三方库集合”暴露一个build.gn供外部使用
  • 又因为自己写的代码里不想有平台相关,所以在third下又中继了一个BUILD.gn
  • 我把外部库理解成(假装)通过源码编译的库(target),而不是引入了加给编译器的参数(config),所以我就用group表示,引入时候就使用“deps=[xxx]”这种语法
  • 另外一种做法是 按照第三方库名字放文件夹例如 /third/spdlog/BUILD.gn,如果团队大部分人都要多平台支持,那最好使用这种(反正也不会节约空间)

关于Win32和Win64

  • win32和win64是两种win(不同操作系统 可以并列)
  • win64编译win32是cross build
  • win64 运行win32程序 不是native的,理解成Linux下的WINE

其他

  • 优化需要-march匹配目标cpu的指令集,也就是third文件夹都是要尽量从源码重新编译
  • Windows下 StartMenu -> Visual Studio 2019 -> x64 Native Tools Command Prompt for VS 2019 就有cl可用了
  • 查看属性,路径为"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvars64.bat"
  • gn生成的vs.sln不能在里面直接改nativeDebug/nativeRelease/amd3950Release (vs的debugger真好用,哎真是两难!)
    希望有人扩展下gn
  • vscode下需要插件CodeLLDB用来调试(但需要手动添加target,建议谁撸个vscode extension

参数变量举例
假设我们现在有如下目录格式:

root
|--gn_build
   |--(toolset and args...)
|--app
   |--hello_world
      |--sub
         |--null.cpp
      |--BUILD.gn
      |--hello_world.cpp  
      |--sumfunc.cpp 
      |--sumfunc.h 
|--out
   |--win
      |--(output here)

在root下执行

gn gen out/win
ninja -C out hello_world

举例分析
app/hello_world/BUILD.gn

shared_library("sumfunc") {
    sources = ["sumfunc.cpp", "sumfunc.h", "sub/null.cpp"]
}
executable("hello_world") {
    sources = ["hello.cpp"]
    deps = [":sumfunc"]
}

tool("cxx")中使用的变量

tool("cxx") {
    precompiled_header_type = "msvc"
    depsformat = "msvc"
    description = "CXX {{output}}"
    outputs = [
        "{{source_out_dir}}/{{label_name}}.{{source_name_part}}.obj",
    ]

    # The PDB name needs to be different between C and C++ compiled files.
    command = "cl.exe /nologo /showIncludes {{defines}} {{include_dirs}} {{cflags}} {{cflags_cc}} /c {{source}} /Fo{{output}} /Fd\"{{target_out_dir}}/{{label_name}}_cc.pdb\""
}

针对上述hello_world涉及的3个cpp

  • {{output_dir}}编译器运行目录cwd: out/win
  • {{label_name}} 为target名: sumfunchello_world
  • {{source_out_dir}} 分别为.cpp文件相对cwd路径: obj/app/hello_worldobj/app/hello_world/sub 不知为啥该变量不能用在command中,只能用在outputs数组中,
  • {{source_name_part}} 分别为文件名: sumfunc, null, hello_world
  • {{target_out_dir}} 均为编译的临时obj输出目录: obj/app/hello_world 该目录取自BUILD.gn所在文件夹
  • {{output}} 其实是提示信息,即outputs数组,应该用在description里,用在command里有些tricky,因为不知为啥source_out_dir不能识别
  • 规律: {{source_xxx}} 均指.cpp的源文件所在地
  • 规律: {{target_xxx}} 均指BUILD.gn所在地 和 BUILD.gn内的target名字

常见使用误区

static_lib和source_set的区别
在编译dll时 linker有一参数 (toolchain{tool("solink")}部分)

  • --whole-archive 将static lib的代码带入自己的dll
  • --no-whole-archive 不带入static lib的代码进入dll 即dll使用的symbol都是undefined的

如果exe->.so->.lib间接依赖,同时exe->.lib直接依赖,且lib代码代入.so (编译时使用--whole-archive),此时exe内会有两份.lib的定义,违反ODR。
若使用source_set(),则不被认为是.lib,进而编译.so时一定会带入这部分代码,所以一定会违反ODR原则。
所以解决方案是:对于static lib和shared lib混编的程序,上述toolchain一定使用-Wl,--no-whole-archive 不让dll包含静态库内容,且独立且重复使用部分应使用static_lib而不是source_set。

注意:linker不会主动检查ODR

Upd 2023.03 一个完整的gn工具链开发环境

用了很久,基本问题都可以解决(包括解决互相引用)
待填坑,整理一下发Github


添加新评论