Makefile 实践

Written by with ♥ on in IT

定义 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} 中的每个值,这条规则都会被调用一遍。

参考资料