背景
最近在研究openharmony系统的编译原理,使用了gn+ninja工具,为了更好的理解,就在网上搜索并动手编译出一个hello world,将整个过程记录下来,以便后续回顾学习
工具下载
gn+ninja是谷歌大牛为了增加chrome的编译速度开发的编译工具,类似make+makefile
gn工具源码在googlesource上,但是国内好像打不开。。。
ninja工具源码在github上,可以下载并根据指导进行编译
不过还有一个更好的方法,就是下载openharmony源码,使用hb编译时会自动下载gn+ninja工具,在目录prebuilts/build-tools/linux-x86/bin下,如下
demo代码
看网上说在gn的源码里面有一个demo的,但是网站打不开,所以也无法学习,就根据网上搜索的信息,以及自己的理解,做了一个demo,写好的目录结构如下
下面一步步来说明
首先创建一个工程目录,我这个demo的工程目录为test
gn入口
gn工具要求工程根目录必须有一个.gn
文件(注意,无文件名)。此文件记录编译配置相关文件路径,最简单的内容如下
buildconfig = "//build/BUILDCONFIG.gn"
openharmony的
.gn
文件内容如下# The location of the build configuration file. buildconfig = "//build/lite/config/BUILDCONFIG.gn" # The source root location. root = "//build/lite"
工具链配置
上节中buildconfig的值中//
表示根目录,即在根目录下需要创建一个build目录,里面存放编译需要的整个工具链的配置信息
- 首先是
BUILDCONFIG.gn
文件,内容如下
itsenlin@itsenlin-PC:~/code/test$ cat build/BUILDCONFIG.gn
set_default_toolchain("//build/toolchains:gcc")
cflags_cc = [ "-std=c++11" ]
cflags_cc += [ "-std=c99" ]
说明:
//build/toolchains:gcc
表示build目录下有一个toolchains目录,里面有一个BUILD.gn设置工具链信息,其中定义了一个toolchain名字是gcc
,具体内容在下面再讲cflags_cc
是gn的默认变量,表示编译选项
- toolchain内容如下
itsenlin@itsenlin-PC:~/code/test$ cat build/toolchains/BUILD.gn
toolchain("gcc") {
tool("cc") {
depfile = ".d"
command = "gcc -MMD -MF $depfile -c {{source}} -o {{output}}"
depsformat = "gcc"
description = "CC"
outputs = [
"{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o",
]
}
tool("cxx") {
depfile = ".d"
command = "g++ -MMD -MF $depfile -c {{source}} -o {{output}}"
depsformat = "gcc"
description = "CXX"
outputs = [
"{{source_out_dir}}/{{target_output_name}}.{{source_name_part}}.o",
]
}
tool("alink") {
rspfile = "{{output}}.rsp"
command = "ar rcs {{output}} @\"$rspfile\""
description = "AR"
rspfile_content = "{{inputs}}"
outputs = [
"{{target_out_dir}}/{{target_output_name}}{{output_extension}}",
]
default_output_extension = ".a"
output_prefix = "lib"
}
tool("solink") {
soname = ""
sofile = "/$soname"
rspfile = "{{output}}.rsp"
command = "g++ -shared -o $sofile -Wl, -soname=$soname @$rspfile"
description = "SOLINK $soname"
rspfile_content = "{{inputs}}"
outputs = [
sofile,
]
default_output_extension = ".so"
default_output_dir = ""
link_output = sofile
depend_output = sofile
output_prefix = "lib"
}
tool("link") {
outfile = "{{output_dir}}/bin/{{target_output_name}}{{output_extension}}"
rspfile = "{{output}}.rsp"
command = "g++ -o $outfile @$rspfile"
description = "LINK $outfile"
rspfile_content = "{{inputs}}"
outputs = [
outfile,
]
default_output_dir = "{{root_out_dir}}"
}
tool("stamp") {
command = "touch {{output}}"
description = "STAMP "
}
tool("copy") {
command = "cp -af "
description = "COPY "
}
}
说明:
- 双大括号里面的变量都是ng内置的变量
- stamp只是为了生成一个空的文件,应该是为了判断编译时间,跟对应文件时间做对比,看是否需要重新编译
- 这个demo中应该只用到了toolchain中的cc/cxx/link/stamp
- 每一个tool的说明,可以参考这篇文章
源码以及对应gn文件编写
- 首先在工程目录下创建一个
src
目录,用来存放源码,这里为了演示c/c++程序的编译,所以分别创建了test1.c
和test.cpp
,内容都是输出“hello,world”
itsenlin@itsenlin-PC:~/code/test$ cat src/test.cpp
#include
int main()
{
std::cout << "hello, world" << std::endl;
return 0;
}
itsenlin@itsenlin-PC:~/code/test$ cat src/test1.c
#include
#include
int main()
{
printf("hello, world form test1");
return 0;
}
itsenlin@itsenlin-PC:~/code/test$
- 在src目录下创建一个
BUILD.gn
,配置编译的目标文件、源文件路径之类的,文件内容如下
itsenlin@itsenlin-PC:~/code/test$ cat src/BUILD.gn
executable("hello_world") {
sources = [
"test.cpp",
]
}
executable("hello_world1") {
sources = [
"test1.c",
]
}
说明:
excutable
表示编译成可执行文件,文件名为后面括号里面的字符串;这是gn的内置函数,这里就相当于调用了这个函数,具体原理可以看gn template
的帮助文档sources
也是gn内置的变量,表示编译需要的源文件列表
- 还需要在工程根目录下创建一个BUILD.gn,作为整个工程的根目标,这个文件将会指定默认的构建目标,内容如下
itsenlin@itsenlin-PC:~/code/test$ cat BUILD.gn
group("default") {
deps = [
"//src:hello_world",
"//src:hello_world1",
]
}
itsenlin@itsenlin-PC:
该文件中的内容非常简单,指定了一个虚拟目标default
,deps表示依赖列表,这里定义依赖于src文件夹下BUILD.gn
中的目标hello_world
以及hello_world1
。
GN生成.ninja文件
在工程根目录下使用gn命令将所有.gn文件生成.ninja文件
itsenlin@itsenlin-PC:~/code/test$ ../ohos_3.0/prebuilts/build-tools/linux-x86/bin/gn gen out/test
Done. Made 3 targets from 4 files in 24ms
itsenlin@itsenlin-PC:~/code/test$
会自动创建out/test目录,并将生成的.ninja文件放在out/test目录下,如下图
使用ninja工具进行编译
在工程根目录下执行ninja命令进行编译,命令及结果如下
itsenlin@itsenlin-PC:~/code/test$ ../ohos_3.0/prebuilts/build-tools/linux-x86/bin/ninja -C out/test
ninja: Entering directory `out/test'
[5/5] STAMP
itsenlin@itsenlin-PC:~/code/test$
编译之后会生成过程文件以及结果文件,如下图
测试
可以直接执行结果文件来测试,结果如下
itsenlin@itsenlin-PC:~/code/test$ ./out/test/bin/hello_world
hello, world
itsenlin@itsenlin-PC:~/code/test$ ./out/test/bin/hello_world1
hello, world form test1itsenlin@itsenlin-PC:~/code/test$
从结果看hello_world1
输出的结果没有加换行,看着不太舒服,那就直接修改下源码吧,直接在test1.c
中的printf
里面打印字符串最后加上\n
即可
保存之后可以再次执行ninja命令编译(不需要再重新执行gn
了,gn
只是为了将.gn
文件生成.ninja
文件供ninja
编译时使用),可以看到只有test1.c被重新编译了
再重新执行hello_world1之后,结果看着就舒服多了:)
itsenlin@itsenlin-PC:~/code/test$ ./out/test/bin/hello_world1
hello, world form test1
itsenlin@itsenlin-PC:~/code/test$