Makefile 实践
定义 target:
say:
echo "Hello World"
say 是 target,输入 make say 执行当前 target,在 target 之后的是预置条件或依赖,下面的 echo 语句称为步骤(recipe),步骤根据预置条件来实现目标,语法:
目标:预置条件
<TAB>步骤
final_target: sub_target final_target.c
Recipe_to_create_final_target
sub_target: sub_target.c
Recipe_to_create_sub_target
默认情况下输入 make 会执行第一个 target,大多数项目以 all 作为第一个 target 负责调用它的目标。可以使用 .DEFAULT_GOAL
修改默认目标,在文件开头增加 .DEFAULT_GOAL := clean
修改默认目标为 clean。
all 作为第一个目标的好处是可以调用多个目标,clean 不应该放入 all,应该被手动调用:
.PHONY: all say_hello generate clean
all: say_hello generate
say_hello:
@echo"Hello World"
generate:
@echo"Creating empty text files..."
touchfile-{1..10}.txt
clean:
@echo"Cleaning up..."
rm*.txt
同名 target 如何处理
同名 target 会被合并,但是如果第一个 target 带 cmd 就无法合并,并且重复的前置条件会被忽略。
target1: dep1
target1: dep2
cmd2
# 合并成
target1: dep1 dep2
cmd2
% 模式替换
targets : target-pattern : prereq-pattern
通过 target-pattern 从 targets 中匹配子目标,再通过 prereq-pattern 从子目标生成依赖,进而构成完整的 target。
OBJS := func.o main.o
$(OBJS) : %.o : %.c
gcc -o $@ -c $^
模式替换成:
func.o : func.c
gcc -o func.o -c func.c
main.o : main.c
gcc -o main.o -c main.c
$@、$^、$<、$*
- $@:目标文件
- $^:所有的依赖文件
- $<:第一个依赖文件
$*
: 表示 target 模式中%
之前的部分,如果目标是dir/a.foo.b
,并且目标的模式是a.%.b
,那么,$*
的值就是dir/a.foo
=、:=、?=、+=
- = 是最基本的赋值
- := 是覆盖之前的值
- ?= 是如果没有被赋值过就赋予等号后面的值
- += 是添加等号后面的值
.PHONY 的作用
单词 phony 的意思是:伪造的,假的,在 Makefile 中表示后面的 target 是一个伪造的 target(Makefile 中的 target 默认是文件)
.PHONY: clean
clean:
rm -rf x
如果目录下有 clean 文件,执行:make -f Makefile clear
会执行成功,如果没有 .PHONY target 就会执行失败,因为这个 target 在目录下已经存在,本质上并没有什么其他的作用。
patsubst
替换通配符。
notdir
去除路径。
wildcard 用法
Makefile 规则中,通配符会被自动展开。在变量的定义和函数引用时,通配符将失效,这种情况下如果需要通配符有效,需要使用函数 wildcard
,语法是 $(wildcard PATTERN...)
,在 Makefile 中它被展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表。如果不存在任何符合此模式的文件,函数会忽略模式字符并返回空。
例如使用 $(wildcard *.c)
获取工作目录下所有的 .c
文件列表。复杂一些用法;可以使用 $(patsubst %.c,%.o,$(wildcard *.c))
,首先使用 wildcard
函数获取工作目录下的 .c 文件列表,之后将列表中所有文件名的后缀 .c
替换为 .o
。这样我们就可以得到在当前目录可生成的 .o
文件列表。因此在一个目录下可以使用如下内容的 Makefile
来将工作目录下的所有的 .c
文件进行编译并最后连接成为一个可执行文件:
objects := $(patsubst %.c,%.o,$(wildcard *.c))
foo : $(objects)
cc -o foo $(objects)
建立一个测试目录,在测试目录下建立一个名为sub的子目录
$ mkdir test
$ cd test
$ mkdir sub
在test下,建立a.c和b.c2个文件,在sub目录下,建立sa.c和sb.c2 个文件
建立一个简单的Makefile
src=$(wildcard *.c ./sub/*.c)
dir=$(notdir $(src))
obj=$(patsubst %.c,%.o,$(dir) )
all:
@echo $(src)
@echo $(dir)
@echo $(obj)
@echo "end"
执行结果分析:
第一行输出:
a.c b.c ./sub/sa.c ./sub/sb.c
wildcard把 指定目录 ./ 和 ./sub/ 下的所有后缀是c的文件全部展开。
第二行输出:
a.c b.c sa.c sb.c
notdir把展开的文件去除掉路径信息
第三行输出:
a.o b.o sa.o sb.o
Example
1
// 变量
BINARY_NAME=App
run:
go build -o bin/$(BINARY_NAME) -v
./bin/$(BINARY_NAME)
2
.PHONY = all clean
CC =gcc # compiler to use
LINKERFLAG =-lm
SRCS := $(wildcard *.c)
BINS := $(SRCS:%.c=%)
all: ${BINS}
%:%.o
@echo"Checking.."
${CC} ${LINKERFLAG} $<-o $@
%.o:%.c
@echo"Creating object.."
${CC}-c $<
clean:
@echo"Cleaning up..."
rm-rvf *.o ${BINS}
BINS := $(SRCS:%.c=%)
称为替代引用,如果 SRCS 的值为 ‘foo.c bar.c’,则 BINS的值为 ‘foo bar’。
%:%.o
这种奇怪的语法,假定 foo 是变量 ${BINS} 中的一个值(根据 all)。% 会匹配到 foo(%匹配任意一个目标),下面是规则展开后的内容:
foo: foo.o
@echo"Checking.."
gcc-lm foo.o -o foo
如上所示,% 被 foo 替换掉了。$< 被 foo.o 替换掉。$< 用于匹配第一个预置条件,$@ 匹配目标。对 ${BINS} 中的每个值,这条规则都会被调用一遍。