内建规则
到目前为止,我们已经在makefile文件中确切的指定了如何执行过程的每一步。事实上,makefile有大量的内建规则从而可以很大程度的简化makefile文件,特别是当我们有大量源文件的时候。下面我们创建foo.c,这是一个传统的Hello World程序。
#include <stdlib.h>
#include <stdio.h>
int main()
{
printf(“Hello World\n”);
exit(EXIT_SUCCESS);
}
不指定makefile文件,我们尝试使用make来编译。
$ make foo
cc foo.c -o foo
$
正如我们所看到的,make知道如何调用编译器,尽管在这种情况下,他选择cc而不是gcc(在Linux下这可以正常工作,因为通常cc链接到gcc)。有时,这些内建规则是推断规则(inference rules)。默认的规则使用宏,所以通过为这些宏指定一个新值,我们可以改变默认的行为。
$ rm foo
$ make CC=gcc CFLAGS=”-Wall -g” foo
gcc -Wall -g foo.c -o foo
$
我们可以使用-p选项使得make打印出其内建规则。内建规则太多而不能在这里全部列出,但是下面是GNU版本的make的make -p的简短输出,演示了其中的部分规则:
OUTPUT_OPTION = -o $@
COMPILE.c = $(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -c
%.o: %.c
# commands to execute (built-in):
$(COMPILE.c) $(OUTPUT_OPTION) $<
我们现在可以通过指定构建目标文件的规则使用这些内建规则来简化我们的makefile文件,所以makefile文件的相关部分简化为:
main.o: main.c a.h
2.o: 2.c a.h b.h
3.o: 3.c b.h c.h
后缀与模式规则
我们所看到的内建规则使用后缀进行工作(与Windows和MS-DOS的文件名扩展相类似),所以当指定一个带有扩展名的文件时,make知道应使用哪条规则来创建带有不同扩展名的文件。在这里最通常的规则就是由以.c为结尾的文件创建以.o为结尾的文件。这个规则就是使用编译器编译文件,但是并不链接源文件。
有时我们需要能够创建新规则。程序开发作者过去在一些源文件上需要使用不同的编译器进行编译:两个在MS-DOS下,以及Linux下的gcc。要满足MS-DOS编译器的要求,C++源文件而不是C源文件,需要以.cpp为后缀进行命名。不幸的是,现在Linux下使用的make版本并没有编译.cpp文件的内建规则。(他确实具有一个在Unix下更为常见的.cc的规则)
所以或者是为每一个单独的文件指定一个规则,或者是我们需要教给make一个新的规则来由以.cpp为扩展名的文件创建目标文件。假如我们在这个工程中有大量的源文件,指定一个新规则节省了大量的输入工作,并且使得在工程中添加一个新源文件更为容易。
要添加一个新的后缀规则,我们首先在makefile文件中添加一行,告诉make新的后缀;然后我们就可以使用这个新的后缀来编写一条规则。make使用下面的语法样式来定义一条通用的规则来由具有旧后缀的文件创建具有新后缀的文件:
.<old_suffix>.<new_suffix>:
下面是我们的makefile文件中一条新的通用规则的代码片段,用于将.cpp文件转换为.o文件:
.SUFFIXES: .cpp
.cpp.o:
$(CC) -xc++ $(CFLAGS) -I$(INCLUDE) -c $<
特殊依赖.cpp.o:告诉make接下来的规则用于将以.cpp为后缀的文件转换为以.o为后缀的文件。当我们编写这个依赖时,我们使用特殊的宏名,因为我们并不知道我们将要转换的实际文件名。要理解这条规则,我们只需要简单的回忆起$<会扩展为起始文件名(带有旧后缀)即可。注意,我们只是告诉make如何由.cpp文件得到.o文件;make已经知道如何由一个目标文件获得二进制可执行文件。
当我们调用make时,他使用我们的新规则由bar.cpp获得bar.o,然后使用其内建规则由.o获得一个可执行文件。-xc++标记用于告诉gcc这是一个C++源文件。
在近些时候,make知道如何处理带有.cpp扩展名的C++源文件,但是当将一种文件类型转换为另一种文件类型时,这个技术是十分有用的。
更为旧的make版本包含一个对应的语法用来达到同样的效果,而且更好。例如,匹配规则使用通配符语法来匹配文件,而不是仅依赖于文件扩展名。
对于上面例子中与.cpp规则等同的模式规则如下:
%.cpp: %o
$(CC) -xc++ $(CFLAGS) -I$(INCLUDE) -c $<
使用make管理库
当我们正处理一个大型工程时,使用库来管理多个编译产品通常是比较方便的。库是文件,通常以.a为扩展名,包含一个目标文件的集合。make命令有一个处理库的特殊语法,从而使得他们更易于管理。
这个语法就是lib (file.o),这就意味着目标文件file.o存储在库lib.a中。make具有一个内建的规则用于管理库,通常如下面的样子:
.c.a:
$(CC) -c $(CFLAGS) $<
$(AR) $(ARFLAGS) $@ $*.o
宏$(AR)与$(ARFLAGS)通常分别默认为命令ar与选项rv。这个简短的语法告诉make由一个.c文件得到.a库,他必须执行两条规则:
第一条规则是他必须编译源文件并且生成一个目标文件
第二条规则是使用ar命令来修改库,添加新的目标文件
所以,如果我们有一个库fud,包含文件bas.o,在第一条规则中$<被替换为bas.c。在第二条规则中,$@被替换为库fud.a,而$*被替换为bas。
试验--管理库
实际上,管理库的规则的使用是相当简单的。下面我们修改我们的程序,从而文件2.o与3.o保存在一个名为mylib.a的库中。我们的makefile文件需要一些小的修改,所以Makefile5如下所示:
all: myapp
# Which compiler
CC = gcc
# Where to install
INSTDIR = /usr/local/bin
# Where are include files kept
INCLUDE = .
# Options for development
CFLAGS = -g -Wall -ansi
# Options for release
# CFLAGS = -O -Wall -ansi
# Local Libraries
MYLIB = mylib.a
myapp: main.o $(MYLIB)
$(CC) -o myapp main.o $(MYLIB)
$(MYLIB): $(MYLIB)(2.o) $(MYLIB)(3.o)
main.o: main.c a.h
2.o: 2.c a.h b.h
3.o: 3.c b.h c.h
clean:
-rm main.o 2.o 3.o $(MYLIB)
install: myapp
@if [ -d $(INSTDIR) ]; \
then \
cp myapp $(INSTDIR);\
chmod a+x $(INSTDIR)/myapp;\
chmod og-w $(INSTDIR)/myapp;\
echo “Installed in $(INSTDIR)”;\
else \
echo “Sorry, $(INSTDIR) does not exist”;\
fi
在这里需要注意我们是如何使用默认规则来完成大多数工作的。现在让我们来测试我们的新版本makefile文件。
$ rm -f myapp *.o mylib.a
$ make -f Makefile5
gcc -g -Wall -ansi -c -o main.o main.c
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 Makefile5
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
main.o: main.c a.h
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来处理一些文件,或是生成手册页。我们可以自动化任何文件处理,只要是make由文件的日期与时间信息的修改可以处理的。
分享到:
相关推荐
在IT行业中,快速开发工具是提高开发效率的关键。本文将深入探讨基于C#语言,针对SQL Server数据库的三层架构(Model-BLL-DAL)生成工具,以及如何利用这些工具进行高效开发。 首先,"C#"是一种面向对象的编程语言...
一个优秀的C++开发工具不仅要有强大的编辑和调试功能,还要有良好的社区支持和丰富的第三方库。Visual Studio 2008作为一款成熟的开发工具,满足了这些需求,使得开发者能够专注于代码逻辑,而非工具的复杂性。然而...
《Epulis开发工具类详解》 在软件开发领域,工具类是程序员的得力助手,它们提供了一系列常用功能,使得代码编写更为高效便捷。Epulis开发工具类就是这样一个旨在优化开发流程、提升代码复用性的重要工具集。本文将...
【U8快速客户化开发工具】是针对U8平台的一款专用开发工具,旨在简化和加速U8系统的定制化进程。这款工具特别设计了高效且简洁的单据开发功能,确保开发的自定义单据能保持与U8标准单据相同的样式。通过数据库脚本的...
本源码项目是基于Java的全能第三方支付对接开发工具包设计,包含323个文件,主要使用Java、JavaScript和HTML编程语言。该工具包提供了优雅的轻量级支付模块,可以集成微信、支付宝、银联、友店、富友等多种支付方式...
在Windows环境中,Perl同样可以作为开发工具来使用,为程序员提供高效、灵活的编程环境。以下是对Perl开发工具及其在Windows环境中的应用进行的详细介绍。 首先,Perl 5.8.7是Perl的一个版本,发布于2005年,它包含...
《MCGS脚本驱动开发工具详解》 MCGS(Monitor and Control for General System)是一款广泛应用于工业自动化领域的监控组态软件。它的脚本驱动开发工具为用户提供了强大的自定义功能,使得用户可以根据实际需求编写...
在IT行业中,编程开发工具是程序员日常工作中不可或缺的利器,它们极大地提高了开发效率,简化了复杂的编程任务。本文将深入探讨编程开发工具的各种类型、功能以及如何选择合适的工具。 一、集成开发环境(IDE) ...
由于提供的文件内容不包含具体的人工智能开发工具产品的三大核心竞争力的详细信息,而只是标题、描述和部分内容中带有链接的片段,因此,我将基于标题提供的线索,即“人工智能开发工具产品三大核心竞争力”,展开...
### 三种必需的嵌入式开发工具 在嵌入式系统开发过程中,选择合适的开发工具对于提高工作效率、确保代码质量至关重要。本文将介绍三种在嵌入式开发领域中不可或缺的工具,并简要分析它们的特点与优势。 #### 一、...
### 三层CS信息系统开发工具OpenTools及应用 #### 引言 随着全球经济向知识经济转型以及市场竞争日益加剧,企业对信息系统的需求不断攀升,这不仅体现在系统规模和复杂度上,更在于系统必须能够快速准确地反映各类...
解压缩后请将文件夹下的三个网页文件拷贝到安装目录的Tools\Data-Library之下。 若为默认安装,目录路径为:C:\Program Files\Skyline\TerraExplorer Pro\Tools\Data-Library
《软件开发工具》是高等教育自学考试中的一门重要课程,主要涵盖了软件开发过程中使用的各种工具和技术。本复习资料包括了历年真题、课件、实践和理论知识等多个方面,旨在帮助考生全面掌握该课程的核心内容。 一、...
Eclipse,作为一款知名的Java开发工具,是全球开发者广泛使用的集成开发环境(IDE)。它的核心是一个基于Java的可扩展开发平台,提供了丰富的功能,支持多种编程语言,包括但不限于Java、Python、C++、JavaScript等...
"Delphi中文版开发工具"是一个特别针对中文用户设计的版本,提供了全中文界面和文档,便于中国开发者理解和使用。 Delphi的核心特性包括: 1. **VCL框架**:Visual Component Library(VCL)是Delphi的核心组件库...
在IT行业中,应用软件开发是一项核心任务,而有效的开发工具是提升效率和质量的关键。本教程专注于教授如何利用专业的开发工具来创建商业级的应用软件。下面将详细探讨这个主题,帮助你深入理解并掌握相关知识。 一...
简易C语言开发工具是一款简单易用的C语言集成开发环境(IDE),适合于编写符合ANSI C 标准的C程序,用户可以方便的编写、调试、运行C程序。简易C语言开发工具具有以下特点: 一、以解释的方式执行C程序的,可以很...