在这一章,我们将会讨论在Linux系统上可用的程序开发工具,其中一些在Unix系统上也可用。除了必须的编译器与调试器以外,Linux提供了一个工具集,其中的每一个工具都完成单独的工作,并且允许开发者以新的方式对这些工具进行组合。这就是Linux所继承的Unix哲学的一部分。在这里,我们将会讨论其中的一些重要工具,并且会讨论使用这些工具解决问题的例子,包括:
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正确的确定了所需要的动作。
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正确的确定了所需要的动作。