# 构建系统编ç 规范与最佳实践 ## 概述 gn是generate ninja的缩写,它是一个元编译系统(meta-build system),是ninjaçš„å‰ç«¯ï¼Œgnå’Œninja结åˆèµ·æ¥ï¼Œå®ŒæˆOpenHarmonyæ“作系统的编译任务。 ### gn简介 - ç›®å‰é‡‡ç”¨gn的大型软件系统有:Chromium,Fuchsiaå’ŒOpenHarmony。 - gnè¯æ³•自设计之åˆå°±è‡ªå¸¦å±€é™æ€§ï¼Œæ¯”如ä¸èƒ½æ±‚listçš„é•¿åº¦ï¼Œä¸æ”¯æŒé€šé…符ç‰ã€‚è¿™äº›å±€é™æ€§æºäºŽå…¶ **有所为有所ä¸ä¸º** çš„[设计哲å¦](https://gn.googlesource.com/gn/+/main/docs/language.md#Design-philosophy)。 所以在使用gn的过程ä¸ï¼Œå¦‚æžœå‘现æŸä»¶äº‹æƒ…用gn实现起æ¥å¾ˆå¤æ‚,请先åœä¸‹æ¥æ€è€ƒè¿™ä»¶äº‹æƒ…是å¦çœŸçš„需è¦åšã€‚ - 关于gn的更多详情è§[gn官方文档](https://gn.googlesource.com/gn/+/main/docs/)。 ### æœ¬æ–‡çš„ç›®æ ‡è¯»è€…å’Œè¦†ç›–èŒƒå›´ ç›®æ ‡è¯»è€…ä¸ºOpenHarmony的开å‘者。本文主è¦è®¨è®ºgn的编ç é£Žæ ¼å’Œä½¿ç”¨gn过程ä¸å®¹æ˜“出现的问题,ä¸è®¨è®ºgnçš„è¯æ³•,如需了解gn基础知识,è§[gn reference文档](https://gn.googlesource.com/gn/+/main/docs/reference.md)。 ### 总体原则 在ä¿è¯åŠŸèƒ½å¯ç”¨çš„å‰æä¸‹ï¼Œè„šæœ¬éœ€è¦æ»¡è¶³æ˜“于阅读,便于维护,良好的扩展性和性能ç‰è¦æ±‚。 ## 代ç é£Žæ ¼ ### å‘½å æ€»ä½“上éµå¾ªLinux kernel的命åé£Žæ ¼ï¼Œå³**å°å†™å—æ¯+下划线**的命åé£Žæ ¼ã€‚ #### 局部å˜é‡ 我们这里对局部å˜é‡çš„定义为:在æŸä½œç”¨åŸŸå†…,且ä¸å‘ä¸‹ä¼ é€’çš„å˜é‡ã€‚ 为了更好的区别于全局å˜é‡ï¼Œå±€éƒ¨å˜é‡ç»Ÿä¸€é‡‡ç”¨**下划线开头**。 ```shell # 例1 action("some_action") { ... # _output是个局部å˜é‡ï¼Œæ‰€ä»¥ä½¿ç”¨ä¸‹åˆ’线开头 _output = "${target_out_dir}/${target_name}.out" outputs = [ _output ] args = [ ... "--output", rebase_path(_output, root_build_dir), ... ] ... } ``` #### 全局å˜é‡ 全局å˜é‡ä½¿ç”¨**å°å†™å—æ¯**开头。 如果å˜é‡å€¼å¯ä»¥è¢«gn args修改,则需è¦ä½¿ç”¨declare_argsæ¥å£°æ˜Žï¼Œå¦åˆ™ä¸è¦ä½¿ç”¨declare_args。 ```shell # 例2 declare_args() { # å¯ä»¥é€šè¿‡gn argsæ¥ä¿®æ”¹some_feature的值 some_feature = false } ``` #### ç›®æ ‡å‘½å ç›®æ ‡å‘½å采用**å°å†™å—æ¯+下划线**çš„å‘½åæ–¹å¼ã€‚ 模æ¿ä¸çš„**åç›®æ ‡**命忖¹å¼é‡‡ç”¨"${target_name}+åŒä¸‹åˆ’线+åŽç¼€"çš„å‘½åæ–¹å¼ã€‚è¿™æ ·åšæœ‰ä¸¤ç‚¹å¥½å¤„: - åŠ å…¥"${target_name}"å¯ä»¥é˜²æ¢åç›®æ ‡é‡å。 - åŠ å…¥åŒä¸‹åˆ’线å¯ä»¥å¾ˆæ–¹ä¾¿åœ°åŒºåˆ†å‡ºåç›®æ ‡å±žäºŽå“ªä¸€ä¸ªæ¨¡å—,方便在出现问题时快速定ä½ã€‚ ```shell # 例3 template("ohos_shared_library") { # "{target_name}"(ä¸»ç›®æ ‡å)+"__"(åŒä¸‹åˆ’线)+"notice"(åŽç¼€) _notice_target = "${target_name}__notice" collect_notice(_notice_target) { ... } shared_library(target_name) { ... } } ``` #### 自定义模æ¿çš„å‘½å æŽ¨è采用**动宾çŸè¯**çš„å½¢å¼æ¥å‘½å。 ```shell # 例4 # Good template("compile_resources") { ... } ``` ### æ ¼å¼åŒ– gn脚本在æäº¤ä¹‹å‰éœ€è¦æ‰§è¡Œæ ¼å¼åŒ–ã€‚æ ¼å¼åŒ–å¯ä»¥ä¿è¯ä»£ç 对é½ï¼Œæ¢è¡Œç‰é£Žæ ¼çš„统一。使用gn自带的format工具å³å¯ã€‚命令如下: ```shell $ gn format path-to-BUILD.gn ``` gn formatä¼šæŒ‰ç…§å—æ¯åºå¯¹importæ–‡ä»¶åšæŽ’åºï¼Œå¦‚果想ä¿è¯import的顺åºï¼Œå¯ä»¥æ·»åŠ ç©ºæ³¨é‡Šè¡Œã€‚ å‡è®¾åŽŸæ¥çš„import顺åºä¸ºï¼š ```shell # 例5 import("//b.gni") import("//a.gni") ``` ç»è¿‡format之åŽå˜ä¸ºï¼š ```shell import("//a.gni") import("//b.gni") ``` 如果想ä¿è¯åŽŸæœ‰çš„import顺åºï¼Œå¯ä»¥æ·»åŠ ç©ºæ³¨é‡Šè¡Œã€‚ ```shell import("//b.gni") # Comment to keep import order import("//a.gni") ``` ## ç¼–ç 实践 ### 实践原则 编译脚本实质上完æˆäº†ä¸¤ä»¶å·¥ä½œï¼š 1. **æè¿°æ¨¡å—之间ä¾èµ–关系(deps)** 实践过程ä¸ï¼Œæœ€å¸¸å‡ºçŽ°çš„é—®é¢˜æ˜¯**ä¾èµ–关系缺失**。 2. **æè¿°æ¨¡å—编译的规则(rule)** 实践过程ä¸ï¼Œå®¹æ˜“出现的问题是**è¾“å…¥å’Œè¾“å‡ºä¸æ˜Žç¡®**。 ä¾èµ–缺失会导致两个问题: - **概率性编译错误** ```shell # 例6 # ä¾èµ–关系缺失,导致概率性编译出错 shared_library("a") { ... } shared_library("b") { ... ldflags = [ "-la" ] deps = [] ... } group("images") { deps = [ ":b" ] } ``` 上é¢çš„例åä¸ï¼Œlibb.so在链接的时候会链接liba.so,实质上构æˆbä¾èµ–a,但是bçš„ä¾èµ–列表(depsï¼‰å´æ²¡æœ‰å£°æ˜Žå¯¹açš„ä¾èµ–ã€‚ç”±äºŽç¼–è¯‘æ˜¯å¹¶å‘æ‰§è¡Œçš„,如果libb.so在链接的时候liba.so还没有编译出æ¥ï¼Œå°±ä¼šå‡ºçŽ°ç¼–è¯‘é”™è¯¯ã€‚ 由于liba.so也有å¯èƒ½åœ¨libb.so之å‰ç¼–译出æ¥ï¼Œæ‰€ä»¥ä¾èµ–缺失导致的编译错误是概率性的。 - **ä¾èµ–å…³ç³»ç¼ºå¤±å¯¼è‡´æ¨¡å—æ²¡æœ‰å‚与编译** 还是上é¢çš„例å,如果我们指定ninjaç¼–è¯‘ç›®æ ‡ä¸ºimages,由于imagesä»…ä»…ä¾èµ–b,所以aä¸ä¼šå‚与编译。由于b实质上ä¾èµ–a, 这时b在链接时会出现必现错误。 有一ç§ä¸å¤ªå¸¸è§çš„问题是**过多的ä¾èµ–**。**过多的ä¾èµ–会é™ä½Žå¹¶å‘ï¼Œå¯¼è‡´ç¼–è¯‘å˜æ…¢**。è§ä¸‹é¢çš„例å: _compile_js_targetä¸éœ€è¦ä¾èµ– _compile_resource_targetï¼Œå¢žåŠ è¿™å±‚ä¾èµ–,会导致 _compile_js_target在 _compile_resource_target编译完æˆä¹‹åŽæ‰èƒ½å¼€å§‹ç¼–译。 ```shell # 例7 # 过多的ä¾èµ–å¯¼è‡´ç¼–è¯‘å˜æ…¢ template("too_much_deps") { ... _gen_resource_target = "${target_name}__res" action(_gen_resource_target) { ... } _compile_resource_target = "${target_name}__compile_res" action(_compile_resource_target) { deps = [":$_gen_resource_target"] ... } _compile_js_target = "${target_name}__js" action(_compile_js_target) { # 这个depsä¸éœ€è¦ deps = [":$_compile_resource_target"] } } ``` è¾“å…¥ä¸æ˜Žç¡®ä¼šå¯¼è‡´ï¼š - **代ç 修改了,但增é‡ç¼–è¯‘æ—¶å´æ²¡æœ‰å‚与编译。** - **å½“ä½¿ç”¨ç¼“å˜æ—¶ï¼Œä»£ç å‘生å˜åŒ–,但是缓å˜ä»ç„¶å‘½ä¸ã€‚** 下é¢çš„例åä¸ï¼Œfoo.py引用了bar.pyä¸çš„函数。bar.py实质上是foo.py的输入,需è¦å°†bar.pyæ·»åŠ åˆ°implict_input_actionçš„input或者depfileä¸åŽ»ã€‚å¦åˆ™ï¼Œä¿®æ”¹bar.py,模å—implict_input_actionå°†ä¸ä¼šé‡æ–°ç¼–译。 ```shell # 例8 action("implict_input_action") { script = "//path-to-foo.py" ... } ``` ```shell #!/usr/bin/env # Contents of foo.py import bar ... bar.some_function() ... ``` è¾“å‡ºä¸æ˜Žç¡®ä¼šå¯¼è‡´ï¼š - **éšå¼çš„输出** - **å½“ä½¿ç”¨ç¼“å˜æ—¶ï¼Œéšå¼è¾“å‡ºæ— æ³•ä»Žç¼“å˜ä¸èŽ·å¾—** 下é¢çš„例åä¸ï¼Œfoo.py会生æˆä¸¤ä¸ªæ–‡ä»¶ï¼Œa.outå’Œb.out,但是implict_output_action的输出åªå£°æ˜Žäº†a.outã€‚è¿™ç§æƒ…况下,b.out实质上就是一个éšå¼è¾“出。缓å˜ä¸åªä¼šå˜å‚¨a.out,ä¸ä¼šå˜å‚¨b.out,当缓å˜å‘½ä¸æ—¶ï¼Œb.out就编译ä¸å‡ºæ¥äº†ã€‚ ```shell # 例9 action("implict_output_action") { outputs = ["${target_out_dir}/a.out"] script = "//path-to-foo.py" ... } ``` ```shell #!/usr/bin/env # Contents of foo.py ... write_file("b.out") write_file("a.out") ... ``` ### æ¨¡æ¿ **ä¸è¦ä½¿ç”¨gn的原生模æ¿ï¼Œä½¿ç”¨ç¼–译å系统æä¾›çš„æ¨¡æ¿** 所谓gn原生模æ¿ï¼Œæ˜¯æŒ‡source_set,shared_library, static_library, action, executable,groupè¿™å…个模æ¿ã€‚ 䏿ލè使用原生模æ¿çš„åŽŸå› æœ‰äºŒï¼š - **åŽŸç”Ÿæ¨¡æ¿æ˜¯æœ€å°åŠŸèƒ½æ¨¡æ¿**ï¼Œæ— æ³•æä¾›external_deps的解æžï¼Œnotice收集,安装信æ¯ç”Ÿæˆç‰çš„é¢å¤–功能,这些é¢å¤–功能最好是éšç€æ¨¡å—ç¼–è¯‘æ—¶åŒæ—¶ç”Ÿæˆï¼Œæ‰€ä»¥å¿…须对原生模æ¿åšé¢å¤–的扩展æ‰èƒ½æ»¡è¶³å®žé™…的需求。 - 当输入文件ä¾èµ–的文件å‘生å˜åŒ–时,gn原生的action模æ¿ä¸èƒ½è‡ªåŠ¨æ„ŸçŸ¥åˆ°è¿™ç§å˜åŒ–ï¼Œæ— æ³•é‡æ–°ç¼–译。è§ä¾‹8 原生模æ¿å’Œç¼–译å系统æä¾›çš„æ¨¡æ¿ä¹‹é—´çš„对应关系: | 编译å系统æä¾›çš„æ¨¡æ¿ | åŽŸç”Ÿæ¨¡æ¿ | | :------------------ | -------------- | | ohos_shared_library | shared_library | | ohos_source_set | source_set | | ohos_executable | executable | | ohos_static_library | static_library | | action_with_pydeps | action | | ohos_group | group | ### 使用python脚本 actionä¸çš„script推è使用pythonè„šæœ¬ï¼Œä¸æŽ¨è使用shell脚本。相比于shell脚本,python脚本: - pythonè¯æ³•å‹å¥½ï¼Œä¸ä¼šå› ä¸ºå°‘å†™ä¸€ä¸ªç©ºæ ¼å°±å¯¼è‡´å¥‡æ€ªçš„é”™è¯¯ã€‚ - python脚本有很强的å¯è¯»æ€§ã€‚ - å¯ç»´æŠ¤æ€§å¼ºï¼Œå¯è°ƒè¯•。 - OpenHarmony对python任务åšäº†ç¼“å˜ï¼Œå¯ä»¥åŠ å¿«ç¼–è¯‘é€Ÿåº¦ã€‚ ### rebase_path - 仅在å‘actionçš„å‚æ•°åˆ—表ä¸ï¼ˆargs)调用rebase_path。 ```shell # 例10 template("foo") { action(target_name) { ... args = [ # 仅在argsä¸è°ƒç”¨rebase_path "--bar=" + rebase_path(invoker.bar, root_build_dir), ... ] ... } } foo("good") { bar = something ... } ``` - åŒä¸€å˜é‡åšä¸¤æ¬¡rebase_pathä¼šå‡ºçŽ°æ„æƒ³ä¸åˆ°çš„结果。 ```shell # 例11 template("foo") { action(target_name) { ... args = [ # bar被执行了两次rebase_path, ä¼ é€’çš„bar的值已ç»ä¸å¯¹äº† "--bar=" + rebase_path(invoker.bar, root_build_dir), ... ] ... } } foo("bad") { # ä¸è¦åœ¨è¿™é‡Œè°ƒç”¨rebase_path bar = rebase_path(some_value,root_build_dir) ... } ``` ### 模å—é—´æ•°æ®åˆ†äº« 模å—é—´æ•°æ®åˆ†äº«æ˜¯å¾ˆå¸¸è§çš„事情,比如Aæ¨¡å—æƒ³è¦çŸ¥é“B模å—的输出和deps。 - åŒä¸€BUILD.gn之间数æ®åˆ†äº« åŒä¸€BUILD.gn之间数æ®å¯ä»¥é€šè¿‡å®šä¹‰å…¨å±€å˜é‡çš„æ–¹å¼æ¥å…±äº«ã€‚ 下é¢çš„例åä¸ï¼Œæ¨¡å—a的输出是模å—b的输入,å¯ä»¥é€šè¿‡å®šä¹‰å…¨å±€å˜é‡çš„æ–¹å¼æ¥å…±äº«ç»™b ```shell # 例12 _output_a = get_label_info(":a", "out_dir") + "/a.out" action("a") { outputs = _output_a ... } action("b") { inputs = [_output_a] ... } ``` - ä¸åŒBUILD.gn之间数æ®åˆ†äº« ä¸åŒBUILD.gnä¹‹é—´ä¼ é€’æ•°æ®ï¼Œæœ€å¥½çš„办法是将需è¦å…±äº«çš„æ•°æ®ä¿å˜æˆæ–‡ä»¶ï¼Œç„¶åŽä¸åŒæ¨¡å—之间通过文件æ¥ä¼ 递和共享数æ®ã€‚è¿™ç§åœºæ™¯æ¯”è¾ƒå¤æ‚,读者å¯ä»¥å‚ç…§OpenHarmonyçš„hap编译过程的write_meta_data。 ### forward_variable_from - 自定义模æ¿éœ€è¦é¦–先将testonlyä¼ é€’ï¼ˆforward)进æ¥ã€‚å› ä¸ºè¯¥æ¨¡æ¿çš„target有å¯èƒ½è¢«testonlyçš„ç›®æ ‡ä¾èµ–。 ```shell # 例13 # 自定义模æ¿é¦–å…ˆè¦ä¼ 递testonly template("foo") { forward_variable_from(invoker, ["testonly"]) ... } ``` - 䏿ލè使用*æ¥forwardå˜é‡ï¼Œéœ€è¦çš„å˜é‡åº”该**显å¼åœ°ï¼Œä¸€ä¸ªä¸€ä¸ªåœ°**被forwardè¿›æ¥ã€‚ ```shell # 例14 # Bad,使用*forwardå˜é‡ template("foo") { forward_variable_from(invoker, "*") ... } # Good, 显å¼åœ°ï¼Œä¸€ä¸ªä¸€ä¸ªåœ°forwardå˜é‡ template("bar") { # forward_variable_from(invoker, [ "testonly", "deps", ... ]) ... } ``` ### target_name target_name会éšç€ä½œç”¨åŸŸå˜åŒ–而å˜åŒ–ï¼Œä½¿ç”¨æ—¶éœ€è¦æ³¨æ„。 ```shell # 例15 # target_name会éšç€ä½œç”¨åŸŸå˜åŒ–而å˜åŒ– template("foo") { # æ¤æ—¶æ‰“å°å‡ºæ¥çš„target_name为"${target_name}" print(target_name) _code_gen_target = "${target_name}__gen" code_gen(_code_gen_target) { # æ¤æ—¶æ‰“å°å‡ºæ¥çš„target_name为"${target_name}__gen" print(target_name) ... } _compile_gen_target = "${target_name}__compile" compile(_compile_gen_target) { # æ¤æ—¶æ‰“å°å‡ºæ¥çš„target_name为"${target_name}__compile" print(target_name) ... } ... } ``` ### public_configs 如果模å—需è¦å‘外export头文件,请使用public_configs。 ```shell # 例16 # bä¾èµ–aï¼Œä¼šåŒæ—¶ç»§æ‰¿açš„headers config("headers") { include_dirs = ["//path-to-headers"] ... } shared_library("a") { public_configs = [":headers"] ... } executable("b") { deps = [":a"] ... } ``` ### template 自定义模æ¿ä¸å¿…须有一个åç›®æ ‡çš„åå—æ˜¯target_name。该åç›®æ ‡ä¼šä½œä¸ºtemplateçš„ä¸»ç›®æ ‡ã€‚å…¶ä»–åç›®æ ‡éƒ½åº”è¯¥è¢«ä¸»ç›®æ ‡ä¾èµ–,å¦åˆ™åç›®æ ‡ä¸ä¼šè¢«ç¼–译。 ```shell # 例17 # 自定义模æ¿ä¸å¿…须有一个åç›®æ ‡çš„åå—æ˜¯target_name template("foo") { _code_gen_target = "${target_name}__gen" code_gen(_code_gen_target) { ... } _compile_gen_target = "${target_name}__compile" compile(_compile_gen_target) { # æ¤æ—¶æ‰“å°å‡ºæ¥çš„target_name为"${target_name}__compile" print(target_name) ... } ... group(target_name) { deps = [ # 由于_compile_gen_targetä¾èµ–了_code_gen_targetï¼Œæ‰€ä»¥ä¸»ç›®æ ‡åªéœ€è¦ä¾èµ–_compile_gen_targetå³å¯ã€‚ ":$_compile_gen_target" ] } } ``` ### set_source_assignment_filter set_source_assignment_filter除了å¯ä»¥è¿‡æ»¤sources,还å¯ä»¥ç”¨æ¥è¿‡æ»¤å…¶ä»–å˜é‡ã€‚过滤完æˆåŽè®°å¾—将过滤器和sources置空。 ```shell # 例18 # 使用set_source_assignment_filter过滤ä¾èµ–, 挑选label符åˆ*:*_resçš„æ·»åŠ åˆ°ä¾èµ–åˆ—è¡¨ä¸ _deps = [] foreach(_possible_dep, invoker.deps) { set_source_assignment_filter(["*:*_res"]) _label = get_label_info(_possible_dep, "label_no_toolchain") sources = [] sources = [ _label ] if (sources = []) { _deps += _sources } } sources = [] set_source_assignment_filter([]) ``` 最新版本上set_source_assignment_filter被filter_includeå’Œfilter_excludeå–代。 ### 部件内ä¾èµ–采用deps,跨部件ä¾èµ–采用external_deps - 部件在OpenHarmony上指能æä¾›æŸä¸ªèƒ½åŠ›çš„ä¸€ç»„æ¨¡å—。 - 在模å—定义的时候å¯ä»¥å£°æ˜Žpart_name,用æ¥è¡¨æ˜Žå½“剿¨¡å—属于哪个部件。 - æ¯ä¸ªéƒ¨ä»¶ä¼šå£°æ˜Žå…¶inner_kits,供其他部件调用。部件inner_kitsçš„å£°æ˜Žè§æºç ä¸çš„bundle.json。 - 部件间ä¾èµ–åªèƒ½ä¾èµ–inner_kits,ä¸èƒ½ä¾èµ–éžinner_kits的模å—。 - 如果a模å—å’Œb模å—çš„part_name相åŒï¼Œé‚£ä¹ˆaã€b模å—属于åŒä¸€ä¸ªéƒ¨ä»¶ï¼Œa,b模å—之间的ä¾èµ–关系å¯ä»¥ç”¨depsæ¥å£°æ˜Žã€‚ - 如果aã€b模å—çš„part_nameä¸åŒï¼Œé‚£ä¹ˆaã€b模å—ä¸å±žäºŽåŒä¸€ä¸ªéƒ¨ä»¶ï¼Œaã€b模å—之间的ä¾èµ–关系需è¦é€šè¿‡external_depsæ¥å£°æ˜Žï¼Œä¾èµ–æ–¹å¼ä¸º"部件å:模å—å"的方å¼ã€‚è§ä¾‹19。 ```shell # 例19 shared_library("a") { ... external_deps = ["part_name_of_b:b"] ... } ```