make命令以及makefile
使用RCS与CVS进行源代码控制
编写手册页
使用patch与tar发布软件
开发环境
多源代码的问题
当咱们编写小程序时,许多人都是简单的在编辑后经过从新编译全部的文件从新构建咱们的程序。然而,对于大程序,这种简单构建方法的问题就变得明显了。编辑-编译-测试的循环时间将会变长。即便是最为耐心的程序员也会但愿避免当只修改一个文件时而编译全部的文件。
当建立多个头文件而且在不一样的源文件中包含多个头文件时就会出现一个更为困难的问题。假设咱们有三个头文件a.h,b.h以及c.h,和C源文件main.c,2.c以及3.c。而咱们会具备以下的包含关系:
/*
main.c */
#include “a.h”
...
/* 2.c */
#include “a.h”
#include
“b.h”
...
/* 3.c */
#include “b.h”
#include
“c.h”
...
若是程序员修改了c.h,文件main.c以及2.c并不须要从新编译,由于他们并不依赖于这个头文件。文件3.c依赖于c.h,因此若是c.h被修改了,那么他就应被从新编译。然而,若是b.h被修改了,而程序员忘记从新编译2.c,那么所获得的程序也许就不会正常工做。
make程序经过确保修改所影响的全部文件在须要时进行从新编译来解决全部这些问题。
make命令与Makefile
然而,正如咱们所看到的,make命令有许多内建的知识,他自己并不知道如何构建咱们的程序。咱们必须提供一个文件来告诉make咱们的程序是如何组织的。这个文件就是makefile。
makefile文件一般与工程的源文件位于同一个目录中。咱们能够同时在咱们机子上有多个不一样的makefile文件。确实,若是咱们有一个很是大的工程,咱们也许会选择对于工程的不一样部分使用单独的makefile文件进行管理。
make命令以及makefile文件的组合提供了一个强大的工具来进行工程的管理。他一般不只用于控制源代码的编译,并且也用于准备手册页以及将程序安装到目标目录。
makefile文件的语法
一个makefile文件由一组依赖与规则组成。一个依赖具备一个目标(将要建立的文件)以及他所依赖的源文件集合。规则描述了由依赖文件如何建立目标文件。一般,目标文件是一个可执行文件。
makefile
文件由make命令来执行,从而决定目标文件或是要建立的文件,而后比较源文件的日期与时间用于决定调用哪条规则来构建目标。一般,在构建最终的目标以前必须建立一些中间目标。make命令使用makefile文件来肯定目标构建的正确顺序以及规则调用的正确顺序。
make选项与参数
make程序自己有多个选项。最经常使用的三个为:
-k,这个选项会通知make命令,当发现一个错误时会继续执行,而不是在检测到第一个问题时当即退出。例如,咱们可使用这个选项检测出哪一个文件编译失败。
-n,这个选项会使得make命令打印出此命令将会作的事情而不会实际执行。
-f
<filename>,这个选项会通知make命令使用哪一个文件做为makefile文件。若是咱们没有使用这个选项,make首先在会当前目录查找名为makefile的文件。若是这个文件不存在,他就会查找Makefile。按约定,Linux程序使用Makefile。这会使用
makefile文件在一个尽是小写字母名字的目录中第一个出现。
要告诉make来构建一个特定目录,一般是一个可执行文件,咱们能够将目标名字做为参数传递给make命令。若是没有这样作,make就会试着构建makefile文件中的第一个目标。许多程序在其makefile文件中将
all指定为第一个目标,而后列出其余的all所依赖的目标。这个约定能够很清晰的指出当没有指定目标时makefile应尝试构建的默认目标。咱们建议遵照这个约定。
依赖
依赖指出最终程序中的每个文件如何与源文件相关。在咱们本章开始时所显示的例子中,咱们指出咱们最终的程序依赖于main.o,2.o以及3.o;相似的
main.o依赖于main.c与a.h等。因此main.会受到main.c和a.h修改的影响,若是这两个文件中的任何一个发生变化,都须要经过从新编译main.c来从新建立main.o。
在makefile文件中,咱们编写规则的格式以下:目标名,冒号,空格或是tab,而后是以空格或是tab分隔的用于建立目标文件的列表。咱们例子中的依赖列表以下:
myapp:
main.o 2.o 3.o
main.o: main.c a.h
2.o: 2.c a.h b.h
3.o: 3.c b.h
c.h
这代表myapp依赖于main.o,2.o以及3.o,而main.o依赖于main.c以及a.h,等等。
依赖集合指出了一个层次显示了源文件之间如何彼此相关。咱们能够很容易的看出,若是b.h发生变化,那么咱们须要从新修正2.o以及3.o,而2.o与3.o发生了变化,咱们也须要从新构建myapp。
若是咱们但愿编构建多个文件,那么咱们可使用伪目标all。假设咱们的程序由二进制文件myapp与手册页myapp.1构成。咱们能够用下面的语句来指定目标:
all:
myapp
myapp.1
再一次声明,若是咱们没有指定all目标,make只是简单的建立他在makefile中查找到的第一个目标。
规则
makefile
文件中的第二部分指定了用于描述如何建立一个目标的规则。在咱们前面部分的例子中,在make命令以后应该使用哪一个命令来从新构建2.o呢?看起来也许只是简单的使用gcc
-c
2.c就足够了(并且,正如咱们在后面将会看到的,make确实知道许多默认的规则),可是若是咱们须要指定一个include目录,或是设置用于之后调试的符号信息选项时应怎么办呢?咱们能够经过在makefile文件中显示的指定规则来作到。
注:在这里咱们须要提示makefile文件一个很是奇怪的语法:一个空格与一个tab之间有区别。全部规则所在的行必须以一个tab开始;而不能以一个空格开始。由于多个空格与一个tab看起来很类似,并且由于在绝大多数的其余的Linux程序中在空格与tab之间并无区别,因此若是不加区分就会出现问题。同时,makefile文件中一行结束处的空格也会使得make命令失败。然而,这是一个历史问题,并且已经有许多的makefile文件在尝试改进这个问题,因此咱们要当心。幸运的是,当
make命令不能工做时,一般一个很明显的问题就是丢失了tab。
试验--一个简单的Makefile
大多数规则由本能够在命令行输入的简单命令组成。对于咱们的例子而言,咱们能够建立咱们的第一个makefile文件,Makefile1:
myapp:
main.o 2.o 3.o
gcc -o myapp main.o 2.o 3.o
main.o: main.c a.h
gcc
-c main.c
2.o: 2.c a.h b.h
gcc -c 2.c
3.o: 3.c b.h c.h
gcc -c
3.c
咱们须要使用-f选项来调用咱们的make命令,由于咱们的makefile文件并非一般默认的makefile或是Makefile。若是咱们在一个并不包含任何源码的目录中调用这个代码,咱们就会获得下面的信息:
$
make -f Makefile1
make: *** No rule to make target ‘main.c’, needed by
‘main.o’.
$
make
命令认为makefile文件中的第一个目录myapp是咱们但愿建立的文件。而后他会查找其余的依赖,而且会肯定须要一个名为main.c的文件。由于咱们尚未建立这个文件,而makefile也并不知道如何建立这个文件,因此make命令就会报告错误。下面咱们建立这些源文件而且再次尝试。由于咱们对于结果并不感兴趣,因此这些文件能够很是简单。头文件实际上为空,因此咱们使用touch命令来建立这些文件。
$
touch a.h
$ touch b.h
$ touch c.h
main.c
包含main函数,而且会调用function_two与function_three。其余的两个文件定义了function_two与
function_three。源文件包含#include行来指定头文件,因此他们会依赖所包含的头文件的内容。这并非一个程序,可是咱们在这里列出相应的部分:
/*
main.c */
#include <stdlib.h>
#include “a.h”
extern void
function_two();
extern void function_three();
int main()
{
function_two();
function_three();
exit (EXIT_SUCCESS);
}
/*
2.c */
#include “a.h”
#include “b.h”
void function_two() {
}
/*
3.c */
#include “b.h”
#include “c.h”
void function_three()
{
}
下面咱们再试一次:
$ make
gcc -c
gcc -c
gcc -c
gcc
-o
$
这就是一次成功的make。
工做原理
make
命令处理makefile文件中的依赖部分,而且肯定须要建立的文件以及建立的顺序。尽管咱们首先列出了如何建立myapp,make能够肯定建立文件的正确顺序。而后他会调用咱们在这些规则中所指定的建立这些文件的命令。make命令会在执行相应的命令时显示这些命令。如今咱们能够测试咱们的
makefile文件来肯定他是否正确的处理了b.h的修改。
$ touch b.h
$ make -f Makefile1
gcc
-c 2.c
gcc -c 3.c
gcc -o myapp main.o 2.o
3.o
$
make命令已经读入咱们的makefile文件,肯定从新构建myapp所须要的最小数量的命令,而且以正确的顺序执行这些命令。下面咱们来看一下若是咱们删除一个目标文件时会发生什么:
$
rm 2.o
$ make -f Makefile1
gcc -c 2.c
gcc -o myapp main.o 2.o
3.o
$
再一次,make正确的肯定了所须要的动做。
Makefile中的注释
makefile文件中的注释以#开始,而且直到本行结束。正如在C源文件中同样,makefile文件中的注释有助于做者也其余人更好的理解文件编写时所指望的做用。
Makefile中的宏
即便有make与makefile是管理多个源码文件工程的强大工具。然而,对于由大量的文件所组成的工程来讲,他们仍显得庞大和不灵活。因此Makefile容许咱们使用宏,从而咱们能够将其写为更为通用的形式。
咱们在makefile文件中以MACRONAME=value的形式定义一个宏,而后以$(MACRONAME)或是${MACRONAME}的形式来访问MACRONAME的值。一些版本的make也能够接受$MACRONAME的形式。咱们也能够经过将=以后的部分留空来设置一个空的宏。
在makefile文件中,宏一般用于编译的选项。一般,当正在开发一个程序时,咱们一般并不会使用优化选项来进行编译,可是却须要包含调试信息。而对于发布一个程序一般是相反的状况:发布一个运行速度尽可能快而不带任何调试信息的二进制程序。
Makefile1
的另外一个问题是他假定编译器为gcc。在其余的Unix系统上,咱们也许会使用cc或是c89。若是咱们但愿将咱们的makefile运行在其余版本的
Unix系统上,或是若是咱们在咱们的系统上使用一个不一样的编译器,咱们就须要修改makefile的多行来使其工做。宏是用于收集系统相关部分的好方法,从而能够很容易的进行修改。
宏一般是在makefile文件自己内部定义的,可是也能够经过带有宏定义的make命令来指定,例如
make
CC=c89。相似这样的命令行定义能够覆盖makefile文件中的定义。当在makefile文件外部使用时,宏定义必须做为一个单独的参数进行传递,因此要避免空格或是使用引号的形式:make
"CC =
c89"。
试验--带有宏的Makefile
下面是咱们的makefile的一个修正版本,Makefile2,其中使用一些宏:
all:
myapp
# Which compiler
CC = gcc
# Where are include files
kept
INCLUDE = .
# Options for development
CFLAGS = -g -Wall -ansi
#
Options for release
# CFLAGS = -O -Wall -ansi
myapp: main.o 2.o 3.o
$(CC) -o myapp main.o 2.o 3.o
main.o: main.c a.h
$(CC) -I$(INCLUDE)
$(CFLAGS) -c main.c
2.o: 2.c a.h b.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c
2.c
3.o: 3.c b.h c.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c
3.c
若是咱们删除咱们原有的安装,而使用这个新的makefile来建立一个新的安装,咱们会获得下面的信息:
$ rm *.o
myapp
$ make -f Makefile2
gcc -I. -g -Wall -ansi -c main.c
gcc -I. -g
-Wall -ansi -c 2.c
gcc -I. -g -Wall -ansi -c 3.c
gcc -o myapp main.o 2.o
3.o
$
工做原理
make程序使用合适的定义来替换$(CC),$(CFLAGS)以及$(INCLUDE),与带有#define形式的C编译器相似。如今如是咱们须要修改编译命令,咱们只须要修改makefile文件中的一行。
事实上,make有多个内部宏,从而咱们能够更为简洁的来使用。在下表中,咱们列出其中最经常使用的一些;咱们会在后的例子中看到他们的使用。这些宏中的每个仅会在他们刚要被使用时进行扩展,因此宏的语义会随着makefile文件的处理而变化。事实上,若是这些不以这样的方进行工做,他们就不会太大的做用。
$?
距离当前目标最近一次修改的需求列表
$@ 当前目标的名字
$< 当前需求的名字
$*
不带前缀的当前需求的名字
还有另外两个有用的特殊字符,也许咱们会在makefile文件中的命令前遇到:
-选择make忽略全部错误。例如,若是咱们但愿建立一个目录,可是但愿忽略错误,也许是由于这个目录已经存在了,咱们只须要在mkdir命令以前带有一个负号。咱们将会在后面的章节中看到-的使用。
@告诉make在执行命令以前不要将命令输出到标准输出。若是咱们但愿使用echo来显示一些指令时,这个字符就特别有用。
多个目标
一般须要构建多个目标文件,而不是一个目标文件,或者是在一个地方收集多个命令组。咱们能够扩展咱们的makefile文件来完成这些任务。下面咱们添加一个"clean"选项来移除不须要的目标文件,以及一个"install"选项来将完成的程序移动到另外一个不一样的目录中。
试验--多个目标
下面是下一个版本的makefile,Makefile3。
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
myapp: main.o 2.o 3.o
$(CC) -o myapp main.o 2.o
3.o
main.o: main.c a.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c main.c
2.o:
2.c a.h b.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c 2.c
3.o: 3.c b.h c.h
$(CC) -I$(INCLUDE) $(CFLAGS) -c 3.c
clean:
-rm main.o 2.o
3.o
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中须要注意多个地方。首先,特殊目标all仍然只是指定myapp做为目标。因此,当咱们执行make命令而没有指定一个目标时,默认的行为就是构建目标myapp。
接下来须要特别注意的另外两个目标,clean与install。clean目标使用rm命令来移除目标文件。这个命令以-开头,这会使得make命令忽略命令的结果,因此make
clean总会成功,即便并无目标文件而rm命令返回一个错误。目标"clean"并无为clean指定任何所依赖的条件;clean以后的行是空的。因此这个目标老是被认为是最新的,而且若是clean被指定为一个目标,那么其规则老是被执行。
install目标依赖于
myapp,因此make会知道在执行其余命令运行install以前必须先建立myapp。install的规则由一些shell脚本命令所组成。由于
make会为执行规则而调用shell,而且第一条规则使用一个新的shell,因此咱们必须添加反斜线,这样全部的脚本命令就全在一个逻辑行,而且会所有传递给一个单独的shell调用。这个命令以@开头,这会通知make命令在执行规则以前不要在标准输出上打印出命令。
install目标顺序执行命令将程序安装在其最终位置上。他在执行下一条命令以前并不会检测前一条命令是否执行成功。若是只有前一条命令成功执行以后才能够执行后续的命令,那么咱们必须使用&&符号将其联合,以下所示:
@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” ; false ; \
fi
也许咱们会回想起第2章的内容,这就是一个shell
"and"命令,并且其效果是只有前一条命令执行成功以后才会执行后续的命令。在这里咱们并不会关心是否保证前一条命令执行成功,因此咱们只是使用这种较为简单的形式。
也许咱们做为一个普通用户并不具备在/usr/local/bin目录下安装程序的权限。咱们能够修改makefile文件从而使用另外一个不一样的安装目录,或是改变这个目录的权限,或者是在执行make
install以前切换到root用户。
$ rm *.o myapp
$ make -f Makefile3
gcc -I. -g
-Wall -ansi -c main.c
gcc -I. -g -Wall -ansi -c 2.c
gcc -I. -g -Wall -ansi
-c 3.c
gcc -o myapp main.o 2.o 3.o
$ make -f Makefile3
make: Nothing to
be done for ‘all’.
$ rm myapp
$ make -f Makefile3 install
gcc -o myapp
main.o 2.o 3.o
Installed in /usr/local/bin
$ make -f Makefile3 clean
rm
main.o 2.o
3.o
$
工做原理
首先,咱们删除myapp与全部的目标文件。make命令会使用目标all,从而构建myapp。接下来咱们再次运行make玲,由于myapp已是最新的了,因此make不会作任何事情。而后咱们删除myapp而且运行make
install。这会从新构建这个二进制文件,而且将其拷贝到安装目录。最后咱们运行make
clean,这会删除目标文件。
内建规则
到目前为止,咱们已经在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由文件的日期与时间信息的修改能够处理的。
发布软件
程序发布的主要问题就是要保证包含全部的文件以及确切的版本。幸运的是,网络程序社区已经发展出一个健壮的方法集合能够不少的解决这个问题。这些方法包括:
使用在全部的Unix机器上都可用的标准工具将全部的组件文件打包进入一个包文件中
控制软件包的版本号
包文件采用包含版本号的命名约定从而用户能够很容易分辨出他们正在处理的版本
包中子目录的使用能够保证当文件由包文件中解压出来时,他们就会位于一个单独的目录中,这样就不会弄混哪些包含在包文件中而哪些没有
这些方法的发展就意味着程序能够很容易而且可靠的发布。程序安装的简便性是另外一个问题,由于他会依赖于程序与所要安装的系统,可是至少咱们能够保证咱们拥有合适的组件文件。
patch程序
当程序发布之后,几乎是不可避免的出现用户发现bug或是程序的做者但愿执行程序增长或是更新的状况。看成者将程序做为二进制文件发布时,他们一般只是简单的传递新的二进制文件。有时(更为常常),提供者只是简单的放出程序的一个新版本,一般是晦涩引用以及关于程序修改内容的少许信息。
另外一个方面,将咱们的软件做为一个源码包进行发布是一个好主意,由于这能够容许用户知道咱们是如何实现的以及如何使用这些特性。也能够容许用户检测程序实际所作的工做而且能够重用部分源代码。
然而,例如Linux内核这样几十兆压缩源代码的重量级程序,传输内核源码的更新集合将会占用大量的资源,而事实上,在每一个版本之间只有不多一部分的源代码进行了修改。
幸运的,有一个实用程序能够解决这个问题:patch。他是由Larry
Wall编写的,他同时也编写了Perl程序语言。patch命令可使得咱们只发布两个版本之间相区别的部分,这样任何具备版本1文件以及一个由版本1
到版本2的区别文件的人就可使用patch命令本身生成版本2文件。
若是咱们由版本1文件开始,
This is file
one
line 2
line 3
there is no line 4, this is line 5
line
6
而后咱们建立版本2文件,
This is file two
line 2
line 3
line
4
line 5
line 6
a new line 8
咱们可使用diff命令来建立一个区别列表:
$
diff file1.c file2.c > diffs
diffs文件包含以下内容:
1c1
< This is
file one
—
> This is file two
4c4,5
< there is no line 4, this
is line 5
—
> line 4
> line 5
5a7
> a new line
8
这其实是将一个文件修改成另外一个文件的编辑器命令集合。假设咱们有文件file1.c以及diffs文件,咱们可使用patch命令来更新咱们的文件:
$
patch file1.c diffs
Hmm... Looks like a normal diff to me...
Patching file
file1.c using Plan A...
Hunk #1 succeeded at 1.
Hunk #2 succeeded at
4.
Hunk #3 succeeded at
7.
done
$
如今patch命令就已经将文件file1.c修改成与file2.c相同的ywyr
patch还有一个小技巧:去补丁的能力。假如咱们不喜欢这些更改而且退回咱们原始的file1.c文件。没有问题,只须要再次使用patch命令,使用-R选项便可。
$
patch -R file1.c diffs
Hmm... Looks like a normal diff to me...
Patching
file file1.c using Plan A...
Hunk #1 succeeded at 1.
Hunk #2 succeeded at
4.
Hunk #3 succeeded at
6.
done
$
file1.c已经回退到其原始状态了。
patch还有许多其余的选项,可是一般很善于由其输入来肯定咱们正在尝试作什么,而后简单的完成这些事情。若是patch命令失败,他就会建立一个以.rej为扩展名包含不能进行补丁操做的部分。
当咱们正在处理软件补丁时,使用diff
-c选项是一个好主意,这会生成一个"context
diff"。这会在每个修改以后与以后提供几行文本,从而patch在应用补丁以前能够验证内容匹配,而补丁文件也更容易阅读。
其余的发布程序
Linux
程序与源码一般以包含版本号的名字,而且以.tar.gz或是.tgz的扩展名进行发布。这些是使用gzip压缩的TAR文件,也就是所熟知的
tarball。若是咱们使用一般的tar,咱们必须经过两步来处理这些文件。下面咱们来为咱们的程序建立一个gzip文件。
$ tar cvf
myapp-1.0.tar main.c 2.c 3.c *.h myapp.1
Makefile5
main.c
2.c
3.c
a.h
b.h
c.h
myapp.1
Makefile5
$
如今咱们有一个TAR文件。
$
ls -l *.tar
-rw-r--r-- 1 neil
$
咱们可使用gzip压缩程序使其变得更小:
$
gzip myapp-1.0.tar
$ ls -l *.gz
-rw-r--r-- 1
neil
$
正如咱们所看到的,其结果在尺寸上很是小。.tar.gz而后能够简单的重命名为.tgz扩展名:
$ mv
myapp-1.0.tar.gz
myapp_v1.tgz
以逗点和三个字符进行重命名的习惯彷佛是对Windows软件的让步,由于与Linux和Unix不一样,Windows很依赖正在处理的正确的扩展名。要获得咱们的文件,咱们能够解压而且由tar文件中释放这些文件:
$
mv myapp_v1.tgz myapp-1.0.tar.gz
$ gzip -d myapp-1.0.tar.gz
$ tar xvf
myapp-1.0.tar
main.c
2.c
3.c
a.h
b.h
c.h
myapp.1
Makefile5
$
使用GNU版本的tar,事情会变得更为简单--咱们能够一步建立压缩归档:
$
tar zcvf myapp_v1.tgz main.c 2.c 3.c *.h myapp.1
Makefile5
main.c
2.c
3.c
a.h
b.h
c.h
myapp.1
Makefile5
$
咱们也能够进行简单的解压:
$
tar zxvf
myapp_v1.tgz
main.c
2.c
3.c
a.h
b.h
c.h
myapp.1
Makefile5
$
若是咱们但愿知道归档的内容而不实际的进行解压,咱们可使用另外一个不一样的tar
ztvf选项来调用tar程序。
咱们在前面使用tar做为例子,而并无描述除必须的选项之外的其余选项。下面咱们简单的看一下tar命令以及一些经常使用的选项。正如由例子中所看到的,基本语法为:
tar
[options] [list of
files]
列表中的第一个项目为目标,尽管咱们只是处理文件,他能够处理设备。列表中的其余项目要添加到一个新的或是已存在的归档中,这依赖于咱们所使用的选项。这个列表也能够包含目录,在这种状况下,全部子目录默认状况下都会包含在文件中。若是咱们释放文件,没有必要指定名字,由于tar会保存彻底路径。
在这一部分,咱们使用了六个不一样选项的组合:
c:建立一个新的归档
f:指定目标是一个文件而不是一个设备
t:列出一个归档中的内容而不实际释放
v:tar显示出处理消息
x:由归档中释放文件
z:在GNU
tar中使用gzip过滤归档
tar命令还有更多的选项容许咱们对于命令的操做以及他所建立的文档进行更好的控制。查看tar手册页能够获得更为详细的信息。
c++