文章大纲
- 引言
- 一、GN常用的内置变量
- 二、GN常用的内置函数
- 三、CMake 重要语法
- 1、生成动态库
- 2、生成静态库
- 3、生成OBJECT 库
- 4、重要的函数和模块
- 4.1、add_definitions
- 4.2、execute_process
- 4.3、add_dependencies
- 4.4、install
- 4.5、FetchContent
- 四、GN 重要语法
- 1、编译Target
- 2、预构建Target
- 3、group
- 4、external_deps 和 deps
- 五、编写bundle.json 配置模块信息
- 1、inner_kits声明对外开放的动态库(可选)
- 2、使用inner_kits
引言
BUILD.gn 是 Google 开发的一种构建脚本语言,用于描述项目依赖关系、编译规则等信息,以便由 ninja 构建系统生成构建文件并进行编译。华为基于gn进行了一些扩展并把其作为主要的构建系统,换句话说无论是完全全采用gn来构建还是通过gn间接触发cmake等其他构建系统也好,OpenHarmony中都是要先改成gn的,移植主要有以下步骤:
- GN 方式进行构建
- 编写bundle.json 配置Module及其部件信息
- 注册Module 到指定的subsystem
一、GN常用的内置变量
名称 | 描述 |
---|---|
current_cpu | 当前工具链的处理器架构 |
current_os | 当前工具链的操作系统类型 |
current_toolchain | 表示当前使用的工具链 |
default_toolchain | 表示默认使用的工具链 |
target_cpu | 表示目标平台的CPU类型 |
target_os | 表示目标平台的操作系统类型 |
root_build_dir | 表示根目录的构建目录 |
root_gen_dir | 表示根目录的生成目录 |
root_out_dir | 表示根目录的输出目录 |
target_out_dir | 表示目标文件的输出目录 |
target_gen_dir | 表示中间文件的生成目录 |
defines | 表示当前目标的预定义宏列表 |
include_dirs | 表示当前目标的头文件搜索路径列表 |
cflags | 表示当前目标的C语言编译选项列表 |
cxxflags | 表示当前目标的C++语言编译选项列表 |
ldflags | 表示当前目标的链接选项列表 |
asmflags | 表示当前目标的汇编语言编译选项列表 |
libs | 表示当前目标依赖的库文件列表 |
二、GN常用的内置函数
名称 | 描述 |
---|---|
assert() | 断言函数,如果条件不成立,则会抛出一个异常 |
defined() | 判断变量是否已经定义 |
exec_script() | 执行一个Python脚本 |
get_label_info() | 获取标签信息,例如标签的名称、路径、类型等等 |
get_path_info() | 获取路径信息,例如路径是否存在、是否是目录、是否是文件等等 |
group() | 将一组目标文件组合成一个库文件 |
import() | 导入其他GN构建文件 |
read_file() | 读取文件内容 |
read_json() | 读取JSON格式的文件 |
read_path() | 读取路径中的内容,返回一个字符串列表 |
rebase_path() | 重新定位路径,将路径中的某个部分替换为新的值 |
write_file() | 写入文件内容 |
template() | 处理字符串模板,将模板中的变量替换为实际的值,其功能类似与函数 |
action() | 定义一个自定义的构建动作,通过action调用python脚本完成期望动作 |
action_foreach() | 针对每个元素执行一个自定义的构建动作 |
executable() | 定义一个可执行文件 |
shared_library() | 定义一个动态库 |
static_library() | 定义一个静态库 |
三、CMake 重要语法
在 CMake 中,add_library 命令用于创建库目标,可以是静态库(Static Library)、动态库(Shared Library)或目标对象(Object Library)。
1、生成动态库
- 使用
add_library(${target_name} SHARED ${ARGN})
创建动态库,它会生成一个共享对象文件,如libexample.so
。 - 动态库在运行时被可执行文件加载,而不是在链接时复制到可执行文件中。
- 静态库和动态库可以直接被可执行文件或其他库目标链接
- 动态库是一个共享对象文件,用于在运行时共享,适用于当你想要运行时共享代码,节省内存占用,或在不重启应用程序的情况下更新功能。
- 动态库需要在编译对象文件后进行额外的生成共享对象。
function(onnxruntime_add_shared_library target_name)
add_library(${target_name} SHARED A R G N ) o n n x r u n t i m e c o n f i g u r e t a r g e t ( {ARGN}) onnxruntime_configure_target( ARGN)onnxruntimeconfiguretarget({target_name})
endfunction()
2、生成静态库
- 使用
add_library(${target_name} STATIC ${ARGN})
创建静态库,它会将对象文件存档成一个单一的文件,如libexample.a
。 - 静态库在链接时会被复制到最终的可执行文件中。
- 静态库和动态库可以直接被可执行文件或其他库目标链接。
- 静态库是一个存档文件,包含所有成员对象文件,适用于当你想要分发包含所有依赖的单一文件,便于部署。
- 静态库需要在编译对象文件后进行额外的存档步骤
function(onnxruntime_add_static_library target_name)
add_library(${target_name} STATIC A R G N ) o n n x r u n t i m e c o n f i g u r e t a r g e t ( {ARGN}) onnxruntime_configure_target( ARGN)onnxruntimeconfiguretarget({target_name})
endfunction()
3、生成OBJECT 库
- 使用
add_library(${target_name} OBJECT ${ARGN})
创建的是目标对象库,它是一个包含编译后对象文件的集合。 - 这些对象文件不会自动被存档成库文件(
.a
或.so
),而是可以直接被其他库或可执行文件目标链接。 - 对象库不能直接被链接,它的对象文件需要被其他库或可执行文件显式链接。
- 对象库实际上是不存档的,它只是一组编译后的对象文件,适用于当你想要在多个目标之间共享编译结果,避免重复编译相同的代码。
- 对象库不涉及额外的编译步骤来创建库文件。
创建目标对象库的示例
add_library(${target_name} OBJECT ${ARGN})
${target_name}
是库目标的名称,${ARGN}
是传递给 add_library
命令的所有额外参数,通常是源文件列表。这样创建的对象库可以被其他目标通过 target_sources
命令添加到它们的源文件列表中。
依赖头文件和库
include_directories(
${ONNXRUNTIME_INCLUDE_DIR}
${REPO_ROOT}/include/onnxruntime/core/session
)
4、重要的函数和模块
4.1、add_definitions
add_definitions 函数用于向项目中添加编译器定义。这些定义会被转换为预处理器宏,可以在源代码中使用 #ifdef、#ifndef、#define 等预处理器指令来控制条件编译。比如说add_definitions(-DOPENVINO_CONFIG_NPU=1) 相当于是-DOPENVINO_CONFIG_NPU=1在编译过程中,对于所有的源文件,都定义一个名为 OPENVINO_CONFIG_NPU 的宏,并将其值设置为 1。在 C++ 源代码中,你可以使用 #ifdef OPENVINO_CONFIG_NPU 或 #if defined(OPENVINO_CONFIG_NPU) 来检查这个宏是否被定义。
#ifdef OPENVINO_CONFIG_NPU
// 这部分代码只有在 OPENVINO_CONFIG_NPU 宏被定义时才会被编译
// 实现与 NPU 相关的功能
#endif
4.2、execute_process
用于执行一个外部进程,并可以捕获其输出。它允许 CMake 脚本与系统上的其他命令行工具交互。
execute_process(
COMMAND <cmd> [args1 [args2 ...]]
[WORKING_DIRECTORY <dir>]
[TIMEOUT <seconds>]
[RESULT_VARIABLE <variable>]
[OUTPUT_VARIABLE <variable>]
[ERROR_VARIABLE <variable>]
[OUTPUT_STRIP_TRAILING_WHITESPACE]
[ERROR_STRIP_TRAILING_WHITESPACE]
[ENCODING <encoding>]
[ECHO [STDOUT|STDERR]]
[RESULT_ERROR_VARIABLE <variable>]
[OUTPUT_FILE <file>]
[ERROR_FILE <file>]
[USE_RESULT_VARIABLE]
[RESULT_CODE <variable>]
[OUTPUT_QUIET]
[ERROR_QUIET]
[COMMAND_ERROR_IS_FATAL]
)
execute_process 执行了 ls -l 命令,并将输出和错误分别存储在 output 和 error 变量中。然后使用 message 函数打印输出结果。
# 执行一个命令并捕获输出
execute_process(
COMMAND ls -l
OUTPUT_VARIABLE output
ERROR_VARIABLE error
OUTPUT_STRIP_TRAILING_WHITESPACE
)
# 打印输出结果
message(STATUS "Command Output:\n${output}")
if(error)
message(STATUS "Command Error:\n${error}")
endif()
4.3、add_dependencies
是 CMake 中的一个指令,用于定义目标(通常是可执行程序或库)之间的依赖关系。当你使用 add_dependencies 命令时,你告诉 CMake 在构建某个目标之前需要先构建其他的目标。
add_dependencies(onnxruntime_flatbuffers flatc)
onnxruntime_flatbuffers 这个目标在构建之前需要先构建 flatc 目标。如果你还没有定义 onnxruntime_flatbuffers 和 flatc,你需要先定义它们:
add_executable(flatc src/flatc.cpp)
# 假设 onnxruntime_flatbuffers 依赖于由 flatc 生成的源代码
add_library(onnxruntime_flatbuffers STATIC
src/onnxruntime_generated.cpp
# 其他源文件
)
# 设置 onnxruntime_flatbuffers 对 flatc 的依赖
add_dependencies(onnxruntime_flatbuffers flatc)
4.4、install
用于指定在安装目标(可执行文件、库、头文件等)时应采取的操作。这个函数定义了如何将构建的文件和目录复制到系统的标准安装位置,或者用户指定的任何位置。
- ${CMAKE_INSTALL_LIBDIR} 共享库、静态库或者库相关的其他文件的标准目录。
- ${CMAKE_INSTALL_BINDIR} 可执行文件的标准目录。
除了可以通过CMake脚本配置还可以在命令行配置 -DCMAKE_INSTALL_LIBDIR=some/path 或 -DCMAKE_INSTALL_BINDIR=some/path 来指定自定义的安装路径。 - 安装可执行文件到默认的 bin 目录
install(TARGETS my_executable RUNTIME DESTINATION bin) - 安装库文件到默认的 lib 目录
install(TARGETS my_library LIBRARY DESTINATION lib ARCHIVE DESTINATION lib) - 安装头文件到特定目录
install(FILES my_header.h DESTINATION include) - 安装数据文件到特定目录
install(FILES my_header.h DESTINATION include) - 设置文件权限
install(FILES my_file.txt DESTINATION /etc/my_project PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ)
4.5、FetchContent
include(FetchContent) 是一个命令,用于包含 FetchContent 模块。FetchContent 是 CMake 的一个内置模块,它提供了一种机制来下载、缓存和包含外部项目或依赖项,这些项目或依赖项可以是源代码仓库或二进制文件。使用 FetchContent 模块,你可以将第三方库或项目作为依赖项集成到你的 CMake 项目中,而无需手动下载和集成这些依赖项。这个模块会自动处理依赖项的下载和配置
- 自动下载:如果指定的依赖项不在缓存中,FetchContent 会自动从指定的 URL 下载依赖项。
- 缓存机制:下载的依赖项会被缓存,以避免未来的重新下载,提高构建效率。
- 可配置性:你可以配置下载的源代码的子模块、分支、标签或提交,以确保获取正确的依赖项版本。
- 依赖项包含:下载的依赖项可以作为子目录包含在项目中,或者作为独立的外部项目使用。
- 兼容性:FetchContent 模块兼容 CMake 3.11 及以上版本。
使用步骤 - include(FetchContent)
- FetchContent_Declare(子模块名) 获取项目。
- FetchContent_MakeAvailable(子模块),再引入我们的项目中
- target_link_libraries(主项目 PRIVATE 子模块::子模块)
四、GN 重要语法
GN 是以target 为基础对象构建组织的,所以CMake中的动态库、静态库在GN中映射为对应的Target,而华为为每一种Target定义了一套对应的模板,直接按照相应语法配置即可。
/build/templates/cxx/cxx.gni
GN中通用的规则
- .gn 文件所在的目录是构建根目录。
- 路径以“//” 开头的表示绝对路径,以“/“或者则表示相对路径
//path/to/proprietary_library:libproprietary 冒号前部分是库所在的目录绝对路径,冒号后部分是该库在 BUILD.gn 文件中定义的Target名称 - gn中不支持以通配符的形式把相应的源文件打包起来,gn中的源文件需要逐条列出,头文件可以只配置对应目录
- declare_args 定义变量,这个变量疑似是全局唯一的所有在同一个系统内部名称不能重复
declare_args() {
allow_sanitize_debug = false
}
1、编译Target
-
ohos_shared_library 编译动态库
-
ohos_static_library编译静态库
-
ohos_executable 编译可以执行文件
ohos_source_set配置源集
2、预构建Target
/build/templates/cxx/prebuilt.gni
所谓prebuilt 从某种程度上来说就是把现有的产物复制到对应的路径下,复制过程中根据需要进行相关的依赖关系处理。
- ohos_prebuilt_executable 不额外配置路径的话会保存到${install_images}/bin目录下
- ohos_prebuilt_shared_library 不额外配置路径的话会保存到${install_images}/lib 或者lib64目录下
- ohos_prebuilt_static_library 不额外配置路径的话会保存到${install_images}/lib 或者lib64目录下
- ohos_prebuilt_etc 不额外配置路径的话会保存到${install_images}/etc目录下
ohos_prebuilt_etc(“mnist.onnx”) {
source = “//third_party/onnxrt/test/demo/mnist.onnx”
subsystem_name = “third_party”
part_name = “onnx_runtime”
install_images = [ chipset_base_dir ] #会保存到 vendor/etc下
install_enable = true
}
3、group
group作用就是把所有的Target 的编译入口整合到一个group Target里,在bundle.json里就不需要逐条去声明了,只需要声明一个group就会触发。
group("onnx_runtime") {
deps = [
":onnxrt_providers_shared",
":onnxrt_1.18.0",
":onnxrt_link",
]
if(enable_demo){
deps += [
":onnxrt_demo",
":mnist.onnx",
]
}
}
4、external_deps 和 deps
external_deps 节点用于指定项目所依赖的外部(非本Module内部)库或模块。而deps 属性用于指定一个构建目标(如 executable、shared_library、static_library 等)所依赖的其他目标。这些依赖可以是同一项目内的其他构建目标,也可以是通过 external_deps 声明的外部库。
在添加一个模块的时候,需要在BUILD.gn中声明它的依赖,部件内依赖deps和部件间依赖external_deps。
- 部件内依赖: 现有模块module1属于部件part1,要添加一个属于部件part1的模块module2,module2依赖于module1,这种情况就属于部件内依赖。
import(“//build/ohos.gni”)
ohos_shared_library("module1") {
……
part_name = "part1" # 必选,所属部件名称
……
}
import("//build/ohos.gni")
ohos_shared_library("module2") {
……
deps = [
"module1的gn target",
……
] # 部件内模块依赖
part_name = "part1" # 必选,所属部件名称
}
- 部件间依赖: 现有模块module1属于部件part1,要添加一个模块module2,module2依赖于module1,module2属于部件part2。模块module2与模块module1分属于两个不同的部件,这种情况就属于部件间依赖。
import("//build/ohos.gni")
ohos_shared_library("module1") {
……
part_name = "part1" # 必选,所属部件名称
……
}
import("//build/ohos.gni")
ohos_shared_library("module2") {
……
external_deps = [
"part1:module1",
……
] # 部件间模块依赖,这里依赖的模块必须是依赖的部件声明在inner_kits中的模块
part_name = "part2" # 必选,所属部件名称
}
五、编写bundle.json 配置模块信息
OpenHarmony中以模块Module 进行组织,其配置文件为bundle.json。
{
#HPM部件英文名称,格式"@组织/部件名称"
"name": "@ohos/alsa-lib",
# 部件功能一句话描述
"description": "The alsa-lib is a library to interface with ALSA in the Linux kernel and virtual devices using a plugin system.",
# 版本号,版本号与OpenHarmony版本号一致
"version": "3.1",
# 部件License
"license": "GNU Lesser General Public License v2.1",
# HPM包的发布方式,当前默认都为code-segment
"publishAs": "code-segment",
# 发布类型为code-segment时为必填项,定义发布类型code-segment的代码还原路径(源码路径)
"segment": {
"destPath": "third_party/alsa-lib"
},
# HPM包的目录结构,字段必填内容可以留空
"dirs": {},
# HPM包定义需要执行的脚本,字段必填,值非必填
"scripts": {},
# 部件License路径,字段和值非必填
"licensePath": "COPYING",
# 部件说明路径,字段和值非必填
"readmePath": {
"en": "README.md",
"zh": "README_zh.md"
},
# 部件属性
"component": {
# 部件名称
"name": "alsa-lib",
# 部件所属子系统,字段必填,值非必填
"subsystem": "thirdparty",
# 部件为应用提供的系统能力,在component下加入关键字syscap,对内部配置相应的系统能力。系统能力若无赋值,则默认为true,若有赋值,则按实际值为准。若值为true,则表示该部件默认开启此系统能力,若值为false,则表明该部件默认关闭此系统能力。WIFI的STA、AP、和HotspotExt三个系统能力是打开的,而P2P和Core是关闭的。
"syscap": [
"SystemCapability.Communication.WiFi.STA = true",
"SystemCapability.Communication.WiFi.AP = true",
"SystemCapability.Communication.WiFi.P2P = false",
"SystemCapability.Communication.WiFi.Core = false",
"SystemCapability.Communication.WiFi.HotspotExt"
],
# 部件对外的可配置特性列表,一般与build中的sub_component对应,可供产品配置
"features": [],
# 轻量(mini)小型(small)和标准(standard),可以是多个比如:["standard", "small"],
"adapted_system_type": [ "standard" ],
# 部件ROM值
"rom": "950KB",
# 部件RAM估值
"ram": "988KB",
"deps": {
"components": [], # 部件依赖的其他部件
"third_party": [] # 部件依赖的其他部件
},
# 编译相关配置
"build": {
# 部件编译入口,模块在此处配置组件产物的so、静态库的BUILD.gn,可执行文件不能在这里配置
"sub_component": [ "//third_party/alsa-lib:libasound" ],
# 对外开放的模块在此处配置
"inner_kits": [],
# 部件测试用例编译入口
"test": []
}
}
}
1、inner_kits声明对外开放的动态库(可选)
似乎目前OpenHarmony只支持以so的形式对外提供,也就是external_deps 部分中所依赖到的外部库,本质上都是其他模块对外提供的bundle.json中的inner_kits部分的。
2、使用inner_kits
这样子声明之后就可以在external_deps里像 c_utils:c_utils这样直接形如”模块名:inner_kits “即 onnx_runtime:onnxrt_1.18.0
- 使用这个inner_kits模块的bundle.json 下的 “component” 下的"deps"里"components"中配置上inner_kits所在Component的name
根据具体情况再不同的子节点中配置即可,有些可能需要配置在third_party下的。
- 在使用这个inner_kits模块的BUILD.gn下配置需要引入的inner_kits的头文件和库Target