前言:编译大的工程会花费很长的时间,这个章节需要实际练习,多多练习即可,复杂但不难。
1 makefile简介
1、工程管理器,顾名思义,是指管理较多的文件。
2、Make工程管理器也就是个“自动编译管理器”,这里的“自动”是指它能够根据文件时间戳自动发现更新过的文件而减少编译的工作量,同时,它通过读入Makefile文件的内容来执行大量。
3、作用
Make将只编译改动的代码文件,而不用完全编译。
根据文件时间戳自动发现更新过的文件。
读入Makefile 文件编辑内容来执行编译工作。
2 makefile基本结构
Makefile是Make读入的唯一配置文件
2.1 Makefile格式
target:dependency_files
<TAB>command
例如:
hello.o:hello.c hello.h
gcc –c hello.c –o hello.o
- target代表生成什么?由make工具创建的目标体(target),通常是目标文件或可执行文件
- dependency_file代表由谁生成?要创建的目标体所依赖的文件(dependency_file)
- command代表怎么生成的?创建每个目标体时需要运行的命令(command)
- 注意:命令行前面必须是一个”TAB键”,否则编译错误为:*** missing separator. Stop.
2.2 Makefile案例演示
创建f1.c f2.c main.c head.h文件,创建makefile文件,分别在文件中添加内容:
f1.c
#include<stdio.h>
void print1(){
printf("this is print1\n");
}
f2.c
#include<stdio.h>
void print2(){
printf("this is print2\n");
}
head.h
void print1();
void print2();
makefile
test:f1.o f2.o main.o
gcc f1.o f2.o main.o -o test
f1.o:f1.c
gcc -c f1.c -o f1.o
f2.o:f2.c
gcc -c f2.c -o f2.o
main.o:main.c
gcc -c main.c -o main.o
.PHONY:clean
clean:
rm *.o test
伪目标的作用:.PHONY是一个伪目标,可以防止在Makefile中定义的只执行命令的目标和工作目录下的实际文件出现名字冲突,另一种是提交执行makefile时的效率。
执行编译命令:make(后面会讲具体make使用)
删除.o文件命令:make clean
最后看运行结果
注意:关于gcc的命令,点击gcc基本用法跳转查看相关章节gcc基本用法
3 makefile创建和使用
3.1 创建变量的目的:用来代替一个文本字符串
在使用makefile文件编写命令时,不少命令不够直观,不能一下读懂语义,使用变量的目的就是使makefile编写更流畅,更易懂,可复制到其他项目中去。包括:
- 系列文件的名字 f1.c f2.c
- 传递给编译器的参数 -o -c
- 需要运行的程序
- 需要查找源代码的目录
- 你需要输出信息的目录
- 你想做的其它事情。
3.2 自定义变量
VAR = a 变量在声明时需要给予初值(递归展开方式)
$(x)或${x} 取值
变量使用$(VAR),用”$”则用”$$”来表示、类似于编程语言中的宏。
3.3 变量的赋值
(1)递归展开方式VAR=a讲解(继续向后引用变量,直到不能引用为止)
缺点:不能对该变量进行循环扩展,例如 CFLAGS = $(CFLAGS) -O 会造成死循环
例:请问foo的值为?
foo = $(bar)
bar = $(ugh)
ugh = Huh?
test2:
@echo $(foo)
答:代码复制到makefile进行make编译。foo去找bar,bar去找ugh,ugh=Huh?,所以结果:
(2)简单方式VAR:=var讲解(只引用一次)
用这种方式定义的变量,会在变量的定义点,按照被引用的变量的当前值进行展开。这种定义变量的方式更适合在大的编程项目中使用,因为它更像我们一般的编程语言。
例:请问x、y的值为?
m:= mm
x:= $(m)
y:= $(x) bar
x:= later
test2:
@echo $(x)$(y)
答:第一个x是mm,第二个x是later。结果:
(3)用?=定义变量(如果被重复定义过,那么什么都不做)
例:FOO的值是?
dir:= /foo/bar
FOO?= bar
test:
@echo $(FOO)
答:空。含义是,如果FOO没有被定义过,那么变量FOO的值就是“bar”,如果FOO先前被定义过,那么这条语将什么也不做。结果:
如果上述代码改成如下,输出结果为abc。
dir:= /foo/bar
FOO:= abc
FOO?= bar
test:
@echo $(FOO)
(4)用+=为变量添加值
你可以通过+=为已定义的变量添加新的值
main=hello.o hello-1.o
main+=hello-2.o
test:
@echo $(main)
输出结果为:hello.o hello-1.o hello-2.o
(5)预定义变量(隐含变量)
AR 库文件维护程序的名称,默认值为ar。
AS 汇编程序的名称,默认值为as。
CC C编译器的名称,默认值为cc。
CPP C预编译器的名称,默认值为$(CC) –E。
CXX C++编译器的名称,默认值为g++。
FC FORTRAN编译器的名称,默认值为f77
RM 文件删除程序的名称,默认值为rm -f
ARFLAGS 库文件维护程序的选项,无默认值。
ASFLAGS 汇编程序的选项,无默认值。
CFLAGS C编译器的选项,无默认值。
LDFLAGS 链接器的选项,无默认值
CPPFLAGS C预编译的选项,无默认值。
CXXFLAGS C++编译器的选项,无默认值。
FFLAGS FORTRAN编译器的选项,无默认值。
通过echo命令可以进行查看打印,上述命令已在前面的例子中演示过。
test:
@echo $(AR)
3.4 自动变量
$* 不包含扩展名的目标文件名称 //hello.o 可以用$*.c 代替目标文件
$+= 所有的依赖文件,以空格分开,并以出现的先后为序,可能 包含重复的依赖文件
$< 第一个依赖文件的名称 //test:f1.o f2.o main.o $<代表f1.o
$? 所有时间戳比目标文件晚的的依赖文件,并以空格分开
$@ 目标文件的完整名称 //相当于test
$^ 所有不重复的目标依赖文件,以空格分开 //相当于f1.o f2.o main.o
$% 如果目标是归档成员,则该变量表示目标的归档成员名称
根据上文案例利用自动变量修改makefile:
OBJS = f1.o f2.o main.o
CC = gcc
CFLAGS = -c
test:$(OBJS)
$(CC) $^ -o $@
f1.o:f1.c
$(CC) $(CFLAGS) $< -o $@
f2.o:f2.c
$(CC) $(CFLAGS) $< -o $@
main.o:main.c
$(CC) $(CFLAGS) $< -o $@
.PHONY:clean
clean:
rm *.o test
(7)环境变量
- make在启动时会自动读取系统当前已经定义了的环境变量,并且会创建与之具有相同名称和数值的变量
- 如果用户在Makefile中定义了相同名称的变量,那么用户自定义变量将会覆盖同名的环境变量
3、Make使用
(1)直接运行make及命令参数选项
- -C dir读入指定目录下的Makefile
- -f file读入当前目录下的file文件作为Makefile //make -f file1
- -i忽略所有的命令执行错误 //通常碰到错误会暂停,时间比较长就可以忽略错误让其他文件继续编译,后面再去编译看哪个文件有错
- -I dir指定被包含的Makefile所在目录
- -n只打印要执行的命令,但不执行这些命令 //make -n打印不执行
- -p显示make变量数据库和隐含规则
- -s在执行命令时不显示命令
- -w如果make在执行过程中改变目录,打印当前目录名
(2)makefile的遗憾规则
隐含规则1:编译C程序的隐含规则
“<n>.o”的目标依赖目标会自动推导为“<n>.c”并且其生成命令是
$(CC) -c $(CPPFLAGS) $(CFLAGS)
隐含规则2:链接Object恩建的隐含规则
“<n>”目标依赖于“<n>.o”,通过运行C的编译器来运行链接程序生成(一般是“ld”),其生成命令是:
$(CC) $(LDFLAGS) <n>.o
隐含规则3:$(LOADLIBES) $(LDLIBS)
这个规则对于只有一个源文件的工程有效,同时也对多个Object文件(由不同的源文件生成)的也有效。
如下规则:
x:x.o y.o z.o并且“x.c”、“y.c”和“z.c”都存在时,隐含规则将执行如下命令:
cc -c x.c -o x.o
cc -c y.c -o y.o
cc -c z.c -o z.o
cc x.o y.o z.o -o x
如果没有一个源文件(如上例中的x.c)和你的目标名字(如上例中的x)相关联,那么,你最好写出自己的生成规则,不然,隐含规则会报错的。
规则1举例,还是上面的例子:
OBJS = f1.o f2.o main.o
CC = gcc
CFLAGS = -c
test:$(OBJS)
$(CC) $^ -o $@
f1.o:f1.c
# $(CC) $(CFLAGS) $< -o $@ //#相当于注释掉了,去掉3天命令, make也不会报错
f2.o:f2.c
# $(CC) $(CFLAGS) $< -o $@
main.o:main.c
# $(CC) $(CFLAGS) $< -o $@
.PHONY:clean
clean:
rm *.o test
规则3举例,还是上面的例子:
OBJS = f1.o f2.o main.o
CC = gcc
CFLAGS = -c
f1:f1.o f2.o main.o
# <n>.o的目标依赖目标会自动推导为“<n>.c”并且其生成命令是$(CC) -c $(CPPFLAGS) $(CFLAGS)
# 相当于cc x.o y.o z.o -o x
.PHONY:clean
clean:
rm *.o test
示例
//1
#目标:依赖
# 命令
#一步编译.c->exe
#test:add.c sub.c test.c
# gcc add.c sub.c test.c -o test
#两步编译.c->.o->exe
test:add.o sub.o test.o
echo $(LDFLAGS)
gcc add.o sub.o test.o -o test
add.o:add.c
gcc -c add.c -o add.o
sub.o:sub.c
gcc -c sub.c -o sub.o
test.o:test.c
gcc -c test.c -o test.o
#伪目标
.PHONY:clean
clean:
rm *.o
//2
#变量的定义和赋值
#SRC = add.o sub.o test.o
SRC = add.o
SRC += sub.o
SRC += test.o
#两步编译.c->.o->exe
test:$(SRC)
gcc ${SRC} -o test
add.o:add.c
gcc -c add.c -o add.o
sub.o:sub.c
gcc -c sub.c -o sub.o
test.o:test.c
gcc -c test.c -o test.o
#伪目标
.PHONY:clean
clean:
rm *.o
//3
#自动变量
#SRC = add.o sub.o test.o
SRC = add.o
SRC += sub.o
SRC += test.o
#两步编译.c->.o->exe
test:$(SRC)
gcc ${SRC} -o $@
#@echo $$+ = $+
#@echo $^
#@echo $<
add.o:add.c
gcc -c $^ -o $@
#echo $*
#echo $@
sub.o:sub.c
gcc -c $^ -o $@
test.o:test.c
gcc -c $^ -o $@
#伪目标
.PHONY:clean
clean:
rm *.o
//4
#隐含变量
#SRC = add.o sub.o test.o
SRC = add.o
SRC += sub.o
SRC += test.o
CC = gcc
#CC = arm-linux-gcc 你的环境如果没有该编译器,会报它找不到
CFLAGS = -c -g -Wall -I /home/linux/Makefile/include/
#两步编译.c->.o->exe
test:$(SRC)
$(CC) ${SRC} -o $@
%.o:%.c
$(CC) $(CFLAGS) $^ -o $@
#伪目标
.PHONY:clean
clean:
$(RM) *.o
//5
all:$(OBJ)
$(OBJDIR)/%.o:%.c
$(CC) $(CFLAGS) $^ -o $@
(3)VPATH的用法
VPATH:虚路径
- 在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在不同的目录中。所以,当make需要去寻找文件的依赖关系,你可以在文件前加上路径,但最好的方法是把一个路径告诉make,让make在自动去找。
- Makefile文件中的特殊变量“VPTH”就是完成这个功能的,如果没有指明这个变量,make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么make就会在当前目录找不到的情况下,到所指定的目录中去找寻文件了。
- VPATH = src:../headers
- 上面的定义指定两个目录,“src”和“../headers”,make会按照这个顺序进行搜索。目录由“冒号”分隔。当然,当目前目录永远是最高的优先搜索的地方
举例,还是上面的例子,我们建立文件路径,把之前的.c和.h放入路径内
老方法执行
CC = gcc
CFLAGS = -c -I include #-I include包含head.h的路径
f1:src1/f1.o src2/f2.o main/main.o #这是写路径的方式
.PHONY:clean
clean:
find ./ -name "*.o" -exec rm {} \;;rm f1
VPATH执行(效率更高)
CC = gcc
CFLAGS = -c -I include
VPATH=src1:src2:main
f1:f1.o f2.o main.o
.PHONY:clean
clean:
find ./ -name "*.o" -exec rm {} \;;rm f1
# 正则表达式:找一下当前目录下哪里有.o,然后执行一下rm,然后把.o删除,然后把f1删除
(4)嵌套的Makefaile总结案例
案例:我们沿用之前的例子,这次在f1、f2、include、main、obj文件夹下,多了一个makefile文件
目录:
#f1 makefile
../$(OBJS_DIR)/f1.o:f1.c
$(CC) -c $^ -o $@
#f2 makefile
../$(OBJS_DIR)/f2.o:f2.c
$(CC) -c $^ -o $@
#main makefile
../$(OBJS_DIR)/main.o:main.c
$(CC) -c $^ -o $@
#obj makefile
../$(BIN_DIR)/$(BIN):$(OBJS)
$(CC) -o $@ $^
#根目录下的makefile
CC = gcc
SUBDIRS=f1 \
f2 \
main \
obj
OBJS = f1.o f2.o main.o
BIN=myapp
OBJS_DIR=obj
BIN_DIR=bin
export CC OBJS BIN OBJS_DIR BIN_DIR
#export是把上面用到的变量,传给下面子目录makefile使用
all:CHECK_DIR $(SUBDIRS) #
CHECK_DIR:
mkdir -p $(BIN_DIR) #生成bin目录
$(SUBDIRS):ECHO
make -C $@ #执行指定目录下的makefile
ECHO:
@echo $(SUBDIRS) #打印
@echo begin compile
clean:
@$(RM) $(OBJS_DIR)/*.o #把OBJS_DIR下的.o删除
@rm -rf $(BIN_DIR) #把mayapp删除
最终效果: