Android编译系统最佳实践

原文:https://android.googlesource.com/platform/build/soong/+/master/docs/best_practices.md

只读源码目录树

构建期间永远不要写入源码目录,始终写入$OUT_DIR目录。我们希望将来能够强制执行此操作。如果需要验证/提供已签入生成的源文件的更新,请在构建期间将该文件生成在$OUT_DIR目录中,使构建失败,要求用户运行命令(直接命令,签入脚本,生成脚本等),将文件从output显示拷贝到源码目录树中。

网络访问

在构建期间永远不要访问网络。我们希望将来能够强制执行此操作,尽管像distcc和goma等工具会有一定程度的例外情况。

路径

不要在Ninja文件中使用绝对路径(使用make $(abspath)或类似的),因为这可能会在源目录被移动时触发额外的重新构建。
假定源目录是 $PWD。尤其是在脚本要更改目录和需要将输入从相对转换为绝对路径时更应如此。
不要在构建中间体或输出中编码绝对路径。这将很难在其他机器上重新构建。
不要以为$OUT_DIR就是out。现在源码和输出目录树都非常大,所以有些人会把它们放在不同的磁盘上。还有许多其他用途。
不要以为$OUT_DIR目录就在$PWD目录下,用户可以将其设置为相对路径或绝对路径。

$(shell)在Android.mk文件中的使用

不用用 $(shell)写文件,创建符号链接等。我们希望将来能够强制执行此操作。 将它们编码为构建图中的构建规则。这可能在很多方面存在问题:

  • $(shell)调用在每次构建开始时运行,至少这会减慢构建启动速度,但它也可以触发更多的构建步骤来运行,因为这些文件的更改频率会超出必要的范围。
  • 精简产品配置不再可能选择退出这些创建的文件。最好设置实际规则和依赖项,以便不浪费空间,但必要时文件就在那里。

头文件

LOCAL_COPY_HEADERS已弃用。Soong模块不能使用这些头,并且当启用VNDK时,Make中的系统模块也不能声明或使用它们。构建系统提供的全局包含路径集也将被删除。它们已经从使用-isystem切换到用-I,并且在某些环境中完全删除(启用VNDK时的供应商代码)。
取而代之,使用LOCAL_EXPORT_C_INCLUDE_DIRS/export_include_dirs。如果您链接到相关代码,这些允许自动访问头文件。
如果您的库使用LOCAL_EXPORT_C_INCLUDE_DIRS/export_include_dirs,并且导出的头文件引用了一个您链接的库,请使用LOCAL_EXPORT_SHARED_LIBRARY_HEADERS/LOCAL_EXPORT_STATIC_LIBRARY_HEADERS/LOCAL_EXPORT_HEADER_LIBRARY_HEADERSexport_shared_lib_headers/export_static_lib_headers/export_header_lib_headers)将必要的头文件重新导出给您的用户。

不要在你的LOCAL_EXPORT_C_INCLUDE_DIRS中使用非本地路径,而是使用其中一个LOCAL_EXPORT_*_HEADERS代替。Soong不支持非本地导出目录。您可能需要将模块定义上移一个目录(如:假设您有./src/./include/目录,您可能想在./Android.bp中定义模块,而不是在./src/Android.bp中),定义一个头文件库并重新导出它,或将头文件移动到一个更合适的位置。

仅当头文件实际上是独立的并且没有关联的代码时,才更倾向于使用头文件库(BUILD_HEADER_LIBRARY/ cc_library_headers)。有时候,头文件只有头部分,但也定义了库的接口。希望将这些仅限标题的部分拆分为仅包含仅标题部分的单独的仅包含头文件的库,并从现有库重新导出该头文件库。这样可以防止意外链接超出您需要的代码(在构建和/或运行时更慢),或者意外地不链接到实际需要的库。

LOCAL_EXPORT_C_INCLUDE_DIRSLOCAL_C_INCLUDES更好。最终我们想删除LOCAL_C_INCLUDES,但首先需要进行大量清理。这对于检测模块使用不可用的头文件的情况是必要的 - 通常由于缺乏ABI / API保证,但由于各种其他原因:分层违规,计划弃用,潜在的优化,如C ++模块等

使用默认值而不是变量

Soong支持Android.bp文件中的变量定义,但多数情况下,最好使用默认模块像cc_defaults, java_defaults等。

  • 它会在值旁边移动更多信息 - 字符串数组将用作源文件列表对人类和自动化工具都很有用。如果它在体系结构或目标特定属性中使用,则更有用。
  • 它可以将多条信息一起收集到可以使用单个属性选择的逻辑可继承组中。

客制化构建工具

如果从工具中编写多个文件,请在构建图中声明它们。

  • Make: 使用.KATI_IMPLICIT_OUTPUTS
  • Android.bp: 只需将它们添加到生成规则的 out列表中
  • 客制化Soong插件: 添加到 OutputsImplicitOutputs

声明工具读取的所有文件,如果可以,则使用依赖项,或者通过编写依赖项文件。Ninja支持一组相当有限的依赖文件格式。您可以使用以下命令验证是否正确读取了依赖项:

NINJA_ARGS="-t deps <output_file>" m

更喜欢在命令行上列出输入文件,否则我们可能不知道在添加新输入文件时重新运行命令。Ninja不会将依赖关系中的更改视为使操作无效的事情 - 命令行需要更改,或者其中一个输入需要比输出文件更新。如果未在命令行中包含输入,则可能需要将目录添加到依赖项列表或依赖项文件中,以便这些目录中的任何添加或删除都会触发您的工具重新运行。这可能比必要的更昂贵,因为许多编辑器会将临时文件写入同一目录,因此更改README可能会触发目录的时间戳更新。
仅控制基于命令行的输出文件,而不是输入文件。我们需要知道哪些文件将在读取任何输入之前被创建,因为我们在读取源文件或运行工具之前生成整个构建图。这经常会出现基于Java的工具 - 它们将根据输入文件中声明的类生成不同的输出文件。我们使用“srcjar”概念解决了这些工具,它只是一个包含生成源文件的jar文件。我们的Java编译任务理解* .srcjar文件,并在将它们传递给编译器之前将它们提取出来。

PRODUCT_PACKAGES中的库

大多数库不必包括在PRODUCT_PACKAGES中,除非它们通过dlopen动态使用。如果它们仅通过LOCAL_SHARED_LIBRARIES / shared_libs使用,则这些依赖项将在必要时触发它们安装。添加不必要的库PRODUCT_PACKAGES会强制它们始终安装,浪费空间。

推荐阅读更多精彩内容