gn+ninja实践demo

背景

最近在研究openharmony系统的编译原理,使用了gn+ninja工具,为了更好的理解,就在网上搜索并动手编译出一个hello world,将整个过程记录下来,以便后续回顾学习

工具下载

gn+ninja是谷歌大牛为了增加chrome的编译速度开发的编译工具,类似make+makefile
gn工具源码在googlesource上,但是国内好像打不开。。。
ninja工具源码在github上,可以下载并根据指导进行编译
不过还有一个更好的方法,就是下载openharmony源码,使用hb编译时会自动下载gn+ninja工具,在目录prebuilts/build-tools/linux-x86/bin下,如下

ng-ninja工具路径.png

demo代码

看网上说在gn的源码里面有一个demo的,但是网站打不开,所以也无法学习,就根据网上搜索的信息,以及自己的理解,做了一个demo,写好的目录结构如下


demo_dir.png

下面一步步来说明
首先创建一个工程目录,我这个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目录,里面存放编译需要的整个工具链的配置信息

  1. 首先是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的默认变量,表示编译选项
  1. 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文件编写

  1. 首先在工程目录下创建一个src目录,用来存放源码,这里为了演示c/c++程序的编译,所以分别创建了test1.ctest.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$
  1. 在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内置的变量,表示编译需要的源文件列表
  1. 还需要在工程根目录下创建一个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目录下,如下图


gn命令生成ninja文件.png

使用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$ 

编译之后会生成过程文件以及结果文件,如下图


ninja编译之后结果.png

测试

可以直接执行结果文件来测试,结果如下

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被重新编译了

修改文件重新编译结果.png

再重新执行hello_world1之后,结果看着就舒服多了:)

itsenlin@itsenlin-PC:~/code/test$ ./out/test/bin/hello_world1 
hello, world form test1
itsenlin@itsenlin-PC:~/code/test$ 

你可能感兴趣的:(gn+ninja实践demo)