Makefile笔记

导论

  • 自动化实现的逻辑:利用函数转化文件名,获取所有.cpp/.c文件对应的.o 文件名。将所有.o文件都列为是目标程序的依赖项,根据makefile的隐含规则,make会编译所有的.cpp/.c文件,生成对应的.o文件,链接.o生成目标程序。
  • make编译.cpp/.c文件,依靠的是makefile的隐含规则,可以通过自定义隐含规则实现对这一过程的控制。
  • 依靠变量实现Makefile的可配置化
  • 使用伪目标,实现多目标构建,工程相关指令封装(clean,install)
  • 引用其他Makefile,实现工程嵌套,从而构建大工程
  • 扩展源文件搜寻目录,减少路径相关操作
  • 本文基于陈皓大神的《跟我一起写Makefiel》

Makefile 入门

三个重要变量

  • $@: 目标文件
  • $^:所有的依赖文件,
  • $<:第一个依赖文件

重点语法规则

  • 反斜杠(\)是换行符的意思
  • Shell命令,一定要以一个Tab键作为开头

make自动推导

make 具有隐晦规则,可自动推导,比如make看到一个whatever.o文件,会自动的把whatever.c文件加在依赖关系,cc -c whatever.c 也会被推导出来

Makefile的构成

Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。

  • 显式规则:要生成的文件,文件的依赖文件,生成的命令。
  • 隐晦规则:make自动推导的。
  • 变量的定义:字符串,C语言中的宏,当Makefile被执行时,变量都会被扩展到相应的引用位置上
  • 文件指示
    • 引用另一个Makefile
    • 根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样
    • 定义一个多行的命令。
  • 注释:Makefile中只有行注释"#"

Makefile的文件名

  • 默认的情况下,make命令会在当前目录下按顺序找寻文件"GNUmakefile"、“makefile”、“Makefile”.
  • 可以指定特定的Makefile,使用make的"-f"和"–file"参数,如:make -f Make.Linux或make --file Make.AIX。

引用其他Makefile

include foo.make *.mk $(bar)

  • include 支持引入其他make,和变量,且支持通配符
  • make 是使用--include-dir或者--I指定,引用目录
  • 搜索路径:当前目录 -> 指定目录 -> <prefix>/include(一般是:/usr/local/bin 或/usr/include)
  • 兼容其他版本make使用sinclude

make是如何工作的(重点)

  • make程序会在当前目录下找名字叫"Makefile"或"makefile"的文件。
  • 读入被include 的其它Makefile
  • 初始化变量
  • 设置目标(一次make,只有一个最终目标),第一个目标为默认最终目标,如果要make非默认最终目标,需要显示执行,如 make clean
  • 如果目标文件所依赖的.o文件不存在,就先生成该.o
  • 然后层层递归,从而生成一条编译顺序链,非最终目标的的依赖对象不会在编译链中,所以不会被执行
  • 最后执行编译命令,层层返回直到生成目标文件
  • 没有和目标文件直接或间接关联,不会被自动执行,只是被定义。

不重要的知识点

环境变量MAKEFILES

如果存在环境变量MAKEFILES,make会自动引用,但与include不同的是,MAKEFILES不会主动执行

建议不要用,因为所有的Makefile都会受他影响

清空目标文件的规则

.PHONY : clean 
clean : 
	-rm edit $(objects)
  • .PHONY意思表示clean是一个"伪目标"。
  • rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事
  • 不成文的规矩是——“clean从来都是放在文件的最后”

如何编写规则

通配符

  • *: 略
  • ?:上一命令的结果
  • ~:家目录,windows下是环境变量HMOE

源文件搜寻

  • 特殊变量"VPATH"

    • VPATH = src:../headers
    • 给Makefile指定源文件所在目录
    • 目录间以冒号:隔开
  • 关键字"vpath"

    • vpath <pattern> <directories> #指定某个目录的某种文件
      • vpath %.c foo
    • vpath <pattern> #清除已指定的某种类型的文件
      • vpath %.c
    • vpath #清除所有已指定的文件
      • vpath
    • %:表示匹配0或若干个字符(同*)

伪目标

  • 顾名思义,具有目标的属性,但不是真正的文件,不会生成目标文件,并且需要指定依赖关系
  • 伪目标可不用声明,若伪目标与文件同名,则伪目标必须用.PHONY 声明,否则编译会报错
  • 伪目标可以作为Makefile的默认目标,比如声明一个名为"all"伪目标,依赖多个真实的目标,从而实现一次生成多个目标的目的

多目标(目标变量)

  • @ : 目标变量
  • 这一节书中说的有绕,我的理解是,有多个目标,不用每个目标都写一套指令,指令要复用,指令涉及到目标名的,用$@替代

静态模式

<targets ...> : <target-pattern> : <prereq-patterns ...>

objects = foo.o bar.o
all:$(objects)
$(objects): %.o: %.c		#%.c表示.o同名的.c文件
$(CC) -c $(CFLAGS) $< -o $@

目前不大理解其用途,意思就是筛选目标集,并制定依赖关系

自动生成依赖关系

个人感觉没啥用,现在的编译器可以不依赖头文件
利用 gcc/g++的"-M"的选项,获取源文件包含的头文件,然后把所有依赖文件放入一个.d中,编译时把.d文件引入

命令的书写

说明:个人认为,这一部分有挺多知识点都不常用,这些知识点就不记录了

命令出错

  • 命令前加一个减号"-"(在Tab 键之后),标记为不管命令出不出错都认为是成功的
  • 给 make 加上"-i",所有命令都会忽略错误,"-k",出错则终止

嵌套执行

  • $(MAKE) 执行子Makefile
  • make的-C选项可以先进入一个文件夹,在执行make
  • 嵌套时传递变量:export variable;拦截变量传递:unexport variable
  • 如果make有参数,除"-C","-f","-h""-o"和"-W" 以外的参数都将传递
  • make的-w选项:输出当前目录,-C时,默认-w

定义命令包

  • 以"define"开始,以"endef"结束,如

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

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

    在命令包的定义中run-yacc," " 就 是 " f o o . y " , " ^"就是"foo.y"," ""foo.y""@“就是"foo.c”

    命令包的执行需要使用$()

变量的使用

变量的声明

  • 变量名可以是数字开头,但不应该含有":"、"#"、"="或是空字符(空格、回车等)。变量是大小写敏感的
  • 变量在声明时,需要给予初值
  • 变量在使用时,需要给在变量名前加上"$“符号,最好用小括号”()“或是大括号”{}"把变量给包括起来
  • MakeFile的变量类似C/C++中的宏,在指定为位置展开,不同的是可以重定义

变量给变量赋值

  • 变量可以先使用在定义,但相互嵌套会报错

    foo = $(bar)
    bar = $(ugh)
    ugh = Huh?
    
  • := 表示不使用未声明的变量;?= 表示如果未定义则定义,已定义则跳过

  • 如果变量后面有n个空格,变量在展开时,也会附带一个空格

追加变量

  • 以使用"+="操作符给变量追加值,如objects += another.o
  • +=
    • 变量未定义过,那么,"+=“会自动变成”=",
    • 变量已定义,会继承于前次操作,如果前一次的是":=",那么"+=“会以”:="作为其赋值符

系统环境变量

  • make在开始运行时,会载入系统环境变量
  • 默认系统环境变量是可覆盖的,make的"-e"参数,表示不覆盖

局部变量

局部变量分两种,目标变量和模式变量,区别在于作用不同:目标变量的作用域为目标生成的范围内,模式变量的作用域为符合模式的文件范围

目标变量

格式:<target ...> : <variable-assignment>

在目标后对变量赋值,如下

prog : CFLAGS = -g
prog : prog.o foo.o bar.o
$(CC) $(CFLAGS) prog.o foo.o bar.o

模式变量

格式:<pattern ...>; : <variable-assignment>

如:%.o : CFLAGS = -O

不重要的知识点

  • override 指示符:命令行定义的变量需要override来修改

  • 变量值的替换

    • ${var:a=b}:把变量"var"中所有以"a"字串"结尾"的"a"替换成"b"字串。这里的"结尾"意
      思是"空格"或是"结束符"
    • bar := $(foo:%.o=%.c):静态模式
  • 变量嵌套:把变量值当变量

    $($(x))

  • 多行变量:使用define&endef定义多行变量,无[Tab] 键开头为变量,有则为命令包

条件判断

  • 关键字:ifeq ifneq ifdef ifndef else endif

  • 说明make提供四种判断,相等,不相等,定义,未定义;未提供eles if

  • 格式:

    ifdef foo
    	frobozz = yes
    else
    	frobozz = no
    endif
    

函数

函数调用:$(<fun> <arg>)

字符串处理函数

  • 模式替换 patsubst :$(patsubst <pattern>,<replacement>,<text>)
  • 字符串替换 subst:$(subst <from>,<to>,<text>)
  • 去头尾空格 strip:$(strip <string>)
  • 字符串查找 findstring:$(findstring <find>,<in>)
  • 过滤 filter:$(filter <pattern...>,<text>)
  • 反向过滤 filter-out:$(filter-out <pattern...>,<text>)
  • 单词排序 sort :$(sort <list>)
  • 取单词 word:$(word <n>,<text>)
  • 取单词串 wordlist:$(wordlist <ss>,<e>,<text>)
  • 单词个数统计 words:$(words <text>)
  • 首单词 firstword:$(firstword <text>)

文件名操作函数

  • 取目录 dir:$(dir <names...>)
  • 取文件 notdir:$(notdir <names...>)
  • 取后缀 suffix:$(suffix <names...>)
  • 取前缀 basename:$(basename <names...>)
  • 加后缀 addsuffix:$(addsuffix <suffix>,<names...>)
  • 加前缀 addprefix:$(addprefix <prefix>,<names...>)
  • 连接 join——两个list按下标结合,如a[1],b[1]结合:$(join <list1>,<list2>)
  • wildcard——替换 Bash 的通配符:$(wildcard PATTERN…)

其他函数

  • foreach ——遍历list,操作其中的每个元素:$(foreach <var>,<list>,<text>)

  • if——功能类似ifeq:$(if <condition>,<then-part>) 或者 $(if <condition>,<then-part>,<else-part>)

  • call——调用自定义函数变量

    reverse = $(1) $(2)
    foo = $(call reverse,a,b)
    
  • origin——获取变量来源:$(origin 😉

  • shell——执行shell命令,获取返回值:$(shell cat foo)

  • error——报错,make暂停(可以继续):$(error <text …>;)

  • warning——报警,但make不会停:$(warning <text …>;)

隐含规则

C/C++隐含规则

  • 编译 C 程序的隐含规则:".o"的目标的依赖目标会自动推导为".c",并且其生成命令是$(CC) –c $(CPPFLAGS) $(CFLAGS)

  • 编译 C++ 程序的隐含规则:".o"的目标的依赖目标会自动推导为".cc"或是".C",并且其生成命令是$(CXX) –c $(CPPFLAGS) $(CFLAGS)(建议使用".cc"作为 C++ 源文件的后缀,而不是".C")

隐含规则使用的变量

命令的变量

  • CC:C 语言编译程序。默认命令是"cc"
  • CXX:C++ 语言编译程序。默认命令是"g++"
  • AR: 函数库(Object文件)打包程序。默认命令是"ar"
  • CPP:C 程序的预处理器(输出是标准输出设备)。默认命令是"$(CC) –E"
  • RM: 删除文件命令。默认命令是"rm –f"

命令参数的变量

  • ARFLAGS: 函数库打包程序 AR 命令的参数。默认值是"rv"
  • ASFLAGS: 汇编语言编译器参数。(当明显地调用".s"或".S"文件时)
  • CFLAGS:C 语言编译器参数
  • CXXFLAGS:C++ 语言编译器参数
  • CPPFLAGS:C 预处理器参数

自定义模式规则

使用"%“来定义一个隐含规则 ,目标的”%“的意思是表示一个或多个任意字符,目标的”%",取决于依赖。变量与函数的"%"在Makefile载入是被展开,模式则在运行时展开

使用自动化变量

一般而言,使用模式是为了实现自动化,所以在模式中需要使用自动化变量实现对具体的文件的"抽象",自动化变量的定义见附录

  • @ 、 @、 @<、 %、 *在扩展时只会有一个文件

  • ? 、 ?、 ?^、$展开时一个文件列表

  • 这些变量使用"D",“F”,可以获取目录或文件名,如$(<D), $(<F)

模式的匹配

​ “%“所匹配的内容叫做"茎”,当一个模式匹配包含有斜杠的文件时,目录部分会首先被移开,然后进行匹配,成功后,再把目录加回去。如"e%t”,"src/eat"匹配于该模式,%为a,但目录部分src/也会随之出现在目标中

函数库

​ 函数库文件(.a/.lib)是对 Object 文件(程序编译的中间文件)的打包文件,由命令“ar”来完成打包工作。隐含规则:如"foo.a(bar.o bar1.o)"中bar.o,bar1.o编译成功后,会自动调用ar命令打包,不过个人更喜欢显式的输入ar命令进行打包

附录

GUN制定的伪目标

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

make 的参数

参数含义
-b/-m忽略和其它版本 make 的兼容性
-B重编译
-C 指定读取 makefile 的目录
-debug[=]输出 make 的调试信息
-e环境变量的值覆盖 makefile 中定义的变量的值
-f=,指定需要执行的 makefile
-h显示帮助信息
-i在执行时忽略所有的错误
-I 指定makefile 的搜索目录。可以使用多个“-I”参数来指定多个目录
-j []指定同时运行命令的个数
-k出错也不停止运行
-l 指定 make 运行命令的负载
-n仅输出执行过程中的命令序列,但并不执行。
-o 不更新生成的指定的
-p输出所有的规则和变量
-q是检查所指定的目标是否需要更新,0更新,2有错误发生
-r禁止 make 使用任何隐含规则
-R禁止 make 使用任何作用于变量上的隐含规则
-s在命令运行时不输出命令的输出
-S取消“-k”选项的作用
-t把目标的修改日期变成最新的(导致目标不会更新)
-v输出 make 程序的版本
-w输出运行 makefile 之前和之后的信息
-W 假 定 目 标需要更新

自动化变量

变量含义
$@目标文件集
$%当目标是函数库文件时有效,表示目标成员名。如是"foo.a(bar.o)",目标为foo.a,$%表示bar.o
$<第一个依赖目标
$?所有比目标新的依赖目标的集合(需要更新的依赖)
$^所有依赖目标的集合,去除重复
$+所有依赖目标的集合,不去除重复
$*目标模式中"%“及其之前的部分,。如目标是"dir/a.foo.b”,模式是"a.%.b",那么,"$*“就是"dir/a.foo”

特殊的目标

名称功能
.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进程中,命令行可以并行执行。此目标不应该有依赖文件,所有出现的依赖文件将会被忽略。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值