目錄

編譯程式


In this chapter, we will look at how to build programs by compiling source code. The availability of source code is the essential freedom that makes Linux possible. The entire ecosystem of Linux development relies on free exchange between developers. For many desktop users, compiling is a lost art. It used to be quite common, but today, distribution providers maintain huge repositories of precompiled binaries, ready to download and use. At the time of this writing, the Debian repository (one of the largest of any of the distributions) contains almost 23,000 packages.

在這一章中,我們將看一下如何透過編譯原始碼來建立程式。原始碼的可用性是至關重要的自由,從而使得 Linux 成為可能。 整個 Linux 開發生態圈就是依賴於開發者之間的自由交流。對於許多桌面使用者來說,編譯是一種失傳的藝術。以前很常見, 但現在,由系統發行版提供商維護巨大的預編譯的二進位制儲存庫,準備供使用者下載和使用。在寫這篇文章的時候, Debian 儲存庫(最大的發行版之一)包含了幾乎23,000個預編譯的包。

So why compile software? There are two reasons:

那麼為什麼要編譯軟體呢? 有兩個原因:

  1. Availability. Despite the number of precompiled programs in distribution repositories, some distributions may not include all the desired applications. In this case, the only way to get the desired program is to compile it from source.

  2. Timeliness. While some distributions specialize in cutting edge versions of programs, many do not. This means that in order to have the very latest version of a program, compiling is necessary.

  1. 可用性。儘管系統發行版儲存庫中已經包含了大量的預編譯程式,但是一些發行版本不可能包含所有期望的應用。 在這種情況下,得到所期望程式的唯一方式是編譯程式原始碼。

  2. 及時性。雖然一些系統發行版專門打包前沿版本的應用程式,但是很多不是。這意味著, 為了擁有一個最新版本的程式,編譯是必需的。

Compiling software from source code can become very complex and technical; well beyond the reach of many users. However, many compiling tasks are quite easy and involve only a few steps. It all depends on the package. We will look at a very simple case in order to provide an overview of the process and as a starting point for those who wish to undertake further study.

從原始碼編譯軟體可以變得非常複雜且具有技術性;許多使用者難以企及。然而,許多編譯任務是 相當簡單的,只涉及到幾個步驟。這都取決於程式包。我們將看一個非常簡單的案例, 為的是給大家提供一個對編譯過程的整體認識,併為那些願意進一步學習的人們構築一個起點。

We will introduce one new command:

我們將介紹一個新命令:

什麼是編譯?

Simply put, compiling is the process of translating source code (the human-readable description of a program written by a programmer) into the native language of the computer’s processor.

簡而言之,編譯就是把原始碼(一個由程式設計師編寫的人類可讀的程式的說明)翻譯成計算機處理器的語言的過程。

The computer’s processor (or CPU) works at a very elemental level, executing programs in what is called machine language. This is a numeric code that describes very small operations, such as “add this byte,” “point to this location in memory,” or “copy this byte.”

計算機處理器(或 CPU)工作在一個非常基本的水平,執行用機器語言編寫的程式。這是一種數值編碼,描述非常小的操作, 比如“加這個位元組”、“指向記憶體中的這個位置”或者“複製這個位元組”。

Each of these instructions is expressed in binary (ones and zeros). The earliest computer programs were written using this numeric code, which may explain why programmers who wrote it were said to smoke a lot, drink gallons of coffee, and wear thick glasses.This problem was overcome by the advent of assembly language, which replaced the numeric codes with (slightly) easier to use character mnemonics such as CPY (for copy) and MOV (for move). Programs written in assembly language are processed into machine language by a program called an assembler. Assembly language is still used today for certain specialized programming tasks, such as device drivers and embedded systems.

這些指令中的每一條都是用二進位制表示的(1和0)。最早的計算機程式就是用這種數值編碼寫成的,這可能就 解釋了為什麼編寫它們的程式設計師據說吸很多煙,喝大量咖啡,並帶著厚厚的眼鏡。隨著組合語言的出現,這個問題得到克服。 組合語言使用諸如CPY(複製)和 MOV(移動)之類別(略微)易用的字元助記符代替了數值編碼 。用匯編語言編寫的程式透過 彙編器處理為機器語言。今天為了完成某些特定的程式任務,組合語言仍在被使用,例如裝置驅動和嵌入式系統。

We next come to what are called high-level programming languages. They are called this because they allow the programmer to be less concerned with the details of what the processor is doing and more with solving the problem at hand. The early ones (developed during the 1950s) included FORTRAN (designed for scientific and technical tasks) and COBOL (designed for business applications). Both are still in limited use today.

下一步我們談論一下什麼是所謂的高階程式語言。之所以這樣稱呼它們,是因為它們可以讓程式設計師少操心處理器的 一舉一動,而更多關心如何解決手頭的問題。早期的高階語言(二十世紀50年代期間研發的)包括 FORTRAN(為科學和技術任務而設計)和 COBOL(為商業應用而設計)。今天這兩種語言仍在有限的使用。

While there are many popular programming languages, two predominate. Most programs written for modern systems are written in either C or C++. In the examples to follow, we will be compiling a C program.

雖然有許多流行的程式語言,兩個占主導地位。大多數為現代系統編寫的程式,要麼用 C 編寫,要麼是用 C++ 編寫。 在隨後的例子中,我們將編寫一個 C 程式。

Programs written in high-level programming languages are converted into machine language by processing them with another program, called a compiler. Some compilers translate high-level instructions into assembly language and then use an assembler to perform the final stage of translation into machine language.

用高階語言編寫的程式,經過另一個稱為編譯器的程式的處理,會轉換成機器語言。一些編譯器把 高階指令翻譯成組合語言,然後使用一個彙編器完成翻譯成機器語言的最後階段。

A process often used in conjunction with compiling is called linking. There are many common tasks performed by programs. Take, for instance, opening a file. Many programs perform this task, but it would be wasteful to have each program implement its own routine to open files. It makes more sense to have a single piece of programming that knows how to open files and to allow all programs that need it to share it. Providing support for common tasks is accomplished by what are called libraries. They contain multiple routines, each performing some common task that multiple programs can share. If we look in the /lib and /usr/lib directories, we can see where many of them live. A program called a linker is used to form the connections between the output of the compiler and the libraries that the compiled program requires. The final result of this process is the executable program file, ready for use.

一個稱為連結的過程經常與編譯結合在一起。有許多常見的由程式執行的任務。以開啟檔案為例。許多程式執行這個任務, 但是讓每個程式實現它自己的開啟檔案功能,是很浪費資源的。更有意義的是,擁有單獨的一段知道如何開啟檔案的程式, 並允許所有需要它的程式共享它。對常見任務提供支援由所謂的函式庫完成。這些函式庫包含多個程式,每個程式執行 一些可以由多個程式共享的常見任務。如果我們看一下 /lib 和 /usr/lib 目錄,我們可以看到許多函式庫定居在那裡。 一個叫做連結器的程式用來在編譯器的輸出結果和要編譯的程式所需的函式庫之間建立連線。這個過程的最終結果是 一個可執行程式檔案,準備使用。

所有的程式都是可編譯的嗎?

No. As we have seen, there are programs such as shell scripts that do not require compiling. They are executed directly. These are written in what are known as scripting or interpreted languages. These languages have grown in popularity in recent years and include Perl, Python, PHP, Ruby, and many others.

不是。正如我們所看到的,有些程式比如 shell 指令碼就不需要編譯。它們直接執行。 這些程式是用所謂的指令碼或解釋型語言編寫的。近年來,這些語言變得越來越流行,包括 Perl、 Python、PHP、Ruby和許多其它語言。

Scripted languages are executed by a special program called an interpreter. An interpreter inputs the program file and reads and executes each instruction contained within it. Ingeneral, interpreted programs execute much more slowly than compiled programs. This is because that each source code instruction in an interpreted program is translated every time it is carried out, whereas with a compiled program, a source code instruction is only translated once, and this translation is permanently recorded in the final executable file.

指令碼語言由一個叫做直譯器的特殊程式執行。一個直譯器輸入程式檔案,讀取並執行程式中包含的每一條指令。 通常來說,解釋型程式執行起來要比編譯程式慢很多。這是因為每次解釋型程式執行時,程式中每一條原始碼指令都需要翻譯, 而一個已經編譯好的程式,一條原始碼指令只翻譯了一次,翻譯後的指令會永久地記錄到最終的執行檔案中。

So why are interpreted languages so popular? For many programming chores, the results are “fast enough,” but the real advantage is that it is generally faster and easier to develop interpreted programs than compiled programs. Programs are usually developed in a repeating cycle of code, compile, test. As a program grows in size, the compilation phase of the cycle can become quite long. Interpreted languages remove the compilation step and thus speed up program development.

那麼為什麼解釋型程式這樣流行呢?對於許多程式設計任務來說,原因是“足夠快”,但是真正的優勢是一般來說開發解釋型程式 要比編譯程式快速且容易。通常程式開發需要經歷一個不斷重複的寫碼、編譯和測試周期。隨著程式變得越來越大, 編譯階段會變得相當耗時。解釋型語言刪除了編譯步驟,這樣就加快了程式開發。

編譯一個 C 語言

Let's compile something. Before we do that however, we’re going to need some tools like the compiler, the linker, and make. The C compiler used almost universally in the Linux environment is called gcc (GNU C Compiler), originally written by Richard Stallman. Most distributions do not install gcc by default. We can check to see if the compiler is present like this:

讓我們編譯一些東西。在我們編譯之前,然而我們需要一些工具,像編譯器、連結器以及 make。 在 Linux 環境中,普遍使用的 C 編譯器叫做 gcc(GNU C 編譯器),最初由 Richard Stallman 寫出來的。 大多數 Linux 系統發行版預設不安裝 gcc。我們可以這樣檢視該編譯器是否存在:

[me@linuxbox ~]$ which gcc
/usr/bin/gcc

The results in this example indicate that the compiler is installed.

在這個例子中的輸出結果表明安裝了 gcc 編譯器。


Tip: Your distribution may have a meta-package (a collection of packages) for soft- ware development. If so, consider installing it if you intend to compile programs on your system. If your system does not provide a meta-package, try installing the gcc and make packages. On many distributions, this is sufficient to carry out the exercise below.

小提示: 你的系統發行版可能有一個用於軟體開發的 meta-package(軟體包的集合)。如果是這樣的話, 若你打算在你的系統中編譯程式就考慮安裝它。若你的系統沒有提供一個 meta-package,試著安裝 gcc 和 make 工具包。 在許多發行版中,這就足夠完成下面的練習了。 —

得到原始碼

For our compiling exercise, we are going to compile a program from the GNU Project called diction. This is a handy little program that checks text files for writing quality and style. As programs go, it is fairly small and easy to build.

為了我們的編譯練習,我們將編譯一個叫做 diction 的程式,來自 GNU 專案。這是一個小巧方便的程式, 檢查文字檔案的書寫品質和樣式。就程式而言,它相當小,且容易建立。

Following convention, we’re first going to create a directory for our source code named src and then download the source code into it using ftp:

遵照慣例,首先我們要建立一個名為 src 的目錄來存放我們的原始碼,然後使用 ftp 協議把原始碼下載下來。

[me@linuxbox ~]$ mkdir src
[me@linuxbox ~]$ cd src
[me@linuxbox src]$ ftp ftp.gnu.org
Connected to ftp.gnu.org.
220 GNU FTP server ready.
Name (ftp.gnu.org:me): anonymous
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> cd gnu/diction
250 Directory successfully changed.
ftp> ls
200 PORT command successful. Consider using PASV.
150 Here comes the directory listing.
-rw-r--r-- 1 1003 65534 68940 Aug 28 1998 diction-0.7.tar.gz
-rw-r--r-- 1 1003 65534 90957 Mar 04 2002 diction-1.02.tar.gz
-rw-r--r-- 1 1003 65534 141062 Sep 17 2007 diction-1.11.tar.gz
226 Directory send OK.
ftp> get diction-1.11.tar.gz
local: diction-1.11.tar.gz remote: diction-1.11.tar.gz
200 PORT command successful. Consider using PASV.
150 Opening BINARY mode data connection for diction-1.11.tar.gz
(141062 bytes).
226 File send OK.
141062 bytes received in 0.16 secs (847.4 kB/s)
ftp> bye
221 Goodbye.
[me@linuxbox src]$ ls
diction-1.11.tar.gz

Note: Since we are the “maintainer” of this source code while we compile it, we will keep it in ~/src. Source code installed by your distribution will be installed in /usr/src, while source code intended for use by multiple users is usually installed in /usr/local/src.

注意:因為我們是這個原始碼的“維護者”,當我們編譯它的時候,我們把它儲存在 ~/src 目錄下。 由你的系統發行版原始碼會把原始碼安裝在 /usr/src 目錄下,而供多個使用者使用的原始碼,通常安裝在 /usr/local/src 目錄下。


As we can see, source code is usually supplied in the form of a compressed tar file. Sometimes called a tarball, this file contains the source tree, or hierarchy of directories and files that comprise the source code. After arriving at the ftp site, we examine the list of tar files available and select the newest version for download. Using the get command within ftp, we copy the file from the ftp server to the local machine.

正如我們所看到的,通常提供的原始碼形式是一個壓縮的 tar 檔案。有時候稱為 tarball,這個檔案包含原始碼樹, 或者是組成原始碼的目錄和檔案的層次結構。當到達 ftp 站點之後,我們檢查可用的 tar 檔案列表,然後選擇最新版本,下載。 使用 ftp 中的 get 命令,我們把檔案從 ftp 伺服器複製到本地機器。

Once the tar file is downloaded, it must be unpacked. This is done with the tar program:

一旦 tar 檔案下載下來之後,必須解包。透過 tar 程式可以完成:

[me@linuxbox src]$ tar xzf diction-1.11.tar.gz
[me@linuxbox src]$ ls
diction-1.11
diction-1.11.tar.gz

Tip: The diction program, like all GNU Project software, follows certain stan- dards for source code packaging. Most other source code available in the Linux ecosystem also follows this standard. One element of the standard is that when the source code tar file is unpacked, a directory will be created which contains the source tree, and that this directory will be named project-x.xx, thus containing both the project’s name and its version number. This scheme allows easy installation of multiple versions of the same program. However, it is often a good idea to examine the layout of the tree before unpacking it. Some projects will not create the directory, but instead will deliver the files directly into the current directory. This will make a mess in your otherwise well-organized src directory. To avoid this, use the following command to examine the contents of the tar file:

小提示:該 diction 程式,像所有的 GNU 專案軟體,遵循著一定的原始碼打包標準。其它大多數在 Linux 生態系統中 可用的原始碼也遵循這個標準。該標準的一個條目是,當原始碼 tar 檔案開啟的時候,會建立一個目錄,該目錄包含了原始碼樹, 並且這個目錄將會命名為 project-x.xx,其包含了專案名稱和它的版本號兩項內容。這種方案能在系統中方便安裝同一程式的多個版本。 然而,通常在開啟 tarball 之前檢驗原始碼樹的佈局是個不錯的主意。一些專案不會建立該目錄,反而,會把檔案直接傳遞給當前目錄。 這會把你的(除非組織良好的)src 目錄弄得一片狼藉。為了避免這個,使用下面的命令,檢查 tar 檔案的內容:

tar tzvf tarfile | head ---

檢查原始碼樹

Unpacking the tar file results in the creation of a new directory, named diction-1.11. This directory contains the source tree. Let's look inside:

開啟該 tar 檔案,會建立一個新的目錄,名為 diction-1.11。這個目錄包含了原始碼樹。讓我們看一下里面的內容:

[me@linuxbox src]$ cd diction-1.11
[me@linuxbox diction-1.11]$ ls
config.guess     diction.c          getopt.c      nl
config.h.in      diction.pot        getopt.h      nl.po
config.sub       diction.spec       getopt_int.h  README
configure        diction.spec.in    INSTALL       sentence.c
configure.in     diction.texi.in    install-sh    sentence.h
COPYING en       Makefile.in        style.1.in
de               en_GB              misc.c        style.c
de.po            en_GB.po           misc.h        test
diction.1.in     getopt1.c          NEWS

In it, we see a number of files. Programs belonging to the GNU Project, as well as many others, will supply the documentation files README, INSTALL, NEWS, and COPYING.

在原始碼樹中,我們看到大量的檔案。屬於 GNU 專案的程式,還有其它許多程式都會,提供文件檔案 README,INSTALL,NEWS,和 COPYING。

These files contain the description of the program, information on how to build and in- stall it, and its licensing terms. It is always a good idea to carefully read the README and INSTALL files before attempting to build the program.

這些檔案包含了程式描述,如何建立和安裝它的資訊,還有其它許可條款。在試圖建立程式之前,仔細閱讀 README 和 INSTALL 檔案,總是一個不錯的主意。

The other interesting files in this directory are the ones ending with .c and .h:

在這個目錄中,其它有趣的檔案是那些以 .c 和 .h 為字尾的檔案:

[me@linuxbox diction-1.11]$ ls *.c
diction.c getopt1.c getopt.c misc.c sentence.c style.c
[me@linuxbox diction-1.11]$ ls *.h
getopt.h getopt_int.h misc.h sentence.h

The .c files contain the two C programs supplied by the package (style and diction), divided into modules. It is common practice for large programs to be broken into smaller, easier to manage pieces. The source code files are ordinary text and can be examined with less:

這些 .c 檔案包含了由該軟體包提供的兩個 C 程式(style 和 diction),被分割成模組。這是一種常見做法,把大型程式 分解成更小,更容易管理的程式碼塊。原始碼檔案都是普通文字,可以用 less 命令檢視:

[me@linuxbox diction-1.11]$ less diction.c

The .h files are known as header files. These, too, are ordinary text. Header files contain descriptions of the routines included in a source code file or library. In order for the com- piler to connect the modules, it must receive a description of all the modules needed to complete the entire program. Near the beginning of the diction.c file, we see this line:

這些 .h 檔案被稱為標頭檔案。它們也是普通檔案。標頭檔案包含了程式的描述,這些程式被包括在原始碼檔案或函式庫中。 為了讓編譯器連結到模組,編譯器必須接受所需的所有模組的描述,來完成整個程式。在 diction.c 檔案的開頭附近, 我們看到這行程式碼:

#include "getopt.h"

This instructs the compiler to read the file getopt.h as it reads the source code in diction.c in order to “know” what’s in getopt.c. The getopt.c file supplies routines that are shared by both the style and diction programs.

當它讀取 diction.c 中的原始碼的時候,這行程式碼指示編譯器去讀取檔案 getopt.h, 為的是“知道” getopt.c 中的內容。 getopt.c 檔案提供由 style 和 diction 兩個程式共享的例行程式。

Above the include statement for getopt.h, we see some other include statements such as these:

在 getopt.h 的 include 語句上面,我們看到一些其它的 include 語句,比如這些:

#include <regex.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

These also refer to header files, but they refer to header files that live outside the current source tree. They are supplied by the system to support the compilation of every program. If we look in /usr/include, we can see them:

這些檔案也是標頭檔案,但是這些標頭檔案在當前原始碼樹的外面。它們由作業系統供給,來支援每個程式的編譯。 如果我們看一下 /usr/include 目錄,能看到它們:

[me@linuxbox diction-1.11]$ ls /usr/include

The header files in this directory were installed when we installed the compiler.

當我們安裝編譯器的時候,這個目錄中的標頭檔案會被安裝。

建構程式

Most programs build with a simple, two-command sequence:

大多數程式透過一個簡單的,兩個命令的序列建構:

./configure
make

The configure program is a shell script which is supplied with the source tree. Its job is to analyze the build environment. Most source code is designed to be portable. That is, it is designed to build on more than one kind of Unix-like system. But in order to do that, the source code may need to undergo slight adjustments during the build to accommodate differences between systems. configure also checks to see that necessary external tools and components are installed. Let's run configure. Since configure is not located where the shell normally expects programs to be located, we must explicitly tell the shell its location by prefixing the command with ./ to indicate that the program is located in the current working directory:

這個 configure 程式是一個 shell 指令碼,由原始碼樹提供。它的工作是分析程式建構環境。大多數原始碼會設計為可移植的。 也就是說,它被設計成能夠在不止一種類別 Unix 系統中進行建構。但是為了做到這一點,在建立程式期間,為了適應系統之間的差異, 原始碼可能需要經過輕微的調整。configure 也會檢查是否安裝了必要的外部工具和元件。讓我們執行 configure 命令。 因為 configure 命令所在的位置不是位於 shell 通常期望程式所呆的地方,我們必須明確地告訴 shell 它的位置,透過 在命令之前加上 ./ 字元,來表明程式位於當前工作目錄:

[me@linuxbox diction-1.11]$ ./configure

configure will output a lot of messages as it tests and configures the build. When it finishes, it will look something like this:

configure 將會輸出許多資訊,隨著它測試和配置整個建構過程。當結束後,輸出結果看起來像這樣:

checking libintl.h presence... yes
checking for libintl.h... yes
checking for library containing gettext... none required
configure: creating ./config.status
config.status: creating Makefile
config.status: creating diction.1
config.status: creating diction.texi
config.status: creating diction.spec
config.status: creating style.1
config.status: creating test/rundiction
config.status: creating config.h
[me@linuxbox diction-1.11]$

What’s important here is that there are no error messages. If there were, the configuration failed, and the program will not build until the errors are corrected.

這裡最重要的事情是沒有錯誤資訊。如果有錯誤資訊,整個配置過程失敗,然後程式不能建構直到修正了錯誤。

We see configure created several new files in our source directory. The most impor- tant one is Makefile. Makefile is a configuration file that instructs the make pro- gram exactly how to build the program. Without it, make will refuse to run. Makefile is an ordinary text file, so we can view it:

我們看到在我們的原始碼目錄中 configure 命令建立了幾個新檔案。最重要一個是 Makefile。Makefile 是一個配置檔案, 指示 make 程式究竟如何建構程式。沒有它,make 程式就不能執行。Makefile 是一個普通文字檔案,所以我們能檢視它:

[me@linuxbox diction-1.11]$ less Makefile

The make program takes as input a makefile (which is normally named Makefile), that describes the relationships and dependencies among the components that comprise the finished program.

這個 make 程式把一個 makefile 檔案作為輸入(通常命名為 Makefile),makefile 檔案 描述了包括最終完成的程式的各元件之間的關係和依賴性。

The first part of the makefile defines variables that are substituted in later sections of the makefile. For example we see the line:

makefile 檔案的第一部分定義了變數,這些變數在該 makefile 後續章節中會被替換掉。例如我們看看這一行程式碼:

CC=                 gcc

which defines the C compiler to be gcc. Later in the makefile, we see one instance where it gets used:

其定義了所用的 C 編譯器是 gcc。檔案後面部分,我們看到一個使用該變數的範例:

diction:        diction.o sentence.o misc.o getopt.o getopt1.o
                $(CC) -o $@ $(LDFLAGS) diction.o sentence.o misc.o \
                getopt.o getopt1.o $(LIBS)

A substitution is performed here, and the value $(CC) is replaced by gcc at run time. Most of the makefile consists of lines, which define a target, in this case the executable file diction, and the files on which it is dependent. The remaining lines describe the command(s) needed to create the target from its components. We see in this example that the executable file diction (one of the final end products) depends on the existence of diction.o, sentence.o, misc.o, getopt.o, and getopt1.o. Later on, in the makefile, we see definitions of each of these as targets:

這裡完成了一個替換操作,在程式執行時,$(CC) 的值會被替換成 gcc。大多數 makefile 檔案由行組成,每行定義一個目標檔案, 在這種情況下,目標檔案是指可執行檔案 diction,還有目標檔案所依賴的檔案。剩下的行描述了從目標檔案的依賴元件中 建立目標檔案所需的命令。在這個例子中,我們看到可執行檔案 diction(最終的成品之一)依賴於檔案 diction.o,sentence.o,misc.o,getopt.o,和 getopt1.o都存在。在 makefile 檔案後面部分,我們看到 diction 檔案所依賴的每一個檔案做為目標檔案的定義:

diction.o:       diction.c config.h getopt.h misc.h sentence.h
getopt.o:        getopt.c getopt.h getopt_int.h
getopt1.o:       getopt1.c getopt.h getopt_int.h
misc.o:          misc.c config.h misc.h
sentence.o:      sentence.c config.h misc.h sentence.h
style.o:         style.c config.h getopt.h misc.h sentence.h

However, we don’t see any command specified for them. This is handled by a general target, earlier in the file, that describes the command used to compile any .c file into a .o file:

然而,我們不會看到針對它們的任何命令。這個由一個通用目標解決,在檔案的前面,描述了這個命令,用來把任意的 .c 檔案編譯成 .o 檔案:

.c.o:
            $(CC) -c $(CPPFLAGS) $(CFLAGS) $<

This all seems very complicated. Why not simply list all the steps to compile the parts and be done with it? The answer to this will become clear in a moment. In the meantime, Let's run make and build our programs:

這些看起來非常複雜。為什麼不簡單地列出編譯每個部分的步驟,那樣不就行了?一會就知道答案了。同時, 讓我們執行 make 命令並建構我們的程式:

[me@linuxbox diction-1.11]$ make

The make program will run, using the contents of Makefile to guide its actions. It will produce a lot of messages.

這個 make 程式將會執行,使用 Makefile 檔案的內容來指導它的行為。它會產生很多資訊。

When it finishes, we will see that all the targets are now present in our directory:

當 make 程式執行結束後,現在我們將看到所有的目標檔案出現在我們的目錄中。

[me@linuxbox diction-1.11]$ ls
config.guess  de.po             en              en_GB           sentence.c
config.h      diction           en_GB.mo        en_GB.po        sentence.h
config.h.in   diction.1         getopt1.c       getopt1.o       sentence.o
config.log    diction.1.in      getopt.c        getopt.h        style
config.status diction.c         getopt_int.h    getopt.o        style.1
config.sub    diction.o         INSTALL         install-sh      style.1.in
configure     diction.pot       Makefile        Makefile.in     style.c
configure.in  diction.spec      misc.c          misc.h          style.o
COPYING       diction.spec.in   misc.o          NEWS            test
de            diction.texi      nl              nl.mo
de.mo         diction.texi.i    nl.po           README

Among the files, we see diction and style, the programs that we set out to build. Congratulations are in order! We just compiled our first programs from source code! But just out of curiosity, Let's run make again:

在這些檔案之中,我們看到 diction 和 style,我們開始要建構的程式。恭喜一切正常!我們剛才原始碼編譯了 我們的第一個程式。但是出於好奇,讓我們再執行一次 make 程式:

[me@linuxbox diction-1.11]$ make
make: Nothing to be done for `all'.

It only produces this strange message. What’s going on? Why didn’t it build the program again? Ah, this is the magic of make. Rather than simply building everything again, make only builds what needs building. With all of the targets present, make determined that there was nothing to do. We can demonstrate this by deleting one of the targets and running make again to see what it does. Let's get rid of one of the intermediate targets:

它只是產生這樣一條奇怪的資訊。怎麼了?為什麼它沒有重新建構程式呢?啊,這就是 make 奇妙之處了。make 只是建構 需要建構的部分,而不是簡單地重新建構所有的內容。由於所有的目標檔案都存在,make 確定沒有任何事情需要做。 我們可以證明這一點,透過刪除一個目標檔案,然後再次執行 make 程式,看看它做些什麼。讓我們去掉一箇中間目標檔案:

[me@linuxbox diction-1.11]$ rm getopt.o
[me@linuxbox diction-1.11]$ make

We see that make rebuilds it and re-links the diction and style programs, since they depend on the missing module. This behavior also points out another important feature of make: it keeps targets up to date. make insists that targets be newer than their dependencies. This makes perfect sense, as a programmer will often update a bit of source code and then use make to build a new version of the finished product. make ensures that everything that needs building based on the updated code is built. If we use the touch program to “update” one of the source code files, we can see this happen:

我們看到 make 重新建構了 getopt.o 檔案,並重新連結了 diction 和 style 程式,因為它們依賴於丟失的模組。 這種行為也指出了 make 程式的另一個重要特徵:它保持目標檔案是最新的。make 堅持目標檔案要新於它們的依賴檔案。 這個非常有意義,做為一名程式設計師,經常會更新一點原始碼,然後使用 make 來建構一個新版本的成品。make 確保 基於更新的程式碼建構了需要建構的內容。如果我們使用 touch 程式,來“更新”其中一個原始碼檔案,我們看到發生了這樣的事情:

[me@linuxboxdiction-1.11]$ ls -l diction getopt.c
-rwxr-xr-x 1 me me 37164 2009-03-05 06:14 diction
-rw-r--r-- 1 me me 33125 2007-03-30 17:45 getopt.c
[me@linuxboxdiction-1.11]$ touch getopt.c
[me@linuxboxdiction-1.11]$ ls -l diction getopt.c
-rwxr-xr-x 1 me me 37164 2009-03-05 06:14 diction
-rw-r--r-- 1 me me 33125 2009-03-05 06:23 getopt.c
[me@linuxbox diction-1.11]$ make

After make runs, we see that it has restored the target to being newer than the dependency:

執行 make 之後,我們看到目標檔案已經更新於它的依賴檔案:

[me@linuxbox diction-1.11]$ ls -l diction getopt.c
-rwxr-xr-x 1 me me 37164 2009-03-05 06:24 diction
-rw-r--r-- 1 me me 33125 2009-03-05 06:23 getopt.c

The ability of make to intelligently build only what needs building is a great benefit to programmers. While the time savings may not be very apparent with our small project, it is very significant with larger projects. Remember, the Linux kernel (a program that undergoes continuous modification and improvement) contains several million lines of code.

make 程式這種智慧地只建構所需要建構的內容的特性,對程式來說,是巨大的福利。雖然在我們的小專案中,節省的時間可能 不是非常明顯,在龐大的工程中,它具有非常重大的意義。記住,Linux 核心(一個經歷著不斷修改和改進的程式)包含了幾百萬行程式碼。

安裝程式

Well-packaged source code will often include a special make target called install. This target will install the final product in a system directory for use. Usually, this directory is /usr/local/bin, the traditional location for locally built software. However, this directory is not normally writable by ordinary users, so we must become the superuser to perform the installation:

打包良好的原始碼經常包括一個特別的 make 目標檔案,叫做 install。這個目標檔案將在系統目錄中安裝最終的產品,以供使用。 通常,這個目錄是 /usr/local/bin,為在本地所建構軟體的傳統安裝位置。然而,通常普通使用者不能寫入該目錄,所以我們必須變成超級使用者, 來執行安裝操作:

[me@linuxbox diction-1.11]$ sudo make install

After we perform the installation, we can check that the program is ready to go:

執行了安裝後,我們可以檢查下程式是否已經可用:

[me@linuxbox diction-1.11]$ which diction
/usr/local/bin/diction
[me@linuxbox diction-1.11]$ man diction

And there we have it!

完美!

總結

In this chapter, we have seen how three simple commands:

在這一章中,我們已經知道了三個簡單命令:

./configure
make
make install

can be used to build many source code packages. We have also seen the important role that make plays in the maintenance of programs. The make program can be used for any task that needs to maintain a target/dependency relationship, not just for compiling source code.

可以用來建構許多原始碼包。我們也知道了在程式維護過程中,make 程式起到了舉足輕重的作用。make 程式可以用到 任何需要維護一個目標/依賴關係的任務中,不僅僅為了編譯原始碼。

拓展閱讀


Go to Table of Contents