Makefile是项目工程中的编译规则文件,定义了哪些文件先行编译,哪些文件需要后编译,以及文件的链接顺序等编译规则。make是UNIX和Linux系统下执行Makefile文件的命令,源文件到可执行文件的过程称为编译,编译顺序的安排称为构建。
make与Makefile的关系
make是一个命令工具,用来解释Makefile中的指令。在Makefile文件中描述了整个工程所有文件的编译顺序、编译规则。
cmake是什么
你或许听过好几种 Make 工具,例如 GNU Make ,QT 的 qmake ,微软的 MSnmake,BSD Make(pmake),Makepp等等。这些 Make 工具遵循着不同的规范和标准,所执行的 Makefile 格式也千差万别。这样就带来了一个严峻的问题:如果软件想跨平台,必须要保证能够在不同平台编译。而如果使用上面的 Make 工具,就得为每一种标准写一次 Makefile ,这将是一件让人抓狂的工作。
CMake就是针对上面问题所设计的工具:它首先允许开发者编写一种平台无关的 CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。从而做到“Write once, run everywhere”。显然,CMake 是一个比上述几种 make 更高级的编译配置工具。一些使用 CMake 作为项目架构系统的知名开源项目有 VTK、ITK、KDE、OpenCV、OSG 等。
Makefile基本语法:
目标:依赖
Tab 命令 #注意:一定是要“Tab键”,空格不行
首先,了解一下make命令的基本用法
make [-f file] [options] [target]
执行make命令,默认在当前目录中寻找GUNmakefile、makefile文件,作为make命令的输入文件
- -f 可指定上述文件名之外的文件作为输入文件
- -v 显示版本号
- -n 只输出命令,但并不执行,一般用于调试或测试
- -s 只执行命令,但不显示具体命令,此处可在命令中用@符号抑制命令输出
- -w 显示执行前、后的路径
- -C [dir] 指定makefile的目录
没有指定目标时,默认使用第一目标
如果指定,则执行对应的命令
make 按默认配置編译
make -j8 8个进程同时编译,缩短总的时间
make world 编译所有能够编译的(postgresql 经常同时編译文档及附加模块(contrib)
make check 回归测试,用于安装完成后测试功能完整性,不能以 root 身份运行
make install 安装程序
make install-docs 安装文档(info、man手册)
make install-world 安装所有可安装的
make uninstall 删除安装的文件(生成的目录无法删除)
make clean 清除 make 生成的文件,但保留 configure 生成的文件
make distclean 将源码恢复为原始状态,即同时删除 make 与 configure 阶段生成的文件
make -C 指定源文件目录,仅安装程序的特定部分;
如只安装客户端应用和接口:
make -C src/bin install;
make -C src/include install;
make -C src/interfaces install;
make -C doc install
Makefile文件中的几种赋值与引用方式:=、:= 、?=、+=,赋值操作一般是针对变量的,Makefile中的变量可以任意命名,原则上尽量见名知意。
(1)= 递归式变量赋值,左侧为变量名,右侧为变量的值,优点是变量可以重复多次赋值
xxx@XXX:~/test$ cat Makefile
var1=1
var2=$(var1)
var2=2
print:
echo $(var2)
xxx@XXX:~$ make
echo 2
2
(2):= 展开式变量赋值,定义变量时,变量右侧的值立即替换到变量值中,前面的变量不能使用后面的变量,即变量一旦赋值,即使后面再次赋值,也不会覆盖前面定义的变量值
xxx@XXX:~/test$ cat Makefile
var1=012
var2:=$(var1)
var1=345
print:
echo $(var2)
xxx@XXX:~/test$ make
echo 012
012
(3)?= 条件赋值,用于判断左边的变量是否被赋值过,如果是,不会执行此次赋值操作;如果不是,则给该变量赋值:
xxx@XXX:~/test$ cat Makefile
var1=012
var2=$(var1)
var2 ?=345
print:
echo $(var2)
xxx@XXX:~/test$ make
echo 012
012
(4)+= 一般用于新增目标文件编译操作,例如:
#新增目标前
objs=main.o add.o
main=$(objs)
#此时需要新增一个sub.o文件
objs=main.o add.o
objs+=sub.o
main=$(objs)
常见的自动变量类型
自动变量名 | 解释说明 |
---|---|
$@ | 表示规则的目标文件名。如果目标是一个文档文件(Linux 中,一般成 .a 文件为文档文件,也称为静态的库文件),那么它代表这个文档的文件名;在多目标模式规则中,它代表的是触发规则被执行的文件名 |
$% | 当目标文件是一个静态库文件时,代表静态库的一个成员名;如果目标文件不是静态库文件,其值为空 |
$< | 规则的第一个依赖的文件名。如果是一个目标文件使用隐含的规则来重建,则它代表由隐含规则加入的第一个依赖文件。 |
$? | 所有比目标文件更新的依赖文件列表,空格分隔。如果目标文件时静态库文件,代表的是库文件(.o 文件)。 |
$^ | 代表的是所有依赖文件列表,使用空格分隔。如果目标是静态库文件,它所代表的只能是所有的库成员(.o 文件)名。一个文件可重复的出现在目标的依赖中变量“ ” 只 记 录 它 的 第 一 次 引 用 的 情 况 。 就 是 说 变 量 “ ^”只记录它的第一次引用的情况。就是说变量“ ”只记录它的第一次引用的情况。就是说变量“^”会去掉重复的依赖文件。 |
$+ | 类似“$^”,但是它保留了依赖文件中重复出现的文件。主要用在程序链接时库的交叉引用场合。 |
$* | 在模式规则和静态模式规则中,代表“茎”。“茎”是目标模式中“%”所代表的部分(当文件名中存在目录时,“茎”也包含目录部分)。 |
CURDIR := /home/zht # 记录当前路径
SHELL = /bin/sh
MAKEFILE_LIST := Makefile
.DEFAULT_GOAL := all
MAKEFLAGS = p
HOSTARCH := x86_64
CC = cc # C语言编译器的名称
CPP = $(CC) -E # C语言预处理器的名称 $(CC) -E
CXX = g++ # C++语言的编译器名称
RM = rm -f # 删除文件程序的名称
CFLAGS # C语言编译器的编译选项,无默认值
CPPFLAGS # C语言预处理器的编译选项,无默认值
CXXFLAGS # C++语言编译器的编译选项,无默认值
......
模式规则类似于普通规则,只是在模式规则中,目标名中需要包含有模式字符“%”(一个),包含有模式字符“%”的目标被用来匹配一个文件名,“%”可以匹配任何非空字符串。规则的依赖文件中同样可以使用“%”,依赖文件中模式字符“%”的取值情况由目标中的“%”来决定。
例如:对于模式规则“%.o : %.c”,它表示的含义是:所有的.o文件依赖于对应的.c文件。我们可以使用模式规则来定义隐含规则。要注意的是:模式字符“%”的匹配和替换发生在规则中所有变量和函数引用展开之后,变量和函数的展开一般发生在make读取Makefile时,而模式规则中的“%”的匹配和替换则发生在make执行时。
Makefile中存在一种伪目标,我们生成了许多文件编译文件,我们也应该提供一个清除它们的“目标”以备完整地重编译而用。 (以“make clean”来使用该目标) ,调用相应的规则,来清除许多编译的文件(如:*.o文件)因为,我们并不生成“clean”这个文件。“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显示地指明这个“目标”才能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。
当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。
.PHONY : clean
只要有这个声明,不管是否有“clean”文件,要运行“clean”这个目标,只有“make clean”这样。于是整个过程可以这样写:
.PHONY: clean
clean:
rm *.o temp
伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”,只要将其放在第一个。示例,如果你的Makefile需要一口气生成若干个可执行文件,但你只想简单地敲一个make,并且,所有的目标文件都写在一个Makefile中,那么你可以使用“伪目标”这个特性:
all : prog1 prog2 prog3
.PHONY : all
prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o
prog2 : prog2.o
cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o
动态链接库:在编译链接时,没有把库文件的代码加入到可执行文件中,在程序执行时由链接文件加载库文件;
Windows静态库:xxx.lib Windows动态库:xxx.dll
linux静态库:libxxx.a linux动态库:libxxx.so
参数:
-fPIC 产生位置无关的代码
-shared 共享
-l(小写L) 指定动态库
-I(大写i) 指定头文件目录,默认当前目录
-L 手动指定库文件搜索目录,默认只链接共享目录
├── dynamic_lib.c
└── dynamic_lib.h
dynamic_lib.c文件:
#include "dynamic_lib.h"
int add(int a, int b)
{
int c;
c = a + b;
return c ;
}
dynamic_lib.h文件:
#pragma onece
int add(int a, int b);
生成动态库文件:
gcc -fPIC -shared dynamic_lib.c -o libdynamic_lib.so
编写一个新代码调用动态库:
#include "dynamic_lib.h"
int main()
{
int a = 0, b = 2, c;
c = add(a, b);
return 0;
}
编译调用:gcc lib_test.c -o lib_test -L ../dynamic_lib/ -ldynamic_lib -I ../dynamic_lib/
注意:指定动态库时要去掉头尾
├── static_lib.c
└── static_lib.h
static_lib.c代码:
#include "static_lib.h"
int add(int a, int b)
{
int c;
c = a + b;
return c ;
}
static_lib.h代码:
#pragma onece
int add(int a, int b);
编译过程分两步,先生成.o文件,然后根据.o文件归档为.a静态库文件
gcc -c static_lib.c
然后归档为静态库文件,这样就生成了静态库libstatic_lib.a
ar crv libstatic_lib.a static_lib.o
ar命令的用法参考:跳转地址
测试代码lib_test.c:
#include "static_lib.h"
int main()
{
int a = 0, b = 2, c;
c = add(a, b);
return 0;
}
编译:gcc lib_test.c -o lib_test -L ../static_lib/ -lstatic_lib -I ../static_lib/