第三方开发工具,iii电动工具
内置规则
到目前为止,我们已经详细说明了如何在makefile文件中执行流程的每一步。其实makefile有大量的内置规则,可以大大简化makefile文件,尤其是当我们有大量源文件的时候。让我们创建foo.c,这是一个传统的Hello World程序。
#包含stdlib.h
#包含stdio.h
int main()
{
printf(" Hello World/n ");
退出(EXIT _ SUCCESS);
}
我们尝试用make编译它,而不是指定makefile文件。
$ make foo
cc福. c -o福
$
我们可以看到,make知道如何调用编译器,虽然在这种情况下,他选择了cc而不是gcc(这在Linux下可以正常工作,因为通常cc是链接到gcc的)。有时候,这些内置规则就是推理规则。的默认规则使用宏,因此我们可以通过为这些宏指定新值来更改默认行为。
$ rm foo
$ make CC=gcc CFLAGS="-Wall -g" foo
gcc -Wall -g foo
$
我们可以使用-p选项让make print输出它的内置规则。内置规则太多,无法在此一一列举,但这里是make的GNU版本make -p的简短输出,演示了其中的一些:
OUTPUT_OPTION=-o $@
compile . c=$(CC)$(CFLAGS)$(CPPFLAGS)$(TARGET _ ARCH)-c
%.o: %。c
要执行的命令数量(内置):
$(COMPILE.c) $(OUTPUT_OPTION) $
现在,我们可以通过指定构建目标文件的规则,使用这些内置规则来简化makefile,因此makefile的相关部分简化如下:
主界面:主界面
2 . o:2 . c a h b h
3.o: 3.c b.h c.h
后缀和模式规则
我们看到的内置规则是和后缀(类似于Windows和MS-DOS中的文件扩展名)一起工作的,所以当指定一个文件的扩展名时,make知道应该使用哪个规则来创建不同扩展名的文件。这里最常见的规则是创建以结尾的文件。o来自以结尾的文件。这个规则是用编译器编译文件,但是不链接源文件。
有时我们需要能够创造新的规则。以前有些源文件是用不同的编译器编译的:MS-DOS下两个,Linux下gcc。为了满足MS-DOS编译器的要求,C源文件,而不是C源文件,需要用。cpp后缀。不幸的是,Linux下使用的make版本现在没有内置的编译规则。cpp文件。(他确实有一个在Unix下更常见的. cc规则)
因此,要么为每个单独的文件指定一个规则,要么我们需要教创建一个新的规则,用。cpp分机。如果我们在这个项目中有大量的源文件,那么指定一个新的规则可以节省大量的输入工作,并且可以更容易地在项目中添加一个新的源文件。
要添加一个新的后缀规则,我们首先在makefile中添加一行来告诉make新的后缀;然后我们可以用这个新的后缀来写一个规则。使用以下语法样式定义一般规则,从带有旧后缀的文件创建带有新后缀的文件:旧_后缀。新后缀:
下面是makefile中一个新的通用规则的代码片段,它用于转换。cpp文件到。o文件:后缀:卡片打印处理机(Card Print Processor的缩写)
. cpp.o:
$(CC)-xc $(CFLAGS)-I $(INCLUDE)-c $。
对cpp.o的特殊依赖:告诉make下一个规则用于转换带有。带cpp后缀的文件。o后缀。当我们编写这个依赖项时,我们使用一个特殊的宏名,因为我们不知道我们要转换的实际文件名。为了理解这个规则,我们只需要回忆一下$将被扩展到起始文件名(带有旧的后缀)。注意,我们只是告诉make如何获取?o文件从。cpp文件;Make已经知道如何从目标文件中获取二进制可执行文件。
当我们调用make时,他用我们的新规则从bar.cpp中获取bar.o,然后用它的内置规则从. o中获取一个可执行文件,-xc标志用来告诉gcc这是一个C源文件。
最近,make知道了如何用。cpp扩展名,但是这种技术在将一种文件类型转换为另一种文件类型时非常有用。
旧版的make包含了相应的语法来达到同样的效果,而且更好。例如,匹配规则使用通配符语法来匹配文件,而不是仅依赖文件扩展名。
模式规则等同于。上述示例中的cpp规则如下:
%.cpp: %o
$(CC)-xc $(CFLAGS)-I $(INCLUDE)-c $。
使用创建管理库
当我们在处理一个大型项目时,使用库来管理多个编译的产品通常很方便。是一个库文件,通常扩展名为。一个,并包含一个对象文件集合。make命令有一个处理库的特殊语法,这使得它们更容易管理。
此语法为lib (file.o),这意味着目标文件file.o存储在库lib中。Make有一个用于管理库的内置规则,通常如下:
c.a:
$(CC) -c $(CFLAGS) $
$(AR) $(ARFLAGS) $@ $*。o
宏$(AR)和$(ARFLAGS)通常分别默认为命令AR和选项rv。这个简短的语法告诉make获取。一个来自. c文件的库,他必须执行两条规则:
第一条规则是他必须编译源文件并生成目标文件。
第二个规则是使用ar命令修改库并添加新的目标文件。
因此,如果我们有一个包含文件bas.o的库fud,那么在第一个规则中,$将被替换为bas.c。在第二个规则中,$ @被替换为库fud.a,而$ *被替换为bas。
测试管理库
事实上,使用管理库的规则非常简单。让我们修改我们的程序,使文件2.o和3.o保存在名为mylib.a的库中。我们的makefile文件需要一些小的修改,因此Makefile5如下所示:
所有人:myapp
#哪个编译器
CC=gcc
#安装位置
INSTDIR=/usr/local/bin
#包含文件保存在哪里
包含=。
#发展选项
CFLAGS=-g -Wall -ansi
#发布选项
# CFLAGS=-O -Wall -ansi
#本地图书馆
MYLIB=mylib.a
myapp: main.o $(MYLIB)
$(CC) -o myapp main.o $(MYLIB)
$(MYLIB):$(MYLIB)(2 . o)$(MYLIB)(3 . o)
主界面:主界面
2 . o:2 . c a h b h
3.o: 3.c b.h c.h
清洁:
-rm main.o 2.o 3.o $(百万英镑)
安装:myapp
@ if[-d $(INSTDIR)];/
然后/
CP myapp $(inst dir);/
chmod a x $(inst dir)/myapp;/
chmod og-w $(inst dir)/myapp;/
echo“安装在$(INSTDIR)”中;/
else /
echo“对不起,$(INSTDIR)不存在”;/
船方不负担装货费用
在这里,我们需要注意我们如何使用默认规则来完成大部分工作。现在让我们测试makefile文件的新版本。
多年期应用程序*。o mylib.a
$ make -f生成文件5
gcc -g -Wall -ansi -c -o main
gcc -g -Wall -ansi -c -o 2.o 2.c
ar rv mylib.a 2.o
a - 2.o
gcc -g -Wall -ansi -c -o 3.o 3.c
ar rv mylib.a 3.o
a - 3.o
gcc -o myapp main.o mylib.a
$ touch c.h
$ make -f生成文件5
gcc -g -Wall -ansi -c -o 3.o 3.c
ar rv mylib.a 3.o
r - 3.o
gcc -o myapp main.o mylib.a
$
操作原理
首先,我们删除所有的目标文件和库,并允许make构建myapp。他在使用库链接main.o之前通过编译和创建库来构建myapp,然后我们测试3.o的测试规则,他会通知make如果C.H .发生变化,那么3.C .必须重新编译。他会正确的完成这些任务,编译3.c并在重新链接之前更新库,这样就创建了一个新的可执行文件myapp。
高级主题:Makefile和子目标
如果我们写一个大项目,有时候把组成库的文件从主文件中分离出来,存放在一个子目录中是非常方便的。Make可以通过两种方式来完成这项任务。
首先,我们可以在这个子目录中有第二个makefile文件来编译该文件,将其存储在一个库中,然后将该库复制到上一级主目录中。高级目录中的主makefile文件有一个构建这个库的规则。调用第二个makefile文件的语法如下:
mylib.a:
(cd mylibdirectory$(MAKE))
也就是说我们要一直尝试构建mylib.a当make调用这个规则来构建库的时候,他会进入子目录mylibdirectory,然后调用一个新的make命令来管理库。因为这会调用一个新的shell,所以使用makefile的程序不会执行cd命令。但是,用于执行规则构建库的被调用shell位于不同的目录中。圆括号可以保证它们都在单独的shell中处理。
第二种方法是在单独的makefile中使用一些额外的宏。这些额外的宏是通过在我们已经讨论过的宏上加上D(代表目录)或F(代表文件)生成的。然后我们可以覆盖内置的。c.o前缀规则具有以下规则:
c.o:
$(CC)$(CFLAGS)-c $(@ D)/$(F)-o $(@ D)/$(@ F)
编译子目录中的文件,并将目标文件留在子目录中。然后,我们可以使用以下依赖项和规则更新当前目录中的库:
mylib.a: mydir/2.o mydir/3.o
ar -rv mylib.a $?
我们需要决定在我们自己的项目中更喜欢哪种方法。很多项目只是简单的避免有子目录,但是这会导致源目录下有大量的文件。正如我们在前面的概述中看到的,我们在子目录中使用make只是增加了复杂性。
GNU make和gcc
如果我们使用GNU make和GNU gcc编译器,还有另外两个有趣的选项:
第一个是make的-jN(jobs )选项。这将使用make同时执行n个命令。此时,make可以同时调用多个规则来独立编译项目的不同部分。根据我们的系统配置,重新编译时这是一个很大的改进。如果我们有多个源文件,尝试这个选项是有价值的。一般来说,一个小数字,比如-j3,是一个很好的起点。如果我们与其他用户共享我们的机器,请小心使用此选项。其他用户可能不喜欢我们每次编译时都启动大量的进程。
另一个有用的选项是gcc的-MM选项。这将产生一个适合make的依赖项列表。在有大量源文件的项目中,每个文件都包含不同的头文件组合。正确获取依赖关系非常困难,但是非常重要。如果我们使用的每个源文件都依赖于每个头文件,有时我们会编译不必要的文件。另一方面,如果我们忽略一些依赖,问题会更严重,因为我们不会编译那些需要重新编译的文件。
测试-gcc-mm
下面我们使用gcc的-MM选项为我们的示例项目生成一个依赖列表:
$ gcc -MM main.c 2.c 3.c
主界面:主界面
2 . o:2 . c a h b h
3.o: 3.c b.h c.h
$
操作原理
Gcc编译只是以适合插入makefile的形式输出所需的依赖行。我们需要做的就是将输出保存到一个临时文件中,然后将它插入makefile文件中,以获得一组完美的依赖规则。如果我们有gcc的输出副本,我们的依赖就没有理由出错。
如果我们对makefile有信心,我们可以尝试使用makedepend工具,它执行与-MM选项类似的功能,但实际上会将依赖项添加到指定makefile的末尾。
在我们离开makefile这个话题之前,可能值得指出的是,我们不能只局限于使用makefile来编译代码或创建库。我们可以用它们来自动化任何任务。例如,有一个序列命令允许我们从一些输入文件中获取一个输出文件。通常“非编译器”用户可能适合调用awk或sed来处理一些文件或生成手册页。我们可以自动处理任何文件,只要通过修改文件的日期和时间信息。