#打卡不停更# [gn+ninja学习 0x05] gn编写规范 原创 精华

zhushangyuan_
发布于 2022-10-24 09:44
浏览
3收藏

[gn+ninja学习 0x05] gn编写规范

OpenHarmony使用gn+ninja来维护开源项目的构建。之前没有接触过gn+ninja,是时候系统性的来学习下了。边学边记录下学习过程,希望对同样需要学习gn+ninja的朋友有所帮助。

这一篇,我们来学习下GN的编写规范,风格指南,或者最佳实践。也可以阅读官方的英文原版内容docs/standalone.md

1、Naming and ordering within the file文件中的命名和排序

1.1、Location of build files构建文件的位置

在靠近代码之处创建更多的构建文件比在顶层创建更少的构建文件更有意义;这与我们对GYP所做的形成鲜明对比。这使得构建文件、源文件更容易找到,需要检视的内容更是,因为更改集中于特定的子目录。-- 注:所以使用gn,会看到较多的BUILD.gn根目录下有BUILD.gn每个子模块有BUILD.gn,通过依赖关系来调用。

1.2、Targets

  • 大多数 BUILD.gn文件应具有与目录同名的目标。此目标应为第一个目标。
  • 其他目标应该按照某种逻辑顺序排列,通常更重要的目标将排在第一位,单元测试目标将紧跟相应的功能模块的目标。如果没有明确的顺序,请考虑按字母顺序排列。
  • 测试支持库应为名为“test_support”的静态库,例如,“//ui/compositor:test_support”。测试支持库应包含非测试支持版本的库作为公共依赖 public deps,以便测试程序只需要依赖于test_support目标(而不是两者)。

命名建议

  • 目标和配置应使用小写字母和下划线分隔单词来命名,除非有充分的理由不这样做。
  • 源代码集source_set、组group和静态库static_library不需要名称全局唯一。更喜欢为此类目标提供简短、非冗余的名称,而不必担心全局唯一性。例如,编写依赖项"//mojo/public/bindings"看起来比编写依赖项"//mojo/public/bindings:mojo_bindings"要好得多。
  • 共享库shared_library(以及扩展的组件components–注:Chrome项目里有这样的components 目标)必须具有全局唯一的输出名称。为此类目标提供上面的简短非唯一名称,然后为该目标提供全局唯一名称output_name。
  • 应为可执行文件和测试指定一个全局唯一的名称。从技术上讲,只有输出名称必须是唯一的,但由于只有输出名称出现在 shell 和bots中,因此如果名称与可执行文件出现的其他位置匹配一致,则迷惑性要小得多。

1.3、Configs配置

  • 与单个target目标关联的config配置应与target目标同名,并用_config跟在它后面。如 target名称为foo,则对应的config名称为foo_config。
  • config配置应紧挨着使用它的相应target目标之前出现。

1.4、Example

示例src/foo/BUILD.gn如下:

# Copyright 2016 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# Config for foo is named foo_config and immediately precedes it in the file.
config("foo_config") {
}

# Target matching path name is the first target.
executable("foo") {
}

# Test for foo follows it.
test("foo_unittests") {
}

config("bar_config") {
}

source_set("bar") {
}

2、Ordering within a target目标内的排序

在一个target目标内,推荐使用如下的排序,输出在前,依赖在后,参与构建的内容在中间。

  • output_name / visibility / testonly
  • sources
  • cflags, include_dirs, defines, configs,这几个可以根据情况排序
  • public_deps
  • deps

2.1、Conditions

仅影响一个变量的简单条件(例如,添加单个源或为一个特定操作系统添加标志)可以位于它们影响的变量之下。影响范围较多的更复杂的条件应该在底部。

编写条件时,应最大程度地减少条件块的数量。

3、Formatting and indenting 格式化和缩进

GN 包含一个内置的代码格式化程序,用于定义格式设置样式。一些额外的说明:

  • 变量为小写带下划线分割单词lower_case_with_underscores
  • 注释应该是完整的句子,末尾有句点。
  • 编译器选项标志等应始终注释它们的作用以及需要标志的原因。

3.1、Sources源文件

优先仅列出一次源文件。可以有条件地包含源文件,而不是将它们全部列在顶部,然后在它们不适用时有条件地排除它们。条件包含通常更清晰,因为文件仅列出一次,并且在阅读时更容易推理。

  sources = [
    "main.cc",
  ]
  if (use_aura) {
    sources += [ "thing_aura.cc" ]
  }
  if (use_gtk) {
    sources += [ "thing_gtk.cc" ]
  }

3.2、Deps依赖

  • 依赖应按字母顺序排列。
  • 当前文件中的 Deps 应首先写入,并且不能使用文件名限定(仅需要:foo )。
  • 其他 deps 应始终使用完全限定的路径名,除非出于某种原因需要相对路径名。
  deps = [
    ":a_thing",
    ":mystatic",
    "//foo/bar:other_thing",
    "//foo/baz:that_thing",
  ]

3.3、Import导入

使用完全限定的路径进行导入:

import("//foo/bar/baz.gni")  # Even if this file is in the foo/bar directory

4、Usage用法

这一节介绍下如何选择使用不同的target目标类型,主要是source_set、shared_library、loadable_module,以及Components(这是Chrome项目定义的模板,可以忽略)。

4.1、Source sets versus static libraries source_set与static_library的对比

在大多数情况下,源代码集和静态库可以互换使用。如果您不确定要使用什么,则source_set几乎永远不会出错,并且不太可能引起问题,但是在大型项目中使用正确类型的target目标类型可能很重要,因此您应该了解以下权衡。

静态库static_library遵循不同的链接规则。当链接静态库时,只有包含未解析符号的对象文件才会被引入构建中。source_set则会链接每个对象文件,都添加到最终二进制文件中。

  • 如果最终将代码链接到组件component、共享库或可加载模块中,通常需要使用source_set。这是因为没有从共享库中引用的符号的对象文件将根本不链接到最终库中。即使该对象文件具有标记为导出的符号,该符号的目标依赖于该共享库的需求,也会发生此遗漏。这将在链接后续目标时导致未定义的符号。

  • 单元测试(以及具有副作用的静态初始值设定项的任何其他内容)必须使用源代码集source_set。gtest TEST 宏创建注册测试的静态初始值设定项。但是,由于没有代码引用对象文件中的符号,因此将测试链接到静态库,然后链接到测试可执行文件意味着测试将被剥离。

  • 在某些平台上,静态库可能涉及复制组成它的对象文件中的所有数据。这会占用更多的磁盘空间,对于具有非常大对象文件的配置中的某些非常大的库,可能会导致超出静态库大小的内部限制。源集没有此限制。某些目标根据生成配置在源集和静态库之间切换,以避免此问题。一些平台(或工具链)可能支持一种称为“薄档案”的东西,它没有这个问题;但你不能依赖它作为便携式解决方案。

  • source_set可以没有源文件,而静态库如果没有源文件,则会给出特定于平台的奇怪错误。如果目标只有头文件(用于包括检查目的)或在某些平台上有条件地没有源文件,请使用source_set。

  • 在特定链接不需要大量符号的情况下(在链接测试二进制文件时尤其如此),将该代码放在静态库static_library中可以显着提高链接性能。这是因为链接不需要的对象文件从一开始就不会被考虑在内,而不是强迫链接器在以后的传递中去除未使用的代码,因为没有代码引用它。

4.2、Components versus shared libraries versus source sets 组件、共享库和source_set的比对

组件是Components Chrome 模板(而不是内置的 GN 概念),不再赘述。

就像源代码集与静态库权衡一样,对于何时应该使用组件,没有硬性规定。使用组件可以显著加快链接速度,从而显著加快增量构建速度,但它们要求您考虑需要从目标导出哪些符号。

4.3、Loadable modules versus shared libraries 可加载模块和共享库的比对

共享库shared_library会被列在依赖它的target目标的链接行上,并在应用程序启动和符号自动解析时由操作系统自动加载。可加载模块loadable_module不会直接链接,应用程序必须手动加载它。

在 Windows 和 Linux 上,共享库和可加载模块会产生相同类型的文件(分别是.dll和 .so)。唯一的区别是它们如何链接到依赖它的目标上。在这些平台上,可加载模块的deps依赖与共享不会进行链接的data_deps依赖是相同的。

在Mac上,这些target目标具有不同的格式:共享库将生成.dylib文件,可加载模块将生成.so文件。

将可加载模块loadable_module用于插件等等。对于类似插件的库,最好同时为目标类型使用可加载模块(即使对于无关紧要的平台),并为依赖于它的目标使用data_deps,以便从两个地方都清楚地知道库将如何链接和加载。

5、Build arguments构建参数

5.1、Scope作用域

构建参数的作用域应限定在一个行为单元,例如启用功能。通常,将在导入的文件中声明一个参数,以将其与可以使用它的构建子集共享。

Chrome项目相关的一些风格指导可以自行查看原文,基本上不需要了解。

5.2、Type类型

参数支持所有GN语言的类型,字符串、列表、条件、循环、作用域等。

在绝大多数情况下,布尔值类型boolean是首选类型,因为大多数参数都启用或禁用功能或包含。

String字符串类型通常用于文件路径。字符串也用于枚举,尽管有时也使用整形integer。

5.3、Naming conventions命名约定

虽然围绕参数命名没有硬性规定,但有许多共同的约定。如果要查看当前构建目录的参数名称和默认值的参数列表,请使用gn args out/Debug --list --short

  • use_foo- 指示要包含的依赖项或主要代码路径(例如use_open_ssl,use_ozone,use_cups)

  • enable_foo- 表示要启用的功能或工具(例如enable_google_now,enable_nacl,enable_remoting,enable_pdf)

  • disable_foo - 不建议使用,请使用enable_foo-,然后改变其默认值

  • is_foo- 通常是全局状态描述符(例如is_chrome_branded,is_desktop_linux); 非全局变量的不推荐使用

  • foo_use_bar- 前缀可用于指示参数的限制的作用域(例如,rtc_use_h264,v8_use_snapshot)

5.4 Variables变量命名约定

在.gni文件中的顶级局部变量前面加上下划线前缀。此前缀会导致变量无法被导入到其他构建文件。

_this_var_will_not_be_exported = 1
but_this_one_will = 2

6、小结

本篇,我们学习了GN的编写规范,风格指南,包含命名、排序,格式化和缩进,用法,格式参数等的推荐用法。

参考资料

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
标签
3
收藏 3
回复
举报
2条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

了解下基础的规范,打好基础很重要。

回复
2022-10-24 10:23:36
诺舒华吃西瓜
诺舒华吃西瓜

这个格式和命名挺有意思

回复
2022-10-26 14:37:56
回复
    相关推荐