文章目录
- 参考资料
- make 如何工作
- Makefile 规则
- 练习
- 如果文件并不多,可以手写Makefile,简单省事
- 多文件目录下makefile文件递归执行编译所有c文件
- 引用其它 Makefile?
- $@、$< 等自动变量的使用?
- wildcard 的使用?
参考资料
gcc和Makefile,多文件编译神器是怎么练成的,解放程序员的双手就这么简单跟我一起写Makefile
Makefile:一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为 makefile就像一个Shell脚本一样,也可以执行操作系统的命令。
make 如何工作
- make 命令会寻找当前目录下的 Makefile 文件;(默认的情况下,make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件,找到了解释这个文件,如果要指定特定的Makefile,你可以使用make的 -f 和 --file 参数,如:
make -f Make.Linux
或make --file Make.AIX
) - 确定目标文件,即将文件中的第一个目标文件确定为最终目标文件;
- 如果目标文件不存在,或是依赖的文件修改时间比目标文件新,那么就会执行 cmd 来生成目标文件,可以仅仅针对部分修改的文件进行编译;
- 如果目标文件所依赖的文件不存在,那么就会寻找依赖文件的依赖文件来生成依赖文件,递归;
在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。
GNU的make工作时的执行步骤如下:(想来其它的make也是类似)
- 读入所有的Makefile。
- 读入被include的其它Makefile。
- 初始化文件中的变量。
- 推导隐晦规则,并分析所有规则。
- 为所有的目标文件创建依赖关系链。
- 根据依赖关系,决定哪些目标要重新生成。
- 执行生成命令。
make会以UNIX的标准Shell,也就是 /bin/sh 来执行命令
Makefile 规则
Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
- 显式规则。显式规则说明了如何生成一个或多个目标文件。这是由Makefile的书写者明显指出要生成的文件、文件的依赖文件和生成的命令。
- 隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较简略地书写 Makefile,这是由make所支持的。
- 变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点像你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。
- 文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。
- 注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用 # 字符,这个就像C/C++中的 // 一样。如果你要在你的Makefile中使用 # 字符,可以用反斜杠进行转义,如: # 。
最后,还值得一提的是,在Makefile中的命令,必须要以 Tab 键开始。
文件指示
在Makefile使用 include 关键字可以把别的Makefile包含进来,这很像C语言的 #include ,被包含的文件会原模原样的放在当前文件的包含位置。
include <filename>
在 include 前面可以有一些空字符,但是绝不能是 Tab 键开始。 include 和 可以用一个或多个空格隔开。
include foo.make *.mk $(bar)
make命令开始时,会找寻 include 所指出的其它Makefile,并把其内容安置在当前的位置。就好像C/C++的 #include 指令一样。如果文件都没有指定绝对路径或是相对路径的话,make会在当前目录下首先寻找,如果当前目录下没有找到,那么,make还会在下面的几个目录下找:
- 如果make执行时,有 -I 或 --include-dir 参数,那么make就会在这个参数所指定的目录下去寻找。
- 如果目录 /include (一般是: /usr/local/bin 或 /usr/include )存在的话,make也会去找。
如果你想让make不理那些无法读取的文件,而继续执行,你可以在include前加一个减号“-”。如:
-include <filename>
- # : 注释
- 显式规则
目标文件:依赖文件
[TAB]指令 - 第一个文件是最终结果,递归
- 伪目标:.PHONY:
target ... : prerequisites ...
command
...
...
# prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。
# Makefile
hello:hello.o # 链接
gcc hello.o -o hello
hello.o:hello.S # 汇编
gcc -c hello.S -o hello.o
hello.S:hello.i # 编译
gcc -S hello.i -o hello.S
helo.i:hello.c # 预处理
gcc -E hello.c -o hello.i
.PHONY: # 固定格式
clean: # 自定义,label。其冒号后什么也没有,那么,make就不会自动去找它的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make命令后明显得指出这个label的名字。
rm -rf hello.o hello.S hello.i hello
cleanAll:
rm -rf hello.o hello.S hello.i hello.c hello
make clear
# circle.c circle.h cube.c cube.h main.c main.h
test:circle.o cube.o main.o
gcc circle.o cube.o main.o test
circle.o:circle.c
gcc -c circle.c -o circle.o
cube.o:cube.c
gcc -c cube.c -o cube.o
main.o:main.c
gcc -c main.c -o main.o
.PHONY:
cleanall:
rm -rf circle.o cube.o main.o test
clean:
rm -rf circle.o cube.o main.o
宏定义,变量
= 替换 += 追加 := 恒等于
TAR = test
OBJ = circle.o cube.o main.o
CC := gcc
$(TAR):$(OBJ)
$(CC) $(OBJ) -o $(TAR)
circle.o:circle.c
$(CC) -c circle.c -o circle.o
cube.o:cube.c
$(CC) -c cube.c -o cube.o
main.o:main.c
$(CC) -c main.c -o main.o
.PHONY:
cleanall:
rm -rf $(OBJ) $(TAR)
clean:
rm -rf $(OBJ)
隐含规则
%.c %.o 任意的 .c 或者 .o,*.c *.o 所有的 .c .o
只要make看到一个 .o 文件,它就会自动的把 .c 文件加在依赖关系中,如果make找到一个 whatever.o ,那么 whatever.c 就会是 whatever.o 的依赖文件。并且 cc -c whatever.c 也会被推导出来,于是,我们的makefile再也不用写得这么复杂。
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -o edit $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h
.PHONY : clean
clean :
rm edit $(objects)
TAR = test
OBJ = circle.o cube.o main.o
CC := gcc
$(TAR):$(OBJ)
$(CC) $(OBJ) -o $(TAR)
%.o:%.c
$(CC) -c %.c -o %.o
.PHONY:
cleanall:
rm -rf $(OBJ) $(TAR)
clean:
rm -rf $(OBJ)
通配符
- $^ 所有依赖文件名称
- $+ 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件
- $< 第一个依赖文件的名称
- $? 所有时间戳比目标文件晚的依赖文件,并以空格分开
- $@ 所有目标文件
TAR = test
OBJ = circle.o cube.o main.o
CC := gcc
RMRF := rm -rf
$(TAR):$(OBJ)
$(CC) $^ -o $@
%.o:%.c
$(CC) -c %.c -o %.o
.PHONY:
cleanall:
$(RMRF) $(OBJ) $(TAR)
clean:
$(RMRF) $(OBJ)
练习
jiaming@jiaming-VirtualBox:~/Documents/make_project$ tree
.
├── ExAdd.c
├── ExDiv.c
├── ExHeader.h
├── ExMul.c
├── ExSub.c
├── main.c
└── Makefile
0 directories, 7 files
jiaming@jiaming-VirtualBox:~/Documents/make_project$ cat main.c
#include <stdio.h>
#include "ExHeader.h"
int main(void)
{
double a = 12;
double b = 2;
double valAdd = add(a, b);
double valSub = sub(a, b);
double valMul = mul(a, b);
double valDiv = div(a, b);
printf("a=%.4f\tb=%.4f\nvalAdd=%.4f\nvalSub=%.4f\nvalMul=%.4f\nvalDiv=%.4f\n", a, b, valAdd, valSub, valMul, valDiv);
return 0;
}
jiaming@jiaming-VirtualBox:~/Documents/make_project$ cat ExHeader.h
//防止同一个 .c 多次引用某个文件
#ifndef ExHeader_h
#define ExHeader_h
double add(double a, double b);
double sub(double a, double b);
double mul(double a, double b);
double div(double a, double b);
#endif
jiaming@jiaming-VirtualBox:~/Documents/make_project$ cat ExAdd.c
double add(double a, double b)
{
return a + b;
}
jiaming@jiaming-VirtualBox:~/Documents/make_project$ cat Makefile
TAR = out
OBJ = main.o ExAdd.o ExDiv.o ExMul.o ExSub.o
CC := gcc
# link
$(TAR):$(OBJ)
$(CC) $(OBJ) -o $(TAR)
# compile
*.o:*.c
$(CC) -c *.c
.PHONY: # 声明伪目标
clean:
rm $(OBJ)
建立脚本,将当前文件内的所有 .c 变为可执行文件:
jiaming@ubuntu:~/Documents/C_project$ cat makefile
CFLAGS = -g -Wall -Werror
LDFLAGS = -lpthread
src = $(wildcard *.c)
target = $(patsubst %.c, %, ${src})
.PHONY: all clean
%:%.c
$(CC) ${CFLAGS} ${LDFLAGS} $^ -o $@
all: ${target}
clean:
rm -f ${target}
如果文件并不多,可以手写Makefile,简单省事
alinx@ubuntu:~/Documents/testQT/project3/APP$ tree .
.
├── include
│ ├── env.h
│ └── tools.h
├── main.c
├── Makefile
└── src
└── tools.c
2 directories, 5 files
alinx@ubuntu:~/Documents/testQT/project3/APP$ cat include/tools.h main.c src/tools.c
// tools.h
long long pow(int a, int x);
int add(int x, int y);
int multi(int x , int y);
// main.c
#include <stdio.h>
// comfirm include/ has same path with this file
#include "include/env.h"
#include "include/tools.h"
// replace #include "../include/tools.h"
// extern long long pow(int a, int x);
int main(void)
{
printf("%s", sayHello);
printf("%d %d\n", add(1, 2), multi(3, 4));
printf("%lld\n", pow(2, 6));
return 0;
}
// src/tools.c
#include "../include/tools.h"
long long pow(int a, int x)
{
long long out = 1;
while(x--)
{
out *= a;
}
return out;
}
int add(int x, int y)
{
return x + y;
}
int multi(int x , int y)
{
return x*y;
Makefile
CROSS_COMPILE = x86_64-linux-gnu-
DEL_FILE = rm -f
CC = $(CROSS_COMPILE)gcc
# -Wall : 允许发出 GCC 提供的所有有用的报警信息
# -O2 : “-On”优化等级
# -g : 在可执行程序中包含标准调试信息
# -I : 指定头文件路径(可多个)
CFLAGS := -Wall -O2 -g -c
BIN := APP
OBJ := src/tools.o main.o
.PHONY: all distclean clean
all:
# $(CC) $(CFLAGS) $^ -o $@
$(CC) $(CFLAGS) src/tools.c -o src/tools.o
$(CC) $(CFLAGS) main.c -o main.o
$(CC) main.o src/tools.o -o $(BIN)
distclean : clean
-$(DEL_FILE) $(BIN)
clean:
-$(DEL_FILE) $(OBJ)
多文件目录下makefile文件递归执行编译所有c文件