选中内容(绿色)时除了会搜索文章名,还会搜索文章内容
点击结果中的文章名进入文章界面后可以按Ctrl+F在页面内搜索
  • 版权:CC BY-SA 4.0
  • 创建:2019-10-14
  • 更新:2019-10-19
Makefile 笔记


makefile的规则

组成

  • target
  • prerequisites
  • command
    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会被重链接。

语法

基本格式

  1. target:prerequisites
  2. command

或者

  1. target:prerequisites;command

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

  1. cd /home;pwd

  1. cd home
  2. pwd

是不同的,command可以多行

注释

只支持行注释,用#

默认target

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

  1. default: modules

变量

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

    1. x := foo
    2. y := $(x) bar
    3. x := later

    等价于

    1. y := foo bar
    2. x := later

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

    1. y := $(x) bar
    2. x := foo

    y=bar,x=foo

  • ?=:没有定义这个变量就定义一个

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

    1. %.o : CFLAGS = -O

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

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

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

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

override指示符

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

  1. override <variable> = <value>
  2. override <variable> := <value>
  3. 当然,还可以追加:
  4. override <variable> += <more text>
  5. 多行变量:
  6. override define foo
  7. bar
  8. endef

.PHONY(伪目标)

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

  1. .PHONY: modules
  2. modules:

include

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

忽略警告继续执行

  1. clean:
  2. -rm not_exist.file exist.file
  3. -include<filename>

环境变量MAKEFILES

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

通配符

  • ~:当前用户
  • *
  • ?

转义符、单行拆分成多行

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

vpath

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

  1. VPATH = src:../headers

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

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

  1. vpath < pattern> < directories> 为符合模式< pattern>的文件指定搜索目录
  2. vpath < pattern> 清除符合模式< pattern>的文件的搜索目录。
  3. vpath 清除所有已被设置好了的文件搜索目录。
    vapth使用方法中的< pattern>需要包含“%”字符。“%”的意思是匹配零或若干字符,例如,“%.h”表示所有以“.h”结尾的文件。< pattern>指定了要搜索的文件集,而< directories>则指定了的文件集的搜索的目录。例如:
    vpath %.h ../headers
    可以连续地使用vpath语句,以指定不同搜索策略
    1. vpath %.c foo
    2. vpath % blish
    3. vpath %.c bar

多target

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

  1. bigoutput littleoutput : text.g
  2. generate text.g -$(subst output,,$@) > $@

上述规则等价于:

  1. bigoutput : text.g
  2. generate text.g -big > bigoutput
  3. littleoutput : text.g
  4. generate text.g -little > littleoutput

其中,-$(subst output,,$@)中的“$”表示执行一个Makefile的函数,函数名为subst,后面的为参数。关于函数,将在后面讲述。这里的这个函数是截取字符串的意思,“$@”表示目标的集合,就像一个数组,“$@”依次取出目标,并执于命令。

静态模式

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

自动生成依赖性

  1. cc -M main.c

其输出是:

  1. main.o : main.c defs.h

注意gcc

  1. gcc -M main.c

相当于

  1. main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \
  2. /usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \
  3. /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \
  4. /usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \
  5. /usr/include/bits/sched.h /usr/include/libio.h \
  6. /usr/include/_G_config.h /usr/include/wchar.h \
  7. /usr/include/bits/wchar.h /usr/include/gconv.h \
  8. /usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \
  9. /usr/include/bits/stdio_lim.h
  1. gcc-MM main.c

相当于

  1. main.o: main.c defs.h

系统命令

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

命令显示(回显)

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

  1. @echo 'hello'

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

嵌套makefile

  1. subsystem:
  2. cd subdir && $(MAKE)

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

定义命令包(多行变量)

  1. define run-yacc
  2. yacc $(firstword $^)
  3. mv y.tab.c $@
  4. endef
  5. foo.c : foo.y
  6. $(run-yacc)

命令包“run-yacc”中的“$^”就是“foo.y”,“$@”就是“foo.c”

条件判断

  1. ifeq(,) #ifeq ' ' ' ' 或者ifeq " " " "或ifeq ' ' " "
  2. ...
  3. else
  4. ...
  5. endif
  6. ifneq ( , )
  7. ...
  8. endif
  9. ifdef <...>
  10. ...
  11. endif

函数

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

  1. $(<function> <arguments> )
  2. 或是
  3. ${<function> <arguments>}
  • 字符串操作函数
  1. $(subst <from>,<to>,<text> )
  2. 名称:字符串替换函数——subst
  3. 功能:把字串<text>中的<from>字符串替换成<to>。
  4. 返回:函数返回被替换过后的字符串。
  1. $(patsubst <pattern>,<replacement>,<text> )
  2. 名称:模式字符串替换函数——patsubst
  3. 功能:查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern>,如果匹配的话,则以<replacement>替换。这里,<pattern>可以包括通配符“%”,表示任意长度的字串。如果<replacement>中也包含“%”,那么,<replacement>中的这个“%”将是<pattern>中的那个“%”所代表的字串。(可以用“\”来转义,以“\%”来表示真实含义的“%”字符)返回:函数返回被替换过后的字符串。
  1. $(strip <string> )
  2. 名称:去空格函数——strip
  3. 功能:去掉<string>字串中开头和结尾的空字符。
  4. 返回:返回被去掉空格的字符串值。
  1. $(findstring <find>,<in> )
  2. 名称:查找字符串函数——findstring
  3. 功能:在字串<in>中查找<find>字串。
  4. 返回:如果找到,那么返回<find>,否则返回空字符串。
  1. $(filter <pattern...>,<text> )
  2. 名称:过滤函数——filter
  3. 功能:以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词。可
  4. 以有多个模式。
  5. 返回:返回符合模式<pattern>的字串。
  6. 示例:
  7. sources := foo.c bar.c baz.s ugh.h
  8. foo: $(sources)
  9. cc $(filter %.c %.s,$(sources)) -o foo
  1. $(filter-out <pattern...>,<text> )
  2. 名称:反过滤函数——filter-out
  3. 功能:以<pattern>模式过滤<text>字符串中的单词,去除符合模式<pattern>的单词。可
  4. 以有多个模式。
  5. 返回:返回不符合模式<pattern>的字串。
  1. $(sort <list> )
  2. 名称:排序函数——sort
  3. 功能:给字符串<list>中的单词排序(升序)。
  4. 返回:返回排序后的字符串。
  5. 示例:$(sort foo bar lose)返回“bar foo lose
  6. 备注:sort函数会去掉<list>中相同的单词。
  1. $(word <n>,<text> )
  2. 名称:取单词函数——word
  3. 功能:取字符串<text>中第<n>个单词。(从一开始)
  4. 返回:返回字符串<text>中第<n>个单词。如果<n>比<text>中的单词数要大,那么返回空
  5. 字符串。
  1. $(wordlist <s>,<e>,<text> )
  2. 名称:取单词串函数——wordlist
  3. 功能:从字符串<text>中取从<s>开始到<e>的单词串。<s>和<e>是一个数字。
  4. 返回:返回字符串<text>中从<s>到<e>的单词字串。如果<s>比<text>中的单词数要大,那
  5. 么返回空字符串。如果<e>大于<text>的单词数,那么返回从<s>开始,到<text>结束的单
  6. 词串。
  7. 示例: $(wordlist 2, 3, foo bar baz)返回值是“bar baz”。
  1. $(words <text> )
  2. 名称:单词个数统计函数——words
  3. 功能:统计<text>中字符串中的单词个数。
  4. 返回:返回<text>中的单词数。
  5. 示例:$(words, foo bar baz)返回值是“3”。
  6. 备注:如果我们要取<text>中最后的一个单词,我们可以这样:$(word $(words <text>
  7. ),<text> )。
  1. $(firstword <text> )
  2. 名称:首单词函数——firstword
  3. 功能:取字符串<text>中的第一个单词。
  4. 返回:返回字符串<text>的第一个单词。
  5. 示例:$(firstword foo bar)返回值是“foo”。
  6. 备注:这个函数可以用word函数来实现:$(word 1,<text> )。
  • 文件名操作函数
  1. $(dir <names...> )
  2. 名称:取目录函数——dir
  3. 功能:从文件名序列<names>中取出目录部分。目录部分是指最后一个反斜杠(“/”)之
  4. 前的部分。如果没有反斜杠,那么返回“./”。
  5. 返回:返回文件名序列<names>的目录部分。
  6. 示例: $(dir src/foo.c hacks)返回值是“src/ ./”。
  1. $(notdir <names...> )
  2. 名称:取文件函数——notdir
  3. 功能:从文件名序列<names>中取出非目录部分。非目录部分是指最后一个反斜杠(“/”
  4. )之后的部分。
  5. 返回:返回文件名序列<names>的非目录部分。
  6. 示例: $(notdir src/foo.c hacks)返回值是“foo.c hacks”。
  1. $(suffix <names...> )
  2. 名称:取后缀函数——suffix
  3. 功能:从文件名序列<names>中取出各个文件名的后缀。
  4. 返回:返回文件名序列<names>的后缀序列,如果文件没有后缀,则返回空字串。
  5. 示例:$(suffix src/foo.c src-1.0/bar.c hacks)返回值是“.c .c”。
  1. $(basename <names...> )
  2. 名称:取前缀函数——basename
  3. 功能:从文件名序列<names>中取出各个文件名的前缀部分。
  4. 返回:返回文件名序列<names>的前缀序列,如果文件没有前缀,则返回空字串。
  5. 示例:$(basename src/foo.c src-1.0/bar.c hacks)返回值是“src/foo src-1.0/bar h
  6. acks”。
  1. $(addsuffix <suffix>,<names...> )
  2. 名称:加后缀函数——addsuffix
  3. 功能:把后缀<suffix>加到<names>中的每个单词后面。
  4. 返回:返回加过后缀的文件名序列。
  5. 示例:$(addsuffix .c,foo bar)返回值是“foo.c bar.c”。
  1. $(addprefix <prefix>,<names...> )
  2. 名称:加前缀函数——addprefix
  3. 功能:把前缀<prefix>加到<names>中的每个单词后面。
  4. 返回:返回加过前缀的文件名序列。
  5. 示例:$(addprefix src/,foo bar)返回值是“src/foo src/bar”。
  1. $(join <list1>,<list2> )
  2. 名称:连接函数——join
  3. 功能:把<list2>中的单词对应地加到<list1>的单词后面。如果<list1>的单词个数要比<
  4. list2>的多,那么,<list1>中的多出来的单词将保持原样。如果<list2>的单词个数要比
  5. <list1>多,那么,<list2>多出来的单词将被复制到<list2>中。
  6. 返回:返回连接过后的字符串。
  7. 示例:$(join aaa bbb , 111 222 333)返回值是“aaa111 bbb222 333”。

foreach函数

  1. $(foreach <var>,<list>,<text> )
  1. names := a b c d
  2. files := $(foreach n,$(names),$(n).o)
  3. 上面的例子中,$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以,$(f
  4. iles)的值是“a.o b.o c.o d.o”。
  5. 注意,foreach中的<var>参数是一个临时的局部变量,foreach函数执行完后,参数<var>的变量将不在作用,其作用域只在foreach函数当中。
  • if函数
  1. $(if <condition>,<then-part> )
  2. 或是
  3. $(if <condition>,<then-part>,<else-part> )
  • call函数
    call函数是唯一一个可以用来创建新的参数化的函数。你可以写一个非常复杂的表达式,这个表达式中,你可以定义许多参数,然后你可以用call函数来向这个表达式传递参数。其语法是:
    1. $(call <expression>,<parm1>,<parm2>,<parm3>...)
    1. reverse = $(1) $(2)
    2. foo = $(call reverse,a,b)
    3. foo值:a b
    4. reverse = $(2) $(1)
    5. foo = $(call reverse,a,b)
    6. 此时的foo的值就是“b a”。
  • origin函数
    origin函数不像其它的函数,他并不操作变量的值,他只是告诉你你的这个变量是哪里来的?其语法是:
    1. $(origin <variable> )

取值:

  1. undefined
  2. 如果<variable>从来没有定义过,origin函数返回这个值“undefined”。
  3. default
  4. 如果<variable>是一个默认的定义,比如“CC”这个变量,这种变量我们将在后面讲述。
  5. environment
  6. 如果<variable>是一个环境变量,并且当Makefile被执行时,“-e”参数没有被打开。
  7. file
  8. 如果<variable>这个变量被定义在Makefile中。
  9. command line
  10. 如果<variable>这个变量是被命令行定义的。
  11. override
  12. 如果<variable>是被override指示符重新定义的。
  13. automatic
  14. 如果<variable>是一个命令运行中的自动化变量。关于自动化变量将在后面讲述。
  • shell 函数
    shell 函数也不像其它的函数。顾名思义,它的参数应该就是操作系统Shell的命令。它和反引号“`”是相同的功能。这就是说,shell函数把执行操作系统命令后的输出作为函数
    返回。于是,我们可以用操作系统命令以及字符串处理命令awk,sed等等命令来生成一个变量,如:

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

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

  • 控制make的函数

    1. $(error <text ...> )
    1. $(warning <text ...> )

make退出码

  1. 0 —— 表示成功执行。
  2. 1 —— 如果make运行时出现任何错误,其返回1
  3. 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时加参数
  1. 有时候,我们不想让我们的makefile中的规则执行起来,我们只想检查一下我们的命令,或是执行的序列。于是我们可以使用make命令的下述参数:
  2. “-n
  3. “--just-print
  4. “--dry-run
  5. “--recon
  6. 不执行参数,这些参数只是打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,但不执行,这些参数对于我们调试makefile很有用处。
  7. “-t
  8. “--touch
  9. 这个参数的意思就是把目标文件的时间更新,但不更改目标文件。也就是说,make假装编译目标,但不是真正的编译目标,只是把目标变成已编译过的状态。
  10. “-q
  11. “--question
  12. 这个参数的行为是找目标的意思,也就是说,如果目标存在,那么其什么也不会输出,当然也不会执行编译,如果目标不存在,其会打印出一条出错信息。
  13. “-W <file>
  14. “--what-if=<file>”
  15. “--assume-new=<file>”
  16. “--new-file=<file>”
  17. 这个参数需要指定一个文件。一般是是源文件(或依赖文件),Make会根据规则推导来运行依赖于这个文件的命令,一般来说,可以和“-n”参数一同使用,来查看这个依赖文件
  18. 所发生的规则命令。
  19. 另外一个很有意思的用法是结合“-p”和“-v”来输出makefile被执行时的信息(这个将在后面讲述)。
  • 在makefile中添加调试信息
    使用
    1. $(`信息等级` `...调试信息..`)
    2. 如:
    3. $(info "hello")
    4. $(warning "hello")
    5. $(error "hello")
    或者使用
    1. @echo ......
    这种的局限就是只能在目标后面使用

make参数

参见make --help或者man make

隐含规则

报错立即停止

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

  1. makefile执行错误,结果还会继续执行,此处是由于是上层makefile调用下层子makefile,子makefile执行出错,停止返回到上层后,上层没有判断返回值,导致还是会继续执行。
  2. 解决办法是,对于子makefile调用,判断返回值,
  3. 比如将:
  4. make $@;
  5. 改为:
  6. make $@ || exit "$$?";
  7. 这样make执行错误返回值为非0,然后就可以执行后面的exit而退出了。

==================================

学习资料

Makefile经典教程

文章有误?有想法想讨论?查看或者发起勘误/讨论 主题
(发起评论需要先登录 github)

/wallpaper/wallhaven-j5lk95.jpg