0%

Makefile

1. make

1.1 make是什么

make是一个命令,是管理文件的自动编译管理器,这里的自动是指能根据文件时间戳自动发现更新过的文件而减少编译的工作量,同时通过读取makefile的文件的内容来进行预期的编译工作,make将只编译有改动的文件,而不用完全编译。

1.2工作原理
1
2
3
4
5
6
7
8
app:main.o hello.o
gcc -o app main.o hello.o
main.o:main.c hello.h
gcc -c main.c
hello.o:hello.c hello.h
gcc -c hello.c
clean:
rm app main.o hello.o
  • make在当前目录寻找Makefilemakefile文件。
  • 如果找到,它会找文件中的第一个目标文件target,如上例它找到app这个目标文件,把这个文件作为最终的目标文件。如果app文件不存在,或是app所依赖的后面的.o文件的文件修改时间要比app这个文件新,那么make命令就会执行后面所定义的命令来生成app这个文件。
  • 如果app所依赖的.o文件也不存在,那么make命令会在当前文件中寻找目标为.o文件的依赖关系。如果找到,则再根据那一个规则生成.o文件,根据.o文件依赖的.c文件和.h文件,执行规则生成.o文件。
  • 然后make再用.o文件生成执行文件app

总而远之,make会依据依赖关系一层一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,如最后被依赖的文件找不到,那么 make 就会直接退出并报错。

但是像clean这种没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行。不过,可以显式指定make执行clean,即make clean同时只要修改了与第一个目标文件存在直接或间接依赖关系的文件,都会发生重新编译和重新链接

1.3 make命令

make是一个命令工具,它解释Makefile 中的指令(应该说是规则)

1
make [ -f file ][ options ][ targets ]

  • -f file:make默认在工作目录中寻找名为GNUmakefile、makefile、Makefile的文件作为makefile输入文件,但有时可能命名不是这些,则-f可以指定以上名字以外的文件作为makefile输入文件

  • options
    • v:显示make工具的版本信息
    • w:在处理makefile之前和之后显示工作路径
    • C dir:读取makefile之前改变工作路径至dir目录
    • n:只打印要执行的命令但不执行
    • s:执行但不显示执行的命令
  • targets:使用make命令时没有指定目标,则make工具默认会实现makefile文件内的第一个目标,如果指定了make工具要实现的目标则该目标为最终目标(目标可以是一个或多个)

2. Makefile

Makefile是一个脚本文件,其内部编写一些符和make工具解析的语法规则来进行执行一些命令。

2.1 Makefile是什么

makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。Makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。makefile文件由make命令工具来执行。

make主要解决两个问题:

  • 1) 大量代码的关系维护:大项目中源代码比较多,手工维护、编译时间长而且编译命令复杂,难以记忆及维护,把代码维护命令及编译命令写在makefile文件中,然后再用make工具解析此文件自动执行相应命令,可实现代码的合理编译
  • 2) 减少重复编译时间,在改动其中一个文件的时候,能判断哪些文件被修改过,可以只对该文件进行重新编译,然后重新链接所有的目标文件,节省编译时间。

Makefile文件命名规则makefileMakefile都可以,推荐使用Makefile,即vim Makefile。学号Makefile只需要学习他的一条规则,三个变量,两个函数就能上手了。

2.2 一条规则

一条规则即指完成单此任务所包含的要素,Makefile的一条规则三要素如下:

1
2
3
4
//一条规则
target... : prerequisites ...
command
......

  • target:通常是要产生的文件名称,目标可以是可执行文件或其它obj文件,也可是一个标签
  • prerequisites:依赖文件,用来输入从而产生目标的文件,一个目标通常有几个依赖文件(也可以没有)。prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。
  • command:make执行的动作,一个规则可以含几个命令(可以没有)。有多个命令时,每个命令占一行。

make工具从上往下找寻target根据target后对应的依赖关系,先去查找依赖项的文件,为索所有的目标文件创建依赖关系链。若依赖项不存在,则会报错:

1
2
trluper@trluper-virtual-machine:~/Documents/staticLib$ make div.o
make: *** No rule to make target 'div.cpp', needed by 'div.o'. Stop.
若存在,在检查依赖关系时,同时会检查目标与源文件的时间戳,当源文件时间戳更新时,make会更新依赖它的链路上所有文件,即调用command部分。
1
2
3
4
5
6
7
8
9
10
11
12
13
//Makefile件
sum.o:sum.cpp
g++ -c sum.cpp -o sum.o
main.o:main.cpp
g++ -c main.cpp -o main.o
main:main.o sum.o
g++ main.o sum.o -o main

//使用make
trluper@trluper-virtual-machine:~/Documents/staticLib$ make sum.o main.o main
make: 'sum.o' is up to date.
g++ -c main.cpp -o main.o
g++ main.o sum.o -o main

2.3 变量

Makefile中使用变量有点类似于C语言中的宏定义,使用该变量相当于内容替换,使用变量可以使Makefile易于维护,修改内容变得简单变量定义及使用。Makefile中由自定义变量和自动变量

2.3.1 等号

在学习变量前需要熟悉Makefile中的等号,等号有4种=,:=,?=,+=

  • ?=表示,如果左边的变量没有被赋值,那么将等号右边的值赋给左边的变量。如果赋过值,则保持原来的值不变。
  • +=表示将等号右边的值追加到左边变量中,但是中间会有一个空格。
  • =:=是比较不好区分的两个等号,可以将”=“理解为"址传递"或引用,”:=“理解为"值传递”。

Makefile中是不允许将变量自己的值赋给自己的,也不允许出现循环引用。

2.3.2 变量名的规则

makefile的变量名的起名规则:

  • makefile变量名可以以数字开头
  • 变量是大小写敏感的
  • 变量一般都在makefile的头部定义
  • 变量几乎可在makefile的任何地方使用
2.3.3 自定义变量
  • 1)定义变量方法:变量名=变量值,变量名?=变量值,变量名+=变量值,变量名:=变量值
    1. 使用变量名:$(变量名)${变量名}
1
2
3
4
objs=add.o sum.o div.o mul.o
target=main
$(target):$(bgjs)
gcc &(objs) -o $(target)
2.3.4 三个重要自动变量

Makefile的自动变量有以下几个:

其中重要常用的三个是:

  • $@: 表示规则中的目标
  • $<:表示规则中依赖性的第一个
  • $^: 表示规则中的所有依赖性, 组成一个列表, 以空格隔开,如果这个列表中有重复的项则消除重复项。
1
2
3
4
5
6
7
8
9
10
objs=add.o sum.o div.o mul.o
target=main
sum.o:sum.cpp
g++ -c $^ -o $@
main.o:main.cpp
g++ -c $< -o $@
$(target):$(bgjs)
g++ &(objs) -o $(target)
clean:
rm -rf $(objs) $(target)

注意:自动变量只能在规则的命令中中使用

2.4 模式规则

如果想要进一步的偷懒,那就必须提到模式规则,模式规则用来匹配当前目录下的符合模式匹配的所有文件。模式规则类似于普通规则,只是在模式规则中,目标名中需要包含有模式字符%,包含有模式字符%的目标被用来匹配一个文件名,%可以匹配任何非空字符串。看到上面你觉得很绕,让我们看实例:

1
2
3
4
#%表示可以匹配当前目录下的所有以.cpp为后缀的源文件,
#并且对应生成相应名称的.o文件(注意是使用$<才能生成对应.o文件
%.o:%.cpp
g++ -c $< -o $@
单独上面的这个makefile不能成功执行,会出现No targets.Stop错误
1
2
3
4
5
6
//直接make,不成功
trluper@trluper-virtual-machine:~/Documents/staticLib$ make
make: *** No targets. Stop.
//传入目标,成功
trluper@trluper-virtual-machine:~/Documents/staticLib$ make main.o
g++ -c main.cpp -o main.o
这是很正常的,因为%是模式匹配规则,你如果没传入目标它就无法执行make的工作机制。下面来讲解对%的解决办法

2.5 函数

makefile中的函数有很多,在这里给大家介绍两个最常用的:

  • wildcard查找指定目录下的指定类型的文件

    1
    src = $(wildcard *.c) //找到当前目录下所有后缀为.c的文件,赋值给src

  • patsubst匹配替换

    1
    obj = $(patsubst %.c,%.o, $(src)) //把src变量里所有后缀为.c的文件替换成.o,然后赋值给obj

注意patsubst不是把src里的文件的后缀改为.o,是在makefile.o文件内部将生成的.o文件替换掉src内的.cpp文件,因此在make的时候一定是要先生成.o文件(这里从两者*%就能看出),从两者的特性能够得到以下的解决:

1
2
3
all:$(patsubst %.cpp,%.o,$(wildcard *.cpp))
%.o:%.cpp
g++ -c $< -o $@
原理:all目标获得了目录内的*.cpp文件,使用patsubst要求将*.cpp替换为*.o,但是目录中还没有*.o文件,则会依据make工作原理进行到下一个目标,刚好这个目标是生成.o文件的,则执行。

2.6 简单示例
1
2
3
4
5
6
7
8
9
10
11
12
src=$(wildcard *.cpp)
objs=$(patsubst %.cpp,%.o,$(src))
target=main
$(target):$(objs)
g++ $(objs) -o $(target)
%.o:%.cpp
g++ -c $< -o $@

#PHONY伪目标,不去判断目标文件是否存在或者更新,无条件执行
.PHONY:clean
clean:
rm -rf $(objs) $(target)
1
2
3
4
5
6
#执行make
trluper@trluper-virtual-machine:~/Documents/staticLib$ make
g++ -c main.cpp -o main.o
g++ -c sum.cpp -o sum.o
g++ main.o sum.o -o main
#执行makeclean