一、实验介绍--Makefile 基础规则(2)
本实验在上一个实验的基础上,继续深入介绍makefile
的基础规则。
1.1 实验内容
- 验证
makefile
的自动推导规则。 - 验证
makefile include
文件规则。 - 验证
makefile
环境变量MAKEFILES,MAKEFILE_LIST
和.VARIABLES
的作用。 - 测试
makefile
的重载。
1.2 实验知识点
1.makefile
文件不存在的情况下也可以利用make的自动推导规则实现代码编译。
2.include
指示符可以让make
读入其指定的文件。
3.include
指定文件时可以支持通配符。
4.include
的默认查找路径:/usr/gnu/include
,/usr/local/include
,/usr/include
。
5.include
可以用-I
选项指定查找路径。
- 变量
MAKEFILES
可以指定需要读入的makefile
文件。 - 变量
MAKEFILES
的使用限制:不可作为终极目标。 - 变量
MAKEFILE_LIST
的作用:将"MAKEFILES"
,命令行指定,默认makefile
文件及“include”指定的文件名都记录下来。9.makefile
重载另一个makefile
的限制条件:规则名称不得重名。10.makefile
的“所有匹配模式”的使用。
1.3 实验环境
Ubuntu系统, GNU gcc工具,GNU make工具
1.4 适合人群
本课程难度为简单,属于入门级别课程,适合有代码编写能力的用户,熟悉和掌握make的一般用法。
1.5 代码获取
可以通过以下命令获取代码:
$ git clone https://github.com/darmac/make_example.git
二、实验原理
依据makefile
的基本规则设计相应的正反示例,验证规则。
三、开发准备
进入实验楼课程即可。
四、项目文件结构
makefile:make工程文件。
五、实验步骤
5.1makefile
的自动推导规则。
5.1.1 抓取源代码
使用如下cmd
获取GitHub
源代码:
cd ~/Code/
git clone https://github.com/darmac/make_example.git
cd make_example/chapter2
5.1.2makefile
自动推导规则说明
makefile 有一套隐含的自动推导规则:
- 对于
xxx.o
目标会默认使用命令“cc -c xxx.c -o xxx.o”
来进行编译。 - 对于
xxx
目标会默认使用命令“cc xxx.o -o xxx”
下面用两个小实验来验证makefile
的自动推导规则。
5.1.3 编写main.c
源文件
代码中已有main.c
文件,内容如下:
#include <stdio.h>
int main(void)
{
printf("Hello world!\n");
return 0;
}
5.1.4 使用make main.o
验证规则
确认当前目录下没有makefile
类型的文件。
输入如下命令:
make main.o
终端打印:
cc -c -o main.o main.c
说明make
自动使用cc -c
命令生成了main.o
文件。
5.1.5 使用make main
验证规则
接下来验证另一条规则,输入如下命令:
make main
终端打印:
cc main.o -o main
说明make
自动使用cc
命令生成了main
文件。
由于main.o
文件是上一个小实验生成的,现在我们删掉它和main文件:
rm main.o main
再次输入:
make main
终端打印:
cc main.c -o main
这说明当main.o
不存在时,make 会尝试直接使用源文件编译来生成目标文件。
实验过程如下图所示:
5.2makefile include
使用规则
makefile 中可以使用include
指令来包含另一个文件。
当make
识别到include
指令时,会暂停读入当前的makefile
文件,并转而读入include
指定的文件,之后再继续读取本文件的剩余内容。
5.2.1 编写makefile
需要包含的文件
makefile_dir
目录下有一份需要被包含的文件inc_a
,文件内容如下:
#this is a include file for makefile
vari_c="vari_c from inc_a"
5.2.2 编写基本的makefile
文件
拷贝makefile_dir
目录下的makefile
文件到当前目录:
cp makefile_dir/makefile ./
makefile
内容如下:
# this is a basic makefile
.PHONY:all clean
vari_a="original vari a"
vari_b="original vari b"
include ./makefile_dir/inc_a
all:
@echo $(vari_a)
@echo $(vari_b)
@echo $(vari_c)
clean:
5.2.3 测试 make 能否正常工作
执行指令:
make
终端打印:
original vari a
original vari b
vari_c from inc_a
从打印信息可以看出来makefile
已经成功包含了inc_a
文件,并且正确获取到了vari_c
变量。
值得一提的是include
指示符所指示的文件名可以是任何shell
能够识别的文件名,这表明include
还可以支持包含通配符的文件名。我们将在下面的实验中进行验证。
5.2.4 新建另一个被包含文件
makefile_dir 目录下有一份需要被包含的文件
inc_b`,文件内容如下:
#this is a include file for makefile
vari_d="vari_d from inc_b"
5.2.5 使用通配符让makefile
包含匹配的文件
修改makefile
,使用通配符同时包含inc_a
和inc_b
文件。
修改后的makefile
内容如下:
# this is a basic makefile
.PHONY:all clean
vari_a="original vari a"
vari_b="original vari b"
include ./makefile_dir/inc_*
all:
@echo $(vari_a)
@echo $(vari_b)
@echo $(vari_c)
@echo $(vari_d)
clean:
执行:
make
终端打印出:
original vari a
original vari b
vari_c from inc_a
vari_d from inc_b
说明文件inc_a
和inc_b
被同时包含到makefile
中。
5.2.6makefile include
文件的查找路径
当include
指示符包含的文件不包含绝对路径,且在当前路径下也无法寻找到时,make 会按以下优先级寻找文件:
1.-I
指定的目录
2./usr/gnu/include
3./usr/local/include
4./usr/include
5.2.7 指定makefile
的include
路径
修改makefile
,不再指定inc_a
和inc_b
的相对路径:
再执行一次make
:
make
终端打印:
makefile:8: inc_*: No such file or directory
make: *** No rule to make target 'inc_*'. Stop.
可以看到makefile
无法找到inc_a
和inc_b
文件。
使用“-I”命令来指定搜寻路径:
make -I ./makefile_dir/
终端依然打印:
makefile:8: inc_*: No such file or directory
make: *** No rule to make target 'inc_*'. Stop.
看起来make
在搜寻“inc_*”
档案。
修改makefile
,将“inc_*”
改为"inc_a"``"inc_b"
include inc_a inc_b
执行:
make -I ./makefile_dir/
终端打印:
original vari a
original vari b
vari_c from inc_a
vari_d from inc_b
可见不使用通配符的情况下include
配合-I
选项才能得到预期效果。
5.2.8makefile include
的处理细节
下面再研究一下make
对include
指示符的处理细节。
前面提到make
读入makefile
时遇见include
指示符会暂停读入当前文件,转而读入include
指定的文件,之后才继续读入当前文件。
拷贝文件makefile_dir/makefile_b
到当前目录并命名为makefile
:
cp makefile_dir/makefile_b ./makefile
查看makefile
的内容:
#this makefile is test for include process
.PHONY:all clean
vari_a="vari_a @ 1st"
include ./makefile_dir/c_inc
vari_a += " @2nd ..."
all:
@echo $(vari_a)
clean:
可以看出makefile
是先设定vari_a
变量,再包含c_inc
文件,之后再修改vari_a
变量。
查看c_inc
文件内容:
#this is a include file for include process
vari_a="vari_a from c_inc"
可以看出c_inc
文件中也设定了vari_a
变量。
执行make
看最终vari_a
变量定义为什么:
make
终端打印:
vari_a from c_inc @2nd ...
这说明vari_a
在include
过程中被修改掉,并在其后添加了字串" @2nd ..."
,结果与预期中make
处理include
指示符的行为一致。
实验过程如下图所示:
5.3makefile
的几个通用变量测试
5.3.1 测试MAKEFILES
变量指定的文件是否能正确被包含
MAKEFILES 环境变量有定义时,它起到类似于include的作用。
该变量在被展开时以空格作为文件名的分隔符。
删掉当前makefile
文件:
rm makefile
新建makefile
内容如下:
#this makefile is test for include process
.PHONY:all clean
vari_a += " 2nd vari..."
all:
@echo $(vari_a)
clean:
执行 make
:
make
终端打印:
2nd vari...
增加环境变量MAKEFILES
:
export MAKEFILES=./makefile_dir/c_inc
再次执行make
:
make
终端打印:
vari_a from c_inc 2nd vari...
可见make
按照MAKEFILES
的文件列表载入了makefile_dir/c_inc
文件。
5.3.2 测试MAKEFILES
变量的使用限制
需要注意:
1.MAKEFILES
指定文件的目标不能作为make
的终极目标。
2.MAKEFILES
是环境变量,它对所有的makefile
都会产生影响,因此尽量不要使用该变量。
新建一个文件aim_b_file
,内容如下:
#this is aim_b file
.PHONY:aim_b
aim_b:
@echo "now we exe aim_b"
此文件定一个aim_b
规则,执行此规则时打印“now we exe aim_b”
。
修改MAKEFILES
变量:
export MAKEFILES=./aim_b_file
执行make
:
make
终端打印:
2nd vari...
可见make
虽然先包含aim_b_file
文件,但依然以makefile
中的all
作为最终目标。
我们再来验证aim_b
规则是否已经被正常解析到,修改makefile
,为all
增加一条依赖:
all: aim_b
这样,执行all
规则之前必须先执行aim_b
规则。
执行make
:
make
终端打印:
now we exe aim_b
2nd vari...
再执行:
make aim_b
终端打印:
now we exe aim_b
“make”
和“makeaim_b”
的打印都说明aim_b
已经能够被正确执行,但它的确不会作为默认的目标规则,只有明确指定此规则时才会执行其对应的命令。
5.3.3 打印MAKEFILE_LIST
所有MAKEFILES
指定的文件名,命令行指定的文件名,默认makefile
文件以及include
指定的文件名都记录下来。
当前路径下总共有./aim_b_file
,./makefile
,./makefile_dir/inc_a
,./makefile_dir/inc_b
,./makefile_dir/c_inc
这5个文件。
现在我们使用不同的方式将它们包含进来。
./aim_b_file
已经被包含在MAKEFILES
变量中。
./makefile
会在执行make
时被自动调用。
修改makefile
用include
指示符包含文件./makefile_dir/inc_a
和./makefile_dir/inc_b。
并在all目标中打印
MAKEFILE_LIST变量,修改后的
makefile`内容如下:
#this makefile is test for include process
.PHONY:all clean
include ./makefile_dir/inc_a ./makefile_dir/inc_b
vari_a += " 2nd vari..."
all:
@echo $(vari_a)
@echo $(MAKEFILE_LIST)
clean:
执行 make
:
make
终端打印:
now we exe aim_b
2nd vari...
./aim_b_file makefile makefile_dir/inc_a makefile_dir/inc_b
第二行打印内容说明MAKEFILE_LIST
已经包含了./aim_b_file makefile makefile_dir/inc_a makefile_dir/inc_b
。
实验过程如下图所示:
5.4 重载另一个makefile
5.4.1 使用make -f
重载另一个makefile
现在拷贝makefile
文件为inc_test
:
cp makefile inc_test
再使用make -f
命令指定需要读取的makefile
文件为inc_test
:
make -f inc_test
终端打印:
now we exe aim_b
2nd vari...
./aim_b_file inc_test makefile_dir/inc_a makefile_dir/inc_b
可见原来默认执行的makefile
文件被替换成了inc_test
文件,且被MAKEFILE_LIST
正确记录。
5.4.2 测试重载makefile
的限制条件
makefile
重载另一个makefile
的时,不允许有规则名重名。
若是有规则发生重名会发生什么状况呢?
修改aim_b_file
增加all
规则:
all:
@echo "all in aim_b"
执行:
make
终端打印:
makefile:10: warning: overriding commands for target `all'
./aim_b_file:9: warning: ignoring old commands for target `all'
now we exe aim_b
2nd vari...
./aim_b_file makefile makefile_dir/inc_a makefile_dir/inc_b
从打印日志中可以看出makefile
重写了aim_b_file
文件中的all
规则。
5.4.3 用“所有匹配模式”重载另一个makefile
从上面的实验中可以看出,对于两个文件中同名的规则,make
后读入的规则会重写先读入的规则。
现在假如有两个makefile
文件,AMake
和BMake
,它们都定义了一条intro
规则,但行为不同。
用户希望执行在生成目标AAim
和BAim
的时候分别调用AMake
和BMake
的intro
规则,要怎样来做呢?
我们无法用include
指示符来包含这两个makefile
,否则会产生重写规则的行为。
此时需要用到重载另一个makefile
的技巧。
具体方法就是在对应的规则中重新调用make
并传入需要重载的makefile
文件名及目标名。
chapter2/makefile_dir/
目录底下的makefile_c`` AMake`` BMake
这三个文件可以演示我们所需的功能。
先拷贝三个文件到当前目录下:
cp makefile_dir/makefile_c makefile
cp makefile_dir/AMake ./
cp makefile_dir/BMake ./
查看makefile
文件,内容如下:
#this is a makefile reload example main part
.PHONY:AAim BAim
AAim:
make -f AMake intro
BAim:
make -f BMake intro
当目标为AAim
时,会执行“make -f AMake intro”
。
也就是会重载AMake
作为makefile
文件并执行intro
规则。
BAim
的处理方式也类似。
现在测试一下执行效果,执行:
make AAim
终端打印:
make -f AMake intro
make[1]: Entering directory '/root/study/make_example/chapter2'
Hello, this is AMake
make[1]: Leaving directory '/root/study/make_example/chapter2'
可见AMake
下的intro
规则的确被执行到了。
再执行BAim
规则:
make BAim
终端打印:
make -f BMake intro
make[1]: Entering directory '/root/study/make_example/chapter2'
Hello, this is BMake
make[1]: Leaving directory '/root/study/make_example/chapter2'
BMake
的规则也被顺利执行。
上述部分是基本的重载方式。
现在我们在多一条需求,希望其它未定义规则都要执行另一条intro
规则,此规则定义在CMake
文件中。
为了匹配其它所有的未定义规则,我们需要用到通配符“%”。
修改makefile
在文件最后加入“所有匹配模式”规则:
%:
make -f CMake intro
并将makefile_dir/CMake
文件拷贝到当前目录下:
cp makefile_dir/CMake ./
随便执行一条规则AAA
:
make AAA
终端打印:
make -f CMake intro
make[1]: Entering directory '/root/study/make_example/chapter2'
Hello, this is CMake
make[1]: Leaving directory '/root/study/make_example/chapter2'
说明这条未定义的规则最后会重载CMake
并执行其intro
规则。
实验效果如图所示:
六、实验总结
本实验验证了makefile
的自动推导规则,一些环境变量的使用,include
指示符的使用和限制,以及makefile
重载的技巧。