Makefile基础教程 8

来源:互联网 发布:淘宝商品发布规则 编辑:程序博客网 时间:2024/06/01 17:46

一、实验介绍--Makefile 规则命令

本次实验将介绍make对规则命令的执行,命令执行过程中的错误处理以及命令包的使用。

1.1 实验内容

1.make对规则命令的执行

2.make的多线程执行

3.make的错误忽略选项

4.make的异常结束

  1. 命令包的使用

1.2 实验知识点

1.make使用$(SHELL)来执行规则命令,make会对$(SHELL)环境变量重新赋值,而非使用系统同名变量。

2.make可以使用-j选项来进行多线程执行。

3.make可以使用-来忽略当前行命令的错误,使用-i忽略所有错误,使用-k跳过依赖项错误继续重建其它依赖项。

4.当make被异常结束时,会删除当前的目标文件。

5.使用define可以定义命令包,其本质与C语言的宏定义相似。

1.3 实验环境

Ubuntu系统, GNU gcc工具,GNU make工具

1.4 适合人群

本课程难度为中等,适合已经初步了解 makefile 规则的学员进行学习。

1.5 代码获取

可以通过以下命令获取代码:

$ git clone https://github.com/darmac/make_example.git

二、实验原理

依据 makefile 的基本规则进行正反向实验,学习和理解规则的使用方式。

三、开发准备

进入实验楼课程即可。

四、项目文件结构

.├── cancel:make 的异常结束处理│   └── makefile├── error:make 命令的错误处理│   ├── iopt.mk│   ├── kopt.mk│   └── makefile├── joption:make 的并行使用│   └── makefile├── pack:命令包使用测试│   └── makefile├── Readme.md└── shell_vari:验证`$(SHELL)`环境变量的传递    └── shell.mk

五、实验步骤

5.1make对规则命令的执行

5.1.1 抓取源代码

使用如下cmd获取GitHub源代码并进入相应章节:

cd ~/Code/git clone https://github.com/darmac/make_example.gitcd make_example/chapter7

5.1.2SHELL环境变量的传递

make 使用环境变量SHELL指定的程序来处理规则命令行,GNU make 中默认的程序是/bin/sh

与其它环境变量不同的是,SHELL 变量会由 GNU make 自行定义,而不会使用当前系统的同名变量。

这样做的理由是:make 认为系统的 SHELL 变量适用于定义人机交互接口,make 没有交互过程,因此不适用。

shell_vari 目录下的shell.mk文件演示了系统环境变量SHELLmake使用的环境变量的差异,

文件内容如下:

#this is a makefile for $(SHELL) test.PHONY:allall:        @echo "\$$SHELL environment is $$SHELL"        @echo "\$$SHELL in makefile is " $(SHELL)

由于符号$是变量引用的起始字符,因此要使用$本身这个字符时需要使用$$进行转义。

all规则的第一条指令是打印系统环境变量SHELL$$代表$字符,所以在终端上印出来的内容相当于:

"\$SHELL environment is $SHELL"

\符号是终端下的转义字符,$符号在终端下同样是变量引用的起始字符,因此$SHELL会被系统环境变量SHELL的内容代替,而\$SHELL会被打印为“$SHELL”

all规则的第二条指令是打印当前make使用的SHELL变量。

进入shell_vari目录,并查看当前系统SHELL变量:

cd shell_vari/;echo $SHELL

终端打印:

/usr/bin/zsh

再执行make看看SHELL变量定义是什么:

make -f shell.mk

终端打印:

$SHELL environment is /usr/bin/zsh$SHELL in makefile is  /bin/sh

可见make使用的是自己默认的变量,与系统SHELL变量无关。

5.1.3SHELL变量传参

下面再实验在执行make时,传入SHELL变量为abc

make -f shell.mk SHELL=abc

终端打印:

make: abc: Command not foundmake: *** [all] Error 127

可见make尝试用我们传入的abc来执行规则结果因为找不到abc导致执行失败。

这说明make自身的SHELL变量也是可以通过传参进行修改的。

实验过程如下图所示:

5.1

5.2make的多线程执行

make 也可以使用多线程进行并发执行,使用方法为执行make时加入命令行选项-jN,N为一个数字,表示要执行的线程数。

make的每个线程会执行一个规则的重建,每条规则只由一个线程执行。

不使用-j选项时为单线程编译。

chapter7/joption/makefile文件给出了测试方法,内容如下:

#this is a makefile for -j option.PHONY:allall:aim1 aim2 aim3 aim4        @echo "build final finish!"aim%:        @echo "now build " $@        @sleep 2        @echo "build " $@ " finish!"

终极目标allaim1aim4四个依赖项,每个依赖项的规则一致,打印信息并睡眠两秒。

进入joption目录,并执行make,完成编译需要8秒。

cd ../joption/;make

终端打印:

now build  aim1build  aim1  finish!now build  aim2build  aim2  finish!now build  aim3build  aim3  finish!now build  aim4build  aim4  finish!build final finish!

现在使用两个线程执行makefile

make -j2

可以看到终端先同时印出aim1 aim2的执行信息,两秒后再打印aim3 aim4的执行信息,用时为4秒,比单线程缩短一倍,内容如下:

now build  aim2now build  aim1build  aim2  finish!build  aim1  finish!now build  aim3now build  aim4build  aim3  finish!build  aim4  finish!build final finish!

大家可以再测试一下三线程和四线程的并行执行过程,并尝试在目标中加入依赖项来限制并行编译的顺序。

实验过程如下图所示:

5.2

5.3make的错误忽略选项

5.3.1make执行过程出错的简单测试

下面我们来看一下make执行出错的状况。

chapter7/error/目录下的makefile文件演示了rm命令的执行状况,内容如下:

#this is a makefile for error handle test.PHONY:allall:pre_a pre_b pre_c        $(RM) pre_a        $(RM) pre_b        $(RM) pre_c        $(RM) d        -rm e        rm f        rm gpre_%:        touch $@

前面三条指令是删除生成的文件,后面四条指令则是删除不存在的文件,在shell使用rm会直接运行失败。

现在执行此makefile并解释执行状况:

cd ../error/;make

终端打印:

touch pre_atouch pre_btouch pre_crm -f pre_arm -f pre_brm -f pre_crm -f drm erm: cannot remove 'e': No such file or directorymake: [all] Error 1 (ignored)rm frm: cannot remove 'f': No such file or directorymake: *** [all] Error 1

make在运行规则命令结束后会检测命令执行的返回状态,返回成功则启动另外一个子shell来执行下一条命令。

makefile执行过程中,先生成pre_a``pre_b``pre_c三个文件,再使用rm -f或者rm删除它们,这个过程没有问题。

第四条指令是删除不存在的文件d,由于使用了-f参数,因此shell也不会返回错误。

第五条指令是删除不存在的文件e,由于命令行起始处使用了符号“-”make会忽略此命令的执行错误,所以shell虽然返回并打印错误,但make继续往下执行。

第六条指令是删除不存在的文件f,由于只使用了rm命令,shell返回错误,make收到错误后不再往下执行,

因此第七条指令已经没机会执行到。

5.3.2 忽略命令执行错误

在某些状况下,用户希望make遇上错误可以继续往下执行。在多人维护的庞大工程中,makefile文件随时可能出现错误,这时用户希望它能继续执行下去方便测试自己的模块,而不是被其他人的错误阻塞住。

此时可以使用-i选项,-i选项会让make忽略所有的错误。

iopt.mk文件可以演示-i选项的用法,内容如下:

#this is a makefile for error handle test.PHONY:allall:        rm a        rm b        rm c        rm d

make会直接调用rm删除四个不存在的文件,每一条指令都会返回错误。

先看正常执行结果,直接使用make

make -f iopt.mk

终端打印:

rm arm: cannot remove 'a': No such file or directorymake: *** [all] Error 1

现在加入 -i 选项:

make -f iopt.mk -i

终端打印:

rm arm: cannot remove 'a': No such file or directorymake: [all] Error 1 (ignored)rm brm: cannot remove 'b': No such file or directorymake: [all] Error 1 (ignored)rm crm: cannot remove 'c': No such file or directorymake: [all] Error 1 (ignored)rm drm: cannot remove 'd': No such file or directorymake: [all] Error 1 (ignored)

现在 make 可以执行到每一条指令了。

5.3.3 忽略依赖项错误

make遇上依赖项不存在时,-i选项就不管用了,因为它不属于命令行错误。

kopt.mk文件用于演示依赖文件错误的状况,内容如下:

#this is a makefile for error handle test.PHONY:allall: h i j        @echo "exe OK!"

分别使用makemake -i执行此文件:

make -f kopt.mk ; make -f kopt.mk -i

终端打印:

make: *** No rule to make target 'h', needed by 'all'.  Stop.make: *** No rule to make target 'h', needed by 'all'.  Stop.

说明在依赖项错误中-i选项没有任何作用。此时可以使用-k选项让其忽略依赖项错误并继续执行:

make -f kopt.mk -k

终端打印:

make: *** No rule to make target 'h', needed by 'all'.make: *** No rule to make target 'i', needed by 'all'.make: *** No rule to make target 'j', needed by 'all'.make: Target 'all' not remade because of errors.

-k选项可以让make继续检查其它依赖项,但并不会执行终极目标的指令。

若有多个依赖项被修改过后,可以使用此选项测试哪些依赖项的修改有问题。

请谨慎使用-i-k选项,以免产生预期外的错误。

实验过程如下图所示:

5.3A

5.3B

5.4make的异常结束

make 若收到致命信号被终止时,它会删除此过程中已经重建的目标文件,以免目标文件出现预期外的错误。

例如某个目标规则需要对目标文件进行多次处理,处理到一半时make被终止,导致目标文件处于异常状态,

因此make会删除此文件以免产生难以察觉的问题。

chapter7/cancel/目录下目录下的makefile用于演示make异常结束的状况,内容如下:

#this is a makefile for cancel handle.PHONY:all cleanall:clean pre_a pre_b pre_c        sleep 1        @echo "exe target all!"clean:        $(RM) pre_*pre_%:        @echo "\n"        touch $@        @echo "generate " $@        @ls -l $@        @echo "sleep 5s before finish..."        sleep 5

终极目标all依赖于pre_a pre_b pre_c文件,这三个文件在建立过程中会sleep五秒钟,方便用户结束make命令。

先正常执行一次:

cd ../cancel/;make

终端显示执行成功,并在当前目录下生成pre_a pre_b pre_c三个目标文件。

现在在产生pre_b的过程中使用ctrl+c结束make进程,终端打印如下:

rm -f pre_*touch pre_agenerate  pre_a-rw-rw-r-- 1 shiyanlou shiyanlou 0 Jul 24 21:44 pre_asleep 5s before finish...sleep 5touch pre_bgenerate  pre_b-rw-rw-r-- 1 shiyanlou shiyanlou 0 Jul 24 21:44 pre_bsleep 5s before finish...sleep 5^Cmake: *** Deleting file `pre_b'make: *** [pre_b] Interrupt

pre_b规则命令使用ls命令查看到当前目录下已经有pre_b文件生成。

make被强制结束后,再次使用ls命令查看当前目录文件:

ls -l pre*

文件内容如下:

-rw-r--r-- 1 root root 0 Jul 24 08:12 pre_a

可见pre_b已经被make自行删除。从make的打印内容中也可以看出它有执行删除pre_b的动作。

实验过程如下图所示:

5.4A

5.4B

5.5 命令包的使用

书写makefile时,可能有多个规则会使用一组相同的命令,就像c语言要调用函数一样,

我们可以使用define来完成这项功能。define“define”开头,以“endef”结束,作用与c语言的宏定义类似。

chapter7/pack/目录下演示了命令包的使用方法,其内容如下:

#this is a makefile for define test.PHONY:alldefine echo-target@echo "now rebuilding target : " $@touch $@endefall:pre_a pre_b pre_c        @echo "final target finish!"pre_%:        $(echo-target)

终极目标all的依赖项都会调用同一组命令包。

进入pack目录并测试执行效果:

cd ../pack/;make

终端打印:

now rebuilding target :  pre_atouch pre_anow rebuilding target :  pre_btouch pre_bnow rebuilding target :  pre_ctouch pre_cfinal target finish!

实验过程如下图所示:

5.5

六、实验总结

本次实验测试了make对规则命令的执行,命令执行过程中的错误处理以及命令包的使用方式。

七、课后习题

1.请使用目标依赖来控制并行编译的顺序。

2.请尝试在命令包中使用变量控制参数的输入和输出。