- 浏览: 21366629 次
- 性别:
- 来自: 杭州
最新评论
-
ZY199266:
配置文件还需要额外的配置ma
Android 客户端通过内置API(HttpClient) 访问 服务器(用Spring MVC 架构) 返回的json数据全过程 -
ZY199266:
我的一访问为什么是 /mavenwebdemo/WEB-I ...
Android 客户端通过内置API(HttpClient) 访问 服务器(用Spring MVC 架构) 返回的json数据全过程 -
lvgaga:
我又一个问题就是 如果像你的这种形式写。配置文件还需要额外的 ...
Android 客户端通过内置API(HttpClient) 访问 服务器(用Spring MVC 架构) 返回的json数据全过程 -
lvgaga:
我的一访问为什么是 /mavenwebdemo/WEB-I ...
Android 客户端通过内置API(HttpClient) 访问 服务器(用Spring MVC 架构) 返回的json数据全过程 -
y1210251848:
你的那个错误应该是项目所使用的目标框架不支持吧
log4net配置(web中使用log4net,把web.config放在单独的文件中)
Managing Projects with GNU Make 笔记二
Managing Projects with GNU Make 笔记二 规则
9/23/2009 10:56:28 PM
Managing Projects with GNU Make 笔记二 规则
这是针对该书第二章所作的笔记。首先确认一下环境及测试文件。在某个目录下,执行如下命令
$mkdir makefile_2
$cd makefile_2
$mkdir src
$mkdir include
编辑8个文件保存在 makefile_2的当前目录下,以下通过cat,显示8个文件的内容,========一如既往的表示当前文件显示结束,但是该文件并不包含这一行。
$cat main.c
#include <stdio.h>
#include "func1.h"
#include "func2.h"
#include "func3.h"
int main(int argv ,char *argc[]){
printf("ok, this is a test for <Managine Project with GNU make>'s chapter 2 by luckystar!/n");
func_1();
func_2();
func_3();
return 1;
}
================================
$cat func1.c
#include <stdio.h>
#include "func1.h"
void func_1(void){
printf("the func 1 called in the func1.c file !/n");
}
================================
$cat func1.h
#ifndef _FUNC_1_H_
#define _FUNC_1_H_
void func_1(void);
#endif
================================
$cat func2.c
#include <stdio.h>
#include "func2.h"
void func_2(void){
printf("the func 2 called in the func2.c file !/n");
}
================================
$cat func2.h
#ifndef _FUNC_2_H_
#define _FUNC_2_H_
void func_2(void);
#endif
================================
$cat func3.c
#include <stdio.h>
#include "func3.h"
void func_3(void){
printf("the func 3 called in the func3.c file !/n");
}
================================
$cat func3.h
#ifndef _FUNC_3_H_
#define _FUNC_3_H_
void func_3(void);
#endif
================================
$cat makefile
chapter_2:main.o func3.o func2.o func1.o
gcc -o chapter_2 main.o func3.o func2.o func1.o
func3.o:func3.c func3.h
gcc -c func3.c
func2.o:func2.c func2.h
gcc -c func2.c
func1.o:func1.c func1.h
gcc -c func1.c
main.o:main.c func1.h func2.h func3.h
gcc -c main.c
clean:
rm -r *.o
rm chapter_2
================================
上述文件整理完毕后,执行 make
$make
$./chapter_2
则会显示一段打印文字如下
ok, this is a test for <Managine Project with GNU make>'s chapter 2 by luckystar!
the func 1 called in the func1.c file !
the func 2 called in the func2.c file !
the func 3 called in the func3.c file !
从第一章内容中就可以看到。makefile文件中,最基本的三个内容是,目标,依赖,执行。而这三种内容需要通过规则来描述,以体现相互关系。
这本书在第二章描述了以下几个规则
explicit rules 显式规则(我自己的粗糙的翻译,希望别使得有人误解)
implicit rules 隐式规则
pattern rules 模式规则
explicit rules
显式规则,非常简单。回到例子,
修改 func1.c func2.c func1.h func2.h如下
$cat func1.h
#ifndef _FUNC_1_H_
#define _FUNC_1_H_
void func_1(void);
void func_12(void);
#endif
================================
$cat func1.c
#include <stdio.h>
#include "func1.h"
#include "func2.h"
void func_1(void){
printf("the func 1 called in the func1.c file !/n");
func_21();
}
void func_12(void){
printf("the func 12 called by func_2 !/n");
}
void func_13(void){
printf("the func 13 called by func_3 !/n");
}
================================
$cat func2.h
#ifndef _FUNC_2_H_
#define _FUNC_2_H_
void func_2(void);
void func_21(void);
#endif
================================
$cat func2.c
#include <stdio.h>
#include "func1.h"
#include "func2.h"
void func_2(void){
printf("the func 2 called in the func2.c file !/n");
func_12();
}
void func_22(void){
printf("the func 21 called by func_1 !/n");
}
================================
此时我们重新调整 makefile如下
$cat makefile
chapter_2:main.o func3.o func2.o func1.o
gcc -o chapter_2 main.o func3.o func2.o func1.o
func3.o:func3.c func3.h
gcc -c func3.c
func2.o func1.o main.o: func2.h func1.h
func2.o : func2.c
gcc -c func2.c
func1.o:func1.c
gcc -c func1.c
main.o:main.c func3.h
gcc -c main.c
clean:
rm -r *.o
rm chapter_2
================================
依次执行
$make clean
$make
$./chapter_2
此时,输出结果为
ok, this is a test for <Managine Project with GNU make>'s chapter 2 by luckystar!
the func 1 called in the func1.c file !
the func 21 called by func_1 !
the func 2 called in the func2.c file !
the func 12 called by func_2 !
the func 3 called in the func3.c file !
这里告所我们两个事实
1、目标需要多个依赖时,可以分开写,例如 target : a b c 可以写成如下格式
target : a
target : b
target : c
2、多个目标需要相同依赖时,可以写在一个目标依赖关系中。如例子中的func2.o func1.o main.o: func2.h func1.h
当然你得确保,如果在多个目标具备相同依赖时,make的 最终目标还是需要写在第一个。而上述例子中 func2.o func1.o的顺序则不会影响make。反之,如果加入了chapter_2 这个目标,而采用 func2.o chapter_2 : func2.h,则make的工作则会完全改变。
需要注意的是,上述func2.o func1.o main.o: func2.h func1.h完全等价于
func2.o : func2.h func1.h
func1.o : func2.h func1.h
main.o : func2.h func1.h
而我们可以继续修改func3.c 和makefile如下
$cat func3.c
#include <stdio.h>
#include "func1.h"
#include "func3.h"
void func_3(void){
printf("the func 3 called in the func3.c file !/n");
func_13();
}
================================
$cat makefile
chapter_2:main.o func3.o func2.o func1.o
gcc -o chapter_2 main.o func3.o func2.o func1.o
*.o:func1.h
func3.o:func3.c func3.h
gcc -c func3.c
func2.o func1.o main.o: func2.h
func2.o : func2.c
gcc -c func2.c
func1.o:func1.c
gcc -c func1.c
main.o:main.c func3.h
gcc -c main.c
clean:
rm -r *.o
rm chapter_2
===============================
执行如下命令
$make clean
$make
$./chapter_2
则打印如下
ok, this is a test for <Managine Project with GNU make>'s chapter 2 by luckystar!
the func 1 called in the func1.c file !
the func 21 called by func_1 !
the func 2 called in the func2.c file !
the func 12 called by func_2 !
the func 3 called in the func3.c file !
the func 13 called by func_3 !
查看makefile 此时存在
*.o:func1.h
这个规则,很显然,没有任何执行部分,只是描述一种依赖关系。而此处使用了通配符 *。至于有多少通配符make可以接受,此处就无法一一列举了。当然 *这个通配符并不是随时随地可以写的。有时会引发一些混乱。后面如有例子,可以再作讨论。
Phony target
伪目标
查看 makefile文件,存在以下段
clean:
rm -r *.o
rm chapter_2
当执行 make clean时,会自动调用起执行命令,两个删除操作。修改makefile如下
$cat makefile
chapter_2:main.o func3.o func2.o func1.o
gcc -o chapter_2 main.o func3.o func2.o func1.o
*.o:func1.h
func3.o:func3.c func3.h
gcc -c func3.c
func2.o func1.o main.o: func2.h
func2.o : func2.c
gcc -c func2.c
func1.o:func1.c
gcc -c func1.c
main.o:main.c func3.h
gcc -c main.c
hehe:
rm -r *.o
rm chapter_2
===============================
按如下顺序执行命令
$make
$make clean
$make hehe
无论make的提示符的内容,此时 make clean的提示如下
make: *** 没有规则可以创建目标“clean”。 停止。
而 make hehe 提示如下
rm -r *.o
rm chapter_2
很显然,make clean 或make hehe 中的 clean ,hehe 只是 makefile里的一个目标的名称。特别是针对 clean 并不是make 的一个参数。而大家为了一个相互的默契,把涉及到删除make生成文件的工作目标,设定为clean。在 chapter 2 page 15列出了,标准伪目标的几个内容,例如 all,install,clean,distchlean 等等。
即你可以把一段涉及删除make生成文件的工作目标定义为任意名称,如此处的hehe。但这样做有两个问题。
1、如果没有一些默契,别人使用此makefile,或使用别人的 makefile。则每次需要查看,一些通用的工作目标被定义成什么特殊及富有个性的名称
2、如果目录下存在hehe这个文件呢?
创建一个新文件在当前目录下,如下
$cat hehe
=======================
可以看出,hehe 只是个空文件,再次执行
$make
$make hehe
此时提示
make: “hehe”是最新的。
即,此时make 把 hehe 当作了一个目标文件。恢复makefile 如下
$cat makefile
chapter_2:main.o func3.o func2.o func1.o
gcc -o chapter_2 main.o func3.o func2.o func1.o
*.o:func1.h
func3.o:func3.c func3.h
gcc -c func3.c
func2.o func1.o main.o: func2.h
func2.o : func2.c
gcc -c func2.c
func1.o:func1.c
gcc -c func1.c
main.o:main.c func3.h
gcc -c main.c
clean:
rm -r *.o
rm chapter_2
===============================
再创立 clean 文件在当前目录下,该文件一样不需要存在任何内容,按如下方式执行
$make
$make clean
此时提示,和 hehe 一样。这里说明,通过make 加目标参数,来处理某个特定的操作,这个目标参数不能和当前目录下的文件同名,因为make 会优先考虑当前目录下的文件。
现在,我们可以保证当前目录下,不保留有clean。但以下两种情况会出现麻烦,虽然不一定是问题
1、工程大到,无法一一判断文件名和目标名的冲突,至少检测这个冲突,想当然的是件非常痛苦的事情
2、别人用此工程,如何保证他的环境下没有诸如 hehe ,clean的文件。
为了解决这个问题,make 存在 伪目标实现方式,修改makefile如下
$cat makefile
chapter_2:main.o func3.o func2.o func1.o
gcc -o chapter_2 main.o func3.o func2.o func1.o
*.o:func1.h
func3.o:func3.c func3.h
gcc -c func3.c
func2.o func1.o main.o: func2.h
func2.o : func2.c
gcc -c func2.c
func1.o:func1.c
gcc -c func1.c
main.o:main.c func3.h
gcc -c main.c
.PHONY: clean
clean:
rm -r *.o
rm chapter_2
===============================
再次运行
$make
$make clean
OK,此时,删除工作重新奏效。
.PHONY实际上告诉make此目标不是一个文件目标或目标文件(因此我在所有的笔记里,都把targets 翻译为目标而不是目标文件,因为这样更准确,当然方法很简单,我拉大了描述对象的集合)。make 本身的工作内容,如前面所说 ,是 目标,依赖,实现方式。但并不是说目标一定要是文件,可以是个描述实现方式的命令,而通过执行这个目标,实际是执行其对应的命令。那么 .PHONY这个规则,则可以确保make区分 target究竟是个文件,或纯粹的执行。为了再次强调,此处再次变态的做个以下改变
$cat makefile
chapter_2:main.o func3.o func2.o func1.o
gcc -o chapter_2 main.o func3.o func2.o func1.o
*.o:func1.h
func3.o:func3.c func3.h
gcc -c func3.c
func2.o func1.o main.o: func2.h
func2.o : func2.c
gcc -c func2.c
func1.o:func1.c
gcc -c func1.c
main.o:main.c func3.h
gcc -c main.c
.PHONY: clean.txt
clean.txt:
rm -r *.o
rm chapter_2
===============================
同时,创建一个文本文件叫做 clean.txt,执行如下命令
$make
$make clean.txt
此时你会发现,make 仍然执行到 clean.txt段,而没有理睬clean.txt文件。这就是 .PHONY,伪目标规则定义的有效性。
Empty targets
空目标
空目标。目前,暂时没有比较好的实例来描述这个empty targets。从empty targets所最常被使用的情况来看,如时间戳文件,似乎和empty targets的定义没有太多关联。就我个人理解。所谓空目标,和伪目标一样,我们并不关心目标对象的输出。而和伪目标不一样的是,其可存在输出。这其实引起了混乱。例如书上的例子,
prog: size prog.o
$(CC) $(LDFLAGS) -o $@ $^
size: prog.o
size $^
touch size
prog.o的文件更新,引发 size段的执行,size $^我们可以理解。打印出文件信息。但是使用touch size创建一个size文件,如果此叫作 空目标,如果文件本身我们并不关心,由何必创建它呢?当然可以按照书本的解释,我们需要一个空文件(毫无意义的文件)的创建时间记录当前整体make工作的时间。但这个时间隶属于文件的性质,难道就可以说这个文件没有意义吗?我养羊毫无意义,因为我是要羊毛而已,所以这是个空羊,呵呵,这个逻辑难道没有问题吗?
显然,可以肯定,此处,是我在抬杠,但抬杠的目的,是对 empty targets的无法理解。参考
http://www.linuxsir.org/main/doc/gnumake/GNUmake_v3.80-zh_CN_html/make-04.html#_gnu_make_4.8
下的一段文字(上述地址的内容,算是makefile 中文手册中比较权威的解释)
:空目标文件是伪目标的一个变种;此目标所在规则执行的目的和伪目标相同——通过make命令行指定将其作为终极目标来执行此规则所定义的命令。和伪目标不同的是:这个目标可以是一个存在的文件,但文件的具体内容我们并不关心,通常此文件是一个空文件。
空目标文件只是用来记录上一次执行此规则命令的时间。在这样的规则中,命令部分都会使用“touch”在完成所有命令之后来更新目标文件的时间戳,记录此规则命令的最后执行时间。make时通过命令行将此目标作为终极目标,当前目录下如果不存在这个文件,“touch”会在第一次执行时创建一个空的文件(命名为空目标文件名)。
=====================
以上为摘抄部分,可以看出,Empty targets (空目标)如果需要输出文件时,此和我们平时的目标文件没有区别。我个人的认为,唯一区别是目标文件内部的内容没有意义而已。之所以再次抬杠,只是想指出,与其拿empty targets于 phony targets (伪目标)作联系,进行解释或分类,倒不如将其归入传统目标文件中。其和传统目标最大的差异,并不是在文件是否用户关系,因为make 肯定不考虑这点,而在于其可以类似像phony(伪目标)那样,没有输出。就是没有任何新文件产生。
Variables
变量
变量可以说毫无用处,也可以说非常有用。毫无用处,是针对 make本生。对C了解的人,可以把 变量看作类似C里的一种宏,#define。当然只能说是类似,其并不完全等待。而make 在处理makefile文本时,会根据变量,进行展开。这里重复一下书本中对于变量的一些规则约束。
标记一个单词或称连续字符串是变量的方式是 前面 加上 $ ,而如果变量名称超过一个字符,则需要用 ()在$后包含住变量。当然需要注意,这和我写的笔记中,黑体的诸如$cat 中的$不是一个东西。后者只是表示说这是在终端提示符下的一个命令而已。
例如,假设存在变量
a
ab
abc
则可以用 $a $(a) $(ab) $(abc)来描述,但不能 用 $abc,此时$只能涵盖 a这个字符,而不能把bc作为变量符号的一部分。
修改makefile 如下
$cat makefile
a = main.o
ab = func3.o
abc = func2.o
chapter_2:$a $(ab) $(abc) func1.o
gcc -o ~/makefile_2/chapter_2 main.o func3.o func2.o func1.o
*.o:func1.h
$(ab):func3.c func3.h
gcc -c func3.c
$(abc) func1.o main.o: func2.h
$(abc) : func2.c
gcc -c func2.c
func1.o:func1.c
gcc -c func1.c
$a:main.c func3.h
gcc -c main.c
.PHONY: clean
clean:
rm -r *.o
rm chapter_2
===============================
执行
$make clean
$make
此时可以发现,一切正常,和往常一样正常。所以说 变量毫无用处。只是一个替代。此处我们再修改makefile如下
$cat makefile
a = main.o
f = func
ab = $f3.o
abc = $f2.o
chapter_2:$a $(ab) $(abc) $f1.o
gcc -o ~/makefile_2/chapter_2 main.o $f3.o $f2.o $f1.o
*.o:$f1.h
$(ab):$f3.c $f3.h
gcc -c $f3.c
$(abc) $f1.o main.o: $f2.h
$(abc) : $f2.c
gcc -c $f2.c
$f1.o:$f1.c
gcc -c $f1.c
$a:main.c $f3.h
gcc -c main.c
.PHONY: clean
clean:
rm -r *.o
rm chapter_2
=========================================
继续执行
$make clean
$make
$./chapter_2
一切正常,哦。此处makefile里找不到funcX.h funcX.c funcX.o的文字,此时变量的作用很明显,让makefile所包含的字符更少了。所有func 被 f替代,而引用时只需要$f。但显然,这并不是变量最有用的地方。
假设一个工程中,如 func1.c那样,在 makefile多处出现。而当func这个文件名前缀不再喜欢时,你将func1.c修改为 funny1.c。如果不采用变量的方式,你需要依次修改所有的地方。而现在只需要修改f = func 变为 f = funny。(当然这个用途并不是最终常用用途,但能说明出现变量的意义(仅谈make 和make file,此处的变量和编程语言中的变量没有联系))
Automatic Variables
我不想和大牛Robert Mecklenburg争论英文单词用语,但此处我仍然想把 Automatic翻译为“默认”或“隐式规则”或“内部”而不是如
http://www.linuxsir.org/main/doc/gnumake/GNUmake_v3.80-zh_CN_html/make-06.html#_gnu_make_6.2
中翻译的“自动化"
先列出一些内部变量(按我的翻译)或自动化变量(按字面直译,如很多其他文章所说的)如下
$@ 表示当前目标名称
$% 如果目标是个归档库 archive,则此表示这个库中的一个成员。比如 a.a 存在 b.o ,则 $%表示的是b.o,而不是a.a
$< 代表规则中,第一个依赖
$? 代表依赖中,所有比目标更新的依赖,其名称展开时用空格分割
$^ 代表所有依赖,但出现重复时,则之表示一次
$+ 代表所有依赖,如果出现重复,则全部表示
$* 表示目标名称中,去除了后缀的部分。例如 main.o 是目标的话,则此处之表示 main,而将 .o 去除。
看一下上面的翻译,或许有误,当然可以参考网路上绝大多数的翻译。没有办法说明,上述变量和自动化有任何联系。而上述变量的含义也是无法改变的。这和自动化有什么联系,呵呵,天晓得。你可以尝试挑战这个“自动化”,将比如 @ 等于某个你想描述的内容,你会发现,除了你改写 make 的源代码本身以外,似乎找不到其他方法可以实现。$@始终指向目标。因此,我仍然抬杠的认为 Automatic应该翻译为 "内部“表示你不可修改 ”默认“表示已经包含一定含义,简单的说也可以叫做“隐式规则”
回到正题,简单的通过上述 “内部默认”变量,来调整一下makefile
$cat makefile
chapter_2:main.o func2.o func3.o func1.o
gcc -o $@ $^
*.o:func1.h
func3.o:func3.c func3.h
gcc -c $<
func2.o func1.o main.o: func2.h
func2.o : func2.c
gcc -c $<
func1.o:func1.c
gcc -c $<
main.o:main.c func3.h
gcc -c $<
.PHONY: clean
clean:
rm -r *.o
rm chapter_2
===========================================
执行
$make clean
$make
一切正常,照旧。但你会发现,
chapter_2:main.o func2.o func3.o func1.o
gcc -o $@ $^
这样的书写,简洁,明了,修改方便。这就是变量,及"内部默认“变量的基本价值。
VPATH
路径变量
DOS年代,就有路径变量,当然更早的,更复杂的其他操作系统虽然没有用过,但我相信也有类似的路径变量。与其说是变量,不过说是个容器。
很多简单的编程或文件处理的例子都是针对当前目录下的。如上面个的gcc 所操作的内容。但复杂的工程,往往存在不同的目录保存了不同性质的文件。这需要告知系统,默认情况下,可能需要在非当前目录下查找文件。针对上述实验环境,执行以下命令
$mkdir inc
$mkdir src
$mv *.h inc/.
$mv *.c src/.
$make clean
此时当前目录下,应该只存在 makefile文件。当然可能会有一些上述例子所产生的残渣,比如clean,clean.txt,那么全部删除,只保留makefile文件。执行
$make
则提示如下
make: *** 没有规则可以创建“main.o”需要的目标“main.c”。 停止。
问题出在,makefile在当前目录下,main.c文件在 ./src目录下。此时由于makefile没有定义VPATH,则不会自动在 src目录下进行搜索。因此,makefile文件第一行增加如下内容
VPATH =src
再执行
$make
此时提示是
make: *** 没有规则可以创建“main.o”需要的目标“func3.h”。 停止。
很简单。make 首先分析到最总目标为chapter_2,而其第一个依赖是main.o,则转去执行main.o的规则。而main.o需要main.c和func3.h两个文件。很显然,此时无法找到func3.h。因为其在 inc子目录。那么可以如下修改 VPATH
VPATH = src inc
此处可以看到,VPATH,这个路径变量后面可以定义多个路径。因此其更象个容器,可以包含多个路径,而make根据这些路径依次查找文件。
此时执行
$make
提示为
gcc -c src/main.c
src/main.c:2:19: 错误: func1.h:没有该文件或目录
src/main.c:3:19: 错误: func2.h:没有该文件或目录
src/main.c:4:19: 错误: func3.h:没有该文件或目录
make: *** [main.o] 错误 1
呵呵。make 是没错了。但gcc 报错了。gcc找不到指定文件。此处虽然在谈论 VPATH但需要搞清楚,VPATH是make的,而不是 gcc的。也不是那些其他makefile 里面列出的执行命令的操作路径。换言之,VPATH只对make本身有用,对make本身,作依赖性检测时发挥作用,并将查找到的文件,包含路径传递给gcc。因此此处补充一个 新的默认变量 CPPFLAGS,此不是强制的,就是说,通过别的方式也可以替代,例如
CPPFLAGS = XXX,也可以用 OTHER_TEST = XXX来实现。但用CPPFLAGS 如同 make clean一样,符合大家的默契。
在makefile 第二段,新增一行
CPPFLAGS = -I inc
-I 是 GCC的参数。记得和make本身没有关系。gcc是 C的编译器,而make不是。重复,make只是,在编译,连接,安装工程时,就是说从源代码处理成可以用的软件时,你需要执行多个操作,为了保证多个操作的正确性,可以将多个操作,及其相互依赖关系,通过makefile来描述,并用make执行。而make本身完成依赖关系的检测,和批处理makefile里的各种执行命令。但这些执行命令本身和make 没有毛关系。自然,GCC和make 也是没有毛关系。你可以尝试如下gcc 命令
$gcc -c src/main.c inc/func1.h inc/func2.h inc/func3.h
一样出错。错误在哪?在于main.c里存在#include "funcx.h",而这需要通过gcc -I XXX来告知,make 里的路径只能提供上述诸如 src/main.c这样的信息。并不能和gcc的参数符合联动。
此时makefile 如下
$cat makefile
VPATH =src inc
#CPPFLAGS = -I inc
chapter_2:main.o func2.o func3.o func1.o
gcc -o $@ $(CPPFLAGS) $^
*.o:func1.h
func3.o:func3.c func3.h
gcc -c $(CPPFLAGS) $<
func2.o func1.o main.o: func2.h
func2.o : func2.c
gcc -c $(CPPFLAGS) $<
func1.o:func1.c
gcc -c $(CPPFLAGS) $<
main.o:main.c func3.h
gcc -c $(CPPFLAGS) $<
.PHONY: clean
clean:
rm -r *.o
rm chapter_2
=======================================
执行
$make
OK,此时一切正常。输出照旧。上述例子告诉我们几个信息,重复一边
1、可以通过VPATH,来指定一个目录。而某些文件存在于此
2、可以通过依次列出多个目录,通过空格或":"来分隔,来指定多个目录
3、VPATH所引用的路径目录,只对make有效,所以我们需要对CPPFLAGS这个默认规则的变量来进行针对 gcc这个命令的头文件路径进行引用。引用的方法是通过一个变量,这个变量名叫CPPFLAGS。而这个变量在 make里存在一个默契的约束,是用来提供给gcc的。
VPATH比较粗糙。所谓粗糙,就是不够精细。此时可以通过vpath,注意大小写来实现类似工作。照抄原文
The vpath directive is a more precise way to achieve our goals. The syntax of this directive is :
vpath pattern directory-list
也就是说,可以通过vpath来细分不同的路径。如下,
$cat makefile
vpath func%.c src
vpath main.c src
vpath %.h inc
CPPFLAGS = -I inc
chapter_2:main.o func2.o func3.o func1.o
gcc -o $@ $(CPPFLAGS) $^
*.o:func1.h
func3.o:func3.c func3.h
gcc -c $(CPPFLAGS) $<
func2.o func1.o main.o: func2.h
func2.o : func2.c
gcc -c $(CPPFLAGS) $<
func1.o:func1.c
gcc -c $(CPPFLAGS) $<
main.o:main.c func3.h
gcc -c $(CPPFLAGS) $<
.PHONY: clean
clean:
rm -r *.o
rm chapter_2
===============================
此处特意没有使用书上的例子,书上的描述如下
vpath %.c src
vpath %.h inc
,这样的描述存在一定歧异,在我第一次看到这样的描述时,我的直觉以为如下(是错的)
1、模式必须是一类文件
2、模式对应的路径不能重复定义。
3、%是对文件名的统称。
而实际上呢 ?
vpath的定义描述,翻译过来,因该是
vpath 依赖文件的描述(可以用通配符实现) 路径
依赖文件的描述,可以是一个独立的文件名(完整的),也可以是代通配符的一类文件,或者是一种文件类型的所有文件。
此处,仍然要强调的是,vpath只是针对make本身。用于make在解析makefile文件的工作中使用。和 makefile里的某个 目标的执行操作内部所需要的路径没有关系。如果学习,甚至设计过C编译器本身的人,当然附带处理过C的预处理工作的人,可以理解,make实际可以分两次扫描来完成。而第一次扫描,是展开makefile里的书写规则,第二次扫描开始执行规则中的执行命令(类似DOS下的BAT,批处理)。这让我想到个笑话,曾经有个软驱-COPY->软驱的软件,号称如果存在4M内存,则不需要硬盘。但是在那个年代,386的年代,有4M内存的机器没有硬盘,似乎很难想象。所以处理过C编译器的人,还需要学习makefile,恐怕也少有。此处也不在通过这个方式来类比了。
回来,继续做个我喜爱的事情,就是变态的事情。
在当前目录下,执行这样的命令
$mkdir src1
$cp src/func1.c src1/func1.c
编辑 src/func1.c 和src1/func1.c如下
$cat src1/func1.c
#include <stdio.h>
#include "func1.h"
#include "func2.h"
void func_1(void){
printf("the func 1 called in the /src1/func1.c file !/n");
func_21();
}
void func_12(void){
printf("the func 12 called by func_2 !/n");
}
void func_13(void){
printf("the func 13 called by func_3 !/n");
}
=================================
$cat src/func1.c
#include <stdio.h>
#include "func1.h"
#include "func2.h"
void func_1(void){
printf("the func 1 called in the /src/func1.c file !/n");
func_21();
}
void func_12(void){
printf("the func 12 called by func_2 !/n");
}
void func_13(void){
printf("the func 13 called by func_3 !/n");
}
=================================
继续执行
$make clean
$make
$./chapter_2
此时显示为
ok, this is a test for <Managine Project with GNU make>'s chapter 2 by luckystar!
the func 1 called in the /src/func1.c file !
the func 21 called by func_1 !
the func 2 called in the func2.c file !
the func 12 called by func_2 !
the func 3 called in the func3.c file !
the func 13 called by func_3 !
注意,此处调用的是 /src/func1.c内的函数。
那么我们修改makefile ,如下
$cat makefile
vpath func1.c src1
vpath func%.c src
vpath main.c src
vpath %.h inc
CPPFLAGS = -I inc
chapter_2:main.o func2.o func3.o func1.o
gcc -o $@ $(CPPFLAGS) $^
*.o:func1.h
func3.o:func3.c func3.h
gcc -c $(CPPFLAGS) $<
func2.o func1.o main.o: func2.h
func2.o : func2.c
gcc -c $(CPPFLAGS) $<
func1.o:func1.c
gcc -c $(CPPFLAGS) $<
main.o:main.c func3.h
gcc -c $(CPPFLAGS) $<
.PHONY: clean
clean:
rm -r *.o
rm chapter_2
==============================================
继续执行
$make clean
$make
$./chapter_2
显示如下
ok, this is a test for <Managine Project with GNU make>'s chapter 2 by luckystar!
the func 1 called in the /src1/func1.c file !
the func 21 called by func_1 !
the func 2 called in the func2.c file !
the func 12 called by func_2 !
the func 3 called in the func3.c file !
the func 13 called by func_3 !
呵呵,有点意思吧。可以尝试,将makefile中
vpath func1.c src1
vpath func%.c src
两行的顺序对调,看一下效果。由此我们可以确认一个 vpath的事实
1、vpath可以针对不同的依赖文件指定不同的路径,但当两个vpath出现子集冲突时,则以先描述的为准。也就是说,vpath在make识别中,是采用一个链表方式存放不同的vpath的路径规则,但扫描时,是从第一个规则开始扫描,发现符合后,立刻执行。
因此,先描述vpath func1.c src1则,make在识别到func1.c的依赖文件时,会采用此规则,而忽略后面的 vpath func%.c src的方式。这就解决了,不同路径下,文件重名问题。引出一个话题。
从工程管理的角度来看,只要不在当前目录下的文件。无论你认为肯定某个文件的名称,是一个别人想也想不到,你也不再会想到,而不会导致别的目录出现相同名称的文件名。那你也需要对个这文件路径进行vpath约束。这是个好习惯。特别是针对条件 make时。
到目前为止,我所举的变量的例子,除了Automatic 变量,基本上都是针对依赖文件的。当然不仅仅只能用于依赖文件。看一个描述
.c.o:
# 对隐含规则的搜索尚未完成。
# 从不检查修改时间。
# 文件尚未被更新。
# 要执行的命令 (内置):
$(COMPILE.c) $(OUTPUT_OPTION) $<
这个信息可以从
$make -p
中得到。
这里称述了一个隐形规则即 implicit rules。 其规则描述存在于 implicit rules database中。即,已经帮我们列举好了。这个规则可以看作如下描述
%.o:%.c
而 %.o 和 %.c就是变量。当然,用书本中的模式,来表示更确切些。现不看 .c.o,先修改一下makefile 如下
$cat makefile
vpath func1.c src1
vpath func%.c src
vpath main.c src
vpath %.h inc
CPPFLAGS = -I inc
%.o:%.c
gcc -c $(CPPFLAGS) $<
chapter_2:main.o func2.o func3.o func1.o
gcc -o $@ $(CPPFLAGS) $^
.PHONY: clean
clean:
rm -r *.o
rm chapter_2
=====================================
此处,执行
$make clean
$make
$./chapter_2
呵呵,一切照旧,依然是调用的src1里的func1.c。而且上述描述,比以前的描述简单了许多。上面的例子表达了两个信息
1、%.o:%.c是一个模式规则的描述,即我们可以在规则中的目标和依赖文件,都采用变量符,通配符来实现。来表示,一类目标文件和依赖文件的统一实现目标。
2、但这个规则中的目标是个虚目标,并不会成为make的最终目标,因此你放在最上方,也没有关系。所谓虚目标是指,只有在实际需要该规则时,才会根据已有信息,将该规则实际展开。例如,当chapter_2:main.o 时,需要寻找main.o 的规则,则此时才会将 %.o:%.c套用到 main.o中,此时为 main.o:main.c。并使用gcc -c $(CPPFLAGS) $<来实现。
但需要注意一个问题
1、%.o:%.c描述了扩展名(suffix name),并没有约束文件名(stem name),所以其实其就约束了,这个规则要求,目标和依赖的文件名必须相同。
为什么不尝试一下如下写法,我一向很变态
srcfile = %.c
%.o:srcfile
你会发现,变量在此还是适用的。甚至你可以把 %.o:srcfile段放在第一行,而 srcfile = %.c放在最后一行。你会发现,srcfile = %.c仍然在起作用,而其他vpath 也在其作用。如下
$cat makefile
%.o:srcfile
gcc -c $(CPPFLAGS) $<
vpath func1.c src1
vpath func%.c src
vpath main.c src
vpath %.h inc
CPPFLAGS = -I inc
chapter_2:main.o func2.o func3.o func1.o
gcc -o $@ $(CPPFLAGS) $^
.PHONY: clean
clean:
rm -r *.o
rm chapter_2
srcfile = %.c
============================================
原因很简单,就是我上面说的二次扫描的问题。而回到.c.o:上。
.c.o:在 chapter 2 page 24上明确说明,此等于 %.o:%.c。这就是后缀规则。而其实这个后缀规则,由于被默认包含了
$(COMPILE.c) $(OUTPUT_OPTION) $<
因此,即便你不需要显示的写出这个规则,在implicit rule database里,也存在,如果在当前目录下,makefile甚至可以只有chpater_2段的规则,而对于func1.o func2.o等等可以忽略。make会自动调用 inplicit rule隐式规则,来执行。
因此,完全可以删除掉 makefile 里
%.o:srcfile 段的内容。不过执行make 后的显示为
cc -I inc -c -o main.o src/main.c
cc -I inc -c -o func2.o src/func2.c
cc -I inc -c -o func3.o src/func3.c
cc -I inc -c -o func1.o src1/func1.c
gcc -o chapter_2 -I inc main.o func2.o func3.o func1.o
此处表示,隐式规则里,$(COMPILE.c)是用的 cc ,而不是gcc。当然 $(COMPILE.c)展开后包含很多东西。
这里再次体现了默认规则或默契的重要性。 在.c.o:的描述中,并没有 $(CPPFLAGS)出现。但实际上已经包括了。可以尝试删除掉 makefile里的 CPPFLAGS的定义以及使用,如下
$cat makefile
#%.o:srcfile
# gcc -c $(CPPFLAGS) $<
vpath func1.c src1
vpath func%.c src
vpath main.c src
vpath %.h inc
MYCPPFLAGS = -I inc
chapter_2:main.o func2.o func3.o func1.o
gcc -o $@ $(MYCPPFLAGS) $^
.PHONY: clean
clean:
rm -r *.o
rm chapter_2
#srcfile = %.c
============================================
执行
$make clean
$make
此时出错了。第一行信息就是
cc -c -o main.o src/main.c
这里缺少了 -I inc 这段文字。为什么呢?因为CPPFLAGS,作为一个默认的变量,其有默认的含义,是一种默契,和make的内部库有默契,也和其他使用你的 makefile的人有默契。
因此,现在你还不知道默认变量,默认操作方式不要轻易改变的重要性,我就无语了。类似我前面作的,不采用 make clean,而采用 make hehe来删除文件的操作,可以用来玩一玩。但不能用这样的脾性做事情。
关于规则,到这里,基本上讨论完。当然还存在一个 staic pattern rules,静态模式规则。如下
$(OBJECTS): %.o:%.c
这里描述的含义表示,其操作执行,只针对 OBJECTS变量有效。修改makefile 如下
$cat makefile
vpath func1.c src1
vpath func%.c src
vpath main.c src
vpath %.h inc
CPPFLAGS = -I inc
OBJECTS = main.o
chapter_2:main.o func2.o func3.o func1.o
gcc -o $@ $(CPPFLAGS) $^
$(OBJECTS): %.o:%.c
gcc -c $(CPPFLAGS) $<
.PHONY: clean
clean:
rm -r *.o
rm chapter_2
=============================
$make clean
$make
此时打印的内容如下
gcc -c -I inc src/main.c
cc -I inc -c -o func2.o src/func2.c
cc -I inc -c -o func3.o src/func3.c
cc -I inc -c -o func1.o src1/func1.c
gcc -o chapter_2 -I inc main.o func2.o func3.o func1.o
有点意思吧。第一行,使用了makefile里静态模式规则的定义,而funcX使用了隐式模式的定义。当然不代表你需要使用默认的OBJECTS这个变量。你用其他变量一样可以。
笔记二的后语,我尽量想用例子来说明第二章中的问题,而不是简单的翻译原文。其实参考网络的makefile的中文手册,以及本书的中文翻译版(听说翻译的很烂,我没有看到过因此不想评价,当然大概率事件,应该翻译的比我好,虽然听说其很烂)。原因很简单。包括编程,和其他一些涉及计算机方面的书籍,只是看看,或者简单的把英文翻译成中文,往往并不能理解其含义。就我个人对该书的理解,也是出于以下几个方面
1、文字的直译,通过我浅薄的英语。
2、大体猜测make源代码设计的思路
3、例子。
通过一些极端和变态的例子,基本上可以搞清楚,文字直译过来的描述的基本含义。当然一些变态的例子,是根据我对make 本身的实现方式的猜测。如果有一本介绍make本身设计的工程及源代码说明的书,如同linux内核的分析那样,我会很高兴看,当然要确保有足够的时间,但目前为止,我只能通过猜测其实现方法来理解文中的含义。
那么对于学习计算机方面的新手而言,我个人的建议就是,针对一些书上基本的内容,进行各种例子的实践和理解。这样才能抓住重点,和区分差异,比如C的a++,和 ++a只有在一些变态的例子中,才能体现差异。在后期大工程管理或性能优化等高等级工作中,才能拥有足够的能力,去处理面对的问题。这也是为什么我不上来讨论automake autoconf的原因。搞清楚 make本身,再去看 automake这样更简单,甚至可以自己写一套精简的,符合自己要求的,甚至有些变态但很实用的,自己的automake.
相关推荐
In the third edition of the classic Managing Projects with GNU make, readers will learn why this utility continues to hold its top position in project build software, despite many younger competitors...
Managing Projects with GNU make, 3rd Edition provides guidelines on meeting the needs of large, modern projects
Managing Projects with GNU Make
本书第三版的重点是gnu make,这个版本的make已经成为行业标准。本书将会探索gnu make所提供的强大扩充功能。gnu make之所以广受欢迎是因为它是一个自由软件,并且几乎可以在包括微软windows(作为cygwin项目的一部分...
本书第三版的重点是gnu make,这个版本的make已经成为行业标准。本书将会探索gnu make所提供的强大扩充功能。gnu make之所以广受欢迎是因为它是一个自由软件,并且几乎可以在包括微软windows(作为cygwin项目的一部分...
经典关于GNU Make的一本好书,对于掌握Makefile的规则有很大帮助.学习linux Unix必看的书!
请注意: ... ... 一定要先下载完,再评论。如果先评论后下载,或者在下载的过程中评论,积分同样不会返还。...*************************************************************** ...更多linux、ARM和C语言资源请参考: ...
Managing Projects with GNU make, 3rd Edition
关于如何正确编写Makefile最权威的教材,包含很多高级技巧,原版高清第三版
详细讲述GMU使用规则,并给出了应用实例
Managing Projects with GNU make 3rd Edition
Managing Projects with GNU MAKE(3rd Edition).chm
原版书籍 Managing Projects with GNU Make (3rd edition; Robert) enjoy
His make experience started in 1982 at NASA with Unix Version 7. Robert received his PhD in computer science from the University of Utah in 1991. Since then, he has worked in many fields ...
OReilly_Managing.Projects.with.GNU.Make.3Ed.2004.zip
经典的make提升书籍,使用make来管理你的工程,英文原版
GNU Make Manual 4.0 / GNU.Make.Book / Managing.Projects.with.GNU.Make 英文版,文字清晰、排版良好, 是学习make/makefile的最佳资源
Good introduction to manage projects using gnu make.