GNU Makefile

目录

1、make 的行为

1.1、找文件

1.2、执行 Makefile

2、目标

3、格式

4、Makefile 包含其他 Makefile

5、环境变量 MAKEFILES

6、Wildcard 通配符的使用

6.1、命令中使用通配符

6.2、依赖中使用通配符

6.3、变量定义中使用通配符

6.3.1、使用 *

6.3.2、使用 %

6.3.3、* 和 % 区别

7、伪目标 PHONY

7.1、没有依赖的伪目标

7.2、有依赖的伪目标

7.3、伪目标依赖伪目标

8、使用变量

8.1、变量的引用

8.2、环境变量

8.3、内置变量/默认变量(default)

8.4、自动变量

8.5、Makefile 中自行定义变量

8.4.1、"="

8.4.2、":="

8.4.3、"?="

8.4.4、"=+"

8.4.5、变量替换

8.4.6、变量的传递

9、多 Makefile 层次嵌套

10、条件判断

10.1、ifeq 和 ifneq

10.2、ifdef 和 ifndef

11、使用函数

11.1、字符串处理函数

11.1.1、subst

11.1.2、patsubst

11.1.3、strip

11.1.4、findstring

11.1.5、filter

11.1.6、filter-out

11.1.7、sort

11.1.8、word

11.1.9、wordlist

11.1.10、words

11.1.11、firstword

11.2、文件名相关函数

11.2.1、dir

11.2.2、notdir

11.2.3、suffix

11.2.4、basename

11.2.5、addsuffix

11.2.6、addprefix

11.2.7、join

11.2.8、wildcard

11.3、循环函数

11.3.1、foreach

11.4、条件函数

11.4.1、if

11.5、创建函数

11.5.1、call

11.6、origin 函数

11.7、shell 函数

12、make 命令参数和选项


从工作就接触 make,但还从未仔仔细细看过官方的文档描述和说明,有些小遗憾,现在补充补充;哈哈;好吧,我们开始!

GNU 官方的 make Manual:Top (GNU make)Top (GNU make)https://www.gnu.org/software/make/manual/html_node/index.html

最好最好还是读一读,虽然常用到,但是,总有一些很小的细节需要注意,稍不注意就可能遗漏;

Makefile 本质上来说,是一个用于描述代码的编译、链接、生成最终目标文件的过程和相互依赖的一个文件;当你写好一个 Makefile,并在终端键入 make 指令的时候,此刻,就会按照 Makefile 中指定的规则(依赖)去生成目标文件;

陈皓大神以前写过一篇《跟我一起写 Makefile —— 陈皓》的 pdf,内容非常好,里面也总结了很多于上面这个 GNU 官方的链接的内容,强烈推荐也阅读下这位大佬的文章;

1、make 的行为

当你在终端下敲下 make 的时候,他的行为是什么呢?

1.1、找文件

首先,他会先找当前目录下名为 Makefile 的文件;找到了就用这个文件作为编译、链接的依据;

如果没有找到 Makefile,那么就找名为 makefile 的文件;

1.2、执行 Makefile

找到 Makefile 或者 makefile 后,便开始执行他里面的内容:

1. 找到第一个目标,作为整个 Makefile 的目标

2. 分析这个目标的依赖,并执行这个目标所定义的命令

3. 如果这些依赖,还存在其他依赖,那么继续进行展开;

直到最终编译出第一个目标文件;

注意事项:当然,你也可以非主流的使用 Makefile,就是你定义你的 Makefile 的名字为:Make.Code,或者什么其他奇葩的名字,都行,在编译的时候,使用:make -f 这样的命令来指定 Makefile,比如:make -f Make.Code;

当然,并不推荐这样做,最好是定义 Makefile 这个文件;

OK,说完这个文件的命名,接着说下面的内容,这里,我们默认都遵守规矩,都命名为 Makefile;

2、目标

上面的部分,提到一个字眼,就是目标(target),目标简单的说,就是期望生成的内容,比如,你编译 3 个文件,期望生成一个叫 hello 的文件,那么这个 hello 就是我们的目标;

同样,上面提到一个叫依赖关系的字眼,依赖就是,你这个目标,需要包含哪些文件,比如,你要生成一个可执行程序 hello,那么你需要包含一些 object 文件,这些 object 文件就是这个 hello 的依赖;同样,每个 object 文件也可能依赖一些 .c 或者 .h 文件,这些 .c 和 .h 就是这个 object 文件的依赖;

3、格式

Makefile 的基本格式如下:

target ... : prerequisites ...

        command

        ...

在一个 Makefile 中,可以有多个上述这样的组合存在,这种组合用于描述目标、依赖和目标的生成规则;下面我们就来讲述这三个玩意的含义:

target 指的是目标文件,它可以是 object 文件,也可以是最终生成的可执行文件,也可以仅仅是一个标签;

prerequisites 指的是要生成 target 目标所需要的依赖文件;

command 指的是需要执行的 Shell 命令(注意,这里使用 TAB,而不是 Space);

注意:

这里,我们使用 GNU 官方针对 Makefile,由一个例子引出来:

Here is a straightforward makefile that describes the way an executable file called edit depends on eight object files which, in turn, depend on eight C source and three header files.

In this example, all the C files include defs.h, but only those defining editing commands include command.h, and only low level files that change the editor buffer include buffer.h.

我们构建一个和 GNU 一样的例子出来:

 GNU 的这个 Makefile 的内容为如下:

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

clean :
	rm edit main.o kbd.o command.o display.o \
	insert.o search.o files.o utils.o

可以看到这 Makefile 里面有很多个前面描述的组合,最终生成的目标是 edit ,可执行文件,其中这个 edit 文件依赖了一大堆 .o 的中间文件,当依赖的中间文件全部 Ready 后,会执行下面的:

cc -o edit main.o kbd.o command.o display.o \
	insert.o search.o files.o utils.o

即,把这些 .o 文件链接,生成 edit 目标文件;

而这些个 .o 文件又有各自的依赖文件,比如:

main.o : main.c defs.h
	cc -c main.c

所以,当你在终端敲入 make 的时候呢,它的行为是解析了这个 Makefile,然后根据依赖,进行先编译 C 文件成为 .o 文件,等 edit  这个目标依赖的 .o 都编译完成后,进行链接,最终形成 edit;

4、Makefile 包含其他 Makefile

在 Makefile 中,可以使用关键字 include ,来包含其他的 Makefile;他的语法是:

include <filename>

如果 make 在一个 Makefile 中,读到了 include,那么 make 就会暂停当前 Makefile 的读取和动作,转而将这个 include 的 Makefile 给读出来执行;

换句话来说,你可以简单的理解成为,如果一个 Makefile A 里面,使用了 include ,包含了其他的 Makefile B,那么在 include 的这一行,相当于把 Makefile B 直接在这一行展开了;

比如:

include aaa.mk bbb.mk

5、环境变量 MAKEFILES

如果当前环境中,定义了环境变量 MAKEFILES 的话,你一定要注意了,在 make 的时候,这个环境变量定义的内容也会被包括进来,如果有时候,你觉得你的 Makefile 总是有怪怪的行为,那么看看你的环境变量里面有没有这个玩意;

6、Wildcard 通配符的使用

做软件的朋友们应该都知道,我们为了提高效率,常常使用通配符,比如 *、?,这种字符,同样,在我们的 Makefile 中也是一样的,为了提高效率,也经常使用通配符;

打个比方,如果一个目录下,有很多 c 文件,你的目标文件需要这些所有的 c 文件一起编译生成;那么你把所有的 .c 文件全部列出来,是不是感觉,缺了点啥高端操作?好吧,我们来看看 Makefile 里面的通配符的使用:

6.1、命令中使用通配符

还记得我们的 Makefile 的规则吗?

target ... : prerequisites ...

        command

        ...

我们可以直接在 command 中,使用通配符,比如(官方的例子):

clean:
        rm -f *.o

因为这是 command,会类似像 Shell 一样去执行;

6.2、依赖中使用通配符

print: *.c
        lpr -p $?
        touch print

这个print目标依赖的所有的 .c 文件,所以这里直接使用 *.c;

6.3、变量定义中使用通配符

6.3.1、使用 *

这个需要特别特别的注意了,你肯定会写成下面这种:

objects = *.o

这种写法是错误的,因为这样写了后,objects 这个变量,相当于直接赋值成为了 “*.o”这个字符串了,记住记住,是字符串,字符串,如果我们想要真正的当前的所有 .o 文件,在变量赋值的时候,正确写法需要使用 wildcard 函数:

objects := $(wildcard *.o)

不得不多说一句,* 这种通配符,是针对我们系统;什么叫针对系统,比如,你一个 Makefile 的文件夹下面,有 test1.c、test2.c、test3.c,那么你使用:

src := $(wildcard *.c)

可以得到这个 Makefile 目录下的所有 .c的所有文件;这个是 Shell 目录下的 * 通配符;

6.3.2、使用 %

% 这种通配符,是 Makefile 语法的通配符,是仅限于这个 Makefile 文件推导后的通配符;虽然都是通配符,% 和 * 用法有点不一样,但是如果你把你的 Makefile 里面写入下面:

%.o:%.c
    gcc -o $@ $<

你去执行 make,会出现如下:

Make: *** target not found. stop.

根本原因是:使用 % 通配符,他不是去系统目录下面去找文件,而是看本 Makefile 里面是否有推导出来这个玩意;

所以,改法如下:

all:$(subst .c,.o,$(wildcard *.c))
 
%.o:%.c
    gcc -o $@ $<

上面这段的意义是,先使用 $(wildcard *.c) 去找 Makefile 目录下的所有 .c 文件,然后使用 subst ,将这个 .c 后缀改为 .o 后缀;

这样,Makefile 在推导的时候,会先推导了 all 这个 target 的依赖,得到了这些个 .o,后面就知道了 %o 和 %c 是啥了;

6.3.3、* 和 % 区别

总结起来,他们俩都是通配符,* 针对的是系统目录,文件,行为;而 % 是针对本 Makefile 文件内,经过推导后的变量行为;

也可以参考一下:

Makefile中的%标记和系统通配符*的区别 - 大火人 - 博客园

7、伪目标 PHONY

前面用了伪目标,现在在来好好说一下这个伪目标;

完全可以从字面上理解这个伪目标,他不是 Makefile 里面的一个真正的目标;比如我们 Makefile 都需要一个 clean 的操作,我们可以把它设置成为一个伪目标:

.PHONY : clean
clean :
	rm *.o temp

7.1、没有依赖的伪目标

通常的,类似于我们的 clean 这种伪目标,它是没有任何依赖的,因为它本身就不是目标;这个完全可以理解;用法和上面一样,不再多说了;

7.2、有依赖的伪目标

伪目标,虽然是假的,但是呢,有一种用法还是可以有依赖,就是,如果你想一次性生成多个目标文件,比如,你在一个 Makefile 中,想 make 一下后,生成很多和可执行的文件,那怎么做呢?可以使用如下的方式:

all : prog1 prog2 prog3
.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

伪目标也可以作为默认的 Makefile 的目标(就是你直接敲 make,然后就会找寻第一个目标成为默认的目标),前提是你要把他放到其他的目标前面

这个例子中,默认目标是 all,是一个伪目标,他依赖了 3 个目标,其实我们就可以通过这样的方式,一次性生成 3 个目标文件;

7.3、伪目标依赖伪目标

伪目标,还可以依赖伪目标,说起来有点绕口,我们直接看官方的例子:

.PHONY: cleanall cleanobj cleandiff

cleanall : cleanobj cleandiff
        rm program

cleanobj :
        rm *.o

cleandiff :
        rm *.diff

可以看到:

1. cleanall cleanobj cleandiff 都被定义成为了伪目标;

2. cleanall 这个伪目标,又依赖了 cleanobj cleandiff 这两个伪目标;

那么,如果你输入 make cleanall,就会触发 cleanobj 和 cleandiff,去删除 *.o 和 *.dff,然后删除 program;

当然,你也可以输入 cleandiff 去删除 *.dff

或者输入 cleanobj 去删除 *.o;

用法比较灵活吧,哈哈;

8、使用变量

Makefile 里面使用变量,绝对是很常用的提升效率的方法,便于维护,结构清晰;灵活使用

这里的变量,我希望扩展出来,不仅仅是 Makefile 文件中定义的变量,GNU make/Makefile 中的变量分为很多类型:

1、环境变量;

2、内置变量;

3、自动变量;

4、Makefile 中定义的变量;

8.1、变量的引用

除了自动化变量,其他变量引用方式是用 $(变量) 或者 ${变量};

8.2、环境变量

在make开始运行时被载入到Makefile文件中,但是如果Makefile中已定义了这个变量,或是这个变量由make命令行带入,那么系统的环境变量的值将被覆盖。(如果make指定了“-e”参数,那么,系统环境变量将覆盖Makefile中定义的变量)比如:PATH,PWD,和前面的 MAKEFILES;

8.3、内置变量/默认变量(default)

make/Makefile 还有很多内置的变量,或者称为默认变量(default),可以在 Shell 环境,通过 make -p 打印出来:
这些内置变量可以直接在 Makefile 中使用,比较常用的内置变量有:

CURDIR:当前目录

MAKE:指代 make

SHELL:当前的 Shell

RM:指代 rm -f

CC:指代 cc,当前 C 编译器

LD:当前的 ld

.........

上面只列举了很少一部分内容, 有兴趣的可以使用在线的 Shell ,往里面敲 make -p 试试看:

菜鸟教程在线编辑器https://www.runoob.com/try/runcode.php?filename=helloworld&type=bash

打印出来的内容差不多是这样的:

# GNU Make 4.2.1
# Built for x86_64-pc-linux-gnu
# Copyright (C) 1988-2016 Free Software Foundation, Inc.
# License GPLv3+: GNU GPL version 3 or later 
# This is free software: you are free to change and redistribute it.
# There is NO WARRANTY, to the extent permitted by law.

# Make data base, printed on Wed Feb  9 05:53:57 2022

# Variables

# automatic
# automatic
?F = $(notdir $?)
# default
.SHELLFLAGS := -c
# default
CWEAVE = cweave
# automatic
?D = $(patsubst %/,%,$(dir $?))
# automatic
@D = $(patsubst %/,%,$(dir $@))
# automatic
@F = $(notdir $@)
# makefile
CURDIR := /box
# default
SHELL := /bin/sh
# default
RM = rm -f
# default
CO = co
# environment
_ = /usr/bin/make
# default
PREPROCESS.F = $(FC) $(FFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -F
# default
LINK.m = $(OBJC) $(OBJCFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)
# default
LINK.o = $(CC) $(LDFLAGS) $(TARGET_ARCH)
# default
OUTPUT_OPTION = -o $@
# default
COMPILE.cpp = $(COMPILE.cc)
# makefile
MAKEFILE_LIST := 
# 'override' directive
GNUMAKEFLAGS := 
# default
LINK.p = $(PC) $(PFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)
# default
CC = cc
# default
CHECKOUT,v = +$(if $(wildcard $@),,$(CO) $(COFLAGS) $< $@)
# environment
LIBC_FATAL_STDERR_ = 1
# environment
JUDGE0_HOMEPAGE = https://judge0.com
# default
CPP = $(CC) -E
# default
LINK.cc = $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)
# default
MAKE_HOST := x86_64-pc-linux-gnu
# environment
PATH = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# default
LD = ld
# default
TEXI2DVI = texi2dvi
# default
YACC = yacc
# default
COMPILE.mod = $(M2C) $(M2FLAGS) $(MODFLAGS) $(TARGET_ARCH)
# default
ARFLAGS = rv
# default
LINK.r = $(FC) $(FFLAGS) $(RFLAGS) $(LDFLAGS) $(TARGET_ARCH)
# default
LINT = lint
# default
COMPILE.f = $(FC) $(FFLAGS) $(TARGET_ARCH) -c
# default
LINT.c = $(LINT) $(LINTFLAGS) $(CPPFLAGS) $(TARGET_ARCH)
# default
YACC.m = $(YACC) $(YFLAGS)
# default
YACC.y = $(YACC) $(YFLAGS)
# default
AR = ar
# default
.FEATURES := target-specific order-only second-expansion else-if shortest-stem undefine oneshell archives jobserver output-sync check-symlink load
# default
TANGLE = tangle
# default
GET = get
# automatic
%F = $(notdir $%)
# default
COMPILE.F = $(FC) $(FFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
# default
CTANGLE = ctangle
# default
.LIBPATTERNS = lib%.so lib%.a
# default
LINK.C = $(LINK.cc)
# environment
PWD = /box
# default
.LOADED := 
# default
LINK.S = $(CC) $(ASFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_MACH)
# default
PREPROCESS.r = $(FC) $(FFLAGS) $(RFLAGS) $(TARGET_ARCH) -F
# default
LINK.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) $(TARGET_ARCH)
# default
LINK.s = $(CC) $(ASFLAGS) $(LDFLAGS) $(TARGET_MACH)
# environment
HOME = /tmp
# automatic
^D = $(patsubst %/,%,$(dir $^))
# environment
JUDGE0_VERSION = 1.12.0
# environment
MAKELEVEL := 0
# default
COMPILE.m = $(OBJC) $(OBJCFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
# default
MAKE = $(MAKE_COMMAND)
# environment
SHLVL = 2
# default
AS = as
# default
PREPROCESS.S = $(CC) -E $(CPPFLAGS)
# default
COMPILE.p = $(PC) $(PFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
# default
MAKE_VERSION := 4.2.1
# default
FC = f77
# makefile
.DEFAULT_GOAL := 
# automatic
%D = $(patsubst %/,%,$(dir $%))
# default
WEAVE = weave
# default
MAKE_COMMAND := make

其中,明确表示了变量的类型,写着 automatic 的是自动变量,写着 default 的是默认变量,写着 environment 的是环境变量;

8.4、自动变量

Makefile 定义了一串自动化变量,用起来也非常方便:

$@:表示目标文件的名称,包含扩展名
$^:表示所有的依赖文件,以空格隔开,不重复
$<:表示第一个依赖文件的名称
$+:表示所有的依赖文件,空格隔开,可以重复
$*:表示目标文件的名称,不包含扩展名
$?:依赖项中,所有比目标文件新的依赖文件

打个比方:

test : test.o test1.o test2.o
         gcc -o $@ $^
test.o : test.c test.h
         gcc -o $@ $<
test1.o : test1.c test1.h
         gcc -o $@ $<
test2.o : test2.c test2.h
         gcc -o $@ $<

这个规则模式中用到了 "$@" 、"$<" 和 "$^" 这三个自动化变量:

每一个 "$@" 都代表的是目标文件

“$^” 代表的是所有依赖的文件,展开也就是 test.o test1.o test2.o

“$<”代表的是依赖文件中的第一个,也就是那些个 .c 的文件;

我们在执行 make 的时候,make 会自动识别命令中的自动化变量,并自动实现自动化变量中的值的替换。
 

8.5、Makefile 中自行定义变量

除了上面这些已经有了的,可以直接使用的变量以外,我们还可以在 Makefile 中自己定义想要用到的变量;定义变量有几种方式:

  •  简单赋值 ( := ) 编程语言中常规理解的赋值方式,只对当前语句的变量有效。
  •  递归赋值 ( = ) 变量的值是整个Makefile中最后被指定的值。
  •  条件赋值 ( ?= ) 如果变量未定义,则使用符号中的值定义变量。如果该变量已经赋值,则该赋值语句无效。
  •  追加赋值 ( += ) 原变量用空格隔开的方式追加一个新值。

8.4.1、"="

比较常用的使用 "=" 号定义一个变量:

objects = program.o foo.o utils.o
program : $(objects)
        cc -o program $(objects)

$(objects) : defs.h

引用变量时候可以用  $(objects) 或者是 ${objects};

变量 objects 会在 $(objects) 的地方,精确的展开;

有时候,= 号赋值,容易被搞错,比如:

x = foo
y = $(x) bar
x = later

最后 y 的值是 later bar,而不是 foo bar,因为 Makefile 展开后,会以最后一个赋值的 x 当做最终的值;

8.4.2、":="

:= 这种赋值语句,就和我们正常理解的 C 语言的变量赋值含义完全一样,举个例子:

x := foo
y := $(x) bar
x := later

和上面的 = 号赋值不一样,使用 := 这种赋值,可以理解为顺序执行,y 的值是 foo bar,x 的值开始是 foo,最后的值是 later;

如果是这种情况:

y := $(x) bar
x := foo

即,在 y 赋值之前,没有定义 x,那么 y 的值等于 bar;

8.4.3、"?="

?= 的赋值很好理解,如果变量未定义,则使用符号中的值定义变量。如果该变量已经赋值,则该赋值语句无效;

比如:

FOO ?= bar

如果在这条语句之前,FOO 被定义了,那么这条语句将啥都不做,否则,会将 bar 赋值给 FOO;

8.4.4、"=+"

这个应该比较熟悉了,而且没有任何歧义;指的是在之前的基础之上,在累加上其他变量;

x:=foo
y:=$(x)b
x+=$(y)

最后 y 的值等于 foob,x 的值等于 foo foob;

8.4.5、变量替换

我们可以替换变量中的共有的部分,其格式是“(var:a=b)”或是“{var:a=b}”,其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”,这玩意就很有用,怎么说呢?我们来看看例子:

foo := a.o b.o c.o
bar := $(foo:.o=.c)

这个示例中,我们先定义了一个“(foo)”变量,而第二行的意思是把“(foo)”中所有以“.o”字串“结尾”全部替换成“.c”,所以我们的“$(bar)”的值就是“a.c b.c c.c”。

还有一种写法:

foo:=a.c b.c d.c
obj:=$(foo:%.c=%.o)
All:
    @echo $(obj)

其中的 % 是通配符的意思;前面已经做过解释,不在赘述;

8.4.6、变量的传递

一个大型的软件工程,一般会有多个 Makefile 组成,那么就涉及到一个问题,上一级的 Makefile 定义的变量,下一级 Makefile(子目录下的 Makefile) 是否能用?

一般情况下,有些变量,在顶层的 Makefile 定义好了后, 直接往下传就行了,下面不需要在定义;比如:交叉编译工具链,目录结构,CPU 结构体系等等;

变量的传递,使用关键字 export:如果你想传递一个变量到下级 Makefile,那么:

export <variable ...>

比如,你定义好了你的交叉编译工具链:

CROSS_COMPILE = /usr/local/arm/arm-2009q3/bin/arm-none-linux-gnueabi-

这个变量肯定需要往下一级 Makefile 传递(因为要编译嘛):

export  CROSS_COMPILE

如果不希望变量被传递到下一级 Makefile,使用 unexport 关键字:

unexport <variable ...>

9、多 Makefile 层次嵌套

在正式项目,或者大型的项目工程中,常常使用多 Makefile 组合来描述整个软件系统的编译,通常情况下,代码顶层有一个 Makefile,我们管它叫做总控 Makefile,其余的子目录,也有他自己的 Makefile;

一般的,子目录的 Makefile 描述的是这个目录下的文件的编译规则,顶层 Makefile 统领所有;那就涉及到了顶层 Makefile 如何调用子目录的 Makefile;

GNU make 中,使用

$(MAKE) -C <Dir>

其中,$(MAKE) 指的是引用了 MAKE 这个变量的值,MAKE 这个变量是一个内置变量(默认变量),他相当于 "make" 这个字符串;

所以 $(MAKE) -C <Dir> 展开为:

make -C <Dir>

这个 make -C 是 make 指令的 options,意思是:进入到目录 DIR,然后执行 make

比如,你一个顶层 Makefile,有一个文件夹 subdir,使用如下命令进入这个文件夹,生成目标:

subsystem:
$(MAKE) -C subdir

如果你有多个文件夹,那么可以使用 for 循环:

SUBDIRS=foo bar baz
subdirs:

for dir in $(SUBDIRS); do\

$(MAKE) -C $$dir; \

done

此时,每次 make 进入文件夹的时候都会打印:

make[1]:Entering directory xxxx

执行完毕改条 $(MAKE) -C 后,退出文件夹:

make[1]:Leaving directory xxxx

10、条件判断

Makefile 支持条件判断语句,类似于 C 语言的 #if...#else...#endif 和 #ifdef...#else...#endif

关键字功能
ifeq判断参数是否不相等,相等为 true,不相等为 false。
ifneq判断参数是否不相等,不相等为 true,相等为 false。
ifdef判断是否有值,有值为 true,没有值为 false。
ifndef判断是否有值,没有值为 true,有值为 false。

10.1、ifeq 和 ifneq

他们的用法类似于 C 语言的 #if...#else...#endif,条件判断的使用方式如下:

ifeq (ARG1, ARG2)
ifeq 'ARG1' 'ARG2'
ifeq "ARG1" "ARG2"
ifeq "ARG1" 'ARG2'
ifeq 'ARG1' "ARG2"

官方的例子为判断当前的编译器 CC 是不是 gcc:

libs_for_gcc= -lgnu
normal_libs=
foo:$(objects)
ifeq($(CC),gcc)
    $(CC) -o foo $(objects) $(libs_for_gcc)
else
    $(CC) -o foo $(objects) $(noemal_libs)
endif

ifneq 的用法和 ifeq 完全一样,只不过逻辑相反,不在多说;

10.2、ifdef 和 ifndef

他们的用法类似于 C 语言的 #ifdef...#else...#endif,条件判断的使用方式如下:

ifdef variable-name

它的主要功能是判断变量的值是不是为空,实例 1:

bar =
foo = $(bar)
all:
ifdef foo
    @echo yes
else
    @echo  no
endif

打印 yes

实例 2:

foo=
all:
ifdef foo
    @echo yes
else
    @echo  no
endif

打印 no;

通过两个实例对比说明:通过打印 "yes" 或 "no" 来演示执行的结果。我们执行 make 可以看到实例 1打印的结果是 "yes" ,实例 2打印的结果是 "no" 。其原因就是在实例 1 中,变量“foo”的定义是“foo = $(bar)”。虽然变量“bar”的值为空,但是“ifdef”的判断结果为真,这种方式判断显然是有不行的,因此当我们需要判断一个变量的值是否为空的时候需要使用“ifeq" 而不是“ifdef”。

特别注意的是,make是在读取Makefile时就计算条件表达式的值,并根据条件表达式的值来选择语句,所以,你最好不要把自动化变量(如“$@”等)放入条件表达式中,因为自动化变量是在运行时才有的。

11、使用函数

Makefile 中支持使用函数,而且 Makefile 中预定义了很多对我们高效写 Makefile 有帮助的函数;函数的调用方法为:

$(<function> <arguments>)  或者是  ${<function> <arguments>}

function:函数名

arguments:参数

不同的函数,名字不一样,参数的个数也不尽相同,函数与参数之间使用空格分隔,多个参数之间使用逗号分隔

11.1、字符串处理函数

11.1.1、subst

subst 字符串替换函数,它的格式如下:

$(subst <from>,<to>,<text>)

函数的功能是把字符串中的 form 替换成 to,返回值为替换后的新字符串。实例:

OBJ=$(subst ee,EE,feet on the street)
all:
    @echo $(OBJ)

执行 make 命令,把“feet on the street”中的“ee”替换成“EE”,我们得到的值是“fEEt on the strEEt”

11.1.2、patsubst

patsubst 模式字符串替换函数,它的格式如下:

$(patsubst <pattern>,<replacement>,<text>)

函数功能是查找 text 中的单词是否符合模式 pattern,如果匹配的话,则用 replacement 替换。返回值为替换后的新字符串。实例:

OBJ=$(patsubst %.c,%.o,1.c 2.c 3.c)
all:
    @echo $(OBJ)

<pattern>可以包括通配符 “%”,表示任意长度的字串。 如果<replacement>中也包含“%”, 那么, <replacement> 中的这个“%”将是<pattern>中的那个“%”所代表的字串;执行 make 命令,我们可以得到的值是 "1.o 2.o 3.o",这些都是替换后的值;

11.1.3、strip

strip 去空格函数;函数使用格式如下:

$(strip <string>)

函数的功能是去掉字符串的开头和结尾的字符串,并且将其中的多个连续的空格合并成为一个空格。返回值为去掉空格后的字符串。实例:

OBJ=$(strip    a       b c)
all:
    @echo $(OBJ)

去掉空格后,输出内容为:“a b c”。这个只是除去开头和结尾的空格字符,并且将字符串中的空格合并成为一个空格;

11.1.4、findstring

findstring 查找字符串函数,函数使用格式如下:

$(findstring <find>,<in>)

函数的功能是查找  in 中的 find ,如果我们查找的目标字符串存在,返回值为目标字符串,如果不存在就返回空。实例:

OBJ=$(findstring a,a b c)
all:
    @echo $(OBJ)

在 "a b c" 中查找 a,返回 a;

OBJ=$(findstring a,b c)
all:
    @echo $(OBJ)

在 "b c" 中查找 a,返回空;

11.1.5、filter

filter 过滤函数,函数使用格式如下:

$(filter <pattern>,<text>)

函数的功能是过滤出 text 中符合模式 pattern 的字符串,可以有多个 pattern 。返回值为过滤后的字符串。实例:

sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo

$(filter %.c %.s,$(sources)) 过滤出了 foo.c bar.c baz.s ;

11.1.6、filter-out

filter-out 反向过滤函数,函数使用格式如下:

$(filter-out <pattern>,<text>)

函数的功能是功能和 filter 函数正好相反,但是用法相同。去除符合模式  pattern 的字符串,保留符合的字符串。返回值是保留的字符串。实例:

objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o
$(filter-out $(mains),$(objects))

返回值是“foo.o bar.o”

11.1.7、sort

sort 排序函数,函数使用格式如下:

$(sort <list>)

函数的功能是将<list>中的单词排序(升序)。返回值为排列后的字符串。实例:

$(sort foo bar lose)

返回 “bar foo lose”

注意:sort会去除重复的字符串

11.1.8、word

word 取单词函数,函数使用格式如下:

$(word <n>,<text>)

函数的功能是取出函数据<text>中的第n个单词。返回值为我们取出的第 n 个单词。实例:

$(word 2, foo bar baz)

返回值是“bar”

11.1.9、wordlist

wordlist 取单词串函数,函数使用格式如下:

$(wordlist <s>,<e>,<text>)

函数的功能是从字符串<text>中取从<s>开始到<e>的单词串。<s>和<e>是一个数字;返回字符串<text>中从<s>到<e>的单词字串。如果<s>比<text>中的单词数要大,那么返回空字符串。如果<e>大于<text>的单词数,那么返回从<s>开始,到<text>结束的单词串。示例:

$(wordlist 2, 3, foo bar baz)

返回值是“bar baz”

11.1.10、words

words 单词个数统计函数;函数使用格式如下:

$(words <text>)

统计<text>中字符串中的单词个数,统计<text>中字符串中的单词个数;

$(words, foo bar baz)

返回值是“3”;

如果我们要取<text>中最后的一个单词,我们可以这样:$(word $(words <text>),<text>)。

11.1.11、firstword

firstword 首单词函数,函数使用格式如下:

$(firstword <text>)

取字符串<text>中的第一个单词;示例:

$(firstword foo bar)

返回值是“foo”

11.2、文件名相关函数

获取文件的路径,去除文件的路径,取出文件前缀或后缀等等;下面我们要介绍的函数主要是处理文件名的。每个函数的参数字符串都会被当做一个或是一系列的文件名来对待。

11.2.1、dir

dir 取目录函数,函数使用格式如下:

$(dir <names>)

函数的功能是从文件名序列 names 中取出目录部分,如果没有 names 中没有 "/" ,取出的值为 "./" 。返回值为目录部分,指的是最后一个反斜杠之前的部分。如果没有反斜杠将返回“./”。实例:

OBJ=$(dir src/foo.c hacks)
all:
    @echo $(OBJ)

name 字段有2个,所以 make 后返回为:“src/ ./”

11.2.2、notdir

notdir 取文件函数,函数使用格式如下:

$(notdir <names>)

函数的功能是从文件名序列 names 中取出非目录的部分。非目录的部分是最后一个反斜杠之后的部分。返回值为文件非目录的部分。实例:

OBJ=$(notdir src/foo.c hacks)
all:
    @echo $(OBJ)

返回的值是“foo.c hacks”

11.2.3、suffix

suffix 取后缀名函数,函数使用格式如下:

$(suffix <names>)

函数的功能是从文件名序列中 names 中取出各个文件的后缀名。返回值为文件名序列 names 中的后缀序列,如果文件没有后缀名,则返回空字符串。实例:

OBJ=$(suffix src/foo.c src-1.0/bar.c hacks)
all:
    @echo $(OBJ)

返回值是 ".c .c"

注意,这里取的后缀,是带了那个点. 的

11.2.4、basename

basename 取前缀函数,函数使用格式如下:

$(basename <names>)

函数的功能是从文件名序列 names 中取出各个文件名的前缀部分。返回值为被取出来的文件的前缀名,如果文件没有前缀名则返回空的字符串。实例:

OBJ=$(basename src/foo.c src-1.0/bar.c hacks)
all:
    @echo $(OBJ)

返回的值是 src/foo src-1.0/bar hacks

11.2.5、addsuffix

addsuffix 添加后缀名函数,函数使用格式如下:

$(addsuffix <suffix>,<names>)

函数的功能是把后缀 suffix 加到 names 中的每个单词后面。返回值为添加上后缀的文件名序列。实例:

$(addsuffix .c,foo bar)

返回值是“foo.c bar.c”

值得一提的是,如果文件名存在后缀名,依然会加上:

OBJ=$(addsuffix .c,src/foo.c hacks)
all:
    @echo $(OBJ)

返回 “sec/foo.c.c hack.c”

11.2.6、addprefix

addprefix 添加前缀名函数,函数使用格式如下:

$(addperfix <prefix>,<names>)

函数的功能是把前缀 prefix 加到 names 中的每个单词的前面。返回值为添加上前缀的文件名序列。实例:

OBJ=$(addprefix src/, foo.c hacks)
all:
    @echo $(OBJ)

返回 "src/foo.c src/hacks",这是一种添加路径的方法;

11.2.7、join

join 链接函数,函数使用格式如下:

$(join <list1>,<list2>)

函数功能是把 list2 中的单词对应的拼接到 list1 的后面

  • 如果 list1 的单词要比 list2 的多,那么,list1 中多出来的单词将保持原样,
  • 如果 list1 中的单词要比 list2 中的单词少,那么 list2 中多出来的单词将保持原样。

返回值为拼接好的字符串。实例:

OBJ=$(join src car,abc zxc qwe)
all:
    @echo $(OBJ)

返回值是 “srcabc carzxc qwe”。很显然<list1>中的文件名比<list2>的少,所以多出来的保持不变

11.2.8、wildcard

wildcard 获取匹配模式文件名函数,命令使用格式如下:

$(wildcard PATTERN)

函数的功能是列出当前目录下所有符合模式的 PATTERN 格式的文件名。返回值为空格分隔并且存在当前目录下的所有符合模式 PATTERN 的文件名。实例:

OBJ=$(wildcard *.c  *.h)
all:
    @echo $(OBJ)

得到当前函数下所有的 ".c " 和  ".h"  结尾的文件。这个函数通常跟的通配符 "*" 连用,使用在依赖规则的描述的时候被展开;

11.3、循环函数

11.3.1、foreach

foreach 是用来做循环的函数,他的语法是:

$(foreach <var>,<list>,<text>)

把参数<list>中的单词逐一取出放到参数<var>所指定的变量中,然后再执行<text>所包含的表达式。每一次<text>会返回一个字符串,循环过程中,<text>的返所返回的每个字符串会以空格分割,最后当整个循环结束的时候,<text>所返回的每个字符串所组成的整个字符串(以空格分隔)将会是 foreach 函数的返回值。所以<var>最好是一个变量名,<list>可以是一个表达式,而<text>中一般会只用<var>这个参数来一次枚举<list>中的单词。

实例:

name:=a b c d
files:=$(foreach n,$(names),$(n).o)
all:
    @echo $(files)

$(files) 返回值是 “a.o b.o c.o d.o”

注意,foreach中的<var>参数是一个临时的局部变量,foreach函数执行完后,参数<var>的变量将不在作用,其作用域只在foreach函数当中。

11.4、条件函数

11.4.1、if

Makefile 中使用 if 函数和 make 支持的 ifeq 条件语句非常类似,只不过这个 if 是函数;他的语法有两种,分别是带else的和不带else的:

$(if <condition>,<then-part>) 

$(if <condition>,<then-part>,<else-part>)

即if函数的参数可以是两个,也可以是三个;

<condition>参数是 if 的表达式,如果其返回的为非空字符串,那么这个表达式就相当于返回真,于是,<then-part>会被计算,否则<else-part>会被计算

而if函数的返回值是,如果<condition>为真(非空字符串),那个<then-part>会是整个函数的返回值,如果<condition>为假(空字符串),那么<else-part>会是整个函数的返回值,此时如果<else-part>没有被定义,那么,整个函数返回空字串;

实例:

OBJ:=foo.c
OBJ:=$(if $(OBJ),$(OBJ),main.c)
all:
      @echo $(OBJ)

函数的值是 foo.c,如果变量 OBJ 的值为空的话,我们得到的 OBJ 的值就是main.c

11.5、创建函数

11.5.1、call

call 函数用来创建一个新的函数(功能比较弱),可以支持自定义多个参数;他的语法是:

$(call <expression>,<parm1>,<parm2>,<parm3>,...)

当 make 执行这个函数的时候,expression参数中的变量$(1)、$(2)、$(3)等,会被参数parm1,parm2,parm3依次取代。而expression的返回值就是 call 函数的返回值。

举例 1:

reverse = $(1) $(2)
foo = $(call reverse,a,b)
all:
      @echo $(foo)

foo 的值就是“a b”。当然,参数的次序可以是自定义的,不一定是顺序的,

举例 2:

reverse = $(2) $(1)
foo = $(call reverse,a,b)
all:
      @echo $(foo)

此时的 foo 的值就是“b a”

11.6、origin 函数

origin 函数不像其他的函数,它并不操作变量的值,它只是告诉你这个变量是哪里来的,比较抽象的概念,下面举例子来介绍:

他的语法是:

$(origin <variable>)

注意variable 是变量的名字,不应该是引用,所以最好不要在 variable 中使用“$”字符。origin 函数会员其返回值来告诉你这个变量的“出生情况”。

origin 的返回值有固定的几个:

  • undefined”:如果<variable>从来没有定义过,函数将返回这个值。
  • default”:如果<variable>是一个默认的定义,比如说“CC”这个变量。
  • environment”:如果<variable>是一个环境变量并且当Makefile被执行的时候,“-e”参数没有被打开。
  • file”:如果<variable>这个变量被定义在Makefile中,将会返回这个值。
  • command line”:如果<variable>这个变量是被命令执行的,将会被返回。
  • override”:如果<variable>是被override指示符重新定义的。
  • automatic”:如果<variable>是一个命令运行中的自动化变量。

这些信息对于我们编写 Makefile 是非常有用的,例如假设我们有一个 Makefile ,其包含了一个定义文件Make.def,在Make.def中定义了一个变量bletch,而我们的环境变量中也有一个环境变量bletch,我们想去判断一下这个变量是不是环境变量,如果是我们就把它重定义了。如果是非环境变量,那么我们就不重新定义它。于是,我们在 Makefile 中,可以这样写:

ifdef bletch
ifeq "$(origin bletch)" "environment"
bletch = barf,gag,etc
endif
endif

11.7、shell 函数

shell 函数,顾名思义,可以理解为调用了操作系统的 shell 命令,这样,在 Makefile 中就可以使用诸如什么 awk,sed,cat,这样的指令,比如:

contents := $(shell cat foo)
files := $(shell echo *.c)

注意,这个函数会新生成一个Shell程序来执行命令,所以你要注意其运行性能,如果你的Makefile中有一些比较复杂的规则,并大量使用了这个函数,那么对于你的系统性能是有害的。特别是Makefile的隐晦的规则可能会让你的shell函数执行的次数比你想像的多得多。

12、make 命令参数和选项

前面说的都是 Makefile 以及直接在命令行敲入 make 命令,其实 make 命令是可以带参数的(前面为了执行多个 Makefile 使用了 make -C),make 的格式如下:

make [-f Makefile] [option] [target]

可以通过 -f 指定使用哪个 Makefile,可以带 option,或者指定目标(比如 make clean);

make 的所有的定义,可以使用:

make -h 帮助。更详细帮助可以查看 man make

来查看:

Usage: make [options] [target] ...
Options:
  -b, -m                      Ignored for compatibility.
  -B, --always-make           Unconditionally make all targets.
  -C DIRECTORY, --directory=DIRECTORY
                              Change to DIRECTORY before doing anything.
  -d                          Print lots of debugging information.
  --debug[=FLAGS]             Print various types of debugging information.
  -e, --environment-overrides
                              Environment variables override makefiles.
  --eval=STRING               Evaluate STRING as a makefile statement.
  -f FILE, --file=FILE, --makefile=FILE
                              Read FILE as a makefile.
  -h, --help                  Print this message and exit.
  -i, --ignore-errors         Ignore errors from recipes.
  -I DIRECTORY, --include-dir=DIRECTORY
                              Search DIRECTORY for included makefiles.
  -j [N], --jobs[=N]          Allow N jobs at once; infinite jobs with no arg.
  -k, --keep-going            Keep going when some targets can't be made.
  -l [N], --load-average[=N], --max-load[=N]
                              Don't start multiple jobs unless load is below N.
  -L, --check-symlink-times   Use the latest mtime between symlinks and target.
  -n, --just-print, --dry-run, --recon
                              Don't actually run any recipe; just print them.
  -o FILE, --old-file=FILE, --assume-old=FILE
                              Consider FILE to be very old and don't remake it.
  -O[TYPE], --output-sync[=TYPE]
                              Synchronize output of parallel jobs by TYPE.
  -p, --print-data-base       Print make's internal database.
  -q, --question              Run no recipe; exit status says if up to date.
  -r, --no-builtin-rules      Disable the built-in implicit rules.
  -R, --no-builtin-variables  Disable the built-in variable settings.
  -s, --silent, --quiet       Don't echo recipes.
  -S, --no-keep-going, --stop
                              Turns off -k.
  -t, --touch                 Touch targets instead of remaking them.
  --trace                     Print tracing information.
  -v, --version               Print the version number of make and exit.
  -w, --print-directory       Print the current directory.
  --no-print-directory        Turn off -w, even if it was turned on implicitly.
  -W FILE, --what-if=FILE, --new-file=FILE, --assume-new=FILE
                              Consider FILE to be infinitely new.
  --warn-undefined-variables  Warn when an undefined variable is referenced.
参数选项功能
-b,-m忽略,提供其他版本 make 的兼容性
-B,--always-make强制重建所有的规则目标,不根据规则的依赖描述决定是否重建目标文件。
-C DIR,--directory=DIR在读取 Makefile 之前,进入到目录 DIR,然后执行 make。当存在多个 "-C" 选项的时候,make 的最终工作目录是第一个目录的相对路径。
-dmake 在执行的过程中打印出所有的调试信息,包括 make 认为那些文件需要重建,那些文件需要比较最后的修改时间、比较的结果,重建目标是用的命令,遗憾规则等等。使用 "-d" 选项我们可以看到 make 构造依赖关系链、重建目标过程中的所有的信息。
--debug[=OPTIONS]make 执行时输出调试信息,可以使用 "OPTIONS" 控制调试信息的级别。默认是 "OPTIONS=b" ,"OPTIONS" 的可值为以下这些,首字母有效:all、basic、verbose、implicit、jobs、makefile。
-e,--enveronment
-overrides
使用环境变量定义覆盖 Makefile 中的同名变量定义。
-f=FILE,--file=FILE,
--makefile=FILE
指定文件 "FILE" 为 make 执行的 Makefile 文件
-p,--help打印帮助信息。
-i,--ignore-errors执行过程中忽略规则命令执行的错误。
-I DIR,--include-dir=DIR指定包含 Makefile 文件的搜索目录,在Makefile中出现另一个 "include" 文件时,将在 "DIR" 目录下搜索。多个 "-i" 指定目录时,搜索目录按照指定的顺序进行。
-j [JOBS],--jobs[=JOBS]可指定同时执行的命令数目,没有 "-j" 的情况下,执行的命令数目将是系统允许的最大可能数目,存在多个 "-j" 目标时,最后一个目标指定的 JOBS 数有效。
-k,--keep-going执行命令错误时不终止 make 的执行,make 尽最大可能执行所有的命令,直至出现知名的错误才终止。
-l load,--load-average=[=LOAD],--max-load[=LOAD]告诉 make 在存在其他任务执行的时候,如果系统负荷超过 "LOAD",不在启动新的任务。如果没有指定 "LOAD" 的参数  "-l" 选项将取消之前 "-l" 指定的限制。
-n,--just-print,--dry-run只打印执行的命令,但是不执行命令。
-o FILE,--old-file=FILE,
--assume-old=FILE
指定 "FILE"文件不需要重建,即使是它的依赖已经过期;同时不重建此依赖文件的任何目标。注意:此参数不会通过变量 "MAKEFLAGS" 传递给子目录进程。
-p,--print-date-base命令执行之前,打印出 make 读取的 Makefile 的所有数据,同时打印出 make 的版本信息。如果只需要打印这些数据信息,可以使用 "make -qp" 命令,查看 make 执行之前预设的规则和变量,可使用命令 "make -p -f /dev/null"
-q,-question称为 "询问模式" ;不运行任何的命令,并且无输出。make 只返回一个查询状态。返回状态 0 表示没有目标表示重建,返回状态 1 表示存在需要重建的目标,返回状态 2 表示有错误发生。
-r,--no-builtin-rules取消所有的内嵌函数的规则,不过你可以在 Makefile 中使用模式规则来定义规则。同时选项 "-r" 会取消所有后缀规则的隐含后缀列表,同样我们可以在 Makefile 中使用 ".SUFFIXES",定义我们的后缀名的规则。"-r" 选项不会取消 make 内嵌的隐含变量。
-R,--no-builtin-variabes取消 make 内嵌的隐含变量,不过我们可以在 Makefile 中明确定义某些变量。注意:"-R" 和 "-r" 选项同时打开,因为没有了隐含变量,所以隐含规则将失去意义。
-s,--silent,--quiet取消命令执行过程中的打印。
-S,--no-keep-going,
--stop
取消 "-k" 的选项在递归的 make 过程中子 make 通过 "MAKEFLAGS" 变量继承了上层的命令行选项那个。我们可以在子 make 中使用“-S”选项取消上层传递的 "-k" 选项,或者取消系统环境变量 "MAKEFLAGS" 中 "-k"选项。
-t,--touch和 Linux 的 touch 命令实现功能相同,更新所有的目标文件的时间戳到当前系统时间。防止 make 对所有过时目标文件的重建。
-v,version查看make的版本信息。
-w,--print-directory在 make 进入一个子目录读取 Makefile 之前打印工作目录,这个选项可以帮助我们调试 Makefile,跟踪定位错误。使用 "-C" 选项时默认打开这个选项。
--no-print-directory取消 "-w" 选项。可以是 用在递归的 make 调用的过程中 ,取消 "-C" 参数的默认打开 "-w" 的功能。
-W FILE,--what-if=FILE,
--new-file=FILE,
--assume-file=FILE
设定文件 "FILE" 的时间戳为当前的时间,但不更改文件实际的最后修改时间。此选项主要是为了实现对所有依赖于文件 "FILE" 的目标的强制重建。
--warn-undefined-variables在发现 Makefile 中存在没有定义的变量进行引用时给出告警信息。此功能可以帮助我们在调试一个存在多级嵌套变量引用的复杂 Makefile。但是建议在书写的时候尽量避免超过三级以上的变量嵌套引用。

比较常用的有:

-j 或者 --jobs:并行编译;

-k 或者 --keep-going:如果某规则中的命令出错了,那么就终目该规则的执行,但继续执行其它规则

-w 或者 --print-directory:打印退出、进入目录;

make: Entering directory `/u/gnu/make'

make: Leaving directory `/u/gnu/make'.

-i 或者 --ignore-errors:Makefile中所有命令都会忽略错误

-n 或者 --just-print:只是显示命令,但不会执行命令,利于我们调试我们的 Makefile

-s 或者 --slient:则是全面禁止命令的显示

 参考和借鉴资料:

Top (GNU make)

跟我一起写 Makefile(一)_陈皓专栏 【空谷幽兰,心如皓月】-CSDN博客_makefile

Makefile教程:Makefile文件编写1天入门

  • 8
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值