Makefile使用教程

 什么是makefile?或许很多Winodws的程序员都不知道这个东西,因为那些Windows的IDE都为你做了这个工作,但我觉得要作一个好的和professional的程序员,makefile还是要懂。这就好像现在有这么多的HTML的编辑器,但如果你想成为一个专业人士,你还是要了解HTML的标识的含义。特别在Unix下的软件编译,你就不能不自己写makefile了,会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。因为,makefile关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。

   make命令执行时,需要一个 Makefile 文件,以告诉make命令需要怎么样的去编译和链接程序。

      首先,我们用一个示例来说明Makefile的书写规则。以便给大家一个感兴认识。这个示例来源于GNU的make使用手册,在这个示例中,我们的工程有8个C文件,和3个头文件,我们要写一个Makefile来告诉make命令如何编译和链接这几个文件。我们的规则是:         
            1.如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。

            2.如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。

            3.如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。
 

      只要我们的Makefile写得够好,所有的这一切,我们只用一个make命令就可以完成,make命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译,从而自己编译所需要的文件和链接目标程序。

 

makefile的规则

组成

  • target 也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label),对于标签这种特性,在后续的“伪目标”章节中会有叙述。
  • prerequisites 就是,要生成那个target所需要的文件或是目标。
  • command 也就是make需要执行的命令。(任意的Shell命令)
    target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容

文件名

“GNUmakefile”或“Makefile”或“makefile”的文件(也可以通过make -f或者make --file来指定文件)

makefile工作流程

  1.    读入所有的Makefile。
    
  2.    读入被include的其它Makefile。
    
  3.    初始化文件中的变量。
    
  4.    推导隐晦规则,并分析所有规则。
    
  5.    为所有的目标文件创建依赖关系链。
    
  6.    根据依赖关系,决定哪些目标要重新生成。
    
  7.    执行生成命令。
    

编译多个c文件过程例子
在默认的方式下,也就是我们只输入make命令。那么,

  • make会在当前目录下找名字叫“GNUmakefile”或“Makefile”或“makefile”的文件(也可以通过make -f或者make --file来指定文件)。
  • 如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的目标文件。
  • 如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
  • 如果edit所依赖的.o文件也存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
  • 当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件声明make的终极任务,也就是执行文件edit了。
    这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。
    通过上述分析,我们知道,像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要make执行。即命令——“make clean”,以此来清除所有的目标文件,以便重编译。
    于是在我们编程中,如果这个工程已被编译过了,当我们修改了其中一个源文件,比如file.c,那么根据我们的依赖性,我们的目标file.o会被重编译(也就是在这个依性关系后面所定义的命令),于是file.o的文件也是最新的啦,于是file.o的文件修改时间要比edit要新,所以edit也会被重新链接了(详见edit目标文件后定义的命令)。
    而如果我们改变了“command.h”,那么,kdb.o、command.o和files.o都会被重编译,并且,edit会被重链接。

语法

基本格式

target:prerequisites
    command

或者

target:prerequisites;command

command 前面是tab键,不是空格,可以用\来分行,target、prerequisite、command都可以多个用空格隔开,后面的command要用前一条的结果需要使用分号隔开,比如

cd /home;pwd

cd home
pwd

是不同的,command可以多行

注释

只支持行注释,用#

默认target

只输入make,默认执行第一个target,也可以指定

default: modules

变量

VAR=......
$(VAR)
  • 大小写敏感
  • 要使用$时,用$$,不是\$
  • 除了用=,还可以用:=,这种方法,前面的变量不能使用后面的变量,只能使用前面已定义好了的变量
x := foo
y := $(x) bar
x := later

等价于

y := foo bar
x := later

如果使用定义在后面的变量,则没有值

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

y=bar,x=foo

  • ?=:没有定义这个变量就定义一个
  • $(var.a=b):替换 变量中的值
foo := a.o b.o c.o
bar := $(foo:.o=.c)
  • 变量的值当成变量
x = y
y = z
a := $($(x))
  • 变量组合
first_second = Hello
a = first
b = second
all = $($a_$b)
  • 追加变量值
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
  • 目标变量(Target-specific Variable)
    只在这个目标内生效(类似C局部变量)
<target ...> : <variable-assignment>
<target ...> : overide <variable-assignment>
  • 模式变量(Pattern-specific Variable)
    make的“模式”一般是至少含有一个“%”的,所以,我们可以以如下方式给所有以[.o]结尾的目标定义目标变量:
%.o : CFLAGS = -O

同样,模式变量的语法和“目标变量”一样:

<pattern ...> : <variable-assignment>
<pattern ...> : override <variable-assignment>

override同样是针对于系统环境传入的变量,或是make命令行指定的变量。

  • 自动化变量
    自动化变量,就是这种变量会把模式中所定义的一系列的文件自动地挨个取出,直至所有的符合模式的文件都取完了。这种自动化变量只应出现在规则的命令中。

override指示符

如果有变量是通常make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果你想在Makefile中设置这类参数的值,那么,你可以使用“override”指示符。其语法是:

override <variable> = <value>
override <variable> := <value>
当然,还可以追加:
override <variable> += <more text>
多行变量:
override define foo
bar
endef

.PHONY(伪目标)

target相同名的文件在makefile目录下,执行target而不是

.PHONY: modules
modules:

include

include<filename>
 
  • filename可以是当前操作系统Shell的文件模式(可以保含路径和通配符)
  • 在include前面可以有一些空字符,但是绝不能是[Tab]键开始。include和可以用一个或多个空格隔开。

忽略警告继续执行

clean:
   -rm not_exist.file exist.file

-include<filename>

环境变量MAKEFILES

所有makefile都会include它,影响到所有makefile,不建议使用

通配符

  • ~:当前用户
  • *
  • ?

转义符、单行拆分成多行

clean:
   VAR=find ./ -name "a*"
   echo $(VAR)
   rm a &&\
   touch b

vpath

Makefile文件中的特殊变量“VPATH”就是完成这个功能的,如果没有指明这个变量,make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make就会在当当前目录找不到的情况下,到所指定的目录中去找寻文件了。

VPATH = src:../headers

上面的的定义指定两个目录,“src”和“../headers”,make会按照这个顺序进行搜索。目录由“冒号”分隔。(当然,当前目录永远是最高优先搜索的地方)

另一个设置文件搜索路径的方法是使用make的“vpath”关键字(注意,它是全小写的),这不是变量,这是一个make的关键字,这和上面提到的那个VPATH变量很类似,但是它更为灵活。它可以指定不同的文件在不同的搜索目录中。这是一个很灵活的功能。它的使用方法有三种:

  1.    vpath < pattern> < directories>    为符合模式< pattern>的文件指定搜索目录<directories>。
    
  2.    vpath < pattern>                              清除符合模式< pattern>的文件的搜索目录。
    
  3.    vpath                                                 清除所有已被设置好了的文件搜索目录。
    

vapth使用方法中的< pattern>需要包含“%”字符。“%”的意思是匹配零或若干字符,例如,“%.h”表示所有以“.h”结尾的文件。< pattern>指定了要搜索的文件集,而< directories>则指定了的文件集的搜索的目录。例如:
vpath %.h ../headers
可以连续地使用vpath语句,以指定不同搜索策略

   vpath %.c foo
   vpath %   blish
   vpath %.c bar

多target

$@:目前规则中所有的目标集合

   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

 

其中,

-$(subst output,,$@) 

中的“@”

表示目标的集合,就像一个数组,“$@”依次取出目标,并执于命令。

静态模式

   objects = foo.o bar.o
 
   all: $(objects)
 
   $(objects): %.o: %.c
           $(CC) -c $(CFLAGS) $< -o $@

自动生成依赖性

cc -M main.c

其输出是:

main.o : main.c defs.h

注意gcc

gcc -M main.c

相当于

 main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \
        /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \
        /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \
        /usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \
        /usr/include/bits/sched.h /usr/include/libio.h \
        /usr/include/_G_config.h /usr/include/wchar.h \
        /usr/include/bits/wchar.h /usr/include/gconv.h \
        /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \
        /usr/include/bits/stdio_lim.h

 

gcc-MM main.c

相当于

main.o: main.c defs.h

系统命令

可以使用系统的命令,默认使用/bin/sh

命令显示(回显)

在命令前家@可以不显示执行的命令

@echo 'hello'

不会输出echo `hello`,只输出hello
使用make -n(或者--just-print),只显示过程,真正执行命令,用来调试makefile

嵌套makefile

subsystem:
 cd subdir && $(MAKE)

传递变量:export <variable>
传递所有变量:export
始终会默认传递的变量:MAKEFLAGS,SHELL
记录嵌套层数的变量:MAKELEVEL

定义命令包(多行变量)

 

define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef

foo.c : foo.y
    $(run-yacc)

命令包“run-yacc”中的

foo.y

“@”就是“foo.c”


 

条件判断

ifeq(,) #ifeq ' ' ' '  或者ifeq " " " "或ifeq ' ' " "
...
else
...
endif

ifneq ( , )
...
endif

ifdef <...>
...
endif

ifneq语句

  • ifneq : 判断语句,用于比较两个参数,如果两个参数不等,则该语句通过
# 如果a和b不相等,则do something
ifneq ($(a), $(b))
    # do something
endif

filter语句

  • filter: 过滤语句,过滤调不符合指定的模式的内容。仅保留符合指定的模式的内容。
sources := a b c d f g
# 指定的模式为 a b c ,多个模式间,用空格区分
$(filter a b c , $(sources))
# 上式返回值为
# a b c

ifneq + filter

  • 场景:某项目多个版本(A,B, C),同时进行开发。除了代码中的一些宏开关外,在编译时,也需要进行不同版本的判断。
  • 接上:版本A、B,编译某模块时需要一个特殊参数。版本C,编译该模块时,不需要该特殊参数。如何控制?
# 如果 TARGET 为A 或 B(即不为空),那么加入某些特殊参数
# 这里ifneq第二个参数为NULL
ifneq ($(filter A B , $(TARGET)),)
   #某些特殊参数
endif

其他

  • filter命令在过滤时,大小写敏感(区分大小写) 

 makefile的if语法

#if 函数的语法是:
#$(if <condition>,<then-part> )
#或
#$(if <condition>,<then-part>,<else-part> )
#<condition>参数是if的表达式,如果其返回的为非空字符串,那么这个表达式就相当于返回真,于是,<then-part>会被计算,否则<else-part>会被计算
#
#if函数的返回值是,
#     如果<condition>为真(非空字符串),那个<then-part>会是整个函数的返回值,
#     如果<condition>为假(空字符串),那么<else-part>会是整个函数的返回值,此时如果<else-part>没有被定义,那么,整个函数返回空字串。

SRC_DIR := src

#if函数---设置默认值
#如果变量SRC_DIR的值不为空,则将SRC_DIR指定的目录作为SUBDIR子目录;否则将/home/src作为子目录
SUBDIR += $(if $(SRC_DIR) $(SRC_DIR),/home/src)

all:
    @echo $(SUBDIR)

 

1、wildcard : 扩展通配符

2、notdir : 去除路径

3、patsubst :替换通配符

4、条件表达式的语法 

 

函数

$开头,参数之间用,隔开

$(<function> <arguments> )
或是
${<function> <arguments>}
  • 字符串操作函数
$(subst <from>,<to>,<text> )
名称:字符串替换函数——subst。
功能:把字串<text>中的<from>字符串替换成<to>。
返回:函数返回被替换过后的字符串。

 

$(patsubst <pattern>,<replacement>,<text> )
名称:模式字符串替换函数——patsubst。
功能:查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern>,如果匹配的话,则以<replacement>替换。这里,<pattern>可以包括通配符“%”,表示任意长度的字串。如果<replacement>中也包含“%”,那么,<replacement>中的这个“%”将是<pattern>中的那个“%”所代表的字串。(可以用“\”来转义,以“\%”来表示真实含义的“%”字符)返回:函数返回被替换过后的字符串。

 

$(strip <string> )


名称:去空格函数——strip。
功能:去掉<string>字串中开头和结尾的空字符。
返回:返回被去掉空格的字符串值。

 

$(findstring <find>,<in> )


名称:查找字符串函数——findstring。
功能:在字串<in>中查找<find>字串。
返回:如果找到,那么返回<find>,否则返回空字符串。

 

$(filter <pattern...>,<text> )


名称:过滤函数——filter。
功能:以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词。可
以有多个模式。
返回:返回符合模式<pattern>的字串。
示例:


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

 

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


名称:反过滤函数——filter-out。
功能:以<pattern>模式过滤<text>字符串中的单词,去除符合模式<pattern>的单词。可
以有多个模式。
返回:返回不符合模式<pattern>的字串。

 

$(sort <list> )


名称:排序函数——sort。
功能:给字符串<list>中的单词排序(升序)。
返回:返回排序后的字符串。
示例:$(sort foo bar lose)返回“bar foo lose” 。
备注:sort函数会去掉<list>中相同的单词。

 

$(word <n>,<text> )


名称:取单词函数——word。
功能:取字符串<text>中第<n>个单词。(从一开始)
返回:返回字符串<text>中第<n>个单词。如果<n>比<text>中的单词数要大,那么返回空
字符串。

 

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


名称:取单词串函数——wordlist。
功能:从字符串<text>中取从<s>开始到<e>的单词串。<s>和<e>是一个数字。
返回:返回字符串<text>中从<s>到<e>的单词字串。如果<s>比<text>中的单词数要大,那
么返回空字符串。如果<e>大于<text>的单词数,那么返回从<s>开始,到<text>结束的单
词串。
示例: $(wordlist 2, 3, foo bar baz)返回值是“bar baz”。

 

$(words <text> )


名称:单词个数统计函数——words。
功能:统计<text>中字符串中的单词个数。
返回:返回<text>中的单词数。
示例:$(words, foo bar baz)返回值是“3”。
备注:如果我们要取<text>中最后的一个单词,我们可以这样:$(word $(words <text> 
),<text> )。

 

$(firstword <text> )


名称:首单词函数——firstword。
功能:取字符串<text>中的第一个单词。
返回:返回字符串<text>的第一个单词。
示例:$(firstword foo bar)返回值是“foo”。
备注:这个函数可以用word函数来实现:$(word 1,<text> )。
  • 文件名操作函数

 

$(dir <names...> )


名称:取目录函数——dir。
功能:从文件名序列<names>中取出目录部分。目录部分是指最后一个反斜杠(“/”)之
前的部分。如果没有反斜杠,那么返回“./”。
返回:返回文件名序列<names>的目录部分。
示例: $(dir src/foo.c hacks)返回值是“src/ ./”。

 

$(notdir <names...> )


名称:取文件函数——notdir。
功能:从文件名序列<names>中取出非目录部分。非目录部分是指最后一个反斜杠(“/”
)之后的部分。
返回:返回文件名序列<names>的非目录部分。
示例: $(notdir src/foo.c hacks)返回值是“foo.c hacks”。

 

$(suffix <names...> )


名称:取后缀函数——suffix。
功能:从文件名序列<names>中取出各个文件名的后缀。
返回:返回文件名序列<names>的后缀序列,如果文件没有后缀,则返回空字串。
示例:$(suffix src/foo.c src-1.0/bar.c hacks)返回值是“.c .c”。

 

$(basename <names...> )


名称:取前缀函数——basename。
功能:从文件名序列<names>中取出各个文件名的前缀部分。
返回:返回文件名序列<names>的前缀序列,如果文件没有前缀,则返回空字串。
示例:$(basename src/foo.c src-1.0/bar.c hacks)返回值是“src/foo src-1.0/bar h
acks”。

 

$(addsuffix <suffix>,<names...> )


名称:加后缀函数——addsuffix。
功能:把后缀<suffix>加到<names>中的每个单词后面。
返回:返回加过后缀的文件名序列。
示例:$(addsuffix .c,foo bar)返回值是“foo.c bar.c”。

 

$(addprefix <prefix>,<names...> )


名称:加前缀函数——addprefix。
功能:把前缀<prefix>加到<names>中的每个单词后面。
返回:返回加过前缀的文件名序列。
示例:$(addprefix src/,foo bar)返回值是“src/foo src/bar”。

 

$(join <list1>,<list2> )


名称:连接函数——join。
功能:把<list2>中的单词对应地加到<list1>的单词后面。如果<list1>的单词个数要比<
list2>的多,那么,<list1>中的多出来的单词将保持原样。如果<list2>的单词个数要比
<list1>多,那么,<list2>多出来的单词将被复制到<list2>中。
返回:返回连接过后的字符串。
示例:$(join aaa bbb , 111 222 333)返回值是“aaa111 bbb222 333”。

foreach函数

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

 

names := a b c d


files := $(foreach n,$(names),$(n).o)

上面的例子中,$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以,$(f
iles)的值是“a.o b.o c.o d.o”。


注意,foreach中的<var>参数是一个临时的局部变量,foreach函数执行完后,参数<var>的变量将不在作用,其作用域只在foreach函数当中。
  • if函数
$(if <condition>,<then-part> )

或是

$(if <condition>,<then-part>,<else-part> )
  • call函数
    call函数是唯一一个可以用来创建新的参数化的函数。你可以写一个非常复杂的表达式,这个表达式中,你可以定义许多参数,然后你可以用call函数来向这个表达式传递参数。其语法是:
$(call <expression>,<parm1>,<parm2>,<parm3>...)

 

reverse = $(1) $(2)
foo = $(call reverse,a,b)
(foo值:a b)
reverse = $(2) $(1)
foo = $(call reverse,a,b)
此时的foo的值就是“b a”。
  • origin函数
    origin函数不像其它的函数,他并不操作变量的值,他只是告诉你你的这个变量是哪里来的?其语法是:
$(origin <variable> )

取值:

“undefined”


如果<variable>从来没有定义过,origin函数返回这个值“undefined”。

“default”


如果<variable>是一个默认的定义,比如“CC”这个变量,这种变量我们将在后面讲述。

“environment”


如果<variable>是一个环境变量,并且当Makefile被执行时,“-e”参数没有被打开。


“file”


如果<variable>这个变量被定义在Makefile中。


“command line”


如果<variable>这个变量是被命令行定义的。

“override”


如果<variable>是被override指示符重新定义的。

“automatic”


如果<variable>是一个命令运行中的自动化变量。关于自动化变量将在后面讲述。
  • shell 函数
    shell 函数也不像其它的函数。顾名思义,它的参数应该就是操作系统Shell的命令。它和反引号“`”是相同的功能。这就是说,shell函数把执行操作系统命令后的输出作为函数
    返回。于是,我们可以用操作系统命令以及字符串处理命令awk,sed等等命令来生成一个变量,如:
contents := $(shell cat foo)
files := $(shell echo *.c)

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

  • 控制make的函数
$(error <text ...> )

 

$(warning <text ...> )

make退出码

0 —— 表示成功执行。
1 —— 如果make运行时出现任何错误,其返回1。
2 —— 如果你使用了make的“-q”选项,并且make使得一些目标不需要更新,那么返回2。

make指定目标

GNU这种开源软件的发布时,其 makefile都包含了编译、安装、打包等功能。我们可以参照这种规则来书写我们的makefile中的目标。
**“all” ** 这个伪目标是所有目标的目标,其功能一般是编译所有的目标。
**“clean” **这个伪目标功能是删除所有被make创建的文件。
**“install” **这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。
**“print” **这个伪目标的功能是例出改变过的源文件。
**“tar” ** 这个伪目标功能是把源程序打包备份。也就是一个tar文件。
**“dist” ** 这个伪目标功能是创建一个压缩文件,一般是把tar文件压成Z文件。或是gz文件。
**“TAGS” ** 这个伪目标功能是更新所有的目标,以备完整地重编译使用。
**“check”和“test” **这两个伪目标一般用来测试makefile的流程。

make检查规则 make调试

  • make时加参数
有时候,我们不想让我们的makefile中的规则执行起来,我们只想检查一下我们的命令,或是执行的序列。于是我们可以使用make命令的下述参数:


“-n”
“--just-print”
“--dry-run”
“--recon”
不执行参数,这些参数只是打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,但不执行,这些参数对于我们调试makefile很有用处。


“-t”
“--touch”
这个参数的意思就是把目标文件的时间更新,但不更改目标文件。也就是说,make假装编译目标,但不是真正的编译目标,只是把目标变成已编译过的状态。


“-q”
“--question”
这个参数的行为是找目标的意思,也就是说,如果目标存在,那么其什么也不会输出,当然也不会执行编译,如果目标不存在,其会打印出一条出错信息。


“-W <file>”
“--what-if=<file>”
“--assume-new=<file>”
“--new-file=<file>”
这个参数需要指定一个文件。一般是是源文件(或依赖文件),Make会根据规则推导来运行依赖于这个文件的命令,一般来说,可以和“-n”参数一同使用,来查看这个依赖文件
所发生的规则命令。

另外一个很有意思的用法是结合“-p”和“-v”来输出makefile被执行时的信息(这个将在后面讲述)。
  • 在makefile中添加调试信息
    使用
 $(`信息等级` `...调试信息..`)
如:
$(info "hello")
$(warning "hello")
$(error "hello")

或者使用

@echo ......

这种的局限就是只能在目标后面使用

make参数

参见make --help或者man make

隐含规则

报错立即停止

参考:makefile出现错误却不停止,却继续运行

makefile执行错误,结果还会继续执行,此处是由于是上层makefile调用下层子makefile,子makefile执行出错,停止返回到上层后,上层没有判断返回值,导致还是会继续执行。

解决办法是,对于子makefile调用,判断返回值,
比如将:
make $@;
改为:
make $@ || exit "$$?";
这样make执行错误返回值为非0,然后就可以执行后面的exit而退出了。

 

学习资料

Makefile经典教程

https://blog.csdn.net/weixin_38391755/article/details/80380786

©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页