C实战:项目构建Make,Automake,CMake
在本系列文章《C实战:强大的程序调试工具GDB》中我们简要学习了流行的调试工具GDB的使用方法。本文继续“C实战”的主题,对同样非常流行的构建工具Make的用法和原理一探究竟,并顺便看一下一些高级衍生产品。
1.Make基础
首先我们编写一个简单的C项目,以此项目在实战中学习Make的相关知识。更全面的介绍请参考官方手册。
cdai@vm /syspace/2-ccpp/24-pragmatic/build-tool/make $ tree
.
├── hello.c
├── hello.h
├── main.c
└── Makefile
0 directories, 4 files
整个程序的逻辑非常简单:main.c中包含一个main方法,调用了hello.c中的sayHello()函数,打印了一句话到控制台上。
// cat main.c hello.h hello.c
// ----- main.c -----
#include "hello.h"
int main(void)
{
sayHello("Make");
return 1;
}
// ----- hello.h -----
#ifndef _HELLO_H_
#define _HELLO_H_
void sayHello(char *name);
#endif
// ----- hello.c -----
#include "hello.h"
#include <stdio.h>
void sayHello(char *name)
{
printf("Hello, %s!\n", name);
}
1.1 基本语法
一个简单的Makefile包含很多规则(Rule),每一条规则的语法结构由目标(Target)、先决条件(Prerequisite)、动作(Recipe)三部分组成:
- 目标:通常有两种命名方法,一是与要生成的可执行文件或目标文件同名,二是说明动作的目的,例如最常见的clean清理规则。对于第二种规则命名,为了避免与同名文件冲突,可以将目标名加入到
.PHONY
伪目标列表中。默认情况下,make执行Makefile中的第一个规则,此规则被称为最终目标 - 先决条件:先决条件是用来创建目标的输入文件,一个目标可以依赖多个先决条件
- 动作:动作由Make命令负责执行,可以包含多个命令,每个命令可以另起一行。一定要注意的是:命令必须以
TAB
开头!
target: prerequisite
recipe
下面就看一下示例项目的Makefile是什么样子的。在Makefile中有3个规则,其中目标main依赖于main.o和hello.o,利用gcc执行链接,这与我们的代码结构是相对应的。而main.o和hello.o则分别依赖于各自的源代码.c文件和hello.h头文件,并利用gcc -c执行编译。
main: main.o hello.o
gcc -o main main.o hello.o
main.o: main.c hello.h
gcc -c main.c -o main.o
hello.o: hello.c hello.h
gcc -c hello.c -o hello.o
1.2 实现原理
Make看似非常智能,其实它的原理就像其语法规则一样简单。
- 确定目标:如果没有指明,则执行最终目标,即第一个规则的目标
- 处理变量和规则:替换变量,推导隐式规则(下一节会学习)
- 生成依赖关系链:为所有目标生成依赖关系链
- 递归构建:从依赖链的底部向上,根据先决条件会有三种情况:
4.1 先决条件不存在,则执行规则中的命令
4.2 先决条件存在,且至少一个比目标“更新”,则执行规则中的命令重新生成
4.3 先决条件存在,且都比目标“更旧”, 则什么都不做
了解了Make的原理,就看一下我们的示例项目Make的执行过程。可以看到,Make以第一个目标main作为构建目标,从关系链底部的main.o和hello.o开始构建,最终生成了可执行文件main。接下来就执行main,可以看到控制台输出了”Hello, Make!”,证明我们构建成功了!
cdai@vm /syspace/2-ccpp/24-pragmatic/build-tool/make $ make
gcc -c main.c -o main.o
gcc -c hello.c -o hello.o
gcc -o main main.o hello.o
cdai@vm /syspace/2-ccpp/