嵌入式学习Makefile:伪目标

伪目标(基础中的基础)

伪目标Makefile的一个重要的特殊目标。伪目标:它不代表一个真正的文件名,在执行make时可以指定这个目标来执行其所在规则定义的命令,也可以将一个伪目标称为标签。
使用伪目标有两点原因:
1)避免在我们的Makefile中定义的只执行命令的目标(就是说定义该目标的目的是为了执行一系列的命令,比如clean,为了执行rm的一些操作)和工作目录下的实际文件出现名字冲突
2)提高执行make时的效率,特别是对于一个大型的工程来说,编译的效率也许你同样关心,以下就这两个问题我们进行分析讨论

  1. 如果我们需要书写这样一个规则:规则定义的命令不是去创建目标文件,而是通过make命令行明确指定它来执行一些特定的命令,像常见的clean目标:
clean:
	rm *.o temp

规则中“rm” 不是创建文件“clean”的命令,而是删除当前目录下的所有.o文件和temp文件,当工作目录下不存在“clean”这个文件时,我们输入“make clean”,然后“rm *.o temp”总是被执行,这是我们期望的执行结果。
但是如果在当前工作目录下存在文件“clean”, 情况就不一样了,同样我们输入“make clean”,由于这个规则没有任何依赖文件,所以目标被认为是最新的而不去执行规则所定义的命令,因此命令“rm”将不会被执行,这并不是我们期望的执行结果,为了解决这个问题,我们需要将目标“clean”声明为伪目标,将一个目标声明为伪目标的方法是将它作为特殊目标".PHONY"的依赖,如下:

.PHONY:clean

这样目标“clean” 就被声明为一个伪目标,无论在当前目录下是否存在“clean”这个文件,我们输入“make clean”之后,“rm”命令都会被执行,而且,当一个目标被声明为伪目标后,make在执行此规则时不会去试图去查找隐含规则来创建它这样也提高了make的执行效率,同时也不用担心由于目标和文件名重名而使我们的期望失败。在书写伪目标规则时,首先需要声明目标是一个伪目标,之后才是伪目标的规则定义。
目标“clean”的完整书写格式应该如下:

.PHONY:clean
clean:
	rm *.o temp

2.伪目标的另外一种使用场合是在make的并行和递归执行过程中,此情况下一般会存在一个变量定义为所有需要make的子目录,对多个目录进行make的实现方式可以是:在一个规则的命令行中使用shell循环来完成(python中的循环方式,脚本语言,只不过shell的列表和python的不太一样)。如下:

SUBDIRS=foo bar baz
subdirs:
	for dir in $(SUBDIRS);do\
		$(MAKE) -C $$dir;\
	done

但这种实现方法存在以下几个问题。
1)当子目录执行make出现错误时,make不会退出,就是说,在对某一个目录执行make失败以后,会继续对其他的目录进行make,在最终执行失败的情况下,我们很难根据错误提示定位出具体是在那个目录下执行make时发生错误,这样给问题定位在成了很大的困难,为了解决这个问题,可以在命令行部分加入错误监测,在命令执行错误后主动退出,不行的是,如果在执行make时使用了“-k”选项,此方式将失败
2)另外一个问题就是使用这种shell的循环方式时,没有用到make对目录的并行处理功能,由于规则的命令是一条完整分shell命令,不能被并行处理
有了伪目标以后,我们可以用它来克服以上实现方式所存在的两个问题,

SUBDIRS=foo bar baz
.PHONY:subdirs $(SUBDIRS)
subdirs:$(SUBDIRS)
$(SUBDIRS):
	$(MAKE) -C $@
foo:baz

上边的实现中有一个没有命令行的规则“foo: baz”,此规则用来限制子目录的make顺序。它的作用是限制同步目录“foo”和“baz”的make过程(在处理“foo”目录之前,需要等待“baz”目录处理完成)提醒大家:在书写一个并行执行make的Makefile时,目录的处理顺序是需要特别注意的。
一般情况下,一个伪目标不作为另一个目标的依赖,这是因为当一个目标文件的依赖包含伪目标时,每一次在执行这个规则时伪目标所定义的命令都会被执行(因为它作为规则的依赖,重建规则目标时需要首先重建规划的所有依赖文件),当一个伪目标没有作为任何目标(此目标一个可以被创建或者已存在的文件)的依赖时,我们只能通过make的命令行来表明明确指定它为make的终极目标,来执行它所在规则所定义的命令。例如“make clean”。
在Makefile中,一个伪目标可以有自己的依赖(可以是一个或者多个文件,一个或者多个伪目标)。在一个目录下如果需要创建多个可执行程序,我们可以将所有程序的重建规则在一个Makefile中描述,因为Makefile中第一个目标是“终极目标”,约定的做法是使用一个称为“all”的伪目标来作为终极目标,它的依赖文件就是那些需要创建的程序。下边举个实际的例子看看:

#sample Makefile
all:prog1 prog2 prog3
.PHONY:all

prog1:prog1.o utils.o
	cc -o prog1 prog1.o utils.o
prog2:prog2.o
	cc -o prog2 prog2.o
prog3:prog3.o sort.o utils.o
	cc -o prog3 prog3.o sort.o utils.o

执行make时,目标“all”被作为终极目标。为了完成对它的更新,make会创建(不存在)或者重建(已存在)目标“all”的所有依赖文件(prog1,prog2和prog3),当需要单独更新某一个程序时,我们可以通过make的命令行选项来明确指定需要重建的程序。(例如:“make prog1”).
当一个伪目标作为另一个伪目标依赖时,make将其作为另外一个伪目标的子例程序来处理(可以这样理解:其作为另外一个伪目标的必须执行的部分,就行C语言中的函数调用一样)。下边的例子就是这种用法:

.PHONY:cleanall cleanobj cleandiff
cleanall:cleanobj cleandiff
	rm program
cleanobj:
	rm *.o
cleandiff:
	rm *.diff

“cleanobj” 和"cleandiff"这两个伪目标有点像“子程序”的意思(执行目标“cleanall”时会触发它们所定义的命令被执行)。我们可以输入“make cleanall” 和”make cleanobj“和”make cleandiff“命令来达到清除不同种类文件的目的,例子首先通过特殊目标".PHONY"声明了多个伪目标,它们之间使用空格分隔,之后才是各个伪目标的规则定义。
说明:
通常在清除文件clean的命令中”rm“使用选项”-f“(–force)来防止在缺少删除文件时出错并退出,使”make clean“过程失败,也可以在”rm“之前加上”-“来防止”rm“错误退出,这种方式时make会提示错误信息但不会退出,为了不看到这些讨厌的信息,需要使用上述的第一种方式。
另外make存在一个内嵌隐含信息变量”RM“,它被定义为:”RM = rm -f“,因此在书写”clean“规则的命令行时可以**使用变量”$RM“**来代替”rm“,这样可以避免出现一些不必要的麻烦!

发布了53 篇原创文章 · 获赞 16 · 访问量 2213

猜你喜欢

转载自blog.csdn.net/m0_37757533/article/details/105174776