Linux学习系列----Makefile

Linux学习系列目录

Linux学习系列—Makefile

前言

本篇文档的参考书籍如下:
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.关于程序的编译和链接

通常口头描述的编译是两个过程的集合:编译+链接

  1. 编译用于将源文件生成中间文件,在Linux中是.o后缀的文件,一般一个源文件对应一个中间文件;
  2. 链接用于将中间文件合并,生成可执行文件。

关于编译、链接、库等知识,有兴趣的可以阅读《程序员的自我修养:链接,装载与库》,后续作者也将学习这本书籍的知识,文章将存入Linux学习系列。

二、Makefile介绍

1.Makefile规则

targets...:prerequsites...
[Tab]Command
  1. targets…是一个或多个目标文件,targets也可以是一个标签(如果下面有命令,可看作伪目标);
  2. prerequsites是生成该target 所依赖的文件;
  3. [Tab]是Table键,只有这个放在行首,make才会将这一行的后续内容看作命令。
  4. 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是如何工作的

  1. make 会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。
  2. 在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make 就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make 根本不理。
  3. 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 还会在下面的几个目录下找:

  1. 如果make 执行时,有-I 或–include-dir 参数,那么make 就会在这个参数所指定的目录下去
    寻找。
  2. 如果目录/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实例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值