makefile

makefile

1 make和makefile简介

(1)make

make是一个应用程序,主要用于解析源程序之间的依赖关系,根据依赖关系自动维护编译工作,并执行宿主操作系统中的各种命令。

(2)makefie

①概念

makefile是一个描述文件,可以简单的认为是一个工程文件的编译规则,描述了整个工程的编译和链接等规则。其中包含了哪些文件需要编译,哪些文件不需要编译,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重建等等。makefile拥有特定的语法规则,支持函数定义和函数调用,能够直接集成操作系统中的各种命令。编译整个工程需要涉及到的,在 makefile 中都可以进行描述。换句话说,makefile 可以使得我们的项目工程的编译变得自动化,不需要每次都手动输入一堆源文件和参数。

②makefile 支持多线程并发操作,会极大的缩短我们的编译时间,并且当我们修改了源文件之后,编译整个工程的时候,make 命令只会编译我们修改过的文件,没有修改的文件不用重新编译,也极大的解决了我们耗费时间的问题。

(3)make和makefile的关系

makefile中的描述用于指导make程序如何完成工作;make根据makefile中的规则执行命令,最后完成编译输出。即配置好makefile后,最后通过make命令,即可编译全部文件。

make命令格式:

make [options] [target]

常用的参数不多,说下-f和-n其余的自己可通过make –h了解。

  • -f:指定规则文件(makefile/Makefile可以随意起名)

  • -n: 输出make即将执行的命令(可检查makefile的语法是否正确,要执行的命令是否符合预期)

 

2 makefile编写规则

(1)makefile的规则主要由两部分组成,分别是依赖的关系执行的命令,其结构如下:

targets:prerequisites    
    command

相关说明如下:

  • targets:编译的目标,可以是 Object File(一般称它为中间文件),也可以是可执行文件,还可以是一个标签;

  • prerequisites:编译的目标依赖的条件,要生成 targets 需要的文件或者是目标。可以是多个,也可以是没有;

  • command:make 需要执行的命令(任意的 shell 命令)。可以有多条命令,每一条命令占一行。

    注意:我们的目标和依赖文件之间要使用冒号分隔开,命令的开始一定要使用Tab键

    例如:

test:test.c   
    gcc -o test test.c

上述代码实现的功能就是编译test.c文件,其中test是目标文件,也是我们最终生成的可执行文件,依赖文件为test.c源文件,重建目标文件需要执行的操作是gcc -o test test.c。

(2)规则中的注意事项

[Tab]键:'\t'

  • 每一个命令必须以[Tab]字符开始

  • [Tab]字符告诉make此行是一个命令行

续行符:“\”

  • 可以将内容分开写到下一行,提高可读性

依赖规则

  • 当目标对应的文件不存在,执行对应命令

  • 当依赖在时间上比目标更新,执行对应命令(编译过的不重复编译,除非make clean

  • 当依赖关系连续发生时,对比依赖链上的每一个目标

(3)目标和条件的关系

目标和条件之间的关系是:欲更新目标,必须首先更新它的所有条件;所有条件中只要有一个条件被更新了,目标也必须随之被更新。所谓“更新”就是执行一遍规则中的命令列表(不需要非得生成目标,执行命令了则视为已更新)

目标被更新的依据:

  • 目标没有生成。

  • 某个条件需要更新(条件可以是另一个目标,嵌套递归)。

  • 某个条件的修改时间比目标晚(修改时间戳)。

(4)-o文件

对 ".o" 文件为目标的规则处理有下列三种情况:

  • 目标 ".o" 文件不存在,使用其描述规则创建它;

  • 目标 ".o" 文件存在,目标 ".o" 文件所依赖的 ".c" 源文件 ".h" 文件中的任何一个比目标 ".o" 文件“更新”(在上一次 make 之后被修改)。则根据规则重新编译生成它;

  • 目标 ".o" 文件存在,目标 ".o" 文件比它的任何一个依赖文件(".c" 源文件、".h" 文件)“更新”(它的依赖文件在上一次 make 之后没有被修改),则什么也不做。

编译案例:

hello.out : main.o  func.o
    gcc -o hello.out  main.o  func.o
main.o : main.c
    gcc -o main.o -c main.c
func.o : func.c
    gcc -o func.o -c func.c

(5) 简单的概括一下makefile 中的内容,它主要包含有五个部分,分别是:

  1. 显式规则

显式规则说明了,如何生成一个或多的的目标文件。这是由 makefile 的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。

  1. 隐晦规则

由于我们的 make 命名有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写 makefile,这是由 make 命令所支持的。

  1. 变量的定义

在 makefile 中我们要定义一系列的变量,变量一般都是字符串,这个有点像C语言中的宏,当 makefile 被执行时,其中的变量都会被扩展到相应的引用位置上。

  1. 文件指示

其包括了三个部分,一个是在一个 makefile 中引用另一个 makefile,就像C语言中的 include 一样;另一个是指根据某些情况指定 makefile 中的有效部分,就像C语言中的预编译 #if 一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。

  1. 注释

makefile 中只有行注释,和 UNIX 的 Shell 脚本一样,其注释是用“#”字符,这个就像 C/C++中的“//”一样。如果你要在你的 makefile 中使用“#”字符,可以用反斜框进行转义,如:“#”。

 

3 makefile伪目标

(1)作用

伪目标并不会创建目标文件,只是想去执行这个目标下面的命令,伪目标的存在可以帮助我们找到命令并执行。

使用伪目标有两点原因:

  • 避免我们的 makefile 中定义的只执行的命令的目标和工作目录下的实际文件出现名字冲突。

  • 提高执行 make 时的效率,特别是对于一个大型的工程来说,提高编译的效率也是我们所必需的。

(2)如果需要书写这样一个规则,规则所定义的命令不是去创建文件,而是通过 make 命令明确指定它来执行一些特定的命令。实例如下:

clean:
    rm -rf *.o test

执行make clean时:

1)当目录下不存在名为clean的文件时:会执行rm -rf *.o test

2)当目录下存在名为clean的文件时: 由于这个规则没有依赖文件,所以目标被认为是已存在的而不去执行规则所定义的命令。因此命令 rm 将不会被执行。

(3)第(2)点的解决方法:

1)最蠢的方法,人为确保目标名不要跟工程文件名有重名的情况。

2)目标后面加上两个冒号,代表强制目标。

3)makefile提供了内嵌的关键字.PHONY,可提前声明下clean伪目标。如下所示:

.PHONY:clean
clean:
    rm -rf *.o test

这样 clean 就被声明成一个伪目标,无论当前目录下是否存在 clean 这个文件,当我们执行 make clean 后 rm 都会被执行。而且当一个目标被声明为伪目标之后,make 在执行此规则时不会去试图去查找隐含的关系去创建它。这样同样提高了 make 的执行效率,同时也不用担心目标和文件名重名而使我们的编译失败。

 

4 makefile变量

(1)基本语法

变量的名称 = 值列表

值列表,既可以是零项,又可以是一项或者是多项,例如:

${TARGET} : func.o main.o

(2)变量的引用

调用变量的时候可以用 "$(VALUE_LIST)" 或者是 "${VALUE_LIST}" 来替换,这就是变量的引用,例如:

OBJ=main.o test.o test1.o test2.o
test:$(OBJ)
gcc -o test $(OBJ) 

(3)变量的赋值方式

  • 简单赋值 ( := ) 编程语言中常规理解的赋值方式,只对当前语句的变量有效。

  • 递归赋值 ( = ) 赋值语句可能影响多个变量,所有目标变量相关的其他变量都受影响。

  • 条件赋值 ( ?= ) 如果变量未定义,则使用符号中的值定义变量。如果该变量已经赋值,则该赋值语句无效。

  • 追加赋值 ( += ) 原变量用空格隔开的方式追加一个新值。

(4)自动化变量

自动化变量 说明
$@ 表示规则的目标文件名。如果目标是一个文档文件(Linux 中,一般成 .a 文件为文档文件,也成为静态的库文件), 那么它代表这个文档的文件名。在多目标模式规则中,它代表的是触发规则被执行的文件名。
$% 当目标文件是一个静态库文件时,代表静态库的一个成员名。
$< 规则的第一个依赖的文件名。如果是一个目标文件使用隐含的规则来重建,则它代表由隐含规则加入的第一个依赖文件。
$? 所有比目标文件更新的依赖文件列表,空格分隔。如果目标文件时静态库文件,代表的是库文件(.o 文件)。
$^ 代表的是所有依赖文件列表,使用空格分隔。如果目标是静态库文件,它所代表的只能是所有的库成员(.o 文件)名。 一个文件可重复的出现在目标的依赖中,变量“$^”只记录它的第一次引用的情况。就是说变量“$^”会去掉重复的依赖文件。
$+ 类似“$^”,但是它保留了依赖文件中重复出现的文件。主要用在程序链接时库的交叉引用场合。
$* 在模式规则和静态模式规则中,代表“茎”。“茎”是目标模式中“%”所代表的部分(当文件名中存在目录时, “茎”也包含目录部分)。

下面是一些详细的描述:

变量名 功能
$(@D) 表示文件的目录部分(不包括斜杠)。如果 "$@" 表示的是 "dir/foo.o" 那么 "$(@D)" 表示的值就是 "dir"。如果 "$@" 不存在斜杠(文件在当前目录下),其值就是 "."。
$(@F) 表示的是文件除目录外的部分(实际的文件名)。如果 "$@" 表示的是 "dir/foo.o",那么 "$@F" 表示的值为 "dir"。
$(D) $(F) 分别代表 "茎" 中的目录部分和文件名部分
$(%D) $(%F) 当以 "archive(member)" 形式静态库为目标时,分别表示库文件成员 "member" 名中的目录部分和文件名部分。踏进对这种新型时的目标有效。
$(<D) $(<F) 表示第一个依赖文件的目录部分和文件名部分。
$(^D) $(^F) 分别表示所有依赖文件的目录部分和文件部分。
$(+D) $(+F) 分别表示所有的依赖文件的目录部分和文件部分。
$(?D) $(?F) 分别表示更新的依赖文件的目录部分和文件名部分。

 

5 makefile条件判断

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

 

6 makefile命令参数和选项汇总

参数选项 功能
-b,-m 忽略,提供其他版本 make 的兼容性
-B,--always-make 强制重建所有的规则目标,不根据规则的依赖描述决定是否重建目标文件。
-C DIR,--directory=DIR 在读取 Makefile 之前,进入到目录 DIR,然后执行 make。当存在多个 "-C" 选项的时候,make 的最终工作目录是第一个目录的相对路径。
-d make 在执行的过程中打印出所有的调试信息,包括 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。但是建议在书写的时候尽量避免超过三级以上的变量嵌套引用

 

7 makefile目标类型汇总

名称 功能
.PHONY: 这个目标的所有依赖被作为伪目标。伪目标是这样一个目标:当使用 make 命令行指定此目标时,这个目标所在的规则定义的命令、无论目标文件是否存在都会被无条件执行。
.SUFFIXES: 这个目标的所有依赖指出了一系列在后缀规则中需要检查的后缀名
.DEFAULT: Makefile 中,这个特殊目标所在规则定义的命令,被用在重建那些没有具体规则的目标,就是说一个文件作为某个规则的依赖,却不是另外一个规则的目标时,make 程序无法找到重建此文件的规则,这种情况就执行 ".DEFAULT" 所指定的命令。
.PRECIOUS: 这个特殊目标所在的依赖文件在 make 的过程中会被特殊处理:当命令执行的过程中断时,make 不会删除它们。而且如果目标的依赖文件是中间过程文件,同样这些文件不会被删除。
.INTERMEDIATE: 这个特殊目标的依赖文件在 make 执行时被作为中间文件对待。没有任何依赖文件的这个目标没有意义。
.SECONDARY: 这个特殊目标的依赖文件被作为中过程的文件对待。但是这些文件不会被删除。这个目标没有任何依赖文件的含义是:将所有的文件视为中间文件。
.IGNORE 这个目标的依赖文件忽略创建这个文件所执行命令的错误,给此目标指定命令是没有意义的。当此目标没有依赖文件时,将忽略所有命令执行的错误。
.DELETE_ON_ERROR: 如果在 Makefile 中存在特殊的目标 ".DELETE_ON_ERROR" ,make 在执行过程中,荣国规则的命令执行错误,将删除已经被修改的目标文件。
.LOW_RESOLUTION_TIME: 这个目标的依赖文件被 make 认为是低分辨率时间戳文件,给这个目标指定命令是没有意义的。通常的目标都是高分辨率时间戳。
.SILENT: 出现在此目标 ".SILENT" 的依赖文件列表中的文件,make 在创建这些文件时,不打印出此文件所执行的命令。同样,给目标 "SILENT" 指定命令行是没有意义的。
.EXPORT_ALL_VARIABLES: 此目标应该作为一个简单的没有依赖的目标,它的功能是将之后的所有变量传递给子 make 进程。
.NOTPARALLEL: Makefile 中如果出现这个特殊目标,则所有的命令按照串行的方式执行,即使是存在 make 的命令行参数 "-j" 。但在递归调用的子make进程中,命令行可以并行执行。此目标不应该有依赖文件,所有出现的依赖文件将会被忽略。

 

参考:

  1. http://c.biancheng.net/makefile/

 

posted @ 2021-12-22 16:48  烟消00云散  阅读(334)  评论(0编辑  收藏  举报