什么是makefile
如果您是一个Linux环境下开发的程序猿,那么使用GNU meke来管理自己的项目工程显得尤为重要。在Linex(Unix)环境下使用 GUN 的make工具可以较容易地构建一个自己的工程,而该工程的编译可以通过一个命令就可完成项目的编译、连接直到最后的执行,但这需要投入一定的时间去完成一个或多个Makefile文件的编写。makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极 大的提高了软件开发的效率也就是说Makefile是一个文件,make是一个命令工具,他解释了Makefile中的指令,在Makefile文件中描述了整个工程所有文件的编译顺序和规则,其有自己的书写格式、关键字和函数。
关于程序的编译过程可参考:Linux下gcc编译过程
语法规则
目标文件列表 分隔符 依赖文件列表
[TAB][命令]
[TAB][命令]
····
eg:
target: depend1 depend2 depend3···
action1
action2
target1:
action1
action2
有以下几点需要注意:
- 一个完整的makefile文件由:显式规则(根据规则写出来的命令)、隐式规则(需要判断的逻辑命令)、使用变量、文件指示、注释五部分构成;
- 若命令行单独成一行,则开头必须为Tab键;
- 通常将makefile文件中的第一行目标文件作为最终目标文件;
- 命令行的Tab键后可增加
+、-、@
符号在接命令:
+:代表本行命令始终被执行;
-:代表本行命令若遇到错误继续执行而不退出;
@:代表本行命令执行但不打印该条命令内容。
通过下面的小例子简单了解下makefile编写:
首先编写test.c:
#include <stdio.h>
int main()
{
printf(" 2 + 6 = %d\n", add(2, 6));
printf(" 6 - 2 = %d\n", sub(6, 2));
return 1;
}
然后分别编写add.c和sub.c:
#include "test.h"
int add(int a, int b)
{
return a + b;
}
#include "test.h"
int sub(int a, int b)
{
return a - b;
}
最后编写test.h:
#ifndef _TEST_H
#define _TEST_H
int add(int a, int b);
int sub(int a, int b);
#endif
通过gcc编译过程我们可知道首先要将三个c文件通过gcc -c xxx
命令编译程.o
文件再链接成为可执行文件,因此我们的makefile可这样编写:
#以'#'开头的行表示注释
#这里的test为整个makefile文件的第一个目标,当输入make命令时会完成该目标;
#该目标有三个依赖:add.o sub.o test.o,和一个动作make clean.
#若想执行完最终目标,则要先执行依赖目标,可以理解为递归的过程。
test: add.o sub.o test.o
gcc -o test add.o sub.o test.o
make clean
#在下面的动作中用 @ 符号来让make命令执行但补打印该命令,之输出结果;make命令执行过程中,会多次载入makefile文件。
add.o: add.c test.h
@gcc -c add.c
sub.o: sub.c test.h
@gcc -c sub.c
test.o:
@gcc -c test.c
#该目标被称为伪目标,其不为最终目标生成的依赖,但可通过最终目标动作而执行;
#用来删除所有以.o文件结尾的文件,通过make clean来调用
clean:
rm -rf *.o
运行结果如下:
panghu@Ubuntu-14:~/makefile$ ls
add.c makefile sub.c test.c test.h
panghu@Ubuntu-14:~/makefile$ make
gcc -o test add.o sub.o test.o
make clean
make[1]: 正在进入目录 `/home/panghu/makefile'
rm -rf *.o
make[1]:正在离开目录 `/home/panghu/makefile'
panghu@Ubuntu-14:~/makefile$ ls
add.c makefile sub.c test test.c test.h
panghu@Ubuntu-14:~/makefile$ ./test
2 + 6 = 8
6 - 2 = 4
Make选项:
- -k(常用):使make命令发现错误仍然执行而不是退出,用过该选项可发现未编译成功的源文件;
- -n(常用):让make输出将要执行的操作步骤,但并不真正执行;
- -f(常用):将文件名为filename的文件作为makefile文件,否则make将会在当前目录下寻找makefile或Makefile文件。
- -d:打印所有的调试信息;
- -i :忽略执行过程中产生的错误;
- -t :把所有目标文件的最后修改时间设置为当前系统时间;
- -q :不执行任何命令,只返回一个查询状态,0:没有目标需要重建 1:存在需要重建的目标 2:错误发生。
使用变量
makefile中的变量可理解为c/c++中的宏定义,通常起到一种替换的作用,为了看起来方便些在使用时我们会在变量名前加上“$”
符号,最好用小括号或者大括号括起来,而我们如果需要用到$
符号则需要用$$
来表示,定义变量的形式:变量名 赋值符 变量值
经常用到的有如下三种形式:
#定义变量VAR,强制赋值为test
VAR=$(test)
#在VAR之前定义的值后面再追加app这个值,这时该变量值扩展为testapp
VAR+=$(app)
#如果之前VAR没有被定义,则定义并使用testapp;否则使用之前的值。
VAR?=&(testapp)
下面这个例子:
foo = $(bar)
bar = $(ugh)
ugh = hello
all:
echo $(foo)
我们执行make all
会打印出hello
由此可见变量可使用后面的变量来定义,这可能会使make陷入循环中,因此我们引入另一种操作:=
用来避免使用后面的变量定义:
x := foo
y := $(x) bar
x := later
等价于:
y := foo bar
x := later
下面列出一些系统内的预定义变量:
宏名 | 初始值 | 说明 |
---|---|---|
CFLAGS | -o | 编译器使用的选项 |
CC | cc | 默认使用的编译器 |
MAKE | make | make命令 |
PWD | 运行make命令时的当前目录 | |
AR | ar | 库管理命令 |
ARFLADS | -ruv | 库管理命令选项 |
LIBSUFFIXE | .a | 库后缀 |
A | a | 库扩展名 |
条件判断:
通过ifeq
条件判断来让make根据不同情况执行不同的分支,表达式可以为变量的值或者变量与常量的比较例如:
libs_for_gcc = -lgnu
normal_libs =
foo: $(test)
ifeq ($(CC),gcc)
$(CC) -o foo $(test) $(libs_for_gcc)
else
$(CC) -o foo $(test) $(normal_libs)
endif
巨人的肩膀:Makefile经典教程(掌握这些足够)