知识点

本章节将介绍make对规矩指令的履行,指令履行进程中的过错处理以及指令包的运用。

  • make对规矩指令的履行
  • make的多线程履行
  • make的过错疏忽选项
  • make的反常完毕

本课程项目完成进程中将学习:

  • $(SHELL) 履行规矩指令
  • -j 选项进行多线程履行
  • -、-i、-k 参数的效果
  • make 反常完毕
  • define

本章节的源代码位于 /home/shiyanlou/Code/make_example/chapter7/ 目录下。


项目结构

项目文件结构:

├── cancel # make 的反常完毕处理
│   └── makefile
├── error # make 指令的过错处理
│   ├── iopt.mk
│   ├── kopt.mk
│   └── makefile
├── joption # make 的并行运用
│   └── makefile
├── pack # 指令包运用测验
│   └── makefile
└── shell_vari # 验证`$(SHELL)`环境变量的传递
    └── shell.mk

1️⃣ make 对规矩指令的履行

SHELL环境变量的传递

make 运用环境变量 SHELL 指定的 shell (指令解释器) 来处理规矩指令行,GNU make 中默许的 shell 是 /bin/sh,与其它环境变量不同的是,SHELL 变量会由 GNU make 自行界说,而不会运用当时体系的同名变量。这样做的理由是:make 认为体系的 SHELL 变量适用于界说人机交互接口,make 没有交互进程,因而不适用。

shell_vari 目录下的 shell.mk 文件演示了体系环境变量 SHELL 和 make 运用的环境变量的差异, 文件内容如下:

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

因为符号$是变量引证的开始字符,因而要运用$本身这个字符时需求运用$$进行转义。 all规矩的第一条指令是打印体系环境变量SHELL$$代表$字符,所以它的履行成果与下面这条指令的履行成果是相同的。

echo "\$SHELL environment is $SHELL"

makefile 中 @echo 与 echo 差异:echo 会在终端中先输出 echo 指令本身,再输出成果,而 @echo 是直接输出成果。详见 Difference between echo and @echo in unix shells

「\」符号是终端下的转义字符,「」符号在终端下同样是变量引证的开始字符,因而‘」符号在终端下同样是变量引证的开始字符,因而 `SHELL会被体系环境变量SHELL的内容替代,而SHELL‘会被打印为「SHELL` 会被打印为「SHELL」。all 规矩的第二条指令是打印当时 make 运用的 SHELL 变量。

输入下面的指令进入 shell_vari 目录,并检查当时体系 SHELL 变量。

cd shell_vari/;echo $SHELL

Terminal 的输出成果如下图:

8 make 的进阶操作

履行下面的指令检查,make 在履行 makefile 规矩时所运用到的 shell。

make -f shell.mk

Terminal 的输出成果如下图:

8 make 的进阶操作

从输出成果上看到 make 在履行 makefile 的时候所运用到的 shell 程序是不同于体系默许的 shell 程序。


SHELL变量传参

接下来测验试验在履行 make 时,传入值为 abc 的 SHELL 变量。

make -f shell.mk SHELL=abc

Terminal 的输出成果如图所示:

8 make 的进阶操作

可见make尝试用咱们传入的abc来履行规矩成果因为找不到abc导致履行失利。

这阐明 ‘make‘本身的‘SHELL‘变量也是能够经过传参进行修正的。\color{red}{`make`本身的`SHELL`变量也是能够经过传参进行修正的。}


2️⃣ make 的多线程履行

make 也能够运用多线程进行并发履行,运用方法为履行 make 时参加指令行选项 -jN,其中 N 为一个数字,表明要履行的线程数。而 make 的每个线程会履行一个规矩的重建,每条规矩只由一个线程履行。当不运用 -j 选项时 make 的履行为单线程编译。

经过 chapter7/joption 文件夹中的 makefile 文件可对 make 的多线程进行验证,makefile 文件的内容如下:

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

从内容上看最终方针 allaim1aim4 四个依靠项,每个依靠项的规矩一致,打印信息并睡觉两秒。进入 joption 目录,并履行 make 指令,完成编译需求 8 秒。

运转截图如下:

8 make 的进阶操作


单线程履行

为了能更直观的看到时刻变化进程咱们能够修正 makefile 的内容,咱们能够为每一个输出进程增加一个时刻戳,修正后的内容如下:

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

先履行 make 指令检查在单线程下编译的状况,Terminal 的输出如下图:

8 make 的进阶操作

从输出成果能够看出,从开端编译 aim1aim4 编译完毕,一共耗时 8 秒。


多线程履行

接下来,经过增加 -j2 选项检查在多线程下的履行状况。

make -j2

Terminal 的输出成果如图:

8 make 的进阶操作

能够看出 从开端编译 aim1aim4 编译完成一共耗时 4 秒比单线程所消耗的时刻减少了一半。

大家能够自己再测验一下三线程和四线程的并行履行进程,并尝试在方针中参加依靠项来限制并行编译的次序。

参阅:# 在Makefile中强制依靠项次序

关于Makefile的一个留意事项:多线程编译犯错总结


然后总结出来的3线程次序:

#this is a makefile for -j option
.PHONY:all
all:all2 all4 all1 all3
	@echo "build final finish!"
all2:aim1
all4:aim2
all1:aim3
all3:aim4
aim%:
	@echo "now build $$(date +%T)" $@
	@sleep 2
	@echo "build " $@ " finish! $$(date +%T)"

8 make 的进阶操作


总结出来的四线程次序: 想不出好办法了,用的依靠

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

8 make 的进阶操作


3️⃣ make 的过错疏忽选项

make履行进程犯错的简略测验

下面咱们来看一下 make 履行犯错的状况。 在 chapter7/error/ 目录下的 makefile 文件对 rm 指令的不同履行状况进行了描述,其内容如下:

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

从内容上看前面三条指令是删去生成的文件,后边四条指令则是删去不存在的文件,而在 shell 运用 rm 会直接运转失利。

现在进入到 error 文件夹下并履行此 makefile 并调查履行状况:

cd ../error/;make

Terminal 的输出成果如图:

8 make 的进阶操作

从输出成果上进行分析 make 在运转规矩指令完毕后会检测指令履行的回来状况,回来成功则启动另外一个子 shell 来履行下一条指令。在 makefile 履行进程中,先生成 pre_apre_bpre_c 三个文件,再运用 rm -f 或者 rm 指令来删去它们,这个进程是没有问题的。

但是第四条指令是删去不存在的文件 d,因为运用了 -f 参数,因而 shell 也不会回来过错。

第五条指令是删去不存在的文件 e因为指令行开始处运用了符号「-」,所以 make 会疏忽此指令的履行过错,所以 shell 尽管回来并打印过错,但 make 持续往下履行。

第六条指令是删去不存在的文件 f,因为只运用了 rm 指令,shell 回来过错,make 收到过错后不再往下履行,因而第七条指令现已没机会履行到。


在某些状况下,用户期望 make 遇上过错能够持续往下履行。在多人维护的巨大工程中,makefile 文件随时可能呈现过错,这时用户期望它能持续履行下去便利测验自己的模块,而不是被其他人的过错阻塞住。此刻能够运用 -i 选项,-i 选项会让 make 疏忽一切的过错。

经过提供的 iopt.mk 文件能够对 -i 选项的用法进行演示,iopt.mk 文件内容如下:

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

make 会直接调用 rm 指令删去四个不存在的文件,因而每一条指令都会回来过错。

现在履行 make 指令先并调查不运用 -i 选项时文件履行的成果。

make -f iopt.mk

Terminal 的输出成果如下图:

8 make 的进阶操作

接下来履行加上 -i 选项再履行 iopt.mk 文件。

make -f iopt.mk -i

Terminal 的输出成果如下图:

8 make 的进阶操作

能够看到尽管前一条指令履行时发生了过错,但是后边的指令仍然得到了履行,并没有就此停止履行。

留意:当make遇上依靠项不存在时,‘−i‘选项就不管用了,它不归于指令行过错。\color{red}{留意:当 make 遇上依靠项不存在时,`-i` 选项就不管用了,它不归于指令行过错。}


提供的 kopt.mk 文件能够用来对依靠文件过错的状况进行验证,其内容如下:

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

履行 make -f kopt.mk -i 时 Terminal 的输出成果如下图:

8 make 的进阶操作

履行 make -f kopt.mk 时 Terminal 的输出成果如下图:

8 make 的进阶操作

比照两种状况的输出成果,阐明在依靠项过错中 -i 选项没有任何效果。


关于这种状况能够运用 -k 选项让其疏忽依靠项过错并持续履行,现履行 make 指令。

make -f kopt.mk -k

Terminal 的输出成果如图所示:

8 make 的进阶操作

-k选项能够让make持续检查其它依靠项,但并不会履行终极方针的指令。 若有多个依靠项被修正过后,能够运用此选项测验哪些依靠项的修正有问题。

请谨慎运用-i-k选项,避免发生预期外的过错。


4️⃣ make 的反常完毕

make 若收到致命信号被停止时,它会删去此进程中现已重建的方针文件,避免方针文件呈现预期外的过错。例如某个方针规矩需求对方针文件进行屡次处理,处理到一半时 make 被停止,导致方针文件处于反常状况, 此刻 make 会删去此文件避免发生难以察觉的问题。

chapter7/cancel/ 目录下的 makefile 文件用于对 make 反常完毕的状况的验证,内容如下:

#this is a makefile for cancel handle
.PHONY:all clean
all: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_apre_bpre_c 文件,这三个文件在树立进程中会 sleep 五秒钟,便利用户完毕 make 指令。

现在进入到 cancel 文件夹中履行 make 指令不作其他任何操作并调查输出成果。

cd ../cancel;make

Terminal 的输出成果如下图:

8 make 的进阶操作

此刻经过 ls 指令检查当时目录下新文件的发生状况。

8 make 的进阶操作

可见 pre_apre_bpre_c 三个文件被被成功的生成。


手动模仿 make 履行进程中反常发生

接下来手动模仿 make 履行进程中反常的发生,在 pre_b 文件被生成的进程中,手动键入 ctrl + c 来停止程序的持续履行。

8 make 的进阶操作

现在经过 ls 指令检查当目录中文件的状况。

8 make 的进阶操作

从输出成果能够看出,pre_b 现已被删去,从上一张 make 的履行进程的图片中也能够看出在反常中断的发生进程中 pre_b 文件就被删去了。


5️⃣ 指令包的运用(c函数)

书写makefile时,可能有多个规矩会运用一组相同的指令,就像c言语要调用函数一样, 咱们能够运用define来完成这项功能。

define“define”最初,以“endef”完毕,效果与c言语的宏界说类似。

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

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

从文件内容上看最终方针 all 的依靠项都会调用同一组指令包。进入 pack 目录并测验履行效果:

cd ../pack;make

Terminal 的输出成果如图所示:

8 make 的进阶操作

能够看出在 all 的每个依靠的指令部分都履行了 define 界说的指令包。


本章节测验了 make 对规矩指令的履行,指令履行进程中的过错处理以及指令包的运用方式。