addprefix函数
addprefix函数被用于给名字列表_names中的每一个名字增加前缀_prefix,并将增加了前缀的名字列表返回。其形式:
$(addprefix _prefix, _names)
如下示例使用:
.PHONY : all without_dir = foo.c bar.c main.o with_dir := $(addprefix objs/, $(without_dir)) all: @echo $(with_dir)
执行结果如下:
addsuffix函数
addsuffix函数被用于给名字列表_names中的每个名字增加后缀_suffix,并将增加了后缀_suffix的名字列表返回。其形式如下:
$(addsuffix _suffix, _names)
使用示例:
.PHONY : all without_suffix = foo bar main with_suffix := $(addsuffix .c, $(without_suffix)) all: @echo $(with_suffix)
执行如下:
eval函数
eval函数的存在使得Makefile具有动态语言的特征。eval函数使得make将再一次解析_text语句,eval函数返回值为空字符串,形式如下:
$(eval _text)
如下示例说明了它的用法:
.PHONY : all sources = foo.c bar.c baz.s ugh.h $(eval sources := $(filter %.c %.s, $(sources))) all: @echo $(sources)
执行结果如下:
filter函数
filter函数被用于从一个 名字列表 _text中根据模式_pattern得到满足需要的名字列表并返回。其形式:
$(filter _pattern, _text)
如下示例使用方式:
.PHONY : all sources = foo.c bar.c baz.s ugh.h sources := $(filter %.c %.s, $(sources)) all: @echo $(sources)
执行结果如下:
filter-out函数
filter-out函数被用于从名字列表_text中根据模式_pattern滤除一部分名字,并将滤除后的列表返回。其形式如下:
$(filter-out _pattern, _text)
如下示例说明了它的用法:
.PHONY : all objects = main1.o foo.o main2.o bar.o result = $(filter-out main%.o, $(objects)) all: @echo $(result)
执行结果如下:
notdir函数
notdir函数被用来从路径_names中抽取文件名,并将文件名返回。其形式如下:
$(notdir _names)
如下示例说明了它的用法:
.PHONY : all file_name := $(notdir code/foo/src/foo.c code/bar/src/bar.c) all: @echo $(file_name)
执行结果:
patsubst函数
patsubst函数被用来将名字列表_text中符合_pattern模式的名字替换为_replacement,并将替换后的名字列表返回。其使用形式如下:
$(patsubst _pattern, _replacement, _text)
如下示例说明了它的使用方法,这里采用patsubst函数进行字符串替换,将所有.c结尾的名字替换为.o结尾的文件名。
.PHONY : all mixed = foo.c bar.c main.o objects := $(patsubst %.c, %.o, $(mixed) all: @echo $(objects)
执行结果如下:
#### realpath函数
realpath函数被用于获取_names所对应的真实路径名。其形式如下:
$(realpath _names)
如下示例说明了它的用法:
.PHONY : all ROOT := $(realpath ./..) all: @echo $(ROOT)
执行结果如下:
strip函数
如果希望清除名字列表中的多余空格,strip函数就是最好的选择。strip函数将_string中的多余空格去除后返回。形式如下:
$(strip _string)
示例使用如下:
.PHONY : all original = foo.c bar.c stripped := $(strip $(original)) all: @echo "original = $(original)" @echo "stripped = $(stripped)"
执行后结果如下:
wildcard函数
wildcard是通配符函数,通过它可以得到当前工作目录中满足_pattern模式的文件或目录名列表。形式如下:
$(wildcard _pattern)
如下示例说明了Makefile通过使用wildcard函数得到所以C源文件的名字列表。
.PHONY : all SRCS = $(wildcard *.c) all: @echo $(SRCS)
执行结果如下:
提高编译环境的实用性
之前的Makefile示例也只是为了介绍Makefile中可以用到的各种规则、变量、函数的使用方法。接下来,我们来构建一个略微复杂的虚拟项目——complicated项目,该项目中用到源代码如下:
- foo.h
#ifndef _FOO_H #define _FOO_H void foo(); #endif
- foo.c
#include <stdio.h> #include "foo.h" void foo() { printf("this is foo()!\n"); }
- main.c
#include "foo.h" int main() { foo(); return 0; }
让编译环境更加有序
大多的软件项目都会通过合理地设计目录结构来提高它的可维护性。在编译一个项目时会产生大量的中间文件,如果中间文件与项目的源程序文件直接混放在一起,就会显得混乱不堪而不利于维护。
这里,我们通过使用目录来使得编译环境更加有序。目录的引入并不是一步到位的,我们还将在后面的项目再进一步探讨这个问题。首先,在编写Makefile之前,需要先了解对目录结构的需求,包括:
- 将所有的目录文件放入objs子目录中。
- 将最终生成的可执行程序放入exes子目录中。
目录的自动创建与删除
在编译目录职期间按,需要将存放生成文件的目录准备好。目录可以在项目编译之前通过手工去创建,但是我们更喜欢在编译过程中自动生成的方式。
要实现在编译过程中自动创建目录,需要记住一点:目录也是一个目标。具有自动创建目录的Makefile和其运行结果如图所示:
.PHONY: all MKDIR = mkdir DIRS = objs exes all: $(DIRS) $(DIRS): $(MKDIR) $@
接下来增加一个clean目标用于清除编译目标目录,如下:
.PHONY: all MKDIR = mkdir RM = rm RMFLAGS = -rf DIRS = objs exes all: $(DIRS) $(DIRS): $(MKDIR) $@ clean: $(RM) $(RMFLAGS) $(DIRS)
通过目录管理文件
为了将项目编译时所创建的文件分别放入objs和exes目录中,需要用到Makefile中的一个函数——addprefix。如下修改:
.PHONY: all MKDIR = mkdir RM = rm RMFLAGS = -rf CC = gcc DIR_OBJS = objs DIR_EXES = exes DIRS = $(DIR_OBJS) $(DIR_EXES) EXE = complicated SRCS = $(wildcard *.c) OBJS = $(SRCS:.c=.o) OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS)) all: $(DIRS) $(EXE) $(DIRS): $(MKDIR) $@ $(EXE): $(OBJS) $(CC) -o $@ $^ $(DIR_OBJS)/%.o: %.c $(CC) -o $@ -c $^ clean: $(RM) $(RMFLAGS) $(DIRS) $(EXE)
上面的修改主要有三种变化:
- 通过运用addprefix函数,为每一个生成的目标文件加上“objs/”前缀,以便生成的文件放入objs目录中。
- 在构建目标文件的规则中为目标名字机上“objs/”前缀,即增加“$(*DIR_OBJS)/”前缀。
- 在clean规则的命令中增加对$(EXE)目标的删除。
提升依赖关系管理
现在假设对项目已经进行了一次成功的编译,这一点很重要,否则看不到现有的Makefile存在的问题。接着,将foo.h文件内容进行修改,但是不修改foo.c文件。理论上,头文件和源文件函数声明定义不同应该会编译出错。
当修改后执行make后,结果居然是告诉我们没有什么事可做,此时如果执行make clean后重新make则发现会报错:
那么是为什么make不能发现foo.h已经更改而进行重新编译呢。从Makefile文件中的内容能看出来它并不知道有foo.h文件的存在,而之所以编译会用到foo.h是因为foo.c和main.c中包含了头文件。如下:
为了修复这一点,最简单直接的办法就是将foo.h文件通过依赖关系树纳入make的视野中,改动后的Makefile如下所示:
.PHONY : all clean MKDIR = mkdir RM = rm RMFLAGS = -rf CC = gcc DIR_OBJS = objs DIR_EXES = exes DIRS = $(DIR_OBJS) $(DIR_EXES) EXE = complicated SRCS = $(wildcard *.c) OBJS = $(SRCS:.c=.o) OBJS := $(addprefix $(DIR_OBJS)/, $(OBJS)) all: $(DIRS) $(EXE) $(DIRS): $(MKDIR) $@ $(EXE): $(OBJS) $(CC) -o $@ $^ $(DIR_OBJS)/%.o: %.c foo.h $(CC) -o $@ -c $< clean: $(RM) $(RMFLAGS) $(DIRS) $(EXE)
执行结果如下:
执行后正常,但是如果将每一个头文件都写入到Makefile的相应规则中,那必然很不妥当。