makefile教程(1)

makefile教程

makefile是什么:

makefile是用户自行完成的IDE(integrated development environment集成开发环境)程序,与传统的操作系统下的编译不同,makefile可以通过用户自行安排,决定文件的编译顺序,决定哪些文件需要编译,哪些文件需要重复编译等各种复杂的操作

使用makefile的好处:

可自由编译的好处,能够指定文件编译,将繁杂的编译方法集成到一个makefile文件中,然后使用make进行一键编译,极大的提高了开发效率

关于程序的编译和链接:

关于gcc编译成可执行文件的命令,以及对应命令下生成的文件后缀

命令文件
gcc -E 预处理(宏展开)*.i
gcc -S 编译(生成汇编代码)*.s
gcc -c 汇编(生成机器码)*.o(unix下)
gcc -o 链接(把大量的.o文件合成生成可执行文件) (主要链接的是函数和全局变量)对于a.out文件的重命名可执行文件
  • 链接 link

把大量的.o文件合并生成可执行文件

主要链接的就是函数和全局变量,而存放函数和全局变量的地方通常称为库文件 .lib文件

在unix下也叫做.a文件

  • 编译

在编译时,编译器只会管你程序的语法是否正确,函数和变量是否已经声明,函数如果没有声明也没关系,只会报警告,而在链接这一步,链接器会在所有的.o文件中寻找这个函数,如果没有找到,就报链接错误码

makefile内核

所有的makefile的实际都是:编译+链接

只要记住编译+链接就行

  1. 如果这个工程没有被编译过,那么所有的.c文件都要编译并被链接
  2. 如果这个工程已经编译,但是修改了.c文件,我们需要编译修改的.c文件,并连接目标程序
  3. 如果这个工程已经编译,但是修改了.h文件,我们需要编译修改的.h文件,并连接目标程序

makefile的使用规则:

一共有五大原则:显示规则、隐式规则、变量定义、文件指示、注释说明

显示规则

目标…:依赖…

命令

  • 目标:可以是个文件(object文件或者是可执行文件),也可以是个标签label

  • 依赖:要生成target目标的所需要文件或者是目标

  • 命令:就是使用make之后所要执行的命令(shell命令)

当依赖中的文件比目标中的文件要新的话,就必须执行命令中的内容,这就是makefile的核心

请添加图片描述

请添加图片描述

其中“ \ ”表示换行符

clean不是一个文件,而是一个动作名,我们要使用clean,就需要make clean。而且clean后面也不需要出现依赖。

所以我们也可以在makefile中定义不用的编译或者与编译无关的命令,比如程序的打包,程序的备份等

怎么使得make时会判断哪些是不需要makefile的命令呢,例如clean这个动作写入makefile时当我们使用make时不会自动执行,其中的原理在哪

这就谈到make是如何工作的

make是如何工作的

  1. make会首先在当前文件夹中搜寻makefile或者是Makefile文件
  2. 读入被include的其他makefile
  3. 初始化定义的变量
  4. 为所有的目标建立依赖关系链
  5. 顺序执行第一个target,并且把target作为最终的目标文件,也就是一切都是为了第一个target
  6. make会比较依赖和目标的文件时间戳,如果依赖比目标的文件时间新,则会重新生成target文件
  7. 根据最终目标文件的依赖,首先会查找是否存在这些依赖,如果不存在则会按照是否makefile中存在依赖文件的生成方式,有的话就会执行下去,没有的话就直接退出报错
  8. 为什么要单独对clean进行make,是因为:

第一个target并没有直接或间接利用clean

clean之后没有存在依赖

如果我们改变了“command.h”,那么,kdb.o、command.o 和 files.o

都会被重编译,并且,edit 会被重新链接。

makefile中使用变量(变量定义)

  • 变量的定义
  1. 就好比C语言中宏的作用,当我们在添加或者修改依赖时,至少要修改三遍以上,所以干脆用个宏,修改一处就可以,一般宏设置为 “OBJ、OBJECT、OBJS ”
  • 变量的使用
  1. 一般是变量 = 依赖,首先先把变量声明好并且需要赋予初值

直接使用=表示,在变量的值在使用时变量才会递归展开

例如:

VAR1 = foo

VAR2 = $(VAR1) bar

VAR1 = baz

则最终输出VAR2的值为:baz bar而不是foo bar,因为VAR1在VAR2被展开时的值是最后一次赋值的 “baz”。

  1. 调用变量时使用 $ (OBJ),如果要是使用真实的“$ ”字符,那么需要使用“$$”来表示
  2. 可以是变量 = 变量

first = $(second)

second = $(third)

third = forth

all :

echo $(first)

即打印出来的值就是forth

  1. 可以是 :=操作符 来使用变量

只是简单展开,也就是顺序展开,但是=是递归展开

x := first

y := $(x) second

x := third

则y的值为first second 、 x的值为third

但是不可以倒推比如:

x := $(y) second

y := first

则x的值不能是first second而是second

  1. 可以是 ?=

?= 表示这个变量如果以前被定义过,那这个变量什么也不做,如果以前没有被定义过,那这个变量就赋值为等号后面的值

First ?= bar

  1. 变量的高级用法

变量值的替换

$(var:a=b),意思就是将var变量中所有a替换成为b

eg1:

first := 1.c 2.c 3.c

second := $(first:.c = .o)

eg2:静态模式下的变量替换

first := 1.c 2.c 3.c

second := $(first:%.c = %.o)

把变量的值再当成一个变量

eg:

x = y

y = z

a := $ ($ (x))

$ (x)的值为y,$ (y)的值为z,所以$(a)的值为z

  1. 追加变量值

使用+=操作符来给变量追加值

如果变量之前没有定义过,+=就会自动变成=

如果前一次变量的定义赋值是 :=,那么+=就会以 :=作为其赋值符

  1. override指示符

通常,当我们make时的装有命令行参数的变量在makefile中的赋值就会被忽略。如果我们想在makefile中设置这类参数的值,我们就可以使用 “override”指示符

override < variable> =< value>

override < variable> :=< value>

override < variable> +=< value>

  1. 多行变量

还有一种设置变量值的方法就是使用define,可以去定义多个变量,多个变量分开可以使用换行,结束时使用endef,和命令包的方法一样。如果你的一行开头没有用[tab],就会当成变量,否则就会当成命令

define first

second

third

endef

  1. 目标变量

一般来说,在makefile中定义的变量为全局变量,在整个文件中通用,但是例如 $<这种自动化变量则称为规则型变量

当然我们可以为某个目标自己设置局部变量,他可以与全局变量同名因为此目标只作用于局部

< target…> : < variable assignment>

< target…> : override < variable-assignment>

< variable-assignment>表示赋值表达式:=、+=、?=、:=

pro : CFLAGS = -g

pro : 1.o 2.o 3.o

cc $(CFLAGS) 1.o 2.o 3.o

表示只要在pro这个目标引发的规则下CFLAGS都为-g

  1. 模式变量

我们由目标变量可知,目标也可以成为一个变量。模式变量的好处就是我们可以给定一个模式,然后按照这个模式 我们可以将所有的变量定义在符合这个模式下的目标中

例如make的模式一般至少含有一个%,所以我们可以在%的模式下给所有目标定义目标变量

%.o : CFLAGS = -o

< pattern…> : < variable assignment>

< pattern…> : override < variable-assignment>

  • 自动化变量

$@表示目标文件

$ <依赖目标中的第一个目标名字。如果依赖目标是以模式(即"%“)定义的,那么”$<"将是符合模式的一系列的文件集。注意,其是一个一个取出来的

$ %仅当目标为函数库文件时,表示规则中的目标成员名,例如 foo.a(bar.o),$%表示foo.a

$?所有比目标新的依赖目标的集合,以空格分开

用于更新补丁操作

例如lib库是由1.o、2.o、3.o、4.o所支持的,但我们会不定时的更新1234的包

这样就可以用这个自动化变量

lib : 1.o 2.o 3.o 4.o

ar r lib $?

$^所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量会去除重复的依赖目标,只保留一份

$+所有的依赖目标的集合。以空格分隔。如果在依赖目标中有多个重复的,那个这个变量不会去除重复的依赖目标

$*这个变量表示,目标模式中%(包括%)以及之前的部分

目标是dir/a.foo.b

目标的模式是a.%.b

那么$*表示dir/a.foo

$ (@D)表示 "$ @“的 目录 部 分( 不以斜杠作为结尾 ),如果”$@"值是

“dir/foo.o”,那么"$ (@D)“就是"dir”,而如果"$ @"中没有包含斜杠的话,

其值就是"."(当前目录)。

$ (@F)表示"$ @“的文件部分,如果”$ @“值是"dir/foo.o”,那么"$(@F)"就是

“foo.o”,“$ (@F)“相当于函数”$(notdir $@)”。

“$(*D)”“$(*F)”
“$(%D)”“$(%F)”
“$(<D)”“$(<F)”
“$(^D)”“$(^F)”
“$(+D)”“$(+F)”
“$(?D)”“$(?F)”

makefile自动推导(隐式规则)

一般来说make会自动推导出依赖文件,例如我们的main.o文件就是由main.c得到的,但我们没必要去写:

main.o:main.c defs.h

​ cc -c main.c

直接用 main.o:defs.h

也只是.o对于.c,其余依赖也要补充

请添加图片描述
“隐式规则”是一种惯例,make会按照这种规则来悄悄运行,隐式规则会使用我们的系统变量,我们可以通过改变系统参数的值来控制隐含规则运行时的参数,比如系统变量“CFLAGS”可以控制编译时的编译器参数

例如:
    CFLAGS主要是设置编译器的编译选项,比如警告级别、优化选项、包含路径等
CFLAGS = -wall -O2 -Iinclude
	-wall :启用所有警告信息
    -O2:启用中级优化
    -Iinclude:指定包含文件的搜索路径为include目录

main.o: main.c
    gcc -c $< -o $@ $(CFLAGS)

eg:

foo : 1.o 2.o

cc -o foo 1.o 2.o $(CFLAGS) $(LDFLAGS)

进一步省略了以下两部:

1.o : 1.c

cc -c 1.c $(CFLAGS)

2.o : 2.c

cc -c 2.c $(LDFLAGS)

在make的隐含规则库中,每一条隐含规则都在库中有其顺序,越靠前的则是越被经常使用的,所以有时候这也是一个弊端,为啥:

例如 foo.o : foo.p

依赖文件foo.p有时候会变得没有意义,因为如果你存在foo.c文件,C编译器就会自动使用将.c文件的foo编译成.o文件,因为Pascal的规则出现在C的规则之后,系统使用C编译器认为这个任务就完成了,就不会再寻找下一条规则实际上不是你想要de

那我们如何避免呢:

只能写出依赖的规则,避免隐式规则

隐式规则一览

这里包含着所有的预先设置,也就是make内建的隐含规则,当然我们也可以使用make -r或者–no-builtin-rules选项来取消所有的预设置的隐含规则

在这里只写包含有编译各种程序的哪些规则,具体自己查找了解即可:

编译 C 程序的隐含规则

编译 C++程序的隐含规则

编译 Pascal 程序的隐含规则

编译 Fortran/Ratfor 程序的隐含规则

预处理 Fortran/Ratfor 程序的隐含规则

编译 Modula-2 程序的隐含规则

汇编和汇编预处理的隐含规则

链接 Object 文件的隐含规则

Yacc C 程序时的隐含规则

Lex C 程序时的隐含规则

Lex Ratfor 程序时的隐含规则

从 C 程序、Yacc 文件或 Lex 文件创建 Lint 库的隐含规则

隐含规则中使用的变量

在隐含规则中使用的变量要么是之前预先设置的,要么是通过命令行传入的,要么是在环境变量中设置这些值。

例如:如果我改变C程序的隐含规则,make的默认编译命令是cc,我把$(CC)重定义为gcc,并且改变CFLAGS的值,重定义为-g,那么按照C程序的隐含规则,就会按照:

gcc -c -g $(CPPFLAGS)的样子来执行

通过上述的例子,我们可以将$ (CC)认为是于命令相关的变量,可以将$(CFLAGS)认为是与参数相关变量

关于命令的变量《隐含规则》

变量功能
AR函数库打包程序。默认命令是“ar”。
AS汇编语言编译程序。默认命令是“as”。
CCC 语言编译程序。默认命令是“cc”。
CXXC++语言编译程序。默认命令是“g++”。
CO从 RCS 文件中扩展文件程序。默认命令是“co”。
CPPC 程序的预处理器(输出是标准输出设备)。默认命令是“$(CC) –E
FCFortran 和 Ratfor 的编译器和预处理程序。默认命令是“f77”。
GET从 SCCS 文件中扩展文件的程序。默认命令是“get”。
LEXLex 方法分析器程序(针对于 C 或 Ratfor)。默认命令是“lex”。
PCPascal 语言编译程序。默认命令是“pc”。
YACCYacc 文法分析器(针对于 C 程序)。默认命令是“yacc”。
YACCRYacc 文法分析器(针对于 Ratfor 程序)。默认命令是“yacc –r”。
MAKEINFO转换 Texinfo 源文件(.texi)到 Info 文件程序。默认命令是“makeinfo”。
TEX从 TeX 源文件创建 TeX DVI 文件的程序。默认命令是“tex”。
TEXI2DVI从 Texinfo 源文件创建军 TeX DVI 文件的程序。默认命令是
WEAVE转换 Web 到 TeX 的程序。默认命令是“weave”。
CWEAVE转换 C Web 到 TeX 的程序。默认命令是“cweave”。
TANGLE转换 Web 到 Pascal 语言的程序。默认命令是“tangle”。
CTANGLE转换 C Web 到 C。默认命令是“ctangle”。
RM删除文件命令。默认命令是“rm –f”。

关于命令参数的变量

变量功能
ARFLAGS函数库打包程序 AR 命令的参数。默认值是“rv”。
ASFLAGS汇编语言编译器参数。(当明显地调用“.s”或“.S”文件时)。
CFLAGSC 语言编译器参数。
CXXFLAGSC++语言编译器参数。
COFLAGSRCS 命令参数。
CPPFLAGSC 预处理器参数。( C 和 Fortran 编译器也会用到)
FFLAGSFortran 语言编译器参数。
GFLAGSSCCS “get”程序参数。
LDFLAGS链接器参数。(如:“ld”)
LFLAGSLex 文法分析器参数。
PFLAGSPascal 语言编译器参数。
RFLAGSRatfor 程序的 Fortran 编译器参数。
YFLAGSYacc 文法分析器参数

引用其余的makefile(文件指示)

  • 使用include关键字

include + Makefilename(路径或者通配符)

路径如果没有设置,将会选择是当前文件夹下的makefile

“如果 make 执行时,有“-I”或“–include-dir”参数,那么 make 就会在这个参数所指定”

这个后面再说

  • 环境变量MAKRFILES

如果在当前环境中使用了环境变量MAKEFILES

那么make时会把这个变量MAKEFILES中的值做一个inlcude的动作

但是这个环境变量中的值如果是其他的makefile文件,则会用空格分开

这个变量中的target值在make时也不会起作用,依赖文件发生错误也不会管

通配符的使用

如果要使用一系列的类似的文件,一般使用通配符(* ,?,[…])来概括:

如果文件名中有通配符,我们可以使用 “\ *”来表示真正的“ * ”字符

object = *.o 表示的就是 *.o文件,而没有展开

object = $(wildcard *.o) 就可以得到一个展开后的变量合集

文件搜寻

  • 特殊变量 VPATH

VPATH表示目标或者依赖文件的寻找目录

比如:VPATH = src:…/header

表示目标或者依赖文件寻找的目录为src 或者上一层的header目录

其中:表示目录的分隔

  • 关键字 vpath

可以指定某个文件在哪个目录下寻找:vpath < pattern模式> < directories>

vpath %.h …/header

表示所有的.h文件都可以在上一层的header目录下找到

vpath %.h

表示符合模式%.h的文件目录

vpath

表示清除所有已被设置好了的文件搜索目录

当然目录的优先级最高的还是当前目录下的

连续使用vpath关键字来指定不同的搜索方式:

vpath %.c first

vpath % second

vpath %.c third

其表示“.c”结尾的文件,先在“first”目录,然后是“second”,最后是“third”目录

这里second比较特殊,什么文件都可以在second中搜索,比较浪费时间

可以通过改写成:

vpath %.c first:third

vpath % second

而上面的语句则表示“.c”结尾的文件,先在“first”目录,然后是“third”目录,最后才是“second”目录

伪目标

什么是伪目标

伪目标不是一个文件,所以无法使用依赖关系,我们必须使用一个特殊的标签来注明,使用时也得使用make+标签来表示

伪目标的使用要求

  • 不能与其他目标名重复,我们可以使用.PHONY来指明这是个伪目标

.PHONY:clean

  • 总是被执行的

伪目标同样可作为默认目标,就可以放在开头

all : prog1 prog2 prog3

.PHONY : all

由于伪目标总是被执行的,all作为一个伪目标,如果说prog1更新了,或者是prog3更新了,all也会更新

  • 伪目标也可以成为依赖

.PHONY : cleanall cleanobj cleaniff

cleanall : cleanobj cleaniff

多目标 涉及到函数后续

有时候多个目标可以共同使用一个依赖,这个叫做多目标

静态模式

更好的服务于多目标的使用

目标集合:目标集模式 依赖目标集模式

targets:%.o :%.c

表示对所有的.o文件进行了二次定义,也就是说去掉了.o的结尾,加上了.c的这个结尾

eg1:

objects = first.o second.o

$(object) : %.o : %.c

$(CC) -c $(CFLAGS) $< -o $@

也就是在objects这个变量中 将所有的.o文件修改成.c文件,所以修改之后我们的依赖目标集就是 first.c second.c

所以 $ <表示依赖目标集,$@表示目标集

eg2:

file = 1.o 2.o 3.elc

$ (filter %.o,$(file)) : %.o : %.c

gcc -c $< -o $@

$(filter %.elc,%(file)) : %.elc : %.el

emacs -f batch-byte-compile $<

filter是个函数,第一个参数是过滤条件,第二个参数过滤源

自动生成依赖性

对于每个.c文件,我们都得清楚包含了哪些头文件,这就大大增加了压力,但GNU编译器存在自动搜索头文件功能,使用 -MM参数

gcc -MM main.c = main.o : main.c defs.h

我们要为每个文件生成.d 文件之后才能自动化生成依赖关系

%.d : %.c

gcc -M $< > @ . @. @.$$$

书写命令

  • “@”

我们用“@”字符在命令行前,那么,这个命令将不被 make 显示出来

  • “;”

如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。比如你的第一条命令是 cd 命令,你希望第二条命令得在 cd 之后的基础上运行,那么你就不能把这两条命令写在两行上,而应该把这两条命令写在一行上,用分号分隔。

  • “-”

加一个减号“-”(在 Tab 键之后),标记为不管命令出不出错都认为是成功的

嵌套执行make

在不同的文件夹中编写不同的makefile,再由总控的makefile进行控制,可以有效的进行模块化的维护

例如我们在subdir下有个makefile,如果我们要在总控makefile中使用:

subsystem:

​ cd subdir && $(MAKE)

我们的make需要一点参数,所以定义成变量比较合适
如果你想要总控的makefile中的变量传递到下级的makefile

可以使用:export<variable…>

export variable = value

其中如果定义了MAKEFLAGS和SHELL变量,将总会传递给下级的makefile
请添加图片描述

make时的命令行的参数就会随着MAKEFLAGS传递到下级makefile

就会在终端打印信息

make:Entering directory ‘/home/hchen/gnu/make’

make:Leaving directory `/home/hchen/gnu/make’

使用make -w可以在总控makefile执行时查看 执行到哪一个下级makefile中

如果你不想要总控的makefile中的变量传递到下级的makefile

可以使用:unexport<variable…>

定义命令包

我们可以单独定义一个新的命令包,使用命令序列define和endef来定义一个命令包,以供后面使用

eg:

define run-mvv 表示命令包的名字

mvv $(firstword $^) 表示第一个命令

mv first.c $@ 表示一个命令的作用

endef

将命令包使用时:

%.d : %.n

$(run-mvv)

使用命令包,就会依次执行各行命令,由于我只定义了一个命令,就执行一个。但是若定义了多个,则依次执行

  • 19
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孤独memories

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值