【六、深度解析Makefile】工程文件编译链接的“规则制定者”:带你走进 makefile 的世界(二)

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
函数计算FC,每月15万CU 3个月
简介: 【六、深度解析Makefile】工程文件编译链接的“规则制定者”:带你走进 makefile 的世界

(3)逻辑判断函数

① foreach (遍历)

  • 函数原型
$(foreach <var>,<list>,<text>)
  • 函数功能:把参数 list 中的单词逐一取出放到参数 var 所指定的变量中,然后再执行 text 所包含的表达式。每一次 text 会返回一个字符串,循环过程中, text 返回的每个字符串会以空格分割,最后当整个循环结束的时候, text 所返回的每个字符串组成的整个字符串(以空格分隔)将会是 foreach 函数的返回值。所以 var 最好是一个变量名, list 可以是一个表达式,而 text 中一般会只用 var 这个参数来一次枚举 list 中的单词。其实就是,把 list 中的值依次拿出来传给变量 var ,并依次使用 text 来处理已被赋值的 var ,每次得到的结果作为返回值序列的一个值。它有点类似于 STL 中的遍历算法 for_each ,挨个把 list 中的元素取出来赋给 var ,并执行 text ,并把每次的结果作为返回值序列的一个元素。(STL 中的 for_each 在我的专栏 C++ 与 STL 专栏,《for_each 源码刨析与函数对象本质刨析》一文中有详细介绍)
  • 函数返回:经过 foreach 算法处理后的字符串序列。
  • 用法示例

② if (条件判断)

  • 函数原型
$(if <condition>,<then-part>)
$(if <condition>,<then-part>,<else-part>)
  • 函数功能:condition 是 if 的表达式,如果其返回的是非空的字符串,那么这个表达式返回真,执行 then-part ,否则执行 else-part 。if 函数可以包含else 部分,也可以不包含。其实,简单来说,这就类似于 c/c++ 中的 if 语句,满足 condition ,执行 then-part ,不满足 condition 则执行 else-part ,如果没有 else-part 分支,则不执行任何操作
  • 函数返回:如果 condition 为真(非空字符串),那么 then-part 会是整个函数的返回值;如果 condition 为假(空字符串),那么 else-part 将会是这个函数的返回值;此时如果 else-part 没有被定义,那么整个函数返回空字串符。
  • 用法示例


(4)其他函数

① 表达式传参函数 call

  • 函数原型
$(call <expression>,<parm1>,<parm2>,<parm3>,...)
  • 函数功能:依次使用 parm1,parm2,parm3,…, 来替代 expression 表达式中的参数1,参数2,参数3,… 。注意,在参数传递的时候,是有顺序的,并且顺序可以指定。也就是说,我们可以自定义一个含有任意个参数的表达式 expression ,然后我们可以使用 call 函数来为这个表达式传递参数。我们可以联想 c 语言中的 main 函数的参数列表 argc 和 argv[] ,在执行可执行文件 exe 的时候,我们可以 exe parm1 parm2 … 这样来为 main 的参数列表 argv[] 传值,这一块在我的 Linux 专栏《深入浅出 GDB 调试器》一文中有详细介绍。
  • 函数返回:返回表达式 expression 的返回值。
  • 用法示例

② 变量来源 origin

  • 函数原型
$(origin <variable>)
  • 函数功能:告诉我们变量的来源。variable 是变量的名字,而不是引用,所以最好不要在 variable 中使用 $ 字符,origin 函数会用返回值来告诉我们这个变量的来源。比如我们有一个变量和环境变量同名,我们可以用这个函数判断当前的这个变量来源于环境还是来源于用户。
  • 函数返回:
返回值 含义
undefined 如果 variable 从来没有定义过,函数将返回这个值。
default 如果 variable 是一个默认的定义,比如说 CC 这个变量
environment 如果 variable 是一个环境变量并且当Makefile被执行的时候, -e 参数没有被打开。
file 如果 variable 这个变量被定义在 makefile 中,将会返回这个值。
command line 如果 variable 这个变量是被命令执行的,将会被返回。
override 如果 variable 是被 override 指示符重新定义的。
automatic 如果 variable 是一个命令运行中的自动化变量。
  • 用法示例
ifdef bletch
ifeq "$(origin bletch)" "environment"
bletch = barf,gag,etc
endif
endif

③ shell

shell 函数以 Linux 的 shell 命令为函数参数,并把执行 shell 命令后的输出作为函数返回值。

④ make 控制函数

$(error <text ...>)   产生一个致命的错误,<text ...>是错误信息。
$(warning <text ...>) 这个函数很像error函数,只是它并不会让make退出,只是输出一段警告信息,而make继续执行。

5. makefile 的伪目标

(1)什么是伪目标

有时候,我们并非真正的想要生成一个目标,而是想让 makefile 执行这个目标后面的命令,这时候我们可以通过伪目标来实现。最常见的使用伪目标的例子就是 make clean ,我们执行 make clean 可以把所有的目标文件删除

.PHONY:clean all
clean:
  -@rm -f *.o
  -@rm exe

在这条规则中,clean 是一个伪目标,它没有依赖,我们也不需要去生成这个目标。在 clean 后面的命令并不是创建目标 clean 的命令,而是一条删除命令,用于删除所有的目标文件。我们在 shell 命令行执行

make clean

就可以执行上面的删除语句,删除所有 .o 文件和终极目标 exe 可执行文件。这里的 .PHONY 是声明伪目标的意思。假如说我们不声明伪目标

clean:
  -@rm -f *.o
  -@rm exe

直接通过上面的规则,也就是说 clean 是一个真正的目标,如果当前目录下存在一个名为 clean 的文件,当我们在 shell 中执行命令 make clean,由于这个规则没有依赖文件,所以目标 clean 被认为是最新的,所以不再去执行规则所定义的命令,也就是说 rm 命令将不会被执行。那么我们的目的也就无法达到了。声明为伪目标就可以解决这个问题,其实就是把 clean 作为特殊目标 .PHONY 的依赖,这样就保证了不管当前是否有 clean 同名文件,伪目标后面的命令都可以执行,并且 make 不会去推导构建伪目标的隐含规则,这也提高了编译效率。

伪目标还有两个用途,就是递归调用 makefile 和实现多文件编辑,这里不再详细介绍。下面列出 makefile 常用的一些伪目标以及他们的含义。

(2)GNU 编译、安装、打包相关的伪目标

下面列出的这些伪目标都是 GNU 的一些定义,我们在定义实现下面功能的伪目标时,应尽量使用下面列出的伪目标名称。在大型工程中,这些伪目标是非常有用的,并且它们类似于一种约定俗成的东西,用起来会更加统一。实际上,通过 make 指定伪目标为最终目标,在 make 中是非常常见的,比如 make clean,这在后面会有详细介绍。

伪目标 含义
all 这个伪目标是所有目标的目标,它的功能一般是编译所有的目标。
clean 这个伪目标功能是删除所有被 make 创建的文件。
install 这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。
print 这个伪目标的功能是例出改变过的源文件。
tar 这个伪目标功能是把源程序打包备份,也就是一个 tar 文件。
dist 这个伪目标功能是创建一个压缩文件,一般是把 tar 文件压成 gz 文件。
TAGS 这个伪目标功能是更新所有的目标,以备完整地重新编译使用。
check 和 test 这两个伪目标一般用来测试 makefile 的流程。

6. makefile 的隐含规则

(1)隐含规则的工作原理

隐含规则可以理解为一种惯例,它是一种早就约定好的规则,不需要再显示的写出来,makefile 会自动进行推导这种规则。隐含规则会使用系统变量,我们可以通过系统变量来改变隐含规则运行时的参数,比如系统变量 CFLAGS 可以控制编译器的参数等。另外,我们还可以通过模式规则来写自己的隐含规则或使用后缀规则来保证兼容性。说白了,隐含规则就是我们只写出目标(目标是必不可少的),由 make 自动推导生成目标的依赖和命令,当然,如果我们手动去显示写了命令,make 会执行我们显示写的命令。举例说明:

我们在 makefile 中只写了可执行文件(最终目标)以及依赖 .o 文件,以及通过 .o 文件生成可执行文件的命令,我们没有写 .o 文件的生成规则,并且文件夹中没有 .o 文件

这时,我们执行一下 make

可以看到,执行 make 之后,编译器自动执行了一条命令

cc -c -o test.o test.c

这条命令在 makefile 中,我们并没有显示写出。这就是 makefile 中的隐含规则,他会自动推导出生成 .o 文件的命令。但是有一点要注意,隐含条件只能省略中间目标文件重建的命令和规则,但是最终目标的命令和规则不能省略

实际上,make 中存在一个隐含规则库,这个隐含规则库中的每一条隐含规则都有相应的优先级顺序,优先级也就会越高(顺序在前的优先级高),也就会被优先使用。这里有一个预先设置的后缀列表

.out、.a、.in、.o、.c、.cc、.C、.p、.f、.F、.r、.y、.l、.s、.S、.mod、.sym、.def、.h、.info、.dvi、.tex、.texinfo、.texi、.txinfo、.w、.ch、.web、.sh、.elc、.el

在有些时候,我们使用隐含规则可能会出现问题,比如我们在 makefile 中写一条规则

test.o:test.cc

这里 .cc 是C++源文件,这条规则本意是使用 test.cc 生成 test.o 文件,但是我们没有显示指定命令。这时候,makefile 就会根据隐含规则去推导命令,在推导过程中,如果当前文件夹中同时存在 test.c 和 test.cc 文件,那么根据隐含规则的优先级,会默认选择 tes.c 去生成 test.o 文件(根据后缀列表可以看到优先级顺序),这就和我们本来希望的操作完全不符了,因为目标文件 test.o 已经生成了,所以不会再去继续推导了。

当然,我们可以使用 make 选项 -r 或 -n-builtin-rules 来取消所有的预设的隐含规则,但是即便使用参数,有些隐含规则也会生效。因为大部分隐含规则都是使用了后缀规则来定义的,所以,只要隐含规则中有后缀列表,那么隐含规则就会生效。

(2)隐含规则都有哪些

隐含规则按照执行顺序排列如下:

  • 编译 C 程序的隐含规则
    .o 的目标的依赖目标会自动推导为 .c ,并且其生成命令是 $(CC) –c $(CPPFLAGS) $(CFLAGS)
  • 编译 C++ 程序的隐含规则
    .o 的目标的依赖目标会自动推导为 .cc 或 .C (建议使用 .cc 作为C++源文件的后缀,尽量不用 .C ),并且其生成命令是 $(CXX) –c $(CPPFLAGS) $(CFLAGS)
  • 编译 Pascal 程序
    .o 的目标的依赖目标会自动推导为 .p ,并且其生成命令是 $(PC) –c $(PFLAGS) 。
  • 编译 Fortran/Ratfor 程序
    “.o”的目标的依赖目标会自动推导为“.r”或“.F”或“.f”,并且其生成命令是
    .f $(FC) –c $(FFLAGS)
    .F $(FC) –c $(FFLAGS) $(CPPFLAGS)
    .f $(FC) –c $(FFLAGS) $(RFLAGS)
  • 预处理 Fortran/Ratfor 程序
    .f 的目标的依赖目标会自动推导为 .r 或 .F 。这个规则只是转换 Ratfor 或有预处理的 Fortran 程序到一个标准的 Fortran 程序,使用的命令是
    .F $(FC) –F $(CPPFLAGS) $(FFLAGS)
    .r $(FC) –F $(FFLAGS) $(RFLAGS)
  • 编译 Modula-2 程序
    .sym 的目标的依赖目标会自动推导为 .def ,并且其生成命令是
    $(M2C) $(M2FLAGS) $(DEFFLAGS) ;
    .o 的目标的依赖目标会自动推导为 .mod ,并且其生成命令是
    $(M2C) $(M2FLAGS) $(MODFLAGS) 。
  • 汇编和需要预处理的汇编程序
    .o 的目标的依赖目标会自动推导为 .s ,默认使用编译器 as,并且其生成命令是 $(AS) $(ASFLAGS);
    .s 的目标的依赖目标会自动推导为 .S ,默认使用C预编译器 cpp,并且其生成命令是 $(AS) $(ASFLAGS) 。
  • 链接单 object 文件的隐含规则
    目标依赖于 .o ,通过运行 C 的编译器来运行链接程序生成(一般是 ld),生成命令是 $(CC) $(LDFLAGS) .o $(LOADLIBES) $(LDLIBS)
  • Yacc C 程序时的隐含规则
    .c 的依赖文件被自动推导为 .y (Yacc 生成的文件),生成命令是 $(YACC) $(YFALGS) 。(Yacc 是一个语法、词法分析器)
  • Lex C 程序时的隐含规则
    .c 的依赖文件被自动推导为 n.l (Lex 生成的文件), 生成命令是 $(LEX) $(LFALGS) 。(Lex 是一个语法、词法分析器)
  • Lex Ratfor 程序时的隐含规则
    .r 的依赖文件被自动推导为 n.l (Lex生成的文件),生成命令是 $(LEX
    ) $(LFALGS) 。
  • 从C程序、Yacc文件或Lex文件创建Lint库的隐含规则
    .ln (lint生成的文件)的依赖文件被自动推导为 n.c ,生成命令是 $(LINT) $(LINTFALGS) $(CPPFLAGS) -i ,对于 .y 和 .l 也是同样的规则。

实际上,我们完全可以使用模式规则来重载内建的隐含规则或者取消内建隐含规则,甚至是创建一个我们自己的隐含规则。模式规则将在后面介绍。

另一种创建隐含规则的方式是后缀规则,其中,双后缀规则定义了一对后缀,目标文件的后缀和依赖文件的后缀,比如 .c.o 相当于 %o : %c ;单后缀规则只定义一个后缀,也就是依赖文件的后缀,比如 .c 相当于 % : %.c 。注意,这些后缀必须是 make 认识的后缀。

这里要注意一个库文件的隐含规则,库文件 .a 就是 .o 文件通过 ar 打包出来的文件,关于链接库内容可见我的 Linux 专栏动态库与静态库文章。当 make 搜索一个目标的隐含规则时,有一个特殊的特性是,如果这个目标是 a(m) 形式的,make 会把目标变成 (m) 。比如,如果我们的成员是 %.o 的模式定义,并且如果我们使用 make 111.a(222.o) 的形式调用 makefile 时,隐含规则会去找 222.o 的规则,如果没有定义 222.o 的规则,那么内建隐含规则生效,make 会去找 222.c 文件来生成 222.o,如果找到了 222.c,make 会先生成 222.o,然后用生成的 .o 去用到规则中,最后删除 .o 文件。

(3)隐含变量

包含隐含规则的命令中所使用的变量都是预定义好的,这种变量称为隐含变量。我们可以通过命令行参数传值或者是修改系统环境变量的方式对隐含变量赋值或重定义,也可以通过 make 的 -R 或 --no– builtin-variables 参数来取消你自定义变量对隐含规则的作用。。在上面我们介绍的 makefile 的隐含规则中,都可以看到隐含变量的身影。下面介绍隐含变量。

① 代表命令的隐含变量

隐含变量 变量代表的含义
AR 函数库打包程序,可用于创建静态库 .a 文件(我的 Linux 专栏动静态库文章中已详细介绍 ar 命令),默认命令是 ar
AS 汇编语言编译程序,默认命令是 as
CC C语言编译程序,默认命令是 cc
CXX C++语言编译程序,默认命令是 g++
CO 从 RCS文件中扩展文件程序,默认命令是 co
CPP C程序的预处理器(输出是标准输出设备),默认命令是 $(CC) –E
FC Fortran 和 Ratfor 的编译器和预处理程序,默认命令是 f77
GET 从SCCS文件中扩展文件的程序,默认命令是 get
LEX Lex 方法分析器(针对于C或Ratfor),默认命令是 lex
PC Pascal 语言编译程序,默认命令是 pc
YACC Yacc 文法分析器(针对于C程序),默认命令是 yacc
YACCR Yacc 文法分析器(针对于Ratfor程序),默认命令是 yacc –r
MAKEINFO 转换Texinfo 源文件(.texi)到 Info 文件程序,默认命令是 makeinfo
TEX 从 TeX 源文件创建 TeX DVI 文件的程序,默认命令是 tex
TEXI2DVI 从 Texinfo 源文件创建军 TeX DVI 文件的程序,默认命令是 texi2dvi
WEAVE 转换 Web 到 TeX 的程序,默认命令是 weave
CWEAVE 转换 C Web 到 TeX 的程序,默认命令是 cweave
TANGLE 转换 Web 到 Pascal 语言的程序,默认命令是 tangle
CTANGLE 转换 C Web 到 C,默认命令是 ctangle
RM 删除文件命令,默认命令是 rm –f

② 代表命令参数的隐含变量

这些隐含变量将作为上面代表命令的隐含变量的参数,并且变量的默认值为空,也就是说,如果不指定的话,相当于没有加命令参数。

隐含变量 变量代表的含义
ARFLAGS 函数库打包程序 AR 命令的参数,默认值是 rv
ASFLAGS 汇编语言编译器参数
CFLAGS C 语言编译器参数
CXXFLAGS C++ 语言编译器参数
COFLAGS RCS 命令参数
CPPFLAGS C 预处理器参数( C 和 Fortran 编译器也会用到)
FFLAGS Fortran 语言编译器参数
GFLAGS SCCS get 程序参数
LDFLAGS 链接器参数(比如 ld )
LFLAGS Lex 文法分析器参数
PFLAGS Pascal 语言编译器参数
RFLAGS Ratfor 程序的 Fortran 编译器参数
YFLAGS Yacc文法分析器参数

关于隐含变量的使用,比如说拿第一条隐含规则来举例:编译C程序的隐含规则的命令是 $(CC) –c $(CFLAGS) $(CPPFLAGS),根据我们上面介绍的隐含变量表,make 默认使用的编译命令是 cc (CC 变量的默认值是 cc),如果我们把变量 $(CC) 重定义成 g++,在使用 make 编译的时候就会使用 g++ 编译程序。另外,我们知道,代表命令参数的隐含变量默认值为空,也就是不加参数,如果我们把变量 $(CFLAGS) 重定义成 -g ,那么,在 make 编译的时候就会加上 -g 选项参数来生成调试信息。这样,隐含规则中的命令经过我们的修改,就变成了 g++ –c -g $(CPPFLAGS),使用 make 命令的时候,就会按这条命令来执行。

(4)隐含规则链与中间目标

有时候一个目标的生成可能会有多条隐含规则发生作用,比如一个 .o 文件的生成,如果当前目录中只有一个 .y 文件,没有 .c 文件,那么首先会先由 Yacc C 程序的隐含规则由 .y 生成 .c 然后再由 C 程序编译隐含规则将 .c 生成 .o 。多个隐含规则链式执行,所以把这些隐含规则称为隐含规则链。在这里,因为最开始没有 .c 文件,而 .o 文件的生成依赖 .c 文件,所以 make 会推导生成 .c 文件的规则,这时就通过当前已有的 .y 文件和 Yacc C 隐含规则来生成 .c 文件,因此, .c 文件也被称为中间目标。当然,这是在当前目录中没有 .c 文件的前提下,才会触发 Yacc C 隐含规则,如果当前目录中有 .c 文件,那么会直接使用 .c 文件生成目标 .o 文件,即使存在 .y 文件,也不会执行Yacc C 隐含规则,也就是说,只要生成了目标文件,make 就不会继续推导了。并且,生成目标文件后,中间目标文件会被自动删除。

(5)模式规则

① 什么是模式

模式规则也就是说在规则中存在模式匹配字符 % ,并且是一定要存在 % ,该字符用于对文件名进行匹配。模式匹配字符 % 我们在 makefile 的字符匹配章节已经介绍过了。通过模式规则可以指定多个目标和依赖,make 根据文件名去匹配哪个目标文件对应哪个依赖文件,比如 %.o:%.c ,make 会自动去根据文件名匹配 1.c 生成 1.o,2.c 生成 2.o并推导出这样的规则。

我们举个例子:

%.o:%.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

这就是模式规则最常用的一个例子,我们通过自动化变量章节已经知道 $@ 代表目标文件集, $< 代表依赖文件集,通过这条命令,make 会把所有的 .c 文件挨个执行下面的命令来生成对应的 .o 文件,至于如何对应的,就是通过模式匹配字符 % ,根据相同的文件名把 .c 文件生成同名的 .o 文件。

② 模式的匹配

模式匹配字符 % 看可以代表任意个字符,在一个目标模式中必须要包含 % ,它可以代表文件后缀或者文件名。在模式中,把 % 所匹配的的内容叫做 。比如说,我们在上面的例子中,%.o:%.c,假如当前目录中有 111.c 222.c 333.c ,那么 111 222 333 这些被 % 所代表的内容就是茎,并且当目标和依赖都包含 % 时,依赖的茎会传递给目标,作为目标的茎。比如上面命令生成的所有 .o 目标文件,都会用相应的 .c 文件的名称作为自己的名称,比如 111.c 生成 111.o,222.c 生成 222.o,333.c 生成 333.o,这就是茎的传递。

这里有一点要注意的是,如果模式中包含目录 / ,那么在模式匹配的时候会先去除目录,等模式匹配完成后再加上目录。比如现在有一个依赖的模式是 src/a%c.c,该模式的目标也含有一个模式 d%f.o,实际上就是这样的

d%f.o:src/a%c.c

假如说,依赖文件的模式 src/a%c.c 被匹配为 src/abc.c 文件,那么这个模式的茎应该是 src/b,当把这个茎传递给目标的时候,b 匹配目标中的 % ,也就是 dbf.o ,在加上目录,最终的目标文件应该是 src/dbf.o。

7. makefile 的命令编写

(1)逻辑控制

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

这四个关键字都要搭配 else 和 endif 来使用,并且以 endif 介绍。

例1:如果变量 CC 值为 gcc 使用 gcc 编译,否则使用 g++ 编译。

ifeq($(CC), gcc)
  gcc src.c -o exe
else
  g++ src.c -o exe
endif

例2:如果没有定义变量 Src 则定义该变量

ifndef Src
Src=1.c 2.c
endif

以上四个条件控制关键字只对 makefile 命令起作用,对于 shell 的命令不起作用。

(2)命令回显

make 在执行命令的时候会把命令打印到标准输入输出设备,如果命令前加一个 @ 则不会打印。

可以看到,如果不加 @ 的话,执行 make 时会把要执行的命令打印出来,然后再执行这个命令。有时候还会在 @ 前面加一个 - ,表示即便出错也不报错,继续执行,这个在删除命令中更常见,因为如果重复删除某个文件,shell 命令会报错,可以通过在命令前加 - 或者 rm -f 两种方法解决。

-@echo rm *.c #如果 .c 不存在不会报错
  @echo rm -f *.c #强制删除,.c 不存在也不会报错

另外我们还可以通过 make 的参数来设置。

-n–just-print 参数

执行时只打印所要执行的命令,不执行命令。make 会打印出所有要执行的命令,其中包括了使用的 @ 字符开始的命令。通过这个选项就可以按执行顺序打印出 makefile 中所需要执行的所有命令来达到查看待执行命令的目的。

可以看到,加了这两个参数后,make 只会打印 makefile 中待执行的 shell 命令(包括 @ 开头的),但是不会执行命令。

-s–silent 参数

关闭 make 执行时所有的执行命令的回显,相当于所有的命令行都使用 @ 开头,在命令非常多的时候,要比 @ 更方便。

可以看到,加了这两个参数后,make 只会执行 makefile 中的 shell 命令,但是不会回显命令,即便不加 @ 也不会回显。

make 的更多参数可以通过 man 去查看。

(3)命令打包

通过 defineendef 可以定义一个命令包,来把一连串的命令打包在一起,在执行的时候,会把命令包中的命令一块执行。命令包的使用方法和变量一样,也需要 $ 符来说明。

define 的语法如下:

define name #name是命令包的名字,通过name来调用命令包
cmd1 #多行命令
cmd2
cmd...
endef

(4)文件引入

和 c/c++ 引入头文件一样,makefile 也可以引入其他文件,通过 include 关键字来实现,其语法如下

include <file> #file 是 shell 中所支持的文件
-include <file> #忽略文件不存在或无法创建等错误提示

make 命令在执行的时候,如果遇到 include 关键字,会在当前文件中暂停,转去 include 所引入的文件去读取。 include 关键字所在的行首可以含有(任意个)空格,这些空格在读取的时候会被自动忽略,但是,绝对不能使用 Tab 开始,因为 Tab 开始的都会被作为 shell 命令,会把 include 当作 shell 命令来处理。如果包含多个文件,要使用空格分隔开。使用 include 引入的 makefile 文件中,如果存在函数或者变量的引用,它们会在包含的 makefile 中展开。

make 在搜索引入文件时,假如使用 include 包含文件的时候使用相对路径或者当前目录下没有这个文件,make 会根据文件名首先在我们通过 make -Imake --include-dir 指定的路径中寻找文件(如果我们显示指定了这个参数的话),然后再去 usr/gnu/include 、 usr/local/include 和 usr/include 这几个目录中寻找。在寻搜索过程中,如果找到了那么就会停止搜索,如果没找到的话会按照上面的顺序依次寻找。如果都没有找到,make 将会提示一个文件没有找到的警告(如果不想看到这个警告,可以在 include 前面加一个 - ),但是不会退出,而是继续执行 makefile 的后续内容。当整个 makefile 扫描完毕后,make 会尝试通过规则来创建被 include 引入但搜索失败的的那个文件。如果创建失败,文件将会保存退出。

  • include:make 在处理程序的时候,如果文件列表中的任意文件搜索不到或者没有规则去创建的时候,make 程序将会提示错误并保存退出。
  • -include :当包含的文件不存在或者是没有规则去创建它的时候,make 将会继续执行程序,只有终极目标无法生成或刷新的时候,make 才会提示错误并保存退出。终极目标就是 makefile 中的第一个目标。

(5)链式命令

我们每写一个命令就会换行,重启一行加 Tab 键写第二个命令。这种不在同一行的命令是互不影响的,也就是上一个命令的结果不会影响下一个命令。如果我们希望上一条命令的结果应用在下一条命令时,可以使用分号 ; 分隔这两条命令 ,并把这两个命令写在同一行。

这样的效果有点像 shell 中 -exec、-ok 和 xargs 这样的选项所起到的效果,又有点像 C++ 中的链式编程,我就姑且叫它链式命令吧,哈哈哈。

相关文章
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
77 2
|
2月前
|
Java
Java“解析时到达文件末尾”解决
在Java编程中,“解析时到达文件末尾”通常指在读取或处理文件时提前遇到了文件结尾,导致程序无法继续读取所需数据。解决方法包括:确保文件路径正确,检查文件是否完整,使用正确的文件读取模式(如文本或二进制),以及确保读取位置正确。合理设置缓冲区大小和循环条件也能避免此类问题。
496 2
|
23天前
|
机器学习/深度学习 人工智能 算法
深入解析图神经网络:Graph Transformer的算法基础与工程实践
Graph Transformer是一种结合了Transformer自注意力机制与图神经网络(GNNs)特点的神经网络模型,专为处理图结构数据而设计。它通过改进的数据表示方法、自注意力机制、拉普拉斯位置编码、消息传递与聚合机制等核心技术,实现了对图中节点间关系信息的高效处理及长程依赖关系的捕捉,显著提升了图相关任务的性能。本文详细解析了Graph Transformer的技术原理、实现细节及应用场景,并通过图书推荐系统的实例,展示了其在实际问题解决中的强大能力。
124 30
|
16天前
|
存储 Java 开发者
浅析JVM方法解析、创建和链接
上一篇文章《你知道Java类是如何被加载的吗?》分析了HotSpot是如何加载Java类的,本文再来分析下Hotspot又是如何解析、创建和链接类方法的。
|
2月前
|
自然语言处理 数据处理 Python
python操作和解析ppt文件 | python小知识
本文将带你从零开始,了解PPT解析的工具、工作原理以及常用的基本操作,并提供具体的代码示例和必要的说明【10月更文挑战第4天】
507 60
|
1月前
|
自然语言处理 编译器 Linux
|
1月前
|
消息中间件 存储 Java
RocketMQ文件刷盘机制深度解析与Java模拟实现
【11月更文挑战第22天】在现代分布式系统中,消息队列(Message Queue, MQ)作为一种重要的中间件,扮演着连接不同服务、实现异步通信和消息解耦的关键角色。Apache RocketMQ作为一款高性能的分布式消息中间件,广泛应用于实时数据流处理、日志流处理等场景。为了保证消息的可靠性,RocketMQ引入了一种称为“刷盘”的机制,将消息从内存写入到磁盘中,确保消息持久化。本文将从底层原理、业务场景、概念、功能点等方面深入解析RocketMQ的文件刷盘机制,并使用Java模拟实现类似的功能。
43 3
|
1月前
|
存储
文件太大不能拷贝到U盘怎么办?实用解决方案全解析
当我们试图将一个大文件拷贝到U盘时,却突然跳出提示“对于目标文件系统目标文件过大”。这种情况让人感到迷茫,尤其是在急需备份或传输数据的时候。那么,文件太大为什么会无法拷贝到U盘?又该如何解决?本文将详细分析这背后的原因,并提供几个实用的方法,帮助你顺利将文件传输到U盘。
|
1月前
|
自然语言处理 编译器 Linux
告别头文件,编译效率提升 42%!C++ Modules 实战解析 | 干货推荐
本文中,阿里云智能集团开发工程师李泽政以 Alinux 为操作环境,讲解模块相比传统头文件有哪些优势,并通过若干个例子,学习如何组织一个 C++ 模块工程并使用模块封装第三方库或是改造现有的项目。
|
2月前
|
缓存 前端开发 JavaScript
前端的全栈之路Meteor篇(二):容器化开发环境下的meteor工程架构解析
本文详细介绍了使用Docker创建Meteor项目的准备工作与步骤,解析了容器化Meteor项目的目录结构,包括工程准备、环境配置、容器启动及项目架构分析。提供了最佳实践建议,适合初学者参考学习。项目代码已托管至GitCode,方便读者实践与交流。

推荐镜像

更多