Makefile
makefile
是一个包含一组指令的文件,make build
自动化工具使用这些指令來生成目标。
优点:自动化编译,可以极大的提高了软件开发的效率。
规则
1 | target ... : prerequisites ... |
target
可以是一个object file(目标文件),也可以是一个执行文件,还可以是一个标签(label)。
prerequisites
生成该target所依赖的文件和/
或target。反斜杠( \
)是换行符的意思。
command
该target要执行的命令(任意的shell命令)
1 | hello:hello.c |
工作流程浅析
- make会在当前目录下找
Makefile / makefile
- 找到后,会寻找第一个目标文件(target)
hello
,并作为最终的目标文件。 - 如果
hello
不存在活或其依赖hello.c
的修改时间比hello
新,那么就会执行command
来重新生成target
PS:一定要以一个 Tab
键作为开头
make 会一层一层的寻找文件依赖关系,直至编译出第一个目标文件。如果在寻找的过程中出现错误,那么 make 就会退出并报错。
make 所着重的是寻找依赖关系,command
的命令的正确与否是不关心的。
makefile
书写规则
通配符
make支持三个通配符: *
, ?
和 ~
*
:匹配 0 或多个字符
?
:匹配任意一个字符
~
:Linux,~
= $HOME
Mac,则需要自己设置 HOME
目录
文件搜索
1 | VPATH = src:../headers |
设置搜索路径,make
执行时,如果当前目录找不到,则到指定路径取搜寻。
另一种则是:vpath
关键字,注意全小写。
1 | vpath <pattern> <directories> #为符合模式<pattern>的文件指定搜索目录<directories>。 |
伪目标
“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。
PS:伪目标不能和文件重名。
1 | .PHONY : clean #使用“.PHONY”来显式地指明一个目标是“伪目标” |
例如清空规则:
1 | .PHONY : clean |
书写命令
显示命令
1 | @echo 正在编译XXX模块...... |
@
是必须的,如果make
执行时,带入make
参数 -n
或 --just-print
,那么其只是显示命令,但不会执行命令。
命令执行
1 | exec: |
命令出错
有些命令执行结果的正确与否我们是不关心的,那么有两种办法可以解决:
- 在命令前加
-
- 执行
make
的时候,加上-i / --ignore-errors
参数(这种做法会忽略掉所有命令的错误
另外:参数的是 -k
或是 --keep-going
,指如果某规则中的命令出错了,那么就终止该规则的执行,但继续执行其它规则。
定义命令包
定义这种命令序列的语法以 define
开始,以 endef
结束,骨架如下:
1 | define 包名 |
包名不能和变量名重名。
使用变量
变量命名
1 | objects = hello.c |
变量命名可以包含:数字、字符、下划线(可以是数字开头),但不能含有 :
、 #
、 =
或是空字符(空格、回车等)。变量名对大小写是敏感的。
变量在声明的时候要进行初始化,使用时最好用 ()或{}
将变量包裹起来,eg:$(obj) ${obj}
另外$$
则表示使用$
变量会在使用它的时候精确展开。
变量高级用法
变量值替换
格式: $(var:a=b)
或是 ${var:a=b}
把 a
均替换成 b
变量嵌套
也就是用变量的值去做新的变量。个人觉得追加更简单一些。
1 | x = y |
隐含规则
示例:
1 | #① |
makefile
会有自动推导的过程,所以我们写成①的形式会更简洁一些。
部分隐含规则:
CC
: C语言编译程序。默认命令是cc
CXX
: C++语言编译程序。默认命令是g++
RM
: 删除文件命令。默认命令是rm –f
CFLAGS
: C语言编译器参数。CXXFLAGS
: C++语言编译器参数。LDFLAGS
: 链接器参数。(如:ld
)使用的时候请用:$(CC) 、$(RM) 等
示例:
把所有的 .c
文件都编译成 .o
文件.
1 | %.o : %.c |
其中, $@
表示所有的目标的挨个值, $<
表示了所有依赖目标的挨个值。
自动变量
$@
: 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,$@
就是匹配于目标中模式定义的集合。$%
: 仅当目标是函数库文件中,表示规则中的目标成员名。$<
: 依赖目标中的第一个目标名字。如果依赖目标是以模式(即%
)定义的,那么$<
将是符合模式的一系列的文件集。注意,其是一个一个取出来的。$?
: 所有比目标新的依赖目标的集合。以空格分隔。$^
: 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那么这个变量会去除重复的依赖目标,只保留一份。$+
: 这个变量很像$^
,也是所有依赖目标的集合。只是它不去除重复的依赖目标。
Makefile中一些GNU约定俗成的伪目标
伪目标 | 含义 |
---|---|
all | 所有目标的目标,其功能一般是编译所有的目标 |
clean | 删除所有被make创建的文件 |
install | 安装已编译好的程序,其实就是把目标可执行文件拷贝到指定的目录中去 |
列出改变过的源文件 | |
tar | 把源程序打包备份. 也就是一个tar文件 |
dist | 创建一个压缩文件, 一般是把tar文件压成Z文件. 或是gz文件 |
TAGS | 更新所有的目标, 以备完整地重编译使用 |
check 或 test | 一般用来测试makefile的流程 |
参考:
https://cloud.tencent.com/developer/article/1343894
GN
Overview
GN是一种元构建系统,生成Ninja构建文件(Ninja build files),相较GYP而言,具有如下优点:
- 可读性更好,更容易编写和维护。
- 速度更快,谷歌官方给的数据是20倍的速度提升。
- 修改GN文件后,执行ninja构建时会自动更新Ninja构建文件。
- 更简单的模块依赖,提供了public_deps, data_deps等,在GYP中,只有一种目标依赖,导致依赖关系错综复杂,容易引入不必要的模块依赖。
- 提供了更好的工具查询模块依赖图谱。这在GYP构建系统中是一个噩梦,要查一个目标依赖哪些模块或者一个模块被哪些目标依赖几乎是不可能的。
- 更好的调试支持。在GN中,只需要一条print语句就可以解决。
demo
在 chromium 的 src 目录下创建一个新的 test
文件夹:
1 | air@ubuntu:~/Desktop/chromium/src/test$ ls |
1 | //demo.c |
1 | //BUILD.gn |
然后在根目录的 BUILD.gn
添加如下
1 | group("gn_all") { |
然后编译就行了
1 | air@ubuntu:~/Desktop/chromium/src/test$ gn gen out/test |
构建流程
- 在当前目录中查找
.gn
文件并向上遍历目录树,直到找到.gn
。将此目录设置为根目录,并解释此文件以查找生成配置文件的名称。 - 执行构建配置文件(这是默认工具链)。
- 将
BUILD.gn
文件加载到根目录中。 - 递归加载其他目录中的
BUILD.gn
以解析所有当前依赖项。如果在指定位置找不到生成文件,GN将在tools/GN/secondary
内的相应位置查找 - 解析目标的依赖项后,将
.ninja
文件写出到磁盘。 - 当所有目标都解决后,写出
root build.ninja
文件。
配置文件
执行的第一个文件是构建配置文件。此文件的名称在标记存储库根目录的.gn
文件中指定。在Chrome中是src/build/config/BUILDCONFIG.gn
只有一个构建配置文件。
目标
目标是构建图中的一个节点。它通常代表将要生成的某种类型的可执行文件或库文件。目标取决于其他目标。内置的目标类型(请参阅gn help <targettype>
以获取更多帮助)是:
action
:运行一个脚本来生成一个文件。action_foreach
:为每个源文件运行一次脚本。bundle_data
:声明数据加入到Mac / iOS包。create_bundle
:创建一个Mac / iOS包。executable
:生成一个可执行文件。group
:引用一个或多个其他目标的虚拟依赖关系节点。shared_library
:.dll或.so。loadable_module
:.dll或.so只能在运行时加载。source_set
:一个轻量级的虚拟静态库(通常比真正的静态库更可取,因为它的构建速度会更快)。static_library
:.lib或.a文件(通常你会想要一个source_set
)。
更多资料可以看官方文档。