makefile总结
Makefile
学习视频:1、野火的基础入门篇-第32讲 Makefile三要素_哔哩哔哩_bilibili
2、b站视频04 一个稍复杂的Makefile_哔哩哔哩_bilibili
学习资料:第2个视频对应的Make/make.md · 无限十三年/CPP - 码云 - 开源中国
ch0_Makefile简介
Makefile是什么?
通过make工具和Makefile去管理需要编译的文件
make和Makefile是什么关系?
- make工具:找出修改过的文化,根据依赖关系,找出受影响的相关文件,最后按照规则单独编译这些文件。
- Makeflie文件:记录依赖关系和编译规则
makefile总览
ch1_Makefile三要素
Makefile三要素是什么?
目标、依赖、命令
怎么描述三要素?
目标:依赖的文件或者是其他目标
命令1
命令2
…
命令可以多行,但是前面必须加tap
字符
实验演示
targeta:targetb targetcecho "targeta"
targetb:echo "targetb"
targetc:echo "tarfetc"mzj@ubuntu:~/Makefile$ make
echo "targetb"
targetb
echo "targetc"
targetc
echo "targeta"
targeta
mzj@ubuntu:~/Makefile$ make targetb
echo "targetb"
targetb
mzj@ubuntu:~/Makefile$ make targetc
echo "targetc"
targetc
.PHONY:目标
需要注意当文件路径下有跟目标
相同的文件名时,想要make不被影响,应该在Makefile文件的开头加上.PHONY:目标名
原因:
.PHONY
后面跟的目标都被称为伪目标- 也就是说我们
make
命令后面跟的参数如果出现在.PHONY
定义的伪目标中,那就直接在Makefile
中就执行伪目标的依赖和命令。不管Makefile
同级目录下是否有该伪目标同名的文件,即使有也不会产生冲突。 - 另一个就是提高执行
makefile
时的效率。
.PHONY:targetb
targeta:targetb targetcecho "targeta"
targetb:echo "targetb"
targetc:echo "targetc"mzj@ubuntu:~/Makefile$ ls
Makefile targetb targetc
mzj@ubuntu:~/Makefile$ make targetc
make: “targetc”已是最新。
mzj@ubuntu:~/Makefile$ make targetb
echo "targetb"
targetb
ch2_Makefile变量
系统变量
Makefile_test文件
.PHONY:allall:echo "$(CC)"echo "$(AS)"echo "$(MAKE)"echo "$(MAKE)"mzj@ubuntu:~/Makefile$ make -f Makefile_test
echo "cc"
cc
echo "as"
as
echo "make"
make
echo "make"
make
这里的make -f 指定Makefile文件名字,针对这个这个文件,可能有很多Makefile_test
自定义变量
-
=
:延迟赋值,执行到这个变量时才赋值Makefile_test1文件 A = 123 B = $(A) A = 456.PHONY:all all:echo "$(B)"mzj@ubuntu:~/Makefile$ make -f Makefile_test1 Makefile_test1:7: *** 遗漏分隔符 (null)。 停止。 !!!!!!!这里是tap建被vscode换成4个空格了,需要设置!!!!!!! #这里是执行到$(B)时,B才执行B = $(A) 456 mzj@ubuntu:~/Makefile$ make -f Makefile_test1 echo "456" 456
-
:=
:为立即赋值,与常规思路一样A = 123 B := $(A) A = 456.PHONY:all all:echo "$(B)"mzj@ubuntu:~/Makefile$ make -f Makefile_test1 echo "123" 123
-
?=
:空赋值,只有变量为空时,赋值才有效,后面再赋值没有用,像是一次初始化A ?= 123 A ?= 456.PHONY:all all:echo "$(A)"mzj@ubuntu:~/Makefile$ make -f Makefile_test1 echo "123" 123
-
+=
:追加赋值,不覆盖前面的,在前面的值基础上在尾部继续追加一些值A ?= 123 A += 456.PHONY:all all:echo "$(A)"mzj@ubuntu:~/Makefile$ make -f Makefile_test1 echo "123 456" 123 456
自动化变量
$<
:第一个依赖文件first_tar
$^
:全部的依赖文件first_tar second_tar
$@
:目标all
all:first_tar second_tarecho "$<"echo "$^"echo "$@"
first_tar:
second_tar:mzj@ubuntu:~/Makefile$ make -f Makefile_test1
echo "first_tar"
first_tar
echo "first_tar second_tar"
first_tar second_tar
echo "all"
all
Makefile中还有其它自动化变量,此处仅列出方便以后使用到的时候进行查阅,见下表。
符号 | 意义 |
---|---|
$@ | 匹配目标文件 |
$% | 与 @ 类似,但 @类似,但 @类似,但%仅匹配“库”类型的目标文件 |
$< | 依赖中的第一个目标文件 |
$^ | 所有的依赖目标,如果依赖中有重复的,只保留一份 |
$+ | 所有的依赖目标,即使依赖中有重复的也原样保留 |
$? | 所有比目标要新的依赖目标 |
使用变量编译C文件
CC = gcc
TARGET=mp3
OBJS=main.o mp3.o$(TARGET):$(OBJS)$(CC) $^ -o $@main.o:$(CC) -c main.c -o main.o
mp3.o:$(CC) -c mp3.c -o mp3.o .PHONY:cleanclean:rm $(TARGET)
ch3_Makefile模式匹配和默认规则
-
%
:匹配任意多个非空字符.PHONY:all%:@echo "$@"@echo "$^"@echo "$<" # @ 符号的作用是抑制命令本身的输出,只显示命令执行的结果。mzj@ubuntu:~/Makefile$ make make: *** 无目标。 停止。 mzj@ubuntu:~/Makefile$ make 123 123
@
符号的作用是抑制命令本身的输出,只显示命令执行的结果。命令行输入给了
%
这个位置再看这个模板:这里的.c文件到.o文件的形式基本上一样的,就可以使用模式匹配
%
去代替CC = gcc TARGET=mp3 OBJS=main.o mp3.o$(TARGET):$(OBJS)$(CC) $^ -o $@main.o:main.c$(CC) -c main.c -o main.o mp3.o:mp3.c$(CC) -c mp3.c -o mp3.o ||| %.o:%.c$(CC) -c $< -o $@.PHONY:cleanclean:rm $(TARGET)
这里的
%.o
:包含了所有的.o
文件 -
默认规则:
.o
文件默认使用.c
文件进行编译,所以可以直接不管.o
文件,下面即可CC = gcc TARGET=mp3 OBJS=main.o mp3.o$(TARGET):$(OBJS)$(CC) $^ -o $@.PHONY:cleanclean:rm $(TARGET) *.o
-
默认规则还有很多:后续补充
ch4_Makefile条件分支
ifeq(var1,var2)...
else...
endififneq(var1,var2)...
else...
endif
ifeq
:两个条件相等
ifneq
:两个条件不相等里面需要加上
搭配?=
进行举例
ARCH ?= x86
ifeq($(ARCH),x86)CC = gcc
elseCC = arm-linux-gnueabihf-gcc
endif
TARGET=mp3
OBJS=main.o mp3.o$(TARGET):$(OBJS)$(CC) $^ -o $@.PHONY:cleanclean:rm $(TARGET) *.omzj@ubuntu:~/Makefile$ make
那么,CC = gcc
mzj@ubuntu:~/Makefile$ make ARCH=arm
由于空赋值,命令行中就给ARCH赋值了,CC = arm-linux-gnueabihf-gcc
ch5_Makefile常用函数
很多看官方手册
这里挑四个常用的
- patsubst
patsubst
作用,例如把一堆
.c
文件名转成.o
文件名有两种方法:
objects = foo.o bar.o baz.o
$(objects:.c=.o)
$(patsubst %.o,%.c,$(objects))
- 第一种看起来,更直观,更简洁
$(patsubst %.c,%.o,x.c.c bar.c)
把字串“x.c.c bar.c”中以.c 结尾的单词替换成以.o 结尾的字符。函数的返回结果是“x.c.o bar.o”
$(patsubst %.c, build_dir/%.o, hello_main.c )
#函数的输出为:
build_dir/hello_main.o
#执行如下函数
$(patsubst %.c, build_dir/%.o, hello_main.xxx )
#由于hello_main.xxx不符合匹配规则"%.c",所以函数没有输出
例如:
我们存在一个代表所有.o 文件的变量。定义为
objects = foo.o bar.o baz.o
为了得到这些.o 文件所对应的.c 源文件。我们可以使用以下两种方式的任意一个:
$(objects:.o=.c)$(patsubst %.o,%.c,$(objects))
notdir
作用,把路径去了,只保留文件名
示例:
$(notdir src/foo.c hacks)
foo.c hacks
返回值为:这里的src/
被去掉了,只留下foo.c hacks
。
wildcard
作用:列出当前目录下所有符合模式“PATTERN”格式的文件名。
“PATTERN”使用shell可识别的通配符,包括“?”(单字符)、“*”(多字符)等。
示例:
$(wildcard *.c)#在sources目录下有hello_func.c、hello_main.c、test.c文件
#执行如下函数
$(wildcard sources/*.c)
#函数的输出为:
sources/hello_func.c sources/hello_main.c sources/test.c
返回值为当前目录下所有.c
源文件列表
foreach
我们来看一个例子,实现了将变量“files”赋值为目录“dirs”下所有文件列表:
dirs := a b c d
files := $(foreach dir,$(dirs),$(wildcard $(dir)/*))
例子中,表达式第一次执行时将展开为
$(wildcard a/*)
;第二次执行时将展开为$(wildcard b/*)
;第三次展开为$(wildcard c/*)
;….;以此类推。所以此函数所实现的功能就和一下语句等价:
files := $(wildcard a/* b/* c/* d/*)
ch6_多级结构工程的Makefile
接下来我们使用上面三个函数修改我们的Makefile,以适应包含多级目录的工程,修改后的内容如下所示。
#定义变量
#ARCH默认为x86,使用gcc编译器,
#否则使用arm编译器
ARCH ?= x86
TARGET = hello_main#存放中间文件的路径
BUILD_DIR = build_$(ARCH)
#存放源文件的文件夹
SRC_DIR = sources
#存放头文件的文件夹
INC_DIR = includes .#源文件
SRCS = $(wildcard $(SRC_DIR)/*.c)
#目标文件(*.o)
OBJS = $(patsubst %.c, $(BUILD_DIR)/%.o, $(notdir $(SRCS)))
#头文件
DEPS = $(wildcard $(INC_DIR)/*.h)#指定头文件的路径
CFLAGS = $(patsubst %, -I%, $(INC_DIR))#根据输入的ARCH变量来选择编译器
#ARCH=x86,使用gcc
#ARCH=arm,使用arm-gcc
ifeq ($(ARCH),x86)
CC = gcc
else
CC = arm-linux-gnueabihf-gcc
endif#目标文件
$(BUILD_DIR)/$(TARGET): $(OBJS)$(CC) -o $@ $^ $(CFLAGS)#*.o文件的生成规则
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c $(DEPS)
#创建一个编译目录,用于存放过程文件
#命令前带“@”,表示不在终端上输出
@mkdir -p $(BUILD_DIR)
$(CC) -c -o $@ $< $(CFLAGS)#伪目标
.PHONY: clean cleanall
#按架构删除
clean:rm -rf $(BUILD_DIR)#全部删除
cleanall:rm -rf build_x86 build_arm
ch7_同一项目中有多个Makefile文件
包含其他makefile文件
使用include
指令可以读入其他makefile文件的内容,效果就如同在include的位置用对应的文件内容替换一样。
include mkf1 mkf2 # 可以引入多个文件,用空格隔开
include *.mk # 可以用通配符,表示引入所有以.mk结尾的文件
如果找不到对应文件,则会报错,如果要忽略错误,可以在include
前加-
-include mkf1 mkf2
应用实例:自动生成依赖
objs = block.o command.o input.o main.o scene.o test.osudoku: $(objs)g++ $(objs) -o sudokuinclude $(objs:%.o=%.d)%.d: %.cpp@-rm $@$(CXX) -MM $< > $@.temp@sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.temp > $@@-rm $@.temp%.o : %.cppg++ -c $<@echo $^
嵌套make
如果将一个大项目分为许多小项目,则可以使用嵌套(递归)使用make。具体做法为,写一个总的Makefile,然后在每个子项目中都写一个Makefile,在总Makefile中进行调用。
例如,可以把sudoku项目中除main.cpp,test.cpp外的其他cpp存为一个子项目,编译为一个库文件,main.cpp test.cpp为另一个子项目,编译为.o然后链接库文件成可执行文件:
库文件Makefile
vpath %.h ../includeCXXFLAGS += -I../include -fexec-charset=GBK -finput-charset=UTF-8cpps := $(wildcard *.cpp)
objs := $(cpps:%.cpp=%.o)libsudoku.a: $(objs)ar rcs $@ $^$(objs): %.o : %.cpp %.h
main.cpp test.cpp的Makefile
CXXFLAGS += -I../include -fexec-charset=GBK -finput-charset=UTF-8
vpath %.h ../include
vpath %.a ../lib../sudoku: main.o test.o -lsudoku$(CXX) -o $@ $^
总的Makefile
.PHONY: all cleanall: subsrcsubsrc: sublib$(MAKE) -C srcsublib:$(MAKE) -C libclean:-rm *.exe src/*.o lib/*.o lib/*.a
其中
$(MAKE) -C subdir
这一指令会自动进入subdir文件夹然后执行make。
可以通过export
指令向子项目的make传递变量。
export var # 传递var
export # 传递所有变量
unexport # 取消传递