转自:https://blog.csdn.net/gracioushe/article/details/5914141
Makefile特殊规则
9/23/2009 10:54:15 PM
http://blog.163.com/freeghost@yeah/blog/static/12319996120097913650383/
Managing Projects with GNU Make 笔记三
特殊规则(补充)
这是针对该书第二章后半段读书笔记的一个补充
回顾一下规则 PHONY声明。PHONY的目的是,让目标(TARGET)不再是个文件。例如
假设,当前目录下,存在 clean 文件。makefile文件里 ,存在
clean:
rm *.o
则此时,makefile会认为目标是对应的clean文件。而不是一个独立的目标命令。因此,使用PHONY如下
.PHONY:clean
clean:
rm *.o
此时,make在识别 makefile的clean时,会强制将clean不与文件 clean对应。
这个回顾,有两个确认:
1、PHONY target,假目标,是一种特殊规则。当然是最常用的一种。
2、特殊规则的使用方法是,通过
.XXXX : targets
targets:prerequisites
commands
来实现。
该书列出了部分常用的特殊规则的定义头(我自己的语言)
.INTERMEDIATE 中间目标 (直译,很多地方都这么翻译),临时(我个人的翻译) 其含义是,定义了该特殊标记的目标文件,作为依赖过程的中间文件,在MAKE退出时会自动删掉
.SECONDARY 辅助目标, 其和intermediate几乎一样,但结局相反,make 在退出时不会删除。因此可以看作是make对最终目标文件生成过程中的附属产品,同时最终目标依赖它
.PRECIOUS 重要目标(直译应该是贵重的,可惜不怎么好听),上面两个特殊标记都讨论的是编译完成后的动作。而此讨论的是编译未完成的情况。即,通常情况下,当make在执行中,被打断,则其退出时会把已经生成的文件给删除掉。而定义了这个特殊标记,则会防止这种自动删除。目的很明确。因为有些文件很宝贵,其实任何文件都宝贵,缺一不可完成工程编译。此处的宝贵是指的时间,花了很长时间编译完的文件,因为后面一点小事情产成的中断而被删除,很可惜,再次编译没有改动过的文件,很浪费时间,通过定义 PRECIOUS,则可以防止这种事情发生。之所以不想用贵重这个词来翻译,因为,本生PRECIOUS和文件本身就没有联系。贵重,宝贵的是编译时间。而不是文件本身。与其直译,不如模糊一点,认为这些文件很重要。
.DELETE_ON_ERROR 这个标记,和PRECIOUS一样,反映的是make出错下的操作。和PRECIOUS相反,是删除。此时,似乎有矛盾。既然看上去DELETE_ON_ERROR是PRECIOUS的补集,那又有何意义呢?呵呵, PRECIOUS中我忘了提醒一点。PRECIOUS中指的退出,是受信号中断,而不是makefile里某个command的出错。而DELETE_ON_ERROR是指的所有情况下的make退出。
(注意,以上总结是根据书面描述的理解来进行的。以下实验中,你会发现有不同的情况发生,及我所作的不一样的总结)
下面我们来作些实例。
为了保证能在编译中给出中断信号,可能我们需要很多C代码,除非你的机器足够的慢。因此先作一个C文件如下,例如批处理生成100个C和H的文件。(题外话,为了演示上述四了特殊规则,我在创建下列变态工程时,确实比较深刻理会到上述规则的含义,呵呵,相对于5分钟内看完书上那一页的内容)
如下,先执行一些环境构建的例子
$mkdir makefile_2_ext
$cd makefile_2_ext
进入子目录而已。下面创建如下C文件
$cat create_file.c
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#define MAX_FUNC 2
char linedata[1024];
int max_func = MAX_FUNC;
FILE *create_one_file(const char *fileheader,int i,const char *ext){
FILE *f;
char filename[10];
char tmp_num[10];
strcpy(filename,fileheader);
sprintf(tmp_num,"%d",i);
strcat(filename,tmp_num);
strcat(filename,ext);
if ((f = open(filename,O_TRUNC|O_CREAT | O_RDWR,00700)) == 0){
printf("Could not create the file %s !/n",filename);
return 0;
}
return f;
}
void write_file_C_data(FILE*f,int i){
if (i >= max_func - 1){
sprintf(linedata,"#include <stdio.h>/nvoid func%d(void){/n/tprintf(/"//nOK, this is func %d down!//n/");/n}",i,i);
}else{
sprintf(linedata,"#include <stdio.h>/n#include /"func%d.h/"/nvoid func%d(void){/n/tprintf(/"to call func%d-->/");/n/tfunc%d();/n}",i+1,i,i+1,i+1);
}
write(f,linedata,strlen(linedata));
return;
}
void write_file_H_data(FILE *f,int i){
sprintf(linedata,"#ifndef _FUNC%d_H_/n#define _FUNC%d_H_/nvoid func%d(void);/n#endif/n",i,i,i);
write(f,linedata,strlen(linedata));
return;
}
int main(int argc,char *argv[]){
int i;
FILE *f;
char h_filename[10];
const char file_header[] = "func";
if (argc > 1){
if ((int)atoi(argv[1]) < 101)
max_func = (int)atoi(argv[1]);
}
printf("now begin create func %d file!/n",max_func);
for (i =0 ; i < max_func ; i++){
if ((f = create_one_file(file_header,i,".c")) == 0) return 0;
write_file_C_data(f,i);
close(f);
if ((f = create_one_file(file_header,i,".h")) == 0) return 0;
write_file_H_data(f,i);
close(f);
}
return 1;
}
=======================================================
以上C文件的用途是,创建N个funcX.c和funcX.h的文件。默认情况下,是创建2个。关系如下:
假设创建N个则,存在:
1、func0.c 调用 func1.c里的func1函数。
2、依次, func(i).c里调用 func(i+1).c里的func(i+1)函数。直到func(N-1).c文件里,的func(N-1)函数直接打印一行文字。
你可以COPY上述代码到一个名为create_file.c的空文件里。执行如下命令
$gcc -c create create_file.c
$./create
此时你会发现目录下存在func0.c func0.h func1.c func1.h四个文件。这是因为默认情况下,max_func = 2。也可以执行
$./create 100
此时会生成100个 c文件, 100个h文件。当然此处,参考如下行
if ((int)atoi(argv[1]) < 101)
表示,如果./create后跟的参量大于100则仍然采用默认值。
OK。无论你生成多少文件,我们都需要一个主文件,如下
$cat call_func.c
#include "func0.h"
int main(int agrc,char *argv[]){
func0();
return 1;
}
=======================================================
够简单吧。就是从func0开始调用。放心,它会一个个依次调用由create所生成的所有C文件。
编写如下makefile
$cat makefile
src := $(wildcard func*.c)
obj := $(patsubst %.c,%.o,$(src))
call_func:call_func.c $(obj)
gcc -o $@ $^
==================================================
足够简单吧。执行
$make
我使用的是
$./create 10
因此只有10个funcX.c 和10个funcX.h。执行
$./call_func
会有如下打印
to call func1-->to call func2-->to call func3-->to call func4-->to call func5-->to call func6-->to call func7-->to call func8-->to call func9-->
OK, this is func 9 down!
如果以上,都正常,OK。我们开始做上述特殊规则的实现。首先,解释一下
src := $(wildcard func*.c)
obj := $(patsubst %.c,%.o,$(src))
这里用到了func功能。我先不讨论。只强调如下:
1、src、obj 是变量,在make扫描到它时,则个变量的值就开始进行计算和将结果传递给src或obj。
2、$(wildcard func*.c) 是完成两项工作。第一,寻找当前目录下,所有func*.c的文件。第二,将这些文件名展开,并用空格分割。
3、假设目录下只存在func0.c func1.c则此时 src等于 "func0.c func1.c"如果此时目录下没有func*.c,此时src等于空。
4、obj关于变量本身的特性,和src 一样,而$(patsubst %.c,%.o,$(src))的意思是,查找$(src)中所有的*.c的字符串,将其转换为*.o
因此,上述makefile假设当前目录下只有func0.c func1.c两个func*.c的文件则
src = func0.c func1.c
obj = func0.o func1.o
那么
call_func这行在 make扫描makefile文件是就被扩展为
call_func:call_func.c func0.o func1.o
由于make存在隐含规则,对于o文件,会使用cc -c 同明.c来编译获得,因此不需要描述。
现在我们来实现
.INTERMEDIATE
先执行
$ls *.o
此时我打印的是
func0.o func2.o func4.o func6.o func8.o
func1.o func3.o func5.o func7.o func9.o
因为我./create 10
现在执行如下
$rm *.o
$rm call_func
删除所有文件。
修改makefile 如下
$cat makefile
src := $(wildcard func*.c)
obj := $(patsubst %.c,%.o,$(src))
.INTERMEDIATE:$(obj)
call_func:call_func.c $(obj)
gcc -o $@ $^
==================================================
即,把所有func*.o强制为中间文件。并删除。
执行如下命令
$make
我的打印如下
cc -c -o func0.o func0.c
cc -c -o func1.o func1.c
cc -c -o func2.o func2.c
cc -c -o func3.o func3.c
cc -c -o func4.o func4.c
cc -c -o func5.o func5.c
cc -c -o func6.o func6.c
cc -c -o func7.o func7.c
cc -c -o func8.o func8.c
cc -c -o func9.o func9.c
gcc -o call_func call_func.c func0.o func1.o func2.o func3.o func4.o func5.o func6.o func7.o func8.o func9.o
rm func6.o func5.o func0.o func1.o func7.o func2.o func9.o func4.o func8.o func3.o
此时最后一行多了个rm,而且执行如下命令
$ls *.o
你会发现,func*.o确实被删除了。为什么,因为我们定义了
.INTERMEDIATE:$(obj)
而 $(obj)的内容我上面已经讨论过了。因此不重复。
假设,你觉得func0.o很重要。并不想删除,此时我们再修改makefile如下
$cat makefile
src := $(wildcard func*.c)
obj := $(patsubst %.c,%.o,$(src))
.INTERMEDIATE:$(obj)
.SECONDARY:func0.o
call_func:call_func.c $(obj)
gcc -o $@ $^
==================================================
执行如下命令
$rm *.o
$rm call_func
$make
此时最后一行打印为
rm func6.o func5.o func1.o func7.o func2.o func9.o func4.o func8.o func3.o
再执行
$ls *.o
此时存在
func0.o
SECONDARY的含义看来清楚了。
现在看看后面两个特殊规则。
此时我们不需要修改make。倒是要重新执行一下./create 如下
$./create 100
以为至少我的机器比较快,文件太少,无法及时按 ctrl+c来中断退出。
$make
记住,在make 工作中,及时按下ctrl+c,当我按下了ctrl+c时,此时最后打印如下内容
^Cmake: *** 正在删除文件“func19.o”
make: *** [func19.o] 中断
make: *** 正在删除中间文件“func14.o”
make: *** 正在删除中间文件“func12.o”
make: *** 正在删除中间文件“func10.o”
make: *** 正在删除中间文件“func16.o”
make: *** 正在删除中间文件“func11.o”
make: *** 正在删除中间文件“func17.o”
make: *** 正在删除中间文件“func18.o”
make: *** 正在删除中间文件“func13.o”
make: *** 正在删除中间文件“func15.o”
恩。默认情况下,中间文件因外部信号导致的中断,此时 make 自动会删除他们。不过你执行
$ls *.o
还会存在
func0.o
这是因为定义了
.SECONDARY:func0.o
此时我们对makefile修改如下
$cat makefile
src := $(wildcard func*.c)
obj := $(patsubst %.c,%.o,$(src))
.INTERMEDIATE:$(obj)
.PRECIOUS:func0.o
call_func:call_func.c $(obj)
gcc -o $@ $^
==================================================
执行
$make
$ls *.o
OK。此时还是存在
func0.o
这里表示,func0.o并没有因为信号中断,被make作为中间文件删除。
这就是PRECIOUS的意义,当然从这个实验中也证明了一点书上没有提及到的。即.SECONDARY,并不是完全只对编译未完成的工程起作用。上文中我关于.SECONDARY的定义和总结就不做修改了。那是对应书本的内容。以下摘抄一段来自于http://www.linuxsir.org/main/doc/gnumake/GNUmake_v3.80-zh_CN_html/make-10.html#_gnu_make_10.4
的内容。这几乎是最权威的中文make 的手册:
make的中间过程文件和那些明确指定的文件在规则中的地位完全相同。但make在处理时两者之间存在一些差异:
第一:中间文件不存在时,make处理两者的方式不同。对于一个普通文件来说,因为Makefile中有明确的提及,此文件可能是作为一个目标的依赖,make在执行它所在的规则前会试图重建它。但是对于中间文件,因为没有明确提及,make不会去试图重建它。除非这个中间文件所依赖的文件(上例第二种情况中的文件“N.y”;N.c是中间过程文件)被更新。
第二:如果make在执行时需要用到一个中间过程文件,那么默认的动作将是:这个过程文件在make执行结束后会被删除(make会在删除中间过程文件时打印出执行的命令以显示那些文件被删除了)。因此一个中间过程文件在make执行结束后就不再存在了。
在Makefile中明确提及的所有文件都不被作为中间过程文件来处理,这是缺省地。不过我们可以在Makefile中使用特殊目标“.INTERMEDIATE”来指除将那些文件作为中间过程文件来处理(这些文件作为目标“.INTERMEDIATE”的依赖文件罗列),即使它们在Makefile中被明确提及,这些作为特殊目标“.INTERMEDIATE”依赖的文件在make执行结束之后会被自动删除。
另一方面,如果我们希望保留某些中间过程文件(它没有在Makefile中被提及),不希望make结束时自动删除它们。可以在Makefile中使用特使目标“.SECONDARY”来指出这些文件(这些文件将被作为“secondary”文件;需要保留的文件作为特殊目标“.SECONDARY”的依赖文件罗列)。注意:“secondary”文件也同时被作为中间过程文件来对待。
需要保留中间过程文件还存在另外一种实现方式。例如需要保留所有.o的中间过程文件,我们可以将.o文件的模式(%.o)作为特殊目标“.PRECIOUS”的依赖。
以上是摘抄内容。
这里几乎没有谈论到make 在正常执行,和异常中断下的问题。因此,我没有修改上述我的总结。因为在MANAGING PROJECTS WITH GUN MAKE 内提到过。因此我尽量保证上述总结和书本翻译一致。但不能不强调例子中,.SECONDARY在make 因信号中断导致的退出时也在起作用。
最后一个例子。
修改makefile如下
$cat makefile
src := $(wildcard func*.c)
obj := $(patsubst %.c,%.o,$(src))
.INTERMEDIATE:$(obj)
.DELETE_ON_ERROR:$(obj)
.PRECIOUS:func0.o
call_func:call_func.c $(obj)
gcc -o $@ $^
==================================================
此处
.DELETE_ON_ERROR:$(obj)
表示,在出错时,删除所有func*.o。再执行如下命令
$rm *.o
$make
并记得ctrl+c
OK。一切如常,执行
$ls *.o
只有
func0.o存在。
此处还不能证明DELETE_ON_ERROR的意义。
修改makefile 如下
$cat makefile
src := $(wildcard func*.c)
obj := $(patsubst %.c,%.o,$(src))
.INTERMEDIATE:$(obj)
.DELETE_ON_ERROR:$(obj)
.PRECIOUS: func0.o
call_func:call_func.c $(obj)
gcc
gcc -o $@ $^
==================================================
显然,增加的gcc 是错误的。我们就是要引发一个错误嘛。此时make 会因为错误导致退出。执行
$rm *.o
$rm fun*
$./create 10
$make
此时我的最后打印如下
gcc
gcc: 没有输入文件
make: *** [call_func] 错误 1
rm func6.o func5.o func1.o func7.o func2.o func9.o func4.o func8.o func3.o
makefile里的某个command即"gcc" 出错了。当然错,因为没有参量。,此时make 会自动删除所有文件当然不包括func0.o。
修改makefile 如下
$cat makefile
src := $(wildcard func*.c)
obj := $(patsubst %.c,%.o,$(src))
.INTERMEDIATE:$(obj)
#.DELETE_ON_ERROR:$(obj)
#.PRECIOUS: func0.o
call_func:call_func.c $(obj)
gcc
gcc -o $@ $^
==================================================
执行如下
$rm *.o
$make
OK。令人混淆了。也发生了
rm func6.o func5.o func1.o func7.o func2.o func9.o func4.o func8.o func3.o func0.o
的事情。
呵呵。看来上文中对 DELETE_ON_ERROR的理解也有一定误区。
下面再次总结一下make 的四个特殊规则。此时是根据实验结果来描述:
.INTERMEDIATE 用于声明那些目标文件,为中间文件,这样,无论在任何情况下,这些中间文件,在make最后,会被清理掉。
.SECONDARY,用于声明那些目标文件,当其作为中间文件时,不会被清除掉。无论是make正常执行,还是退出。
.PRECIOUS同上。
.DELETE_ON_ERROR 暂无意义,对于那些被.INTERMEDIATE 强制定义为中间文件的文件。
上述的总结和书本的总结的出入,我想可能是因为如下方面:
1、书本中包含了隐藏规则中的中间文件。这些中间文件不需要用.INTERMEDIATE进行强制定义。
但考虑一个事实,一个安全的工程描述makefile文件,建议把所有涉及到的文件,通过函数,变量等方式简化的显示描述出来,这比隐藏在隐藏规则里更安全。至少我是这么认为的。因此,在这篇总结中。我先按照书本的翻译,进行纸上谈兵的总结。随后,根据实际个人建议,作实验总结。时时有矛盾,处处有矛盾嘛,做人,做工程要实事求是。呵呵。用一个不着边的理由,来解释和结束这篇个人总结。