1. Makefile简介
一个工程中的源文件不计其数,其按照类型,功能,模块分别放在若干个目录中,makefiled定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至进行更复杂的功能操作,因为makefile就像一个shell脚本一样,其中也可以执行操作系统的命令。
Makefile代码的好处就是--“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。Make 是一个命令工具,是一个解析makefile中指令的命令工具,一般来说,大多数的IDE都有整个命令,比如:Delphi的make,Visual C++的nmake, Linux下的GNU Make。可见,makefile都成为了一种在工程方面的编译方法。
1.1. Make主要解决的问题
(1)大量代码的关系维护
大项目中源代码比较多,手工维护,编译时间长而且命令复杂,难以记忆及维护;
把代码维护命令以及编译命令下载makefile文件中,然后再用make工具解析此文件,并自动执行相应的命令,可实现代码的合理编译。
(2)减少重复编译时间
n 在改动其中一个文件的时候,能判断哪些文件被修改过,可以只对该文件进行重新编译,然后重新链接所有的目标文件,节省编译时间。
1.2. Makefile的命令规则
Makefile和makefile都可以作为主动识别的文件名字,但是推荐使用Makefile, 因为比较容易识别。
1.3. Make工具的安装
$ sudo apt-get install make
[sudo] password for host:
Reading package lists... Done
Building dependency tree
Reading state information... Done
make is already the newest version (4.2.1-1.2).
0 upgraded, 0 newly installed, 0 to remove and 48 not upgraded.
2. Makefile语法规则
规则格式:
目标: 依赖文件列表
< tab >执行命令列表
Makefile基本规则三要素:
目标:
·通常是要产生的文件名称,目标可以是可执行文件或其他obj文件,也可是一个动作的名称;
依赖文件:
·用来输入从而产生目标的文件;
·一个目标通常有几个依赖文件(也可以没有);
命令:
·make执行的动作,一个动作可以含有几个命令,也可以没有;
·有多个命令时,每个命令占一行;
注意事项:
(1) 目标: 即要生成的文件。如果目标文件的更新时间晚于依赖文件更新时间,则说明依赖文件没有改动,目标文件不需要重新编译。否则会进行重新编译并更新目标文件。
(2) 默认情况下Makefile的第一个目标为终极目标。
(3) 依赖:即目标文件由哪些文件生成。
(4) 命令:即通过执行命令由依赖文件生成目标文件。注意每条命令之前必须有一个tab保持缩进,这是语法要求(会有一些编辑工具默认tab为4个空格,会造成Makefile语法错误)。
2.1. Make 的命令格式
make是一个命令工具,它解析makefile文件中的指令(规则)。
Make命令格式:
Make [-f file][options][targets]
[-f file]
·make默认在工作目录中寻找名为GNUmakefile, makefile, Makefile的文件作为makefile的输入文件;
·-f 可以指定以上名字以外的文件作为makefile输入文件;
[options]
·-v : 显示make工具的版本信息
·-w: 在处理makefile之前和之后显示工作路径
·-C dir: 读取makefile之前改变工作路径至dir指定的目录
·-n: 只打印要执行的命令但是不执行, 显示但是没有执行,用于测试makefile
·-s: 执行但不显示执行的命令
#编写makefile文件如下
test: obj1 obj2
echo "hello makefile test"
obj1:
echo "hello makefile obj1"
obj2:
echo "hello makefile obj2"
# 执行make -v 参数
$ make -v
GNU Make 4.2.1
Built for x86_64-pc-linux-gnu
Copyright (C) 1988-2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
#执行make [-f]
$ make -f test.mk
echo "hello makefile obj1"
hello makefile obj1
echo "hello makefile obj2"
hello makefile obj2
echo "hello makefile test"
hello makefile test
#make -w 参数
$ make -w -f test.mk
make: Entering directory '/mnt/e/Porject/test'
echo "hello makefile obj1"
hello makefile obj1
echo "hello makefile obj2"
hello makefile obj2
echo "hello makefile test"
hello makefile test
make: Leaving directory '/mnt/e/Porject/test'
#make -C 参数
$ make -C -f ../test1/test1.mk
#make -n 参数
$ make -n -f test.mk
echo "hello makefile obj1"
echo "hello makefile obj2"
echo "hello makefile test"
#make -s 参数
$ make -s -f test.mk
hello makefile obj1
hello makefile obj2
hello makefile test
[targets]:
·若使用make指令是没有指定目标,则make工具默认会实现makefile文件中的第一个目标,然后退出;
·指定了make工具要实现的目标,目标可以是一个或者多个(多个目标间使用空格隔开)
# 指定目标
$ make obj1 -f test.mk
echo "hello makefile obj1"
hello makefile obj1
$ make obj2 -f test.mk
echo "hello makefile obj2"
hello makefile obj2
$ make test -f test.mk
echo "hello makefile obj1"
hello makefile obj1
echo "hello makefile obj2"
hello makefile obj2
echo "hello makefile test"
hello makefile test
3. 变量
在Makefile中使用变量有点类似与C语言中的宏定义,使用该变量相当于内容替换,使用变量可以使Makefile易于维护,修改内容变得简单。
3.1. 变量的定义
变量名=变量值
3.2. 变量的引用
$(变量名) 或者 ${变量名}
3.3. 命名规则
·makefile变量名可以以数字开头
·变量时大小写敏感的
·变量一般都在makefile的头部进行定义
·变量几乎可在makefile的任何地方使用
3.4. 系统变量
除了使用用户自定义的变量,makefile中也提供了一些变量(变量名字大写)供用户直接使用,我们可以直接对其进行赋值。
CC = gcc #arm-linux-gcc
CPPFLAGS C预处理的选项 如 -l,无默认值
CFLAGS C编译器的选项 -Wall -g -c,无默认值
LDFLAGS 连接器选项 -L -l
CURDIR 当前路径
CXX C++语言的编译器名称
RM 删除文件程序的名称
3.5. 自动变量
# 这些变量不能单独使用,只能在命令中使用
$@ 表示目标
$^ 表示所有的依赖
$< 表示第一个依赖
4. 模式规则
# 模式匹配 所有的.o都依赖对应的.c
# 将所有的.c生成对应的.o的规则如下:
# %是一个通配符,用于匹配任意个字符。
%.o: %.c
gcc -c $^ -o $@
5. 函数
Makefile中的函数有很多,在这里给大家介绍几个最常用的。
wildcard - 查找指定目录下的指定类型的文件, 该函数能获取文件列表,并使用空格分隔开
$(wildcard 匹配规则)
# 如:查找当前目录下所有后缀为.c的文件,并赋值给SRC_FILES变量。
SRC_FILES = $(wildcard src/*.c)
patsubst - 函数功能为模式字符串替换,注意逗号
$(patsubst 匹配规则, 替换规则, 输入的字符串)
# 如把SRC_FILES 变量里所有后缀为.c的文件替换成.o:
LBJ = $(patsubst %.c, %.o, $(SRC_FILES))
subst - 函数功能为字符串替换
$(subst 源字符串, 目标字符串, 输入的字符串)
# 将变量INPUT_SRT 变量中的hello字符串替换成HELLO
OUTPUT_STR = $(subst hello, HELLO, $(INPUT_STR))
Notdir - 函数用于去除文件路径中的目录部分
$(notdir 文件名)
# 将路径中的src/去掉的命令如下
SRC_FILES = $(notdir src/src1.c)
6. 伪目标
伪目标声明: .PHONY:clean
声明伪目标之后,makefile将不会判断目标是否存在或者该目标是否需要更新;
clean中的特殊符号:
· “-” 表示此命令出错也会继续执行后续的命令,如 -rm -f *.o;
· “@” 表示不显示命令本身,只显示结果;
7. 项目实例
假如项目文件中有add.c, sub.c, mul.c, div.c, test.c, 如果对项目中的所有文件进行编译,直接使用gcc 也可以,但是如果增加或者删除或者更改的话,就需要重新输入命令,并且在仅仅修改一个文件的情况下,仍然可能需要全部编译;
首先需要安装gcc:
$ sudo apt-get install build-essential
7.1. 第一个版本
下面通过Makefile文件对该项目中的源文件进行管理,编写Makefile文件如下:
$ vi Makefile
test: add.c sub.c mul.c div.c test.c
gcc add.c sub.c mul.c div.c test.c -o test
$ ls
Makefile add.c div.c mul.c sub.c test.c
$ make
gcc add.c sub.c mul.c div.c test.c -o test
$ ls
Makefile add.c div.c mul.c sub.c test test.c
$ ./test
this is add file!
this is a sub file!
this is a mul file!
this is a div file!
this is a test file!
第一个版本的问题:执行make命令时,所有的源文件都会执行一次。
7.2. 第二个版本
改进第一个版本,将依赖文件修改成目标文件,即.o文件。对依赖文件逐个查找执行,最后执行目标的命令。
$ vi Makefile
test: add.o sub.o mul.o div.o test.o
gcc add.o sub.o mul.o div.o test.o -o test
add.o: add.c
gcc -c add.c -o add.o
sub.o: sub.c
gcc -c sub.c -o sub.o
mul.o: mul.c
gcc -c mul.c -o mul.o
div.o: div.c
gcc -c div.c -o div.o
test.o: test.c
gcc -c test.c -o test.o
执行顺序结果如下:
$ ls
Makefile add.c div.c mul.c sub.c test.c
$ make
gcc -c add.c -o add.o
gcc -c sub.c -o sub.o
gcc -c mul.c -o mul.o
gcc -c div.c -o div.o
gcc -c test.c -o test.o
gcc add.o sub.o mul.o div.o test.o -o test
$ ls
Makefile add.c add.o div.c div.o mul.c mul.o sub.c sub.o test test.c test.o
$ vi add.c
$ make
gcc -c add.c -o add.o
gcc add.o sub.o mul.o div.o test.o -o test
第二个版本优点:修改任何一个文件,其他的文件都不会进行重新编译,只是编译新修改的文件。当修改add.c之后,目标的依赖add.o会和add.c进行比较时间,如果add.c比较新,就会重新编译add.c,从而达到对修改的文件重新编译的目的。
第二个版本的缺点:对源文件的书写重复太多,如果源文件太多的话,修改太困难,容易漏写或者重写。
7.3. 第三个版本
针对第二个版本的缺点,在第三个版本中加入变量来代替重复的部分;另外添加了删除目标文件的规则,Makefile的编写如下:
$ vi Makefile
OBJS = add.o sub.o mul.o div.o test.o
test: $(OBJS)
gcc $(OBJS) -o test
add.o: add.c
gcc -c add.c -o add.o
sub.o: sub.c
gcc -c sub.c -o sub.o
mul.o: mul.c
gcc -c mul.c -o mul.o
div.o: div.c
gcc -c div.c -o div.o
test.o: test.c
gcc -c test.c -o test.o
.PHONY:clean
clean:
Rm -f $(OBJS) test
执行命令如下:
$ vi Makefile
$ make clean
rm -f add.o sub.o mul.o div.o test.o test
$ ls
Makefile add.c div.c mul.c sub.c test.c
$ make -n
gcc -c add.c -o add.o
gcc -c sub.c -o sub.o
gcc -c mul.c -o mul.o
gcc -c div.c -o div.o
gcc -c test.c -o test.o
gcc add.o sub.o mul.o div.o test.o -o test
$ make
gcc -c add.c -o add.o
gcc -c sub.c -o sub.o
gcc -c mul.c -o mul.o
gcc -c div.c -o div.o
gcc -c test.c -o test.o
gcc add.o sub.o mul.o div.o test.o -o test
$ ls
Makefile add.c add.o div.c div.o mul.c mul.o sub.c sub.o test test.c test.o
当在命令行敲下make时,默认第一条规则的目标作为整个makefile的最终目标,如果想指定一条固定规则的目标作为编译目标时,就需要在make 命令后显式添加目标名字,make clean命令就是这样子实现的,clean的规则没有依赖,所以会直接执行命令。
有两点需要注意:
- . clean规则的位置不能在最头部,这样子的话,最终的目标最以clean的规则为准,这不是我们想要的,一般都会放在文件的结尾;
- clean应该定义为伪目标(见第7节),因为可能有clean的字符或者文件出现,因为这时候, 后面没有依赖文件,所以make 就认为这个文件是最新的,所以就不会执行后面的指令。所以为了避免这种情况的发生,Makefile使用 .PHONY 来区分伪目标。格式如下:
.PHONY:clean
clean:
rm -f $(OBJS) test
7.4. 第四个版本
该版本引入自动变量(见4.5节)和TARGET自定义变量来进行简化makefile的书写。
$ vi Makefile
OBJS = add.o sub.o mul.o div.o test.o
TARGET=test
$(TARGET): $(OBJS)
gcc $^ -o $@
add.o: add.c
gcc -c $< -o $@
sub.o: sub.c
gcc -c $< -o $@
mul.o: mul.c
gcc -c $< -o $@
div.o: div.c
gcc -c $< -o $@
test.o: test.c
gcc -c $< -o $@
.PHONY:clean
clean:
rm -f $(OBJS) $(TARGET)
7.5. 第五个版本
该版本引入规则匹配,见第5节。
$ vi Makefile
OBJS = add.o sub.o mul.o div.o test.o
TARGET=test
#模式规则匹配
%.o: %.c
gcc -c $< -o $@
.PHONY:clean
clean:
rm -f $(OBJS) $(TARGET)
$ vi Makefile
$ make clean
rm -f add.o sub.o mul.o div.o test.o test
$ make -n
gcc -c add.c -o add.o
gcc -c sub.c -o sub.o
gcc -c mul.c -o mul.o
gcc -c div.c -o div.o
gcc -c test.c -o test.o
gcc add.o sub.o mul.o div.o test.o -o test
$ make
gcc -c add.c -o add.o
gcc -c sub.c -o sub.o
gcc -c mul.c -o mul.o
gcc -c div.c -o div.o
gcc -c test.c -o test.o
gcc add.o sub.o mul.o div.o test.o -o test
$ ls
Makefile add.c add.o div.c div.o mul.c mul.o sub.c sub.o test test.c test.o
7.6. 第六个版本
在第五个版本的基础上,有一个问题,就是变量OBJS 需要列出所有的源文件,如果源文件太多,造成书写困难,并且容易出错。这里就需要makefile的函数(见第6节),makefile的修改如下:
$ vi Makefile
SRC = $(wildcard ./*.c)
OBJS = $(patsubst %.c, %.o, $(SRC))
TARGET=test
$(TARGET): $(OBJS)
gcc $^ -o $@
%.o: %.c
gcc -c $< -o $@
.PHONY:clean
clean:
rm -f $(OBJS) $(TARGET)
7.7. 第七个版本
在第六个版本上添加辅助标记,主要是不显示一些不重要的打印和执行出错时的处理,标记符主要是“@”和“-”;
$ vi Makefile
SRC = $(wildcard ./*.c)
OBJS = $(patsubst %.c, %.o, $(SRC))
TARGET=test
$(TARGET): $(OBJS)
gcc $^ -o $@
%.o: %.c
@gcc -c $< -o $@
.PHONY:clean
clean:
-rm -f $(OBJS) $(TARGET)
结束!
参考资料:
1. Makefile入门(超详细一文读懂)_我的小卷呀的博客-CSDN博客