Makefile 精要(常见规则总结-附示例)

Makefile 是 make 工具执行构建(编译应用程序)的规则文件。 make 和 makefile 在 C/C++ 项目中广泛使用, 甚至其他语言的项目中也会用到。

缺少一篇简明扼要介绍 make 和 makefile 的文章, Google 结果中排名靠前的官方英文文档很长, 中文的文章翻译不当,翻译的一些内容很难理解。因此简要总结一下 Makefile 的使用(大致 15 分钟左右能够读完并且理解)。

一、什么是 Make 与 Makefile?

Make 是代码的自动构建工具,是把项目代码变成可执行文件的构建工具。可以用于不同编程语言的工程构建,实际大量使用在 C/C++ 的工程中。
Makefile 是 make 执行构建(编译应用程序)的规则文件。
比方说我们编译内核的时候,通过三个命令来编译内核,先 ./configure, 然后 make, 再 make install。
configure 是根据系统的环境自动做配置,make 是根据 Makefile 的规则构建出内核文件,make install 安装部署内核到系统中。
往往一个真实工程中的源文件很多,并且按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件先编译,哪些文件后编译,甚至于更复杂的操作。makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。
有了 makefile之后,只需要一个make命令,整个工程自动编译,极大的提高了软件开发的效率。 make是一个命令工具,是一个解释makefile中指令的命令工具。Windows/Linux/Mac 的开发工具中都提供有 make 工具。

二、Makefile 的格式 & Makefile 规则

Makefile 文件由一系列的规则组成。
target : [prerequisites]
[tab]  [commands]
第一行定义了 "目标"(target),和"前置条件"(prerequisites),目标和前置条件由分号隔开;
第二行必须由一个tab键开头,后面跟着"命令"(commands)
"目标"是必需的,不可省略;"前置条件"和"命令"都是可选的,但是两者之中必须至少存在一个。
每条规则就明确两件事:构建目标的前置条件是什么,以及如何构建。
如果Make命令运行时没有指定目标,默认会执行Makefile文件的第一个目标。

target

可以是一个object file(对象码文件),也可以是一个执行文件,还可以是一个标签(label),标签可以作为“伪目标”(Phony),就是不是真实存在的文件。

prerequisites

生成该target所依赖的文件,target(可以为空)

command

该target要执行的命令(可以为空,但命令和前置条件至少一个不为空)

三、Make 命令是如何工作的

Makefile 里的规则定义了如何生成目标。
Make 的工作原理: 前置依赖的文件比target文件要新的话,目标所定义的命令就会被执行
  1. make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
  2. 找到 Makefile 文件中定义的第一个目标文件(target)。
  3. 如果目标文件不存在,或是目标文件所依赖的 文件的修改时间要比目标 文件的新,那么就执行目标所定义的命令来生成目标文件
  4. 如果目标文件  所依赖的 文件也不存在,那么make会在当前文件中找目标为依赖 文件的依赖项,先找到规则生成所有的依赖项(是一个循环递归的过程)。

四、定义变量与引用变量

以下定义了 object  文件列表,以及从 object 文件编译生成 edit 这个目标(可执行文件)的命令。
objects = main.o kbd.o command.o display.o \
     insert.o search.o files.o utils.o
edit : $( objects )
    gcc -o edit $( objects )
main.o : main . c defs . h
    gcc -c main.c
kbd.o : kbd . c defs . h command . h
    gcc -c kbd.c
command.o : command . c defs . h command . h
    gcc -c command.c
display.o : display . c defs . h buffer . h
    gcc -c display.c
insert.o : insert . c defs . h buffer . h
    gcc -c insert.c
search.o : search . c defs . h buffer . h
    gcc -c search.c
files.o : files . c defs . h buffer . h command . h
    gcc -c files.c
utils.o : utils . c defs . h
    gcc -c utils.c
clean :
    rm edit $( objects )

五、自动推导

只要看到一个  .o  文件,make就会自动把  .c  文件加在依赖中,如果make找到一个  whatever.o  ,那么  whatever.c  就会是  whatever.o  的依赖文件。并且  cc  -c  whatever.c  也会被推导出来。
objects = main.o kbd.o command.o display.o \
    insert.o search.o files.o utils.o
edit : $( objects )
    gcc -o edit $( objects )
main.o : defs . h
kbd.o : defs . h command . h
command.o : defs . h command . h
display.o : defs . h buffer . h
insert.o : defs . h buffer . h
search.o : defs . h buffer . h
files.o : defs . h buffer . h command . h
utils.o : defs . h
.PHONY : clean
clean :
    rm edit $( objects )

六、自动推导/多个目标复用依赖关系

objects = main.o kbd.o command.o display.o \
    insert.o search.o files.o utils.o
edit : $( objects )
    gcc -o edit $( objects )
$(objects) : defs . h
kbd.o command.o files.o : command . h
display.o insert.o search.o files.o : buffer . h
.PHONY : clean
clean :
    -rm edit $( objects )
上面的示例里面, kdb.o, command.o, files.o 依赖于 command.h. 这里的 .PHONY 表示是一个伪目标。
而在  rm  命令前面加了一个小减号的意思就是,也许某些文件出现错误,忽略这些错误。

七、Makefile 的内容组成

Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。
  1. 显式规则。显式规则说明了如何生成一个或多个目标文件。
  2. 隐晦规则。自动推导的规则, 更简洁地书写 Makefile。
  3. 变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串。
  4. 文件引用。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。 
  5. 注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用  #  字符
引用其他 makefile :
include <filename>

八、多目标

Makefile的规则中的目标可以不止一个,其支持多目标,有可能我们的多个目标同时依赖于一个文件,并且其生成的命令大体类似。于是我们就能把其合并起来。可以使用一个自动化变量 $@ 引用当前规则中的目标。
bigoutput littleoutput : text.g
    generate text.g -$(subst output,,$@) > $@
上述规则等价于:
bigoutput : text.g
    generate text.g -big > bigoutput
littleoutput : text.g
    generate text.g -little > littleoutput

其中, -$(subst output,,$@) 中的 $ 表示执行一个Makefile的函数,函数名为subst,后面的为参数。这里的这个函数是替换字符串的意思, $@ 表示目标的集合,就像一个数组, $@ 依次取出目标,并执于命令。

九、静态模式规则

静态模式规则是用来指定多个目标以及基于目标名字来构建每一个目标的前置条件的规则。
静态模式规则比一般的多目标规则更加通用,因为目标不是必须有相同的前置条件。 它们的前置条件需要是类似的,但不必完全相同。
比如可以有跟目标同名的 .h 依赖。
静态模式规则语法
<targets ...> : < target - pattern > : < prereq - patterns ...>
    <commands>
    ...
targets:目标文件列表,目标文件的定义中可以有通配符*和?的匹配符号。 
target-pattern:指明了匹配 targets 的模式,每一个目标会匹配这个模式,用 % 从中提取出来目标名称中的一部分,用做枝干。这个枝干可以在依赖模式中使用,除了枝干以外,目标模式中的其他部分必须精确匹配目标。
prereq-patterns: 是目标的前置依赖模式,它使用 目标模式中提取出来的枝干来定义依赖。每一个目标的依赖中的 % 符号会被枝干替代。
比如匹配模式是 '%.o' 时, 目标  foo.o  匹配模式 ‘ %.o ’,  ‘ foo ’  是目标匹配中的枝干。 
前置依赖模式是  %.c 时 , 对于 foo.o 的目标, 会替换 %.c 中的 % 为 枝干的内容 'foo',这样 %.c 就被替换成了 foo.c。写一个不包含 % 的依赖模式也是合法的,这种情况下这个前置依赖是所有目标的依赖条件。 
例如, 下面的例子从对应的 .c 文件编译出 foo.o,  bar.o:
objects = foo.o bar.o

all: $(objects)

$(objects): %.o: %.c
    $(CC) -c $(CFLAGS) $< -o $@

指明了目标从$object中获取, 命令中的 $< 和 $@ 是自动变量, $< 表示当前处理目标的第一个前置依赖文件 xx.c, $@ 表示目标变量 xx.o。于是,上面的规则展开后等价于下面的规则:
foo.o : foo.c
    $(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
    $(CC) -c $(CFLAGS) bar.c -o bar.o

试想,如果我们的 %.o 有几百个,那么我们只要用这种很简单的“静态模式规则”就可以写完一堆规则。

十、自动生成源代码的前置依赖

在Makefile中,我们的依赖关系可能需要包含一系列的头文件,比如 main.c 中有一句 #include "defs.h" ,那么我们的依赖关系应该是:
main.o : main.c defs.h

大型项目中,往往会有大量的头文件依赖,我们可以使用C/C++编译器的自动生成前置依赖的功能。大多数的C/C++编译器都支持一个“-M”的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。例如,如果我们执行下面的命令:
cc -M main.c
其输出是:
main.o : main.c defs.h

扫描二维码关注公众号,回复: 14591670 查看本文章
于是由编译器自动生成的依赖关系,这样一来,你就不必再手动书写若干文件的依赖关系,而由编译器自动生成了。
需要提醒一句的是,对于GNU的C/C++编译器,你得用 -MM 参数,不然, -M 参数会把一些标准库的头文件也包含进来。
gcc -M main.c的输出是:
main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \
    /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \
    /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \
    /usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \
    /usr/include/bits/sched.h /usr/include/libio.h \
    /usr/include/_G_config.h /usr/include/wchar.h \
    /usr/include/bits/wchar.h /usr/include/gconv.h \
    /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \
    /usr/include/bits/stdio_lim.h
gcc -MM main.c的输出则是:
main.o: main.c defs.h

其他

1).回显文本内容:
@ echo 正在进行xxx模块编译
2).变量赋值
VARIABLE = value
# 在执行时扩展,允许递归扩展。
VARIABLE := value
# 在定义时扩展。
VARIABLE ?= value
# 只有在该变量为空时才设置值。
VARIABLE += value
# 将值追加到变量的尾端。
3).可以指定规则文件执行
make --file=Makefile.mac
4).内置变量
Make命令提供一系列内置变量,比如,$(CC) 指向当前使用的编译器,$(MAKE) 指向当前使用的Make工具。
详细的内置变量清单详见手册: Implicit Variables (GNU make)
output:
    $(CC) -o output input.c
5).自动变量
$@: 当前目标
$<: 第一个前置依赖
$?: $? 指代比目标更新的所有前置条件,之间以空格分隔。
$^: 指代所有前置依赖,之间以空格分隔。
还有其他大量内置变量,可以参考 GNU 手册。 
另外 Makefile 中可以使用条件判断,循环来控制逻辑。 可以调用函数。subst 执行文本替换,patsubst 执行模式匹配的替换。

附 - Makefile 示例一: 编译多个可执行的目标文件

编译出多个可执行文件(带有 .out 后缀,如果是 Windows,把 .out 改成 .exe 即可)
CC = gcc          ## C Compiler command
CFLAGS   = -Wall  ## C Flags, -ggdb: generate gdb debug info, -Wall: enable all warning
LDFLAGS  =        ## Library loading flags, like -lbass, -lopencv

all: arraylist_usage pm25_avg_sort word_counter bubble_sort quick_sort radix_sort

arraylist_usage: arraylist_usage.c
	$(CC) $(CFLAGS) -o arraylist_usage.out arraylist_usage.c  $(LDFLAGS)

pm25_avg_sort: pm25_avg_sort.c
	$(CC) $(CFLAGS) -o pm25_avg_sort.out pm25_avg_sort.c $(LDFLAGS)

word_counter: hash_demo.c hashtable.c hashtable.h
	$(CC) $(CFLAGS) -o word_counter.out hash_demo.c hashtable.c $(LDFLAGS)

bubble_sort: bubble_sort.c
	$(CC) $(CFLAGS) -o bubble_sort.out bubble_sort.c $(LDFLAGS)

quick_sort: quick_sort.c
	$(CC) $(CFLAGS) -o quick_sort.out quick_sort.c $(LDFLAGS)

radix_sort: radix_sort.c
	$(CC) $(CFLAGS) -o radix_sort.out radix_sort.c $(LDFLAGS)

clean:
	rm *.out

附 - Makefile 示例二: 简化版本(产出多个可执行文件)

这个示例跟前面的示例一的效果时一样的, 编译出多个可执行文件(带有 .out 后缀,如果是 Windows,把 .out 改成 .exe 即可),但是通过静态模式规则,使得编写的文件简洁了很多。

# make -f compact.makefile
# make -f compact.makefile clean
CC = gcc          ## C Compiler command
CFLAGS   = -Wall  ## C Flags, -ggdb: generate gdb debug info, -Wall: enable all warning
LDFLAGS  =        ## Library loading flags, like -lbass, -lopencv
OBJECTS  = arraylist_usage pm25_avg_sort bubble_sort quick_sort radix_sort

all: $(OBJECTS) word_counter

$(OBJECTS): %: %.c
	$(CC) $(LDFLAGS) $(CFLAGS)  -o $@ $<

word_counter: hash_demo.c hashtable.c hashtable.h
	$(CC) $(CFLAGS) -o word_counter hash_demo.c hashtable.c $(LDFLAGS)

clean:
	-rm  $(OBJECTS) word_counter

后面博客上会更新以上工程的实际代码,供参考。 

参考:

GNU make

GNU make

makefile介绍 — 跟我一起写Makefile 1.0 文档

Make 命令教程 - 阮一峰的网络日志

猜你喜欢

转载自blog.csdn.net/davidullua/article/details/126824454