Linux学习系列目录
前言
本篇文档的参考书籍如下:
1)《GNU make》中文手册,徐海兵翻译;
2)《跟我一起写Makefile》,陈皓。
《GNU make》中文手册是官方GUN make文档的翻译版本,可以作为查找资料使用;陈皓编写的《跟我一起写Makefile》篇幅相对于上一版篇幅较少,由于最近项目需要涉及到Linux应用的开发,以提高工作效率为导向,直接开始学习《跟我一起写Makefile》,下述的文档是记录个人觉得这篇著作中比较重要的部分、个人理解以及实践。
一、概述
大多数IDE工具都在内部集成了make命令,可以一键编译工程;但是在Linux环境下编译工程还是得手写Makefile(也可以使用cmake工具生成Makefile,然后make编译;需要学习《CMakeLists》文件的编写规则),鉴于此,作者以看懂、手写Makefile的目的开启Makefile文档的学习。
Makefile制定了整个工程的编译规则,用户只需要在命令行输入make指令就可使make自动读入makefile,根据其内容完成工程的编译。Makefile和Shell脚本比较像,因为规则的命令是调用shell执行操作系统的命令,因此在命令处可通过shell编程实现复杂的逻辑控制。
Makefile的本质:在文件的依赖关系上做文章。
1.关于程序的编译和链接
通常口头描述的编译是两个过程的集合:编译+链接
- 编译用于将源文件生成中间文件,在Linux中是.o后缀的文件,一般一个源文件对应一个中间文件;
- 链接用于将中间文件合并,生成可执行文件。
关于编译、链接、库等知识,有兴趣的可以阅读《程序员的自我修养:链接,装载与库》,后续作者也将学习这本书籍的知识,文章将存入Linux学习系列。
二、Makefile介绍
1.Makefile规则
targets...:prerequsites...
[Tab]Command
- targets…是一个或多个目标文件,targets也可以是一个标签(如果下面有命令,可看作伪目标);
- prerequsites是生成该target 所依赖的文件;
- [Tab]是Table键,只有这个放在行首,make才会将这一行的后续内容看作命令。
- Command是targets 要执行的命令(任意的shell 命令)
**PS:**所以像写好一个Makefile,还缺少一个元素:Shell编程(目前准备学习《鸟哥的Linux私房菜》中的Chapter13学习Shell Scripts)+《Linux命令行与Shell脚本编程大全》
规则描述如下:
- 如果这个工程没有编译过,那么我们的所有c 文件都要编译并被链接;
- 如果这个工程的某几个c 文件被修改,那么我们只编译被修改的c 文件,并链接目标程序;
- 如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的c 文件,并链接目标程序
#'\'是换行符
edit : main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
#对于没有依赖文件的目标文件的执行,命令行处需要在make命令之后声明目标文件的名称
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
2.make是如何工作的
- make 会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
- 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make 就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make 根本不理。
- make 只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。
意思就是当make找到依赖的源头之后就不干了,接下来就该Shell开秀了。
3.Makefile中使用变量
Makefile中的变量<==>C语言中的宏,会展开。
#使用变量指定文件列表,make命令读入Makefile时会将变量直接展开
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
#'\'是换行符
edit : ${objects}
cc -o edit ${objects}
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
#对于没有依赖文件的目标文件的执行,命令行处需要在make命令之后声明目标文件的名称
clean :
rm edit main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
3.让make自动推导
GNU 的make 很强大,它可以自动推导文件以及文件依赖关系后面的命令。
EX:只要make 看到一个.o 文件,它就会自动的把.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)
cc -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)
PS:make自动推导的命令使用的cc编译器,对于不适用这个编译器的人来说需要手动添加命令。
4.另类风格的Makefiles
由于有多个中间文件(.o)的依赖文件是相同的头文件,因此可以将相同头文件作为依赖的目标合并成一个多目标规则,见下述代码。
objects = main.o kbd.o command.o display.o \
insert.o search.o files.o utils.o
edit : $(objects)
cc -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)
5.清空目标文件的规则
#.PHONY将clean声明为伪目标:防止当目录中存在clean时,它作为目标文件一直是新的,
#其规则定义中的命令就不会执行
.PHONY: clean
clean:
rm edit $(objects)
6.Makefile中有什么
-
显示规则、隐含规则、变量定义、文件指示、注释
-
显式规则:显式规则说明了如何生成一个或多个目标文件。这是由Makefile 的书写者明显指出要生成的文件、文件的依赖文件和生成的命令。
-
隐含规则:由于我们的make 有自动推导的功能,所以隐含的规则可以让我们比较简略地书写Makefile,这是由make 所支持的。
-
文件指示:其包括了三个部分,一个是在一个Makefile 中引用另一个Makefile,就像C 语言中的include 一样;另一个是指根据某些情况指定Makefile 中的有效部分,就像C 语言中的预编译#if一样;还有就是定义一个多行的命令。
7.Makefile的文件名
-
默认情况:make会在当前目录下按照如下优先级去寻找Makefile:GNUmakefile>makefile>Makefile
-
指定情况:Shell在执行make命令时,通过-f选项或–file选项,将后续的文件指定为Makefile,供make命令读取。
8.引用其他的Makefile
在Makefile 使用include 关键字可以把别的Makefile 包含进来,这很像C 语言的#include ,被
包含的文件会原模原样的放在当前文件的包含位置。
include foo.make *.mk $(bar)
make 命令开始时,会找寻include 所指出的其它Makefile,并把其内容安置在当前的位置。就好
像C/C++ 的#include 指令一样。如果文件都没有指定绝对路径或是相对路径的话,make 会在当前目录下首先寻找,如果当前目录下没有找到,那么,make 还会在下面的几个目录下找:
- 如果make 执行时,有-I 或–include-dir 参数,那么make 就会在这个参数所指定的目录下去
寻找。 - 如果目录/include (一般是:/usr/local/bin 或/usr/include )存在的话,make 也
会去找。
如果有文件没有找到的话,make 会生成一条警告信息,但不会马上出现致命错误。
如果你想让make 不理那些无法读取的文件,而继续执行,你可以在include 前加一个减号“-”。
9.环境变量MAKEFILES
建议不要使用:在这里提这个事,只是为了告诉大家,也许有时候你的Makefile 出现了怪事,那么你可以看看当前环境中有没有定义这个变量。
9.make的工作方式
执行步骤如下:
#第一阶段
1. 读入所有的Makefile。
2. 读入被include 的其它Makefile。
3. 初始化文件中的变量。
4. 推导隐含规则,并分析所有规则。
5. 为所有的目标文件创建依赖关系链。
#第二阶段
6. 根据依赖关系,决定哪些目标要重新生成。
7. 执行生成命令。
读取所有Makefile,初始化变量,展开变量,建立目标的依赖关系链,执行shell生成命令。
三、书写规则
规则=依赖关系+生成目标的方法
第一条规则的目标=最终的目标
1.规则的语法
#command要么放在依赖关系后,但以;开始
#command要么单独放在一行,以【tab】键开始
targets : prerequisites ; command
command
...
一般来说,make 会以UNIX 的标准Shell,也就是/bin/sh 来执行命令。
2.在规则中使用通配符
- make 支持三个通配符:* ? ~
- 如果我们的文件名中有通配符,如:* ,那么可以用转义字符\ ,如* 来表示真实的* 字符
#通配符的应用举例
###/*
case1:用于规则中
###*/
print: *.c
lpr -p $?
touch print
###/*
case2.1:用于变量中,此时通配符不会展开,只是作为文本字符串的一个字符
###*/
objects = *.o
###/*
case2.2:要想让通配符在变量中展开,可以按照下例进行书写
###*/
objects:= $(patsubst %.c, %.o, $(wildcard *.c))
foo: $(objects)
gcc -o $@ $^
3.文件搜寻
###/*
case1,若不声明VPATH变量(不是环境变量)或者vpath关键字,make会在当前工作目录下找目标文件
和依赖文件
###*/
###/*
case2,定义VPATH变量时,make在当前工作目录下找不到目标文件和依赖文件时会到VPATH变量
定义的目录下去寻找
###*/
#make会到src目录下,../headers目录下寻找文件
VPATH = src:../headers
###/*
case3,定义vpath关键字时,make在当前工作目录下找不到目标文件和依赖文件时会到vpath关键字
定义的目录下去寻找
###*/
#make找寻.c文件时会先到foo目录下,再到bar目录,最后到blish目录下找文件
#make找寻任意文件时会去blish目录下
vpath %.c foo:bar
vpath % blish
4.伪目标
- 伪目标一般没有依赖文件,如clean:
- 伪目标包含依赖文件时,可以用于给多目标编写规则
###/*
可以将所有不相关的目标文件写在一个Makefile文件中
###*/
all : prog1 prog2 prog3
#使用.PHONY显示声明伪目标,若不书写,make会根据隐含规则推导
.PHONY : all
prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o
prog2 : prog2.o
cc -o prog2 prog2.o
prog3 : prog3.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o
- 伪目标也可以作为依赖文件,和C语言中的程序调用相似
.PHONY : cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
rm program
cleanobj :
rm *.o
cleandiff :
rm *.diff
4.多目标
- 有可能我们的多个目标同时依赖于一个文件,并且其生成的命令大体类似。于是我们就能把其合并起来; @ 表示目标的集合,就像一个数组, @表示目标的集合,就像一个数组, @表示目标的集合,就像一个数组,@依次取出目标,并执于命令。
- $@提供了一种对多目标列表中的不同目标文件执行不同命令的可能。
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
4.静态模式
- 静态模式的语法如下
###/*
下述创建的规则为:目标文件列表中<targets ...> 的<target-pattern> 模式的目标文件,其依赖
文件为<targets ...> 的<prereq-patterns ...>模式的依赖文件
###*/
<targets ...> : <target-pattern> : <prereq-patterns ...>
<commands>
- 举例如下
files = foo.elc bar.o lose.o
$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<
等价于
files = foo.elc bar.o lose.o
all: $(objects)
foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o
foo.elc : foo.el
emacs -f batch-byte-compile $<
4.自动生成依赖关系
由于源文件会包含头文件,因此当头文件改变时,目标文件得更新,那么规则中目标文件的依赖文件列表中必须包含头文件,这样就考验程序员是否能罗列清除头文件。
为了解决这个困扰,选择通过include的方式,将依赖关系直接包含进Makefile中,这样make命令在读取Makefile时,就知道了所有的依赖关系(头文件!!)。
- 本质:大多数的C/C++ 编译器都支持一个“-M”的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。
- 提醒:如果你使用GNU 的C/C++ 编译器,得用-MM 参数,不然,-M参数会把一些标准库的头文件也包含进来。
dep_files := $(foreach f, $(objs), .$(f).d)
#因为后续变量会生成新的.d文件,所以当这次make生成.d文件时,在下次make执行时会包含进来
dep_files := $(wildcard $(dep_files))
# 把依赖文件包含进来,第一次make时,没有.d不会执行,后续make有.d就会执行了。
ifneq ($(dep_files),)
include $(dep_files)
endif
#将生成的依赖关系放到.d文件中
%.o : %.c
gcc -Wp,-MD,.$@.d -c -o $@ $<
四、书写命令
1.显示命令
make会把其要执行的命令行在命令执行前输出到屏幕上。
- 当命令行前有@字符,那么这个命令将不被make显示出来
- make -n或–just-print是只显示命令,但不会执行命令;可以用来观察命令的执行顺序
- make -s或–silent或–quiet是全面禁止命令的显示。
2.命令执行
- 如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令
(这是因为,各个命令是独立的在不同的shell中执行,只有加;分隔才能在一个shell中执行,这样上条命令的结果会应用在下一条命令中) - make 一般是使用环境变量SHELL 中所定义的系统Shell 来执行命令
3.命令出错
- 可在命令行前加一个减号-,将忽略这个命令是否执行成功。
- make -i或–ignore-errors,会忽略Makefile中所有命令的错误
- make -k或–keep-going,会停止执行出错的规则,但是执行其他规则
4.嵌套执行make
在大工程中,会将不同的模块、不同功能的源文件放在不同的目录,这时,可以为每个目录编写Makefile,使每个Makefile文件变得简洁,利于维护,而且利于模块编译和分段编译。
subsystem:
cd subdir && $(MAKE)
等价于
$(MAKE) -C subdir
Makefile中变量的传递:
export#后不接任何标识符表示传递所有变量至下级Makefile并覆盖
export<variable>#传递变量至下级Makefile并覆盖
unexport<variable>#不传递变量至下级Makefile
系统变量SHELL与系统变量MAKEFLAGS的值总是要传递至下级Makefile
MAKEFLAGS是执行make命令或者设置的参数:
- -w 或是 --print-directory参数会在 make 的过程中输出一些信息,让你看到目前的工作目录;
- 参数中有 -s(–slient)或是 --no-print-directory ,那么, -w 总是失效的;
- -C , -f , -h, -o 和 -W这些参数不会传递至下级Makefile。
若不想往下层传递MAKEFLAGS,见下例:
subsystem:
#MAKEFLAGS直接设置为空
cd subdir && $(MAKE) MAKEFLAGS=
5.定义命令包
命令包<==>命令的集合,语法为:以 define 开始,以 endef 结束定义的变量,这个变量就是命令包的接口。
#定义命令包run-yacc
define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef
#执行命令包run-yacc
foo.c : foo.y
$(run-yacc)
五、使用变量
- Makefile中的变量与C/C++中的宏相似(体现在展开上),但是可以更改其值
- 变量是大小写敏感的
1.变量的基础
- 文本替换
- 建议使用时给变量添加括号
2.变量中的变量
- 定义变量时,可以使用其他变量来构造值
a、延时变量 =
foo = $(bar)
bar = $(ugh)
ugh = Huh?
all:
echo $(foo)
将会输出Huh?
用其他变量来构造变量的值时,其他变量可以在后面定义:延时变量,在使用时才展开
b、即时变量 :=
y := $(x) bar
x := foo
#y的值为bar
c、系统变量“MAKELEVEL”:这个变量会记录了我们的当前Makefile 的调用层数。
d、值是一个空格的变量
nullstring :=
space := $(nullstring) # end of the line
#之前有个空格,#代表变量定义的结束,$(变量)代表变量的开始
e、首次使用可定义操作符?=
FOO ?= bar
等价于
ifeq ($(origin FOO), undefined)
FOO = bar
endif
3.变量高级语法
a、变量值的替换
$(var:a=b)
把变量var中的结尾字符串a都换成字符串b
可以使用之前的章节“静态模式”
foo := a.o b.o c.o
bar := $(foo : %.o : %.c)
b、把变量的值再当成变量
x = y
y = z
a := $($(x))
$(x)的值为y,再把y当作变量,使用$(y)获得值z
4.追加变量值
objects = main.o foo.o bar.o utils.o
objects += another.o
等价于
objects = main.o foo.o bar.o utils.o
objects := $(objects) another.o
+=变成:=是因为,若不这样,就会使make递归扩展objects,所以make会自动将其
转换成:=
5.override指示符
Makefile中通过:=、+=、=定义的变量都可以通过命令行重新定义此变量的值,如果不想让命令行改变此变量值或者想对原来的值做追加操作,可以通过override实现此功能。
- :=/=/+=不使用override
test_data = "US"
I_print:
@echo $(test_data)
shell中执行make I_print test_data=better,显示的结果是better;
使用make I_print,是因为得给make命令赋予目标,否则命令不执行
- :=/=使用override
override test_data = "US"
I_print:
@echo $(test_data)
shell中执行make I_print test_data=better,显示的结果是US;
- +=使用override
override test_data += "US"
I_print:
@echo $(test_data)
shell中执行make I_print test_data=better,显示的结果是better US;
在添加编译选项时,可以将公共的选项通过追加的方式定义,可以保证通过命令行添加新的编译选项,实现编译选项的叠加。
6.多行变量
- define变量
define指示符后面跟的是变量的名字,重起一行定义变量的值,定义是endef关键字结束;
变量的值可以包含函数、命令、文字,或是其它变量;。因为命令需要以[Tab]键开头,所以如果你用define 定义的命令变量中没有以Tab 键开头,那么make 就不会把其认为是命令。
define objects
Hello World
endef
Print:
@echo ${objects}
执行make时,会显示Hello World,同时make命令的同时,可以修改objects变量的值
7.环境变量(CFLAGS,目前还未熟悉透)
- make 运行时的系统环境变量可以在make 开始运行时被载入到Makefile 文件中;
- 如果make 命令行带入系统变量值,那么系统的环境变量的值将被覆盖;
- 如果make 指定了“-e”参数,那么,系统环境变量将覆盖Makefile 中定义的变量;
- 如果我们在环境变量中设置了CFLAGS 环境变量,那么我们就可以在所有的Makefile 中使用这个变量了;
- 如果Makefile 中定义了CFLAGS,那么则会使用Makefile 中的这个变量,如果没有定义则使用系统环境变量的值;
- 当make 嵌套调用时,上层Makefile 中定义的变量会以系统环境变量的方式传递到下层Makefile 中。
- 定义在文件中的变量,如果要向下层Makefile 传递,则需要使用exprot 关键字来声明。
8.目标变量
在Makefile 中定义的变量都是“全局变量”,在整个文件,我们都可以访问这些变量,“自动化变量”除外。
可以为某个目标设置局部变量,当局部变量与全局变量标识符相同时,采用局部变量处理规则内部,语法与作用如下:
prog : CFLAGS = -g
prog : prog.o foo.o bar.o
$(CC) $(CFLAGS) prog.o foo.o bar.o
prog.o : prog.c
$(CC) $(CFLAGS) prog.c
foo.o : foo.c
$(CC) $(CFLAGS) foo.c
bar.o : bar.c
$(CC) $(CFLAGS) bar.c
为prog指定了局部变量CFLAGS的值为-g,在prog这个目标的所有规则中,CFLAGS的值保持不变。
9.模式变量
生产目标变量:我们可以给定一种“模式”,可以把变量定义在符合这种模式的所有目标上。
%.o : CFLAGS = -O
会把所有带.o后缀文件的目标变量CFLAGS参数设置为-o
六、使用条件判断
使用条件判断,可以让make 根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较
变量的值,或是比较变量和常量的值。
1.示例
libs_for_gcc = -lgnu
normal_libs =
foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif
判断$(CC) 变量是否gcc
2.语法
1)ifeq (<arg1>, <arg2>)
2)ifneq (<arg1>, <arg2>)
3)ifdef <variable-name>#ifdef 只是测试一个变量是否有值
4)ifndef <variable-name>
PS:不要把自动化变量放到条件表达式中,因为自动化变量是在运行时才有的。
七、使用函数
1.函数调用的语法
$(<function> <arguments>)
函数名和参数之间以“空格”分隔;
参数间以逗号, 分隔。
2.字符串处理函数
$(subst <from>,<to>,<text>) 把字串<text> 中的<from> 字符串替换成<to>
$(patsubst <pattern>,<replacement>,<text>)
查找<text> 中的单词是否符合模式<pattern> ,如果匹配的话,则以<replacement> 替换。
$(strip <string>)
去掉<string> 字串中开头和结尾的空字符;将字符串中间连续的空格缩减为1个
$(findstring <find>,<in>)
在字串<in> 中查找<find> 字串。
$(filter <pattern...>,<text>)
以<pattern> 模式过滤<text> 字符串中的单词,保留符合模式<pattern> 的单词。可以有多个模式。
$(filter-out <pattern...>,<text>)
以<pattern> 模式过滤<text> 字符串中的单词,去除符合模式<pattern> 的单词。可以有多个模式。
$(sort <list>)
给字符串<list> 中的单词排序(升序)。
$(word <n>,<text>)
取字符串<text> 中第<n> 个单词。(从一开始);
如果<n> 比<text> 中的单词数要大,那么返回空字符串。
$(wordlist <ss>,<e>,<text>)
从字符串<text> 中取从<ss> 开始到<e> 的单词串。<ss> 和<e> 是一个数字。
如果<ss> 比<text> 中的单词数要大,那么返回空字符串。如果<e> 大于<text> 的单词数,
那么返回从<ss> 开始,到<text> 结束的单词串。
$(words <text>)
统计<text> 中字符串中的单词个数。
$(firstword <text>)
取字符串<text> 中的第一个单词。
3.文件名操作函数
$(dir <names...>)
从文件名序列<names> 中取出目录部分。目录部分是指最后一个反斜杠(/ )之前的部分。
如果没有反斜杠,那么返回./ 。
$(dir src/foo.c hacks) 返回值是src/ ./
$(notdir <names...>)
从文件名序列<names> 中取出非目录部分。非目录部分是指最後一个反斜杠(/ )之后的部分。
$(suffix <names...>)
从文件名序列<names> 中取出各个文件名的后缀。
$(basename <names...>)
从文件名序列<names> 中取出各个文件名的前缀部分。
$(addsuffix <suffix>,<names...>)
把后缀<suffix> 加到<names> 中的每个单词后面。
$(addprefix <prefix>,<names...>)
把前缀<prefix> 加到<names> 中的每个单词后面。
$(join <list1>,<list2>)
把<list2> 中的单词对应地加到<list1> 的单词后面。
示例:$(join aaa bbb , 111 222 333) 返回值是aaa111 bbb222 333 。
4.foreach函数
foreach 函数是用来做循环用的;
$(foreach <var>,<list>,<text>)
这个函数的意思是,把参数(list)中的单词逐一取出放到参数(variable) 所指定的变量中,然后再执行(text) 所包含的表达式。循环过程中,(text) 的所返回的每个符串会以空格分隔,最后当整个循环结束时,(text) 所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach 函数的返回值。
names := a b c d
files := $(foreach n,$(names),$(n).o)
返回为a.o b.o c.o d.o
5.if函数
$(if <condition>,<then-part>)
$(if <condition>,<then-part>,<else-part>)
如果(condition) 为真(非空字符串),那个(then-part)会是整个函数的返回值,如果(condition) 为假(空字符串),那么(else-part) 会是整个函数的返回值,此时如果(else-part) 没有被定义,那么,整个函数返回空字串。
6.call函数
$(call <expression>,<parm1>,<parm2>,...,<parmn>)
当make 执行这个函数时,<expression> 参数中的变量,如$(1) 、$(2) 等,会被参数<parm1> 、
<parm2> 、<parm3> 依次取代
7.origin函数
origin 函数不像其它的函数,他并不操作变量的值,他只是告诉你你的这个变量是哪里来的.
$(origin <variable>)
8.shell函数
执行/bash/shell处理,将处理结果作为函数的返回值
contents := $(shell cat foo)
八、make的运行
1.重要的参数
- -n, --just-print, --dry-run, --recon 不执行参数,这些参数只是打印命令,不管目标是否更新,把 规则和连带规则下的命令打印出来,但不执行,这些参数对于我们调试makefile 很有用处。
- -W file, --what-if=file, --assume-new=file, --new-file=file 这个参数需要指定一个文件。一般是源文件(或依赖文件),Make会根据规则推导来运行依赖于这个文件的命令,一般来说,可以和“-n”参数一同使用,来查看这个依赖文件所发生的规则命令。
- -C dir, --directory=dir 指定读取makefile 的目录。
- -d 相当于“–debug=a”,输出所有调试信息。
总结
看完了,其中关于隐含规则等知识只理解了其中部分的模式规则,有需要的可以继续学习。
学习完结之后,开始做联系,由于编程习惯是各个模块单独存放在一个目录,因此将做一个关于多目录的Makefile实例进行联系,参考文档如下:
多目录Makefile实例