Makefile 入门

Bash C++ Makefile Unix Windows sed 引用 汇编 编译 进程 字符串 操作系统

make是软件开发中常用的工具程序(Utility software),常被用来构建C程序。make通过读取叫做“makefile”的文件,来自动化建构软件。

虽然make常被用来构建C程序,但它可用于绝大多数的文件处理过程。例如,更新一批图片的缩略图: http://harttle.github.io/2013/10/26/auto-thumb/

它是一种转化文件形式的工具,转换的目标称为“target”; 与此同时,它也检查文件的依赖关系,如果需要的话,它会调用一些外部软件来完成任务。 它的依赖关系检查系统非常简单,主要根据依赖文件的修改时间进行判断。 大多数情况下,它被用来编译源代码,生成结果代码,然后把结果代码连接起来生成可执行文件或者库文件。 它使用叫做Makefile的文件来确定一个target文件的依赖关系,然后把生成这个target的相关命令传给shell去执行。

中文Tutorial

基本知识

编译

对于C语言,产生可执行程序包括这样的步骤:

  1. 预处理源文件(.c
    • 替换预处理命令(如 #define
    • 展开头文件(.h,包括静态链接库的头文件)到引用的源文件
  2. 依次编译处理过的源文件,然后进行汇编,生成对应的目标文件(.o
  3. 链接(静态链接)目标文件和静态链接库(静态链接库的源文件生成的目标文件,.a),生成可执行的二进制文件。

gcc 不会自动链接到静态链接库(libstdio.so除外),需要以参数的形式指定。g++ 会自动解析并进行链接。动态链接库(.so)则是运行时由操作系统解析的。

示例

target: require1 require2
    shell command

make 命令会寻找当前目录下的 makefile,寻找其中的第一条规则,然后执行 shell command 来生成 target。如果发现 requireN 同时也依赖于其他项,则会自动调用其对应的 shell command 并生成它。另外,在整个过程中,make 会比较 targetrequireN 的时间戳,以判断是否需要重新生成。

变量

变量的定义与引用:

# 递归扩展变量,在调用时展开
objects1 = a.o b.o

# 简单扩展变量,在赋值时展开
objects2 = $(objects) c.o

main.o: $(objects)
    gcc -o main.o $(objects2)
  • 如果要在makefile中真实的 $,需要用 $$ 来表示。

  • 注意变量赋值时不能使用通配符。但是在规则中,通配符是可以使用的:

    makefile print: *.c some command

自动化变量

  • $@ 表示规则中的目标文件集。在模式规则中,如果有多个目标,那么,$@就是匹配于目标中模式定义的集合。
  • $% 当目标是函数库文件时,表示规则中的目标成员名。例如,如果一个目标是foo.a (bar.o),那么,$%就是bar.o$@就是foo.a。如果目标不是函数库文件(Unix 下是.a,Windows 下是.lib),那么,其值为空。
  • $< 依赖目标中的第一个目标名字。如果依赖目标是以模式(即%)定义的,那么$<将 是符合模式的一系列的文件集。
  • $? 所有比目标新的依赖目标的集合。以空格分隔。
  • $^ 所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量 会去除重复的依赖目标,只保留一份。
  • $+ 这个变量很像"$^",也是所有依赖目标的集合。只是它不去除重复的依赖目标。
  • $* 这个变量表示目标模式中%及其之前的部分。如果目标是dir/a.foo.b,并且目标的模式是a.%.b,那么,$*的值就是dir/a.foo

貌似共有22个自动化变量,详情请查询:https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html

隐含规则

make 会通过隐含规则自动推导依赖关系,可以帮助我们简化 makefile。例如:

foo : foo.o bar.o 
    cc –o foo foo.o bar.o $(CFLAGS) $(LDFLAGS) 

并没有指明如何生成 .o 文件,这时 make 会生成:

foo.o : foo.c 
    cc –c foo.c $(CFLAGS) 

bar.o : bar.c 
    cc –c bar.c $(CFLAGS)

隐含规则中的编译器与上一级依赖中的相同,因此产生 foo 的规则中,cc 命令是不可省略的。

使用函数

可以使用 make 内建函数来完成某些功能。语法如下:

$(<function> <arguments>) 

如: makefile sources := $(shell ls *.c)

make 中的函数包括:字符串处理,文件名操作,流程控制,call函数,origin函数,shell函数。

多条命令

在同一Make规则中可以执行多条命令,如果第一条出错后续的命令都得不到执行。最典型的情景:

build: foo
    mkdir build
    cp foo build/

build/目录已经存在时mkdir build会返回false,于是cp得不到执行。 为了让cp总可以执行,可以简单地在mkdir build前加一个-来消除这个错误对make的影响:

build: foo
    -mkdir build
    cp foo build/

禁止输出指令

Make在执行每条指令前都会先输出该指令,在命令前添加@可以禁止该行为:

build: foo
    @cp foo build/foo

这样cp foo build/foo就不会被输出了。

进阶技巧

伪目标

伪目标不是一个文件,而只是一个标签,使得依赖关系的书写更加灵活。.PHONY 用来声明伪目标(但不是必须的),这时可以避免 make 去检查同名的文件。例如:

.PHONY: clean 

clean: 
    rm *.o temp

多目标

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 

静态模式

静态模式可以更加容易地定义多目标的规则,语法如下:

<targets ...>: <target-pattern>: <prereq-patterns ...> 
    <commands> 

例如,下面的makefile将对所有的 .o 文件分别生成一条依赖关系。

objects = foo.o bar.o 

all: $(objects) 
$(objects): %.o: %.c 
    $(CC) -c $(CFLAGS) $< -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

总控 makefile

在makefile中,可以执行子文件夹的makefile:

subsystem: 
    $(MAKE) -C subdir

这里的$(MAKE)是为了方便维护make的参数。总控makefile的变量可以传递到下级:

export variable = value

# 或者
variable = value 
export variable 

自动生成依赖

对于每个 .c 文件,我们一般会需要如下的依赖关系:

main.o : main.c defs.h

然而,有时候存在大量的 .c 文件,我们需要得到每个 .c 对应的 .h。借助于 gcc,可以自动探测这些依赖:

sources = main.c stack.c maze.c

-include $(sources:.c=.d)

%.d: %.c
    @set -e; rm -f $@; \
    $(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \
    sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
    rm -f $@.$$$$

如上的makefile定义了 .c 的列表变量,然后载入对应的 .d 文件,这时会有文件不存在的警告产生,此时 make 会采用下面的模式生成 .d

  • 设置shell,发生错误立即退出
  • 得到依赖关系(如 main.o main.c defs.h),存为 .d.XXXXXXXX为当前进程号)
  • sed 替换(例如将 main.o : main.c defs.h 转成 main.o main.d : main.c defs.h),使得 .d 也能得到更新。
  • 得到的新规则存为.d,然后删除临时文件
Harttle

致力于简单的、一致的、高效的前端开发

看看这个?