Linux中的make和makefile

1.1      多源文件問題

我們平常編寫一些小的程序一般都是修改源代碼後,重新編譯所有的源文件,但是對於大型的項目,這種方法就會有許多的問題。edit−compile−test週期耗費的時間比較長,所以當只修改一個文件時,要避免編譯所有的源文件。

     假設有頭文件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應該不需要重新編譯,因爲它們不依賴c.h頭文件。而源文件3.c依賴頭文件c.h需要重新編譯。如果b.h修改了,我們忘記重新編譯2.c也會導致程序功能異常。 而make工具可以解決上面這兩個問題。

1.2      make命令和makefiles

   make命令有一些內置的默認功能,但是光有這個它自己還是不知道怎麼build程序。我們必須提供一個文件告訴make應用程序的構造,這個文件就是makefile.

Make和makefile提供了強大的功能來管理項目的編譯以及發佈install到指定文件夾。

1.2.1 makefiles語法

一個makefile文件包括一系列的依賴(dependency))關係和規則(rules)。一個依賴有一個目標文件和一系列目標文件依賴的源文件。規則描述了怎麼用依賴文件創建目標文件。通常,一個目標文件是單個可執行文件。Make命令通過讀取makefile來決定哪些源文件和目標文件需要重新編譯,根據比較它們的日期和時間來決定使用來個規則來構建targets。

 

1.2.2 make的選項與參數

下面爲make 命令經常用到的三個選項:

   -k:告訴make當發生錯誤時繼續執行,而不是停在第一個錯誤的位置。

   -n:告訴make打印出它將要完成什麼,實際上沒做。

   −f <filename>,告訴make用哪個makefile,如果不使用這個選項,make將搜索當前目錄下第一個名爲makefile的文件,如果還不存在,搜索一個文件名爲Makefile。

 

 依賴:指明瞭每個文件怎麼樣關聯源文件。

makefile文件中,寫依賴的方法爲:目標文件名,冒號,空格或tabs,然後接着是用空格或 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.o3.o,當2.o3.o改變了,需要重新編譯myapp.

 當需要編譯多個文件,可以使用目標文件名all.假設應用程序包含一個二進制文件myapp和一個用戶指導頁面myapp.1,可以使用下面的方式指定。

all:  myapp  myapp.1

規則:makefile的第二部分內容。告訴我們將如何創建目標文件。

make命令決定2.o需要重新編譯,使用什麼命令進行編譯呢?也許最簡單的方式是使用gcc –c 2.c(make命令也有許多默認的規則),但是如果需要包含頭文件的目錄以及爲調試設置符號信息怎麼辦呢?可以在makefile當中使用顯示的規則。

 

 重要:所有的規則都必須要以製表符(tab)開頭的行,空格不行。由於多個空格和tab看起來的效果很相同,但在Unix編程當中,tabsspaces還是有區別的,會導致一些問題。在makefile文件末尾加入空格行會導致make命令不能執行,因爲缺少tab

例子:一個簡單的 makefile,命名爲Makefile1

myapp: main.o 2.o 3.o

gcc −o myapp main.o 2.o 3.o  //該行必須要有一個tab開頭

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


 

使用$ make −f Makefile1

這是由於make命令分析到目標文件爲myapp,然後查看依賴文件main.o,需要main.c來創建,但是開始沒有創建該文件。

 使用下面的命令創建三個空頭文件:

$touch a.h

$touch b.h

$touch c.h

 

/* main.c */

#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 −f Makefile1

Make按順序顯示出了它執行的命令

現在修改b.h頭文件:

$ touch b.h

$ make −f Makefile1

Make只顯示了創建依賴於b.h的目標 文件的命令

刪除2.o

$ rm 2.o

$ make −f Makefile1

由上面可以看到make 可以正確的決定執行gcc命令的順序。

makefile中使用#進行註釋。

1.2.3 makefiles宏

對於文件比較多的項目,可以在makefile中定義宏。

定義:MACRONAME=value

獲取值:$(MACRONAME) or ${MACRONAME}.

宏經常在makefile中用來爲編譯器指定編譯選項。對於發佈版本和調試版本程序需要的信息不同,對於release 版本不需要加入調試信息。對於Makefile1,默認使用的是gcc編譯器,在其他的Unix操作系統,可能使用cc或cc89,如果想讓makefile在不同的操作系統上運行,需要修改makefile文件中的多行。可以使用宏來解決這問題。

Makefile2:

all: myapp

 

# 指定編譯器

CC = gcc

 

# 頭文件目錄

INCLUDE = .

 

# 開發編譯選項

CFLAGS = -g -Wall -ansi

 

# 發佈版本編譯選項

# 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


 

執行下面命令:

$ rm *.o myapp

$ make −f Makefile2

 

實際上,make有許多特殊的內部宏,可以讓makefile更簡潔。下面列出一些經常用到的內部宏。

 

$?

List of prerequisites changed more recently than the current target.

$@

Name of the current target.

$<

Name of the current prerequisite.

$*

Name of the current prerequisite, without any suffix.

 

Makefile中另外兩個非常重要的特殊字符:

“-“make忽略任何錯誤。

“@”make在命令執行前不要向標準輸出打印命令。

1.2.4 多目標文件

  可以使用”clean”選項來清除不想要的目標文件,用”install”選項來將應用程序移動到不同的目錄中。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


 

install依賴於myapp.make需要先生成myapp

If前面的“@”是讓make不要輸出這些命令。

$ 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

1.2.5 內置規則

Makefile能準確判斷每個命令的執行步驟,make中包括許多內置的規則,當有許多源文件時,可以簡化makefiles。創建一個foo.c文件:

#include <stdlib.h>

#include <stdio.h>

int main()

{

printf("Hello World\n");

exit (EXIT_SUCCESS);

}


 

使用make進行編譯,不指定makefile.

$ make foo

Make命令知道怎麼調用編譯器,儘管使用的是默認的編譯器cc.

可以使用 make –p來打印內置的規則。

我們可以用自定義的值來改變內置宏,從而改變它的默認行爲。

$ rm foo

$ make CC = gcc CFLAGS = "−Wall −g" foo

 

1.2.6 後綴規則

 有時源文件需要在不同的編譯器上進行編譯:如在MS-DOS或者是LinuxGCC下。在MS-DOS下的源文件後綴名大多爲.cpp而不是C,而Linux下的make命令沒有內置的規則來編譯.cpp文件(.cc的編譯規則)

我們需要讓make產生一個新規則來生成 .cpp的目標文件,這時需要添加一個後綴規則。Makefile5:

all: myapp

 

.SUFFIXES:      .cpp

 

.cpp.o:

         $(CC) -xc++ $(CFLAGS) -I$(INCLUDE) -c $<

 

# 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


 

執行:

$ cp foo.c bar.cpp

$ make −f Makefile5 bar

gcc −xc++ −g −Wall −ansi −I. −c bar.cpp

 

−xc++:告訴gcc是一個C++源文件

1.2.7 make管理庫文件lib

  當開發一個大的項目,利用一個庫(library)來管理多個編譯版本產品非常方便。庫文件的後綴名爲(.a),它包含許多的目標文件。

Make命令中內置的規則來管理庫:

.c.a:

$(CC) −c $(CFLAGS) $<

$(AR) $(ARFLAGS) $@ $*.o

宏 $(AR) 和 $(ARFLAGS) 指的是命令 ar 和命令選項 rv。含義爲:從一個 .c 文件到一個.a的庫文件需要應用兩條規則。第一條規則:必須編譯源文件爲目標文件;第二條規則:使用ar命令來添加該新的目標文件來生成庫文件。如果目前有一個庫文件fud包含目標文件bas.o,在第一條規則中,$<爲bas.c。在第二條規則中,$@爲fud.a,$*爲bas.

 

下面讓目標文件2.o 和3.o包含在庫文件 mylib.a,如下Makefile6:

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


 

$ rm −f myapp *.o mylib.a

$ make −f Makefile6

gcc −g −Wall −ansi −c main.c −o main.o

gcc −g −Wall −ansi −c 2.c −o 2.o

ar rv mylib.a 2.o

ar: creating mylib.a

c − 2.o

gcc −g −Wall −ansi −c 3.c −o 3.o

ar rv mylib.a 3.o

c − 3.o

gcc −o myapp main.o mylib.a

 

1.2.8 GCC的兩個選項-JN  –MM

-jN可以同時執行N條命令

-MMgcc會輸出目標文件所依賴的源文件。

 

$ 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

Make命令知道怎麼調用編譯器,儘管使用的是默認的編譯器cc.

我們可以用自定義的值來改變內置宏,從而改變它的默認行爲。

$ rm foo

$ make CC = gcc CFLAGS = "−Wall −g" foo

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章