深入理解OpenHarmony中的BUILD.gn:从语法到模块化构建
在 OpenHarmony 中,BUILD.gn 文件是编译构建系统的核心配置文件,用于定义项目的结构、依赖关系、编译规则以及构建目标(如可执行文件、库文件、配置文件等)
后续熟悉OpenHarmony之后,你会发现BUILD.gn语法无处不在,掌握很有必要
1.BUILD.gn 在 OpenHarmony 中的角色
唯一入口:
每个可编译单元(子系统/部件/模块)必须提供 BUILD.gn,由 GN 工具链逐层加载,最终生成 Ninja 文件供 Ninja 执行增量编译。
声明目标:
通过预定义的 ohos_* 模板(executable、static_library …)告诉系统“这里要生成什么”以及“它依赖谁”。
隔离与复用:
变量、config、source_set 仅在当前文件或显式 public_configs/deps 中可见,天然支持模块化。
定义构建目标与输出类型
BUILD.gn 通过声明式语法指定需要构建的产物类型,例如:
可执行文件 (executable 或 ohos_executable):用于生成可直接运行的程序(如系统服务、命令行工具)。 共享库 (shared_library 或 ohos_shared_library):生成动态链接库(.so 文件),供其他模块调用 。 静态库
(static_library 或 ohos_static_library):生成静态链接库(.a 文件),在编译时链接到目标程序 。
配置文件/资源文件 (ohos_prebuilt_etc, ohos_copy,
action):定义策略文件、规则文件、配置文件等的生成、复制和安装路径。 预构建模块
(ohos_prebuilt):用于集成第三方预编译好的库或应用(如 HAP 包)。 构建组
(group):将多个相关目标组织在一起,便于统一管理依赖
2.BUILD.gn中常用的函数总结
函数名 | 说明 | 示例用法 |
---|---|---|
ohos_executable() | 构建可执行程序(如 app、bin) | ohos_executable("hello") { sources = ["main.c"] } |
ohos_static_library() | 构建静态库(.a) | ohos_static_library("libfoo") { sources = ["foo.c"] } |
ohos_shared_library() | 构建动态库(.so) | ohos_shared_library("libbar") { sources = ["bar.c"] } |
ohos_source_set() | 仅编译源文件,不打包为库 | ohos_source_set("utils") { sources = ["util.c"] } |
ohos_group() | 逻辑分组,无实际构建产物 | ohos_group("modules") { deps = [":mod1", ":mod2"] } |
ohos_test() | 构建测试目标(单元测试) | ohos_test("test_foo") { sources = ["test_foo.c"] } |
ohos_prebuilt_executable() | 使用预编译可执行文件 | ohos_prebuilt_executable("tool") { source = "tool.bin" } |
ohos_prebuilt_shared_library() | 使用预编译 .so 文件 | ohos_prebuilt_shared_library("libxx") { source = "libxx.so" } |
ohos_prebuilt_static_library() | 使用预编译 .a 文件 | ohos_prebuilt_static_library("libyy") { source = "libyy.a" } |
3.使用注意事项;
模板 | 快速场景 | 使用禁区 |
---|---|---|
ohos_executable | 产出可执行文件(/bin 或 /test) | ① 必须写 part_name ,否则链接阶段直接丢弃;② 不要 sources += glob(*.c) ——glob 会吃进临时文件;③ 依赖 .so 时,把 .so 目标写在 deps 而非 external_deps ,否则运行时找不到。 4.ohos_executable默认不安装进开发板,如果有需要,需指定 install_enable = true |
ohos_static_library | 把多份 .o 打包成 .a ,供别的模块静态链接 | ① 对外暴露头文件目录一定用 public_configs = [ ":my_inc" ] ,否则父目标找不到头文件;② 不要在该模板里 sources += third_party/xx.c ,第三方代码用 ohos_prebuilt_static_library 或独立 source_set ;③ 如果最终要进系统镜像,确认对应 part_name 在 ohos.build 的 module_list 中,否则镜像里会缺库。 |
ohos_shared_library | 生成 .so 供运行时动态加载 | ① 默认带 -fvisibility=hidden ,想导出符号必须显式 __attribute__((visibility("default"))) 或用 export_symbols 文件;② 不要把 ohos_shared_library 目标放进 external_deps ,只能放在 deps ;③ 若对可执行文件 dlopen ,确保该 .so 的 part_name 与可执行文件同属一个子系统,否则系统编译器会裁掉认为“无人用”。 |
ohos_source_set | 仅编译 .o 不打包,用于中间复用 | ① 不能出现在 ohos.build 的 module_list 里,它没产物;② 别的目标 deps 引用它后,会自动把 .o 合并到最终产物,勿再手动加 .o 到 sources 造成重复定义;③ 不要给 source_set 写 part_name ,写了也会被忽略。 |
ohos_test | 单元/集成测试可执行文件 | ① 必须依赖 gtest 或 hctest 之一,写在 external_deps = [ "gtest:gtest_main" ] ;② 测试二进制默认安装到 /system/bin ,名字冲突会导致 hb 安装失败,加 output_name = "my_test" 区分;③ 想在 CI 里跑,记得在 test_config.json 中注册,否则 xdevice 不会拉起。 |
ohos_group | 逻辑分组,无产物 | ① 仅用于聚合 deps ,不能写 sources ;② 不要给 group 写 part_name ,因为它不参与链接;③ 若只是为了 gn path 可视化,可以嵌套 group ,但层级过深会导致 gn 解析变慢。 |
ohos_prebuilt_executable | 直接使用现成二进制(工具/固件) | ① source = "xxx.bin" 路径必须相对于本 BUILD.gn 或写绝对路径 // ;② 如果该二进制要进镜像,必须再写 install_images = [ "system" ] ,否则只编译不进包;③ 架构不匹配(如把 x86 工具放进 arm 产品)会在 hb build 最后打包阶段才报错,务必确认 target_cpu 。 |
ohos_prebuilt_static_library | 使用已有 .a | ① 必须写 output_name 且不含 lib 前缀和 .a 后缀,否则链接器找不到;② 如果 .a 是用 gcc 编译而当前板子用 clang,一定确认 ABI 一致,不然链接时报 undefined reference to __gnu_* ;③ 不要把第三方 .a 直接 sources += ["libxx.a"] ,会被当成普通文件重新编译。 |
ohos_prebuilt_shared_library | 使用已有 .so | ① 同样需要 install_images = [ "system" ] 才能进镜像;② 若该 .so 还依赖其他 .so ,务必把依赖也列进 deps ,否则运行时 dlopen 失败;③ 版本升级时记得同步更新 .so 并执行 gn clean ,避免旧 .so 被缓存。 |
4.常用字段总结:
字段名 | 说明 |
---|---|
sources | 源文件列表 |
deps | 内部依赖(同 BUILD.gn 中的目标) |
external_deps | 外部部件依赖,格式为 "部件名:模块名" |
public_configs | 对外暴露的编译配置(如头文件路径) |
part_name | 必填,指定所属部件(如 "part_name = "my_part" ) |
include_dirs | 头文件搜索路径(常用于 config() ) |
5.使用实例:
1.ohos_prebuilt_etc(“系统内置的HAp名”),具体参applications\standard\hap\BUILD.gn
import("//build/ohos.gni")
ohos_prebuilt_etc("launcher_hap") {source = "Launcher.hap"module_install_dir = "app/com.ohos.launcher"part_name = "prebuilt_hap"subsystem_name = "applications"
}
2.ohos_shared_library(“动态库名”),可以参考foundation\CastEngine\castengine_wifi_display\services\BUILD.gn
ohos_shared_library("sharing_service") {install_enable = truesanitize = {cfi = truecfi_cross_dso = truedebug = falseboundary_sanitize = trueubsan = trueinteger_overflow = true}deps = ["agent:sharing_agent_srcs","configuration:sharing_configure_srcs","context:sharing_context_srcs","event:sharing_event_srcs","interaction:sharing_interaction_srcs","mediachannel:sharing_media_channel_srcs","mediaplayer:sharing_media_player_srcs","scheduler:sharing_scheduler_srcs",]deps += ["codec:sharing_codec","common:sharing_common","network:sharing_network","protocol/rtp:sharing_rtp","protocol/rtsp:sharing_rtsp","utils:sharing_utils","//third_party/cJSON:cjson_static",]if (sharing_framework_support_wfd) {deps += ["impl/scene/wfd:sharing_wfd_srcs","impl/screen_capture:sharing_screen_capture_srcs","impl/wfd:sharing_wfd_session_srcs",]}configs = [ "$SHARING_ROOT_DIR/tests:coverage_flags" ]external_deps = ["access_token:libaccesstoken_sdk","access_token:libnativetoken","access_token:libtoken_setproc","audio_framework:audio_capturer","audio_framework:audio_client","audio_framework:audio_renderer","graphic_2d:librender_service_base","graphic_surface:surface","hilog:libhilog","samgr:samgr_proxy",]subsystem_name = "castplus"part_name = "sharing_framework"
}
3.ohos_static_library("静态库名“),一般在third_party子系统中用的最多,比如third_party\zlib\BUILD.gn
ohos_static_library("libz") {sources = ["adler32.c","compress.c","contrib/minizip/ioapi.c","contrib/minizip/unzip.c","contrib/minizip/zip.c","crc32.c","crc32.h","deflate.c","deflate.h","gzclose.c","gzguts.h","gzlib.c","gzread.c","gzwrite.c","infback.c","inffast.c","inffast.h","inffixed.h","inflate.c","inflate.h","inftrees.c","inftrees.h","trees.c","trees.h","uncompr.c","zconf.h","zlib.h","zutil.c","zutil.h",]configs = [ ":zlib_config" ]public_configs = [ ":zlib_public_config" ]part_name = "zlib"subsystem_name = "thirdparty"
}
4.ohos_source_set(”仅编译,不打包“),一般在third_party子系统中用的最多。比如third_party\vk-gl-cts\external\openglcts\modules\BUILD.gn
ohos_source_set("glcts_source") {sources = [ "//third_party/vk-gl-cts/external/openglcts/modules/glcTestPackageRegistry.cpp" ]include_dirs = ["//third_party/vk-gl-cts/modules/glshared","//third_party/vk-gl-cts/modules/egl","//third_party/vk-gl-cts/modules/gles2","//third_party/vk-gl-cts/modules/gles3","//third_party/vk-gl-cts/modules/gles31","//third_party/vk-gl-cts/external/openglcts/modules/common","//third_party/vk-gl-cts/external/openglcts/modules/gl","//third_party/vk-gl-cts/external/openglcts/modules/gles2","//third_party/vk-gl-cts/external/openglcts/modules/gles3","//third_party/vk-gl-cts/external/openglcts/modules/gles31","//third_party/vk-gl-cts/external/openglcts/modules/gles32","//third_party/vk-gl-cts/external/openglcts/modules/glesext","//third_party/vk-gl-cts/external/openglcts/modules",]include_dirs += deqp_common_include_dirsdeps = ["//third_party/vk-gl-cts/external/openglcts/modules/common:libdeqp_glcts-common-nocontext-package","//third_party/vk-gl-cts/external/openglcts/modules/gl:libdeqp_glcts-gl","//third_party/vk-gl-cts/external/openglcts/modules/gles2:libdeqp_glcts-es2","//third_party/vk-gl-cts/external/openglcts/modules/gles3:libdeqp_glcts-es3","//third_party/vk-gl-cts/external/openglcts/modules/gles31:libdeqp_glcts-es31","//third_party/vk-gl-cts/external/openglcts/modules/gles32:libdeqp_glcts-es32","//third_party/vk-gl-cts/external/openglcts/modules/glesext:libdeqp_glcts-esext","//third_party/vk-gl-cts/modules/egl:libdeqp-egl","//third_party/vk-gl-cts/modules/gles2:libdeqp-gles2","//third_party/vk-gl-cts/modules/gles3:libdeqp-gles3","//third_party/vk-gl-cts/modules/gles31:libdeqp-gles31",]configs = [ ":glcts_config" ]
}
5.ohos_group(“某类组”),一般用于控制某类具有相同功能、含义的模块
文件foundation\CastEngine\castengine_wifi_display\tests\BUILD.gn:
group("test") {deps = [ "demo:demo_test" ]
}
文件foundation\CastEngine\castengine_wifi_display\tests\demo\BUILD.gn中集体管理demo_test这个分组
import("//build/ohos.gni")
import("//foundation/CastEngine/castengine_wifi_display/config.gni")
group("demo_test") {deps = ["delaytest:loop_back_demo","interaction:sharing_wfd_source_demo","network:sharing_network_client_demo","network:sharing_network_server_demo","network:udp_client_demo","network:udp_server_demo","rtp:sharing_rtp_dec_demo","rtp:sharing_rtp_enc_demo","wfd:sharing_wfd_demo",]
}
这里如果我想要让demo_test下的可执行测试程序都编译,我可以在文件foundation\CastEngine\castengine_wifi_display\BUILD.gn中添加如下:
group("sharing_packages") {deps = ["//foundation/CastEngine/castengine_wifi_display/interfaces/innerkits/native/wfd:sharingwfd_client","//foundation/CastEngine/castengine_wifi_display/interfaces/kits/js/wfd:sharingwfd_napi","//foundation/CastEngine/castengine_wifi_display/sa_profile:sharing_sa_profile","//foundation/CastEngine/castengine_wifi_display/services:sharing_services_package","//foundation/CastEngine/castengine_wifi_display/services/etc:sharing_service.rc",+ "//foundation/CastEngine/castengine_wifi_display/tests:test" #添加这里可以让demo_test测试模块全部编译进去]
}
因为tests目录下依赖test分组。test分组又是demo:demo_test,所有最终实现模块之间的编译
其他关于测试和预编译的可以查阅源码目录下的BUILD.gn配置文件学习,具体不在介绍
“预编译”就是 “不把源码拿过来重新编译,而是直接拿别人已经编译好的产物(.a / .so / bin/.hap等编译产物)参与链接或打包”。