make命令以及makefile

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++

相關文章
相關標籤/搜索