Makefile 光速入门

什么是 Makefile

Makefile 简单来说就是构建某个文件需要的步骤,比如我们要编译一个 C 语言的程序,单个文件时我们可以简单输入一行 gcc 的编译命令:

$ gcc main.c -o main

就可以完成一个编译单文件的操作,但是如果项目比较复杂,涉及到多文件的编译和链接,要输入的命令就比较多,例如:

$ gcc -c module1.c module2.c main.c
$ gcc module1.o module2.o main.o -o main

这还是最简单的多文件编译,当涉及到更复杂的编译链接过程和编译器设置时,每次都敲这么多命令显然是不能接受的。

通过 Makefile,我们可以将编译命令简化为:

$ make main

这个 main 实际是自己取的名字,通过编写 makefile 的规则,可以支持多个 target 的构建,例如 make windows 构建 windows 下的程序,make linux 构建 linux 下的程序,make test 构建测试程序等等。

总之 Makefile 就是简化我们敲命令的过程。

Makefile 概念

Makefile 实际上就是一个描述命令执行过程的文本文件,就是某种意义上的 shell 脚本,由几个部分构成:

<target> : <prerequisites>
	<commands>

target 就是要完成命令的名称,例如我们可以简化最简单的单文件编译命令,因为这里非常简单,没有必要设置前置条件,所以 prerequisites 可以为空:

main:
	gcc main.c -o main

我们在当前目录下建立一个名叫 makefile 的文本文件,输入以上内容,随后执行 make main 即可完成编译。

文件名并不一定要是 makefile,但是约定俗成是这样的。

target

每个 target 就是一个可执行的目标,也就是 make <target> 命令后面所跟的名字,一般来说他通常和文件名一样,构建 main,那么最终的输出文件就是 main。但也可以是一个操作命令,例如经常都会有的 target 就是 clean,通常用 make clean 完成项目目录临时文件的删除。

clean:
      rm *.o

这样的 target 执行之后就是删除当前目录下后缀为 .o 的临时文件。

prerequisites

前置条件代表的是一种依赖关系,例如在单文件编译中,虽然命令只有一个 -o 但实际上还有许多过程,我们假设先将 main.c 编译成程序 main.o,最后再链接成可执行程序 main

main: main.o
	gcc main.o -o main

main.o:
	gcc -c main.c

构建 main 需要 main.o 先构建完,所以会先构建 main.o,再构建 main,可以有多个前置条件,用空格隔开。当存在多个前置条件时,是按从前往后的顺序构建的。

变量

makefile 中可以定义自己的变量,以实现更方便的编写,变量名通常用全大写来表示,例如:

SRC_DIR := ./src
INC_DIR := ./include

makefile 中变量定义的 =:= 是有一定区别的,:= 的值在赋值时就会确定,而 = 是在使用时才确定。只有在非常复杂的构建流程中才会使用到这种区分,目前只需要了解即可。

除了自定义的变量外,makefile 还有一些自动变量:

# $@ 表示构建目标
main:
	echo $@ # $@ = main

# $< 表示依次获得的依赖项
main: module1.o module2.o
	echo $< # $< = module1.o

main: module1.o module2.o
	echo $< $< # $< = module1.o module2.o

# $^ 表示所有依赖项
main: module1.o module2.o
	echo $^ # $< = module1.o module2.o

除此之外还有一些自动变量,本文就不涉及了。

通配符

在 makefile 中 % 表示通配符,匹配任意字符串,除了下一小节常用命令提到的用法,还可以作为 makefile 构建过程展开。

例如:

test_%:
	echo $@

当执行 make test_anything 时,就会打印出 echo test_anything,这里 anything 可以是任何字符串,虽然没有显式规定该目标的构建过程,但是 makefile 会自动匹配并展开。

这个也适用于是依赖项的时候,例如:

main: a.o b.o
	# 编译命令

# a.o 和 b.o 的展开模板
%.o:
	echo $@

这样也不用显式规定 a.ob.o 的构建规则,在遇到时会自动展开,这在大量类似构建过程时,编写一个展开模板就可以省去很多代码量。

常用命令

在 makefile 中,我们可以使用以下方法来获取某个目录下的文件,并转化为某种其他形式:

假设 ./src 目录下有 module1.cmodule2.c

SRC_DIR := ./src
INC_DIR := ./include

# 获取 src 目录下的所有 .c 文件
SRCS := $(wildcard $(SRC_DIR)/*.c)
# 将 src 目录下的所有 .c 文件名改为 .o
OBJS := $(patsubst $(SRC_DIR)/%.c, %.o, $(SRCS))

# 打印查看变量信息
$(info SRCS: $(SRCS)) # SRCS: ./src/module1.c ./src/module2.c
$(info OBJS: $(OBJS)) # OBJS: module1.o module2.o

wildcard 用于展开通配符,通常用于匹配文件名模式,其形式为:$(wildcard pattern)。在本例中,$(SRC_DIR) 实际上是使用变量,可以等价为 $(wildcard ./src/*.c),也就是找下 src 目录下的所有 .c 文件。

patsubst 用于进行模式替换,其形式为:$(patsubst pattern, replacement, text)。在本例中,将变量和通配符完全展开可以等价为:

$(patsubst ./src/add.cpp ./src/sub.cpp, add.o sub.o, ./src/add.cpp ./src/sub.cpp)

其中 % 是 makefile 中的通配符,在本例中则匹配 $(SRCS) 中满足 ./src/%.c 的文本,并替换为 %.o,其中 % 在本例中就会被匹配为 module1module2

常用模板:同目录

目录结构如下:

.
├── add.h
├── sub.h
├── main.c
├── makefile
├── add.c
└── sub.c

同目录下的编译较为简单,可以使用以下模板:

# 定义变量
SRCS := add.c sub.c
OBJS := $(patsubst %.c, %.o, $(SRCS))
TARGET := main

# 打印查看变量信息
$(info SRCS: $(SRCS))
$(info OBJS: $(OBJS))

# 构建目标
$(TARGET): $(OBJS)
	gcc $(TARGET).c $(OBJS) -o $(TARGET)

# 编译规则
%.o: %.c
	gcc -c $< -o $@

# 清理规则
clean:
	rm -f $(OBJS) $(TARGET)

常用模板:项目目录

.
├── include
│   ├── add.h
│   └── sub.h
├── main.cpp
├── makefile
└── src
    ├── add.cpp
    └── sub.cpp

项目目录下的编译复杂一些,但是只是多个了自动获取文件的步骤,可以使用以下模板:

# 定义变量
SRC_DIR := ./src
INC_DIR := ./include
TARGET := main

# 自动获取目录下的源文件和编译目标文件
SRCS := $(wildcard $(SRC_DIR)/*.cpp)
OBJS := $(patsubst $(SRC_DIR)/%.cpp, %.o, $(SRCS))

# 打印查看变量信息
$(info SRCS: $(SRCS))
$(info OBJS: $(OBJS))

# 构建目标
$(TARGET): $(OBJS)
	g++ -I$(INC_DIR) $(OBJS) $(TARGET).cpp -o $(TARGET)

# 编译规则
%.o: $(SRC_DIR)/%.cpp
	g++ -I$(INC_DIR) -c $< -o $@

# 清理规则
clean:
	rm -f $(OBJS) $(TARGET)

注意事项

构建一个过程可能有多条命令,如果直接换行,命令是在不同的 shell 上执行的,也就是命令直接不会相互影响,例如:

test:
	export foo=bar
	echo $$foo

这里 foo 的值是取不到的(两个 $ 是因为 $ 在 makefile 中是转义字符,要使用 $ 就需要再转义一下)。如果要让多条命令在一个 shell 中执行,可以使用 \;,例如:

test:
	export foo=bar \
	echo $$foo

# 另一种写法
test:
	export foo=bar; echo $$foo

Reference

[1] Make 命令教程 - 阮一峰的网络日志 (ruanyifeng.com)

上一篇 下一篇

评论 | 0条评论