home..

Makefile 学习

Table of Contents

Makefile简介

  Make工具最主要也是最基本的功能就是通过makefile文件来描述源程序之间的相互关系并自动维护编译工作。而makefile 文件需要按照某种语法进行编写,文件中需要说明如何编译各个源文件并连接生成可执行文件,并要求定义源文件之间的依赖关系。Makefile在绝大多数的IDE 开发环境中都在使用,已经成为一种工程的编译方法。

Makefile工作规则

  我们编译项目文件的时候,默认情况下,make 执行的是 Makefile 中的第一规则(Makefile 中出现的第一个依赖关系),此规则的第一目标称之为“最终目标”或者是“终极目标”。
  在 shell 命令行执行我们的 make 命令。可以得到可执行文件 “main” 和中间文件 “main.o”、”test1.o” 和 “test2.o”,main 就是我们要生成的最终文件。通过 Makefile 我们可以发现,目标 “main” 在 Makefile 中是第一个目标,因此它就是 make 的终极目标,当修改过任何 C 文件后,执行 make 将会重建终极目标 “main”。
  它的具体工作顺序是:当在 shell 提示符下输入 make 命令以后。 make 读取当前目录下的 Makefile 文件,并将 Makefile 文件中的第一个目标作为其执行的“终极目标”,开始处理第一个规则(终极目标所在的规则)。在我们的例子中,第一个规则就是目标 “main” 所在的规则。规则描述了 “main” 的依赖关系,并定义了链接 “.o” 文件生成目标 “main” 的命令;make 在执行这个规则所定义的命令之前,首先处理目标 “main” 的所有的依赖文件(例子中的那些 “.o” 文件)的更新规则(以这些 “.o” 文件为目标的规则)。

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

  通过上面的更新规则我们可以了解到中间文件的作用,也就是我们编译时生成的 “.o” 文件。检查我们当中的某个源文件是不是进行过修改,最终目标文件是不是需要重建。如果存在 “.o” 文件,我们再去执行 make 的时,只有修改过的源文件或者是不存在的目标文件会进行重建,而那些没有改变的文件不用重新编译,这样会在很大程度上节省我们的时间,提高我们的编程效率。小的工程项目我们可能体会不到,项目工程文件越多,这样的效果才越明显。

Makefile中的变量

变量定义

语法格式:

VALUE = One Two Three #### 变量赋值

变量使用

使用${VALUE}或者$(VALUE)来替换变量值位置.

通配符和自动化变量

shell的通配符在Makefile中通用: 通配符 |使用说明 :—-|:—- * |匹配0个或者是任意个字符 ? |匹配任意一个字符 [] |我们可以指定匹配的字符放在 “[]” 中 特别地,我们在在变量中使用通配符时,必须加上wildcard前缀否则不会自动展开:

OBJ=$(wildcard *.c)
test:$(OBJ)
    gcc -o $@ $^

在上述用到了自动化变量,$@$^,分别代表规则的目标文件和所有依赖的文件列表. 具体所有自动化变量如下:

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

TIPS: .PHONY作为目标值可用于如下用途:

  1. 加快编译效率
  2. 防止目标名和生成文件冲突 如clean目标可能在会和可执行文件clean冲突.
  3. .PHONY:A B可用于监控来两个目标文件的生成和更新.

Makefile目录搜索

项目结构如下:

.
├── bin     可执行文件目录
├── include 头文件目录
├── lib     库目录
└── src     源码目录

分为一般搜索VPATH和选择搜索vpath (大小写敏感):

我们在 Makefile 中可以这样写:

VPATH := src 我们这可以这样理解,把 src 的值赋值给变量 VPATH,所以我们在执行 make 的时候会,从 src 目录下找我们需要的文件。 当存在多个路径的时候我们可以这样写:

VPATH := src car 或者是

VPATH := src:car 要用空格或者是冒号隔开,这样的话我们就会在这两个路径下搜索文件。搜索的顺序是我们定义的顺序,那上面的例子来说,我们应该先搜索 src 目录底下的文件,再搜索 car 目录下的文件。 >**TIPS:** >无论你定义了多少路径,make 执行的时候会先搜索当前路径下的文件,当前目录下没有我们要找的文件,才去 VPATH 的路径中去寻找。如果当前目录下有我们要使用的文件,那么 make 就会使用我们当前目录下的文件。

vpath 更像是添加了限制条件,会过滤出一部分再去寻找.
具体用法:

  1. vpath PATTERN DIRECTORIES
  2. vpath PATTERN清除符合文件 PATTERN 的搜索目录
  3. vpath清除清除符合文件 test.c 的搜索目录有已被设置的文件搜索路径

    ( PATTERN:我们可以理解为我们要寻找的条件,DIRECTORIES:我们要寻找的路径 )

    示例

    • 使用VPATH
      VPATH=src include
      main:main.o list1.o list2.o
       gcc -o $@ $<
      main.o:main.c
       gcc -o $@ $^
      list1.o:list1.c list1.h
       gcc -o $@ $<
      list2.o:list2.c list2.h
       gcc -o $@ $<
      
    • 使用vpath
      vpath %.c src
      vpath %.h include
      main:main.o list1.o list2.o
       gcc -o $@ $<
      main.o:main.c
       gcc -o $@ $^
      list1.o:list1.c list1.h
       gcc -o $@ $<
      list2.o:list2.c list2.h
       gcc -o $@ $<
      

      Makefile隐含规则

      Makefile 隐含规则:即省去不必要的规则和构建命令.例如下例省去了test.o的依赖和生成规则:

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

      注意:我们的的隐含条件只能省略中间目标文件重建的命令和规则,我们的最终目标的命令和规则不能省略。

隐含规则的具体的工作流程:make 执行过程中找到的隐含规则,提供了此目标的基本依赖关系。确定目标的依赖文件和重建目标需要使用的命令行。隐含规则所提供的依赖文件只是一个基本的(在C语言中,通常他们之间的对应关系是:test.o 对应的是 test.c 文件)。当需要增加这个文件的依赖文件的时候要在 Makefile 中使用没有命令行的规则给出。
当然我们也可以使用 make 选项:-r-n-builtin-rules 选项来取消所有的预设值的隐含规则。当然即使是指定了“-r”的参数,某些隐含规则还是会生效。因为有很多的隐含规则都是使用了后缀名的规则来定义的,所以只要隐含规则中含有“后缀列表”那么隐含规则就会生效。

Makefile语法参考

条件语句

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

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

函数调用

$(<function> <arguments>)
${<function> <arguments>}

TODO

特殊目标

名称 |功能 :—-:|:—– .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进程中,命令行可以并行执行。此目标不应该有依赖文件,所有出现的依赖文件将会被忽略。

Makefile手动编写

Makefile

Makefile自动生成

一. 前提依赖

编译环境已存在, 因为文中以C语言为例,固安装GCC支持编译.

二. 工作流程示意

三. 实践

main.c 内容如下:

   #include <stdio.h>
   int main(){
        printf("hello linux world !");
        return 0;
   }

References

  1. GNU:Makefile Conventions
  2. C编程网
  3. 知乎:程序猿成长之路
© 2021 Gsharp   •  Powered by Soopr   •  Theme  Moonwalk