文章

Makefile笔记

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
.PHONY: clean

######################################
# platform
######################################
PLATPORM = linux
# PLATPORM = windows
######################################

# binaries
######################################
CC = gcc

######################################
# target
######################################

OUT_FILE = Project

ifeq ($(PLATPORM), linux)
TARGET = $(addsuffix .out, $(OUT_FILE))
else
TARGET = $(addsuffix .exe, $(OUT_FILE))
endif
######################################

# building variables
######################################
# debug build?
DEBUG = 1
# optimization
OPT = -Og

######################################
# Build path
######################################
BUILD_DIR = build

######################################
# C sources
######################################
C_SOURCES =  \
main.c \
add/add_fun.c \
sub/sub_fun.c \
cout/cout_msg.c  

######################################
# C include
# the compiling request add -I
######################################
C_INCLUDES =  \
-I add \
-I sub \
-I cout

######################################
# C object
######################################
C_OBJS=$(addprefix $(BUILD_DIR)/, $(notdir $(C_SOURCES:.c=.o)))

vpath %.c $(dir $(C_SOURCES))

ifeq ($(DEBUG), 1)
CFLAGS += -g 
endif


$(TARGET) : $(C_OBJS)
$(CC) $^ -o $@

$(BUILD_DIR)/%.o : %.c 
$(CC) -c $(CFLAGS) -MMD $(C_INCLUDES) -o $@ $<

clean:
ifeq ($(PLATPORM), linux)
rm -rf $(C_OBJS) $(TARGET)
else
del /Q /S $(BUILD_DIR)\*.o $(TARGET)
endif

#######################################
# dependencies
#######################################
-include $(wildcard $(BUILD_DIR)/*.d)

多文件Makefile示例

Makefile在windows下运行很多奇奇怪怪的问题,难搞,linux就没有。

step0

在windows下和在linux下的clean命令不一样,注意在windows下面是使用/,$(OBJ_DIR)\*.o而在linux下是$(OBJ_DIR)/*.o,否则会报错“无效开关”,windows如下所示:

1
2
3
4
.PHONY:clean

clean:
	del /Q /S $(OBJ_DIR)\*.o $(BIN)

step1

假设有main.c\add_fun.c\add_fun.h\sub_fun.c\sub_fun.h\cout_msg.c\cout_msg.h,文件都在同一个文件夹内,so easy。

1
2
3
4
5
6
7
8
9
10
11
12
obj=main.o add_fun.o sub_fun.o cout_msg.o

a.out:$(obj)
	gcc $(obj) -o $@

%.o:%.c
	gcc -c $< -o $@

.PHONY:clean

clean:
	rm -rf $(obj) a.out

step2

使用变量定义一下,其中\为换行符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
SRC = \
main.c \
add_fun.c \
sub_fun.c \
cout_msg.c 

INC = -I ./

OBJ=$(SRC:.c=.o)

BIN=a.out

$(BIN):$(OBJ)
	gcc $(OBJ) -o $(BIN)

%.o:%.c
	gcc -c $< -o $@

.PHONY:clean cout

clean:
	rm -rf $(OBJ) $(BIN)

cout:
	@echo "SRC = $(SRC)"
	@echo "INC = $(INC)"
	@echo "OBJ = $(OBJ)"

step3

文件分文件夹进行定义,编译产生的.o文件放入一个文件夹内,切记添加vpath,因为在$(OBJ_DIR)/%.o:%.c这一句里面,需要build/add_fun.c,其中build$(OBJ_DIR),那么%的值为add_fun,依赖也就为add_fun.c,但是查找当前文件夹并没有一个叫add_fun.c的文件,该文件在add/add_fun.c,这时编译就报错make: *** No rule to make target 'build/add_fun.o', needed by 'a.out'. Stop.,我们通过vpath来添加,使用规则为:

1
vpath PATTERN DIRECTORIES

为符合模式“PATTERN”的文件指定搜索目录“DIRECTORIES”。多个目录使用空格或者 冒号(:)分开。

1
vpath PATTERN

清除之前为符合模式“PATTERN”的文件设置的搜索路径。

1
vpath

清除所有已被设置的文件搜索路径。

那么更新之后的Makefile文件如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
SRC = \
main.c \
add/add_fun.c \
sub/sub_fun.c \
cout/cout_msg.c 

INC = \
-I add \
-I sub \
-I cout

OBJ_DIR = build

OBJ=$(addprefix $(OBJ_DIR)/, $(notdir $(SRC:.c=.o)))
#OBJ=$(SRC:.c=.o)

BIN=a.out

# add src file to compiling dir
# no this compiling error
vpath %.c $(dir $(SRC))

$(BIN):$(OBJ)
	gcc $(OBJ) -o $(BIN)

$(OBJ_DIR)/%.o:%.c
	gcc -c $(INC) $< -o $@

.PHONY:clean cout

clean:
	rm -rf $(OBJ) $(BIN)

cout:
	@echo "SRC = $(SRC)"
	@echo "INC = $(INC)"
	@echo "OBJ = $(OBJ)"

step4

调整结构,添加部分注释,添加依赖。

当我们只改变.h文件的时候,存在无法检测到改变,从而不编译,这是我们就需要添加依赖,让它检测到.h文件的变化,从而重新编译包含该文件的源文件。

1、添加了 -include *.d 指令;

2.、gcc 编译指令中,添加了 -MMD 参数;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
.PHONY: clean cout

######################################
# binaries
######################################
CC = gcc

######################################
# target
######################################
TARGET = Project.out

######################################
# building variables
######################################
# debug build?
DEBUG = 1
# optimization
OPT = -Og

######################################
# Build path
######################################
BUILD_DIR = build

######################################
# C sources
######################################
C_SOURCES =  \
main.c \
add/add_fun.c \
sub/sub_fun.c \
cout/cout_msg.c  

######################################
# C include
# the compiling request add -I
######################################
C_INCLUDES =  \
-I add \
-I sub \
-I cout

######################################
# C object
######################################
C_OBJS=$(addprefix $(BUILD_DIR)/, $(notdir $(C_SOURCES:.c=.o)))

vpath %.c $(dir $(C_SOURCES))

ifeq ($(DEBUG), 1)
CFLAGS += -g 
endif


$(TARGET) : $(C_OBJS)
	$(CC) $^ -o $@

$(BUILD_DIR)/%.o : %.c 
	$(CC) -c $(CFLAGS) -MMD $(C_INCLUDES) -o $@ $<

clean:
	rm -rf $(C_OBJS) $(TARGET)

cout:
	@echo "C_OBJS = $(C_OBJS)"
	@echo "CFLAGS = $(CFLAGS)"

#######################################
# dependencies
#######################################
-include $(wildcard $(BUILD_DIR)/*.d)

添加dll动态链接库

1、简介

把一个.c文件编译成可执行文件,一般需要预处理、编译、汇编、链接四个步骤,每个步骤会分别调用预处理器、编译器、汇编器、链接器来完成。在linux中我们使用gcc来编译文件:

1
2
gcc helloworld.c -o a.out
gcc -o a.out helloworld.c

gcc 会分别调用预处理器、编译器、汇编器和链接器来自动完成程序编译的整个过程,不需要用户一个命令一个命令分别输入了。

进行预处理,使用 - E 参数来完成:

1
gcc -E helloworld.c

汇编处理,使用 - S 参数来完成:

1
gcc -S -o helloworld.S helloworld.c

进行编译,不链接,使用 - c 参数来完成:

1
gcc -c -o helloworld.o helloworld.c

只编译,不链接的时候,如果该文件依赖其他文件,在gcc -c -o xx.o xx.c的时候xx.c后面也不能跟其他c文件,这里不能多文件编译生成o文件。

2、规则

规则是 Makefile的基本组成单元。一个规则通常由目标、目标依赖和命令三部分构成:

1
2
3
4
目标:目标依赖
    命令
a.out: hello.c
    gcc -o a.out hello.c

一个规则中的三个部分并不是都必须要有的,但是一定要有一个目标。

一般会选择第一个作为默认目标,同时也可以有多目标,多规则目标等等。

伪目标并不是一个真正的文件,可以看做是一个标签。伪目标一般没有依赖关系,也不会生成对应的目标文件,可以无条件执行,纯粹是为了执行某一个命令,如 clean 执行清理工作。

1
2
3
4
.PHONY: clean
...
clean:
    rm -f a.out hello.o

3、命令

命令一般由 shell命令(echo、ls)和编译器的一些工具(gcc、ld、ar、objcopy 等)组成,使用 tab 键缩进。对于规则中的每一个命令,make 会开一个进程执行,每条命令执行完,make 会监测每个命令的返回码。正确才会执行下一条命令,出错则退出编译流程。

1
Makefile:3: *** missing separator.  Stop.

报错这个一般是有命令不是以tab来缩进的。

在命令前加一个@可以不打印当前执行的命令。

4、变量

例如有一个工程为add.c、add.h、sub.c、sub.h、main.c文件构成,使用变量编写Makefile文件为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.PHONY:clean

CC=gcc
BIN=a.out
OBJS=main.o add.o sub.o

$(BIN):$(OBJS)
	@echo "start compiling"
	$(CC) -o $(BIN) $(OBJS)
	@echo "compiling done"

main.o:main.c add.c sub.c
	@$(CC) -c -o main.o main.c

add.o:add.c
	@$(CC) -c -o add.o add.c

sub.o:sub.c
	@$(CC) -c -o sub.o sub.c


clean:
	rm -f $(BIN) $(OBJS)

变量赋值

Makefile中的变量赋值有多种形式,比如:

  • 条件赋值:?=
  • 追加赋值:+=
  • 立即变量::=
  • 延迟变量:=

自动变量

  • $@:目标
  • $^:所有目标依赖
  • $<:目标依赖列表中的第一个依赖
  • $?:所有目标依赖中被修改过的文件

  • $%:当规则的目标是一个静态库文件时,$% 代表静态库的一个成员名
  • $+:类似 $^,但是保留了依赖文件中重复出现的文件
  • $*:在模式匹配和静态模式规则中,代表目标模式中 % 的部分。比如 hello.c,当匹配模式为 %.c 时,$* 表示 hello
  • $(@D):表示目标文件的目录部分
  • $(@F):表示目标文件的文件名部分
  • $(*D):在模式匹配中,表示目标模式中 % 的目录部分
  • $(*F):在模式匹配中,表示目标模式中 % 的文件名部分
  • -::告诉 make 在编译时忽略所有的错误
  • @: :告诉 make 在执行命令前不要显示命令
  • $:

使用自动变量之后,Makefile为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
.PHONY:clean

CC=gcc
BIN=a.out
OBJS=main.o add.o sub.o


$(BIN):$(OBJS)
	@echo "start compiling"
	$(CC) -o $(BIN) $(OBJS)
	@echo "compiling done"

main.o:main.c 
	@$(CC) -c -o $@ $^

add.o:add.c
	@$(CC) -c -o $@ $^

sub.o:sub.c
	@$(CC) -c -o $@ $^


clean:
	rm -f $(BIN) $(OBJS)

变量替换

使用匹配符 % 匹配变量,使用 % 保留变量值中的指定字符串,然后其他部分使用指定字符串代替。

1
2
3
4
5
6
.PHONY: all
SRC := main.c sub.c
OBJ := $(SRC:%.c=%.o)
all:
    @echo "SRC = $(SRC)"
    @echo "OBJ = $(OBJ)"

环境变量

除了用户自定义的一些变量,make 在解析 Makefile中还会引入一些系统环境变量,如编译参数 CFLAGS、SHELL、MAKE 等。若用户在命令行中传递跟系统环境变量同名的变量,系统环境变量也会被传递的同名变量覆盖。

1
# make HOSTNAME=zhaixue.cc

####

  • 在一个Makefile文件在可以通过make -C subdir,去子文件夹中执行Makefile。
  • 在递归执行的时候,可以通过export将变量从主Makefile传递到子Makefile。
  • 可以通过override定义变量防止命令行中传递进来的值改变Makefile中的变量值。

条件判断

ifeq:判断两个参数是够相等

ifneq:判断参数是否不相等

ifdef:判断一个变量是否已经定义

ifndef:判断一个变量是否没有定义

条件判断语句由三个关键字组成:ifeq、else、endif。ifeq 后面的比较语句使用小括号抱起来,ifeq 和小括号之间要用空格隔开,小括号里的两个参数用逗号隔开。

5、函数

扩展通配符函数:wildcard

1
2
3
4
5
SRC  = $(wildcard *.c)
HEAD = $(wildcard *.h)
all:
    @echo "SRC = $(SRC)"
    @echo "HEAD = $(HEAD)"

用户自定义函数

对于用户自定义函数,在 Makefile 中要使用 call 函数间接调用,各个参数之间使用空格隔开:

1
2
3
4
5
6
7
PHONY: all
define func
    @echo "pram1 = $(0)"
    @echo "pram2 = $(1)"
endef
all:
    $(call func, hello zhaixue.cc)

subst 函数

subst 函数用来实现字符串的替换,将字符串 text 中的 old 替换为 new

1
$(subst old,new,text)

patsubst 函数

patsubst 函数主要用来模式替换:使用通配符 % 代表一个单词中的若干字符,在 PATTERN 和 REPLACEMENT 如果都包含这个通配符,表示两者表示的是相同的若干个字符,并执行替换操作。

1
$(patsubst PATTERN, REPLACEMENT, TEXT)

strip 函数

strip 函数是一个去空格函数:一个字符串通常有多个单词,单词之间使用一个或多个空格进行分割,strip 函数用来将多个连续的空字符合并成一个,并去掉字符串开头、末尾的空字符。

findstring 函数

findstring 函数用来查找一个字符串,如果找到,则返回字符串 FIND,否则,返回空。使用格式如下:

1
$(findstring FIND, IN)

filter 函数

filter 函数用来过滤掉一个指定的字符串,只留下符合 PATTERN 格式的单词,使用格式如下:

1
$(filter PATTERN…,TEXT)

filter-out 函数

filter-out 函数是一个反过滤函数,功能和 filter 函数恰恰相反:该函数会过滤掉所有符合 PATTERN 模式的单词,保留所有不符合此模式的单词。

sort 函数:单词排序

sort 函数对字符串 LIST 中的单词以首字母为准进行排序,并删除重复的单词。使用格式如下:

1
$(sort LIST)

word 函数:取单词

word 函数的作用是从一个字符串 TEXT 中,按照指定的数目 N 取单词,如果 N 的值大于字符串中单词的个数,返回空;如果 N 为 0,则出错。使用格式如下:

1
 $(word N,TEXT)

wordlist 函数:取字串

wordlist 函数用来从一个字符串 TEXT 中取出从 N 到 M 之间的一个单词串,N 和 M 都是从 1 开始的一个数字,函数的返回值是字符串 TEXT 中从 N 到 M 的一个单词串。当 N 比字符串 TEXT 中的单词个数大时,函数返回空。

1
$(wordlist N, M, TEXT)

words 函数:统计单词数目

words 函数用来统计一个字符串 TEXT 中单词的个数,使用格式如下:

1
$(words TEXT)

firstword 函数:取首个单词

firstword 函数用来取一个字符串中的首个单词,firstword 函数其实就相当于 $(word 1,TEXT),使用格式如下:

1
2
$(firstword NAMES…)
$(word 1,TEXT)

dir 函数:取文件路径

dir 函数用来从一个路径名中截取目录的部分。使用格式如下:

1
$(dir NAMES…)

notdir 函数:取文件名

notdir 函数和 dir 函数实现完全相反的功能:从一个文件路径名中去文件名,而不是目录。notdir 函数的使用方法和 dir 函数相同。

1
$(notdir NAMES…)

suffix 函数:取文件名后缀

suffix 函数从一系列文件名序列中,取出各个文件名的后缀。

1
$(suffix NAMES…)

basename 函数:取文件名前缀

basename 函数从一系列文件名序列中,取出各个文件名的前缀部分,如果一个文件名中包括多个点号,basename 函数返回最后一个点号之前的文件名部分;如果一个文件名没有前缀,函数返回空字符串。

1
$(basename NAMES…)

addsuffix 函数:给文件名加后缀

addsuffix 函数的作用是:给文件列表中的每个文件名添加后缀 SUFFIX

1
$(addsuffix SUFFIX, NAMES…)

addprefix 函数:给文件名加前缀

addprefix 函数的作用是:给文件列表中的每个文件名添加一个前缀 PREFIX

1
$(addprefix PREFIX, NAMES…)

join 函数:单词连接

join 函数的作用是:将字符串 LIST1 和字符串 LIST2 的各个单词依次连接,合并为新的单词构成的字符串

1
$(join LIST1,LIST2)

wildcard 函数:列出所有符号匹配模式的文件

wildcard 函数的作用是:列出当前目录下所有符合 PATTREN 模式的文件名,其中 PATTREN 可以使用 shell 能识别的通配符:?、* 等。

1
$(wildcard PATTERN)

foreach 函数:循环或遍历操作

foreach 函数的工作过程是:把 LIST 中使用空格分割的单词依次取出并赋值给变量 VAR,然后执行 TEXT 表达式。重复这个过程,直到遍历完 LIST 中的最后一个单词。函数的返回值是 TEXT 多次计算的结果。

1
$(foreach VAR,LIST,TEXT)

if 函数:上下文中实现条件判断

if 函数提供了在一个函数上下文中实现条件判断的功能,类似于 ifeq 关键字,if 函数的第一个参数 CONDITION 表示条件判断,展开后如果非空,则条件为真,执行 THEN-PART 部分;否则,如果有 ELSE-PART 部分,则执行 ELSE-PART 部分。if 函数的使用格式如下:

1
2
$(if CONDITION,THEN-PART)
$(if CONDITION,THEN-PART[,ELSE-PART])

call 函数:用户自定义的函数

我们给函数传递的参数,在函数内部可以使用 $(0)、$(1)… 直接使用。call 函数是唯一一个可以用来创建新的参数化的函数。call 函数不仅可以用来调用一个用户自定义函数并传参,还可以向一个表达式传参:

1
2
$(function arguments)
${function arguments}

origin 函数:变量是从哪里来的

顾名思义,origin 函数的作用就是告诉你,你所关注的一个变量是从哪里来的。函数的使用格式为:

1
$(origin <variable>)

如果变量没有定义,origin 函数的返回值为:undefined,不同的返回值代表变量的类型不同。常见的返回值如下;

  • default:变量是一个默认的定义,比如 CC 这个变量
  • file:这个变量被定义在 Makefile
  • command line:这个变量是被命令行定义的
  • override:这个变量是被 override 指示符重新定义过的
  • automatic:一个命令运行中的自动化变量

shell函数:shell命令

shell函数的参数是 shell命令,它和反引号 ``具有相同的功能。shell命令的运行结果即为 shell函数的返回值。

1
$(shell pwd)

error 函数:提示信息并终止 make 的继续执行

当遇到 error 函数时,就会给用户一个错误提示信息,并终止 make 的继续执行

1
$(error TEXT…)

warning 函数:提示信息

warning 函数跟 error 函数类似,也会给用户提示信息,唯一的区别是:warning 函数不会终止 make 的运行,make 会继续运行下去。

1
$(warning TEXT…)

通配符

在 Makefile 中可以使用的通配符有:* 、? 、 […]。通配符的使用方法和含义和在 shell中一样,除此之外Makefile 还有经常使用的几个自动变量也可以看做特殊通配符:

  • $@:所有目标文件
  • $^:目标依赖的所有文件
  • $<:第一个依赖文件
  • $?:所有更新过的依赖文件

在 Makefile中,通配符主要用在两个场合:

  • 用在规则的目标和依赖中

  • 用在规则的命令中

本文由作者按照 CC BY 4.0 进行授权