Linux 應用---make及makefile的編寫

  Make 在我們做linux 開發中是必不可少的一部分,它在我們編寫大型項目工程文件中起到非常大的作用。

     Make工程管理器也就是個“自動編譯管理器”,這裏的“自動”是指它能夠根據文件時間戳自動發現更新過的文件而減少編譯的工作量,同時,它通過讀入Makefile文件的內容來執行大量的編譯工作。Make將只編譯改動的代碼文件,而不用完全編譯。

    而Makefile是Make讀入的唯一配置文件,makefile關係到了整個工程的編譯規則。一個工程中的源文件不計數,其按類型、功能、模塊分別放在若干個目錄中,makefile定義了一系列的規則來指定,哪些文件需要先編譯,哪些文件需要後編譯,哪些文件需要重新編譯,甚至於進行更復雜的功能操作,因爲makefile就像一個Shell腳本一樣,其中也可以執行操作系統的命令。

   下面我們通過兩個個實例來學習makefile的編寫:

一、Makefile編寫的基本規則

    我在文件夾下有如下文件

[cpp] view plain copy
  1. fs@ubuntu:~/qiang/makefile/example1$ ls  
  2. buffer.h  defs.h  files.c  main.c  makefile  utils.c  
  3. fs@ubuntu:~/qiang/makefile/example1$   

這裏的代碼就不展示了,我說一下文件包含關係:files.c需要buffer.h及defs.h ,  main.c需要defs.h  ,utils.c需要defs.h。

我把makefile文件內容展示出來:

[cpp] view plain copy
  1. OBJS = main.o files.o utils.o  
  2. CC = gcc  
  3. CFLAGS = -c  
  4.   
  5. Test:$(OBJS)  
  6.     $(CC) -o Test $(OBJS)  
  7. main.o:main.c defs.h  
  8.     $(CC) $(CFLAGS) main.c  
  9. files.o:files.c buffer.h defs.h  
  10.     $(CC) $(CFLAGS) files.c  
  11. utils.o:utils.c  defs.h  
  12.     $(CC) $(CFLAGS) utils.c  
  13.   
  14. .PHONY:clean  
  15. clean:  
  16.     rm -rf *.o Test  
  17.     @echo "Clean done!"  

我們開始學習makefile編寫規範:Makefile的根本任務是根據規則生成目標文件。

1、規則

一條規則包含三個:目標文件,目標文件依賴的文件,更新(或生成)目標文件的命令。

規則:               

<目標文件>:<依賴文件>

         <更新目標的命令>

注意:命令行前面必須是一個“TAB鍵”,否則會編譯錯誤!

Example      

[cpp] view plain copy
  1. hello.o: hello.c hello.h      
  2.     gcc -c hello.c -o hello.o  

目標hello.o 依賴於hello.c,hello.h. 生成hello.o的命令時是“gcc -c hello.c -o hello.o”

2、終極目標

makefile並不會更新所有規則中的目標,它只會更新終極目標以及終極目標依賴的目標。
默認情況下makefile的第一個目標是終極目標,而且大家約定俗成的總是將all作爲第一個目標。環境變量MAKECMDGOALS記錄着終極目標。

[cpp] view plain copy
  1. Test:$(OBJS)  
  2.     $(CC) -o Test $(OBJS)  

我們這裏就是終極目標

注意:終極目標必須放在第一個,其餘的可以隨便放!

 

3、多規則目標

Makefile中,一個文件可以作爲多個規則的目標,這種情形就是多規則目標。
多規則目標下,以這個文件爲目標的所有規則的依賴文件將會被合併成此一個依賴文件列表,但是命令不會合並,而且實際上,這多個規則中至多只能有一個規則定義了更新命令。

all:hello.o             

all:hello.h             

等價於  all: hello.o hello.h

[cpp] view plain copy
  1. main.o:main.c defs.h  

我們這裏就是多規則目標;

 

4、僞目標
一般情況下目標文件是一個具體的文件,但有時候我們只需要一個標籤,如目標clean。

聲明僞目標:   

.PHONY:  <僞目標>

僞目標只是一個標籤,這意味着僞目標的時間戳總是最新的,結果就是makefile每次都會去執行更新僞目標的命令。

[cpp] view plain copy
  1. .PHONY:clean  
  2. clean:  
  3.     rm -rf *.o Test  
  4.     @echo "Clean done!"  

我們這裏clean就是個僞目標,我們可以看到僞目標是沒有依賴文件的,只有用make來調用時,纔會執行。

5、什麼時候更新目標

如果目標不存在或者依賴文件中至少有一個文件的時間戳比目標新,則執行目標更新命令。

我們這裏,如果main.c 或者files被修改時,都會更新目標。

6、創建和使用變量

1)定義變量

makefile的變量定義有四種方法:

1. 立即賦值 a:=b
2. 延遲賦值 a=b
3. 條件賦值 a?=b
4. 附加賦值 a+=b

它們之間的區別是:

第一種方式,會立即計算b的值,並賦值給a;
第二種方式,相當於C++和java的引用。如果後面b的值改變了,那麼a的值也會改變;
第三種方式,如果a沒有定義,則相當於a=b ,否則不執行任何操作;
第四種方式,將b的值添加到a原有的值後面,再賦值給a。

2)獲取變量值

$(var) //表示取變量var的值,記得當變量名多於一個字符時,使用”()”.

[cpp] view plain copy
  1. OBJS = main.o files.o utils.o  
[cpp] view plain copy
  1. Test:$(OBJS)  
  2.     $(CC) -o Test $(OBJS)  

這裏我們可以看到變量的創建與使用。

3)預定義變量

  CC  :C編譯器的名稱,默認值爲cc 。CPP  C預編譯器的名稱,默認值爲$(CC) -E。

[cpp] view plain copy
  1. CC = gcc  
[cpp] view plain copy
  1. Test:$(OBJS)  
  2.     $(CC) -o Test $(OBJS)  

這裏我們可以看到預定義變量CC的使用。

其他的預定義變量:

ARFLAGS  庫文件維護程序的選項,無默認值。

ASFALGS  彙編程序的選項,無默認值。

CFALGS    C編譯器的選項,無默認值。
....

[cpp] view plain copy
  1. CFLAGS = -c  
[cpp] view plain copy
  1. main.o:main.c defs.h  
  2.     $(CC) $(CFLAGS) main.c  

這裏我們可以看到CFLAGS的使用。

4)自動變量

$* 不包含擴展名的目標文件名稱

$< 第一個依賴文件的名稱

$@ 目標文件的完整名稱

$^ 所有不重複的目標依賴文件,以空格分開

...

    變量的使用有助於我們修改makefile,大大加快了我們的開發效率。

 

7、回顯問題

我們知道,makefile中的命令行都會被打印出來的,如果遇到我們不想打印的命令怎麼辦?

[cpp] view plain copy
  1. @echo "Clean done!"  

在命令行前面加 @ ,就是不回顯的意思,這樣"echo "Clean done!" "就不會打印。

 

我們來看看編譯效果:

[cpp] view plain copy
  1. fs@ubuntu:~/qiang/makefile/example1$ ls  
  2. buffer.h  defs.h  files.c  main.c  makefile  utils.c  
  3. fs@ubuntu:~/qiang/makefile/example1$ make  
  4. gcc -c main.c  
  5. gcc -c files.c  
  6. gcc -c utils.c  
  7. gcc -o Test main.o files.o utils.o  
  8. fs@ubuntu:~/qiang/makefile/example1$ ls  
  9. buffer.h  files.c  main.c  makefile  utils.c  
  10. defs.h    files.o  main.o  Test      utils.o  
  11. fs@ubuntu:~/qiang/makefile/example1$ make clean  
  12. rm -rf *.o Test  
  13. Clean done!  
  14. fs@ubuntu:~/qiang/makefile/example1$ ls  
  15. buffer.h  defs.h  files.c  main.c  makefile  utils.c  
  16. fs@ubuntu:~/qiang/makefile/example1$   


二、嵌套執行Makefile

     在一些大的工程中,我們會把我們不同模塊或是不同功能的源文件放在不同的目錄中,我們可以在每個目錄中都書寫一個該目錄的Makefile,這有利於讓我們的Makefile變得更加地簡潔,而不至於把所有的東西全部寫在一個Makefile中,這樣會很難維護我們的Makefile,這個技術對於我們模塊編譯和分段編譯有着非常大的好處。

例如,我們有一個子目錄叫subdir,這個目錄下有個Makefile文件,來指明瞭這個目錄下文件的編譯規則。那麼我們總控的Makefile可以這樣書寫:

    subsystem: 
            cd subdir && $(MAKE)

其等價於:

    subsystem: 
            $(MAKE) -C subdir

定義$(MAKE)宏變量的意思是,也許我們的make需要一些參數,所以定義成一個變量比較利於維護。這兩個例子的意思都是先進入“subdir”目錄,然後執行make命令。

我們把這個Makefile叫做“總控Makefile”,總控Makefile的變量可以傳遞到下級的Makefile中(如果你顯示的聲明),但是不會覆蓋下層的Makefile中所定義的變量,除非指定了“-e”參數。

如果你要傳遞變量到下級Makefile中,那麼你可以使用這樣的聲明:

    export ;

如果你不想讓某些變量傳遞到下級Makefile中,那麼你可以這樣聲明: 

    unexport ;

如: 
     
    示例一:

        export variable = value

        其等價於:

        variable = value 
        export variable

        其等價於:

        export variable := value

        其等價於:

        variable := value 
        export variable

    示例二:

        export variable += value

        其等價於:

        variable += value 
        export variable

如果你要傳遞所有的變量,那麼,只要一個export就行了。後面什麼也不用跟,表示傳遞所有的變量。

需要注意的是,有兩個變量,一個是SHELL,一個是MAKEFLAGS,這兩個變量不管你是否export,其總是要傳遞到下層Makefile中,特別是MAKEFILES變量,其中包含了make的參數信息,如果我們執行“總控Makefile”時有make參數或是在上層Makefile中定義了這個變量,那麼MAKEFILES變量將會是這些參數,並會傳遞到下層Makefile中,這是一個系統級的環境變量。

但是make命令中的有幾個參數並不往下傳遞,它們是“-C”,“-f”,“-h”“-o”和“-W”(有關Makefile參數的細節將在後面說明),如果你不想往下層傳遞參數,那麼,你可以這樣來:

    subsystem: 
            cd subdir && $(MAKE) MAKEFLAGS=

如果你定義了環境變量MAKEFLAGS,那麼你得確信其中的選項是大家都會用到的,如果其中有“-t”,“-n”,和“-q”參數,那麼將會有讓你意想不到的結果,或許會讓你異常地恐慌。

還有一個在“嵌套執行”中比較有用的參數,“-w”或是“--print-directory”會在make的過程中輸出一些信息,讓你看到目前的工作目錄。比如,如果我們的下級make目錄是“/home/hchen/gnu/make”,如果我們使用“make -w”來執行,那麼當進入該目錄時,我們會看到:

    make: Entering directory `/home/hchen/gnu/make'.

而在完成下層make後離開目錄時,我們會看到:

    make: Leaving directory `/home/hchen/gnu/make'

當你使用“-C”參數來指定make下層Makefile時,“-w”會被自動打開的。如果參數中有“-s”(“--slient”)或是“--no-print-directory”,那麼,“-w”總是失效的。

頭的特殊變量,我們會在後面介紹),make在執行命令包時,命令包中的每個命令會被依次獨立執行。

下面我們做一個實驗,學習嵌套執行Makefile編寫的過程:

1、創建頂層目錄

[cpp] view plain copy
  1. fs@ubuntu:~/qiang/makefile$ mkdir makefileTest  
  2. fs@ubuntu:~/qiang/makefile$ cd makefileTest/  
  3. fs@ubuntu:~/qiang/makefile/makefileTest$ mkdir f1 f2 main obj include  
  4. fs@ubuntu:~/qiang/makefile/makefileTest$ ls  
  5. f1  f2  include  main  obj  
  6. fs@ubuntu:~/qiang/makefile/makefileTest$   

在include文件夾中創建一個共用頭文件,在其中輸入#include <stdio.h>

[cpp] view plain copy
  1. fs@ubuntu:~/qiang/makefile/makefileTest$ cd include/  
  2. fs@ubuntu:~/qiang/makefile/makefileTest/include$ vi myinclude.h  
  3. fs@ubuntu:~/qiang/makefile/makefileTest/include$ cat myinclude.h   
  4. #include <stdio.h>  
  5. fs@ubuntu:~/qiang/makefile/makefileTest/include$   

2、創建頂層Makefile文件

[cpp] view plain copy
  1. fs@ubuntu:~/qiang/makefile/makefileTest$ vi Makefile  

內容如下:

[cpp] view plain copy
  1. CC = gcc                                                                                                           
  2. SUBDIRS = f1 \  
  3.      f2 \  
  4.      main \  
  5.      obj  //上面三個'\'後面不能有空格  
  6. OBJS = f1.o f2.o main.o  
  7. BIN = myapp  
  8. OBJS_DIR = obj  
  9. BIN_DIR = bin  
  10. export CC OBJS BIN OBJS_DIR BIN_DIR    //導出環境變量,傳遞到下級目錄  
  11.   
  12. all:CHECK_DIR $(SUBDIRS)  
  13. CHECK_DIR:  
  14.     mkdir -p $(BIN_DIR)  
  15. $(SUBDIRS):ECHO  
  16.     make -C $@ //先進入到SUBDIRS下的目錄,再執行make  
  17.   
  18. ECHO:  
  19.     @echo $(SUBDIRS)  
  20.     @echo begin compile  
  21.   
  22. CLEAN:  
  23.     @$(RM) $(OBJS_DIR)/*.o  
  24.     @rm -rf $(BIN_DIR)  

 3、進入在f1目錄下創建makefile

[cpp] view plain copy
  1. fs@ubuntu:~/qiang/makefile/makefileTest$ cd f1  
  2. fs@ubuntu:~/qiang/makefile/makefileTest/f1$ vi f1.c  

內容如下:

[cpp] view plain copy
  1. #include "../include/myinclude.h"                                                                                
  2.   
  3. void print1()    
  4. {    
  5.     printf("Message f1.c\n");    
  6.     return;    
  7. }   
[cpp] view plain copy
  1. fs@ubuntu:~/qiang/makefile/makefileTest/f1$ vi Makefile  

內容如下:

[cpp] view plain copy
  1. ../$(OBJS_DIR)/f1.o:f1.c                                                                                         
  2.     $(CC) -c $^ -o $@    

 4、進入f2目錄

[cpp] view plain copy
  1. fs@ubuntu:~/qiang/makefile/makefileTest/f1$ cd ../f2  
  2. fs@ubuntu:~/qiang/makefile/makefileTest/f2$ vi f2.c  

內容如下:

[cpp] view plain copy
  1. #include "../include/myinclude.h"                                                                                
  2.   
  3. void print2()    
  4. {    
  5.     printf("Message f2.c\n");    
  6.     return;    
  7. }   
[cpp] view plain copy
  1. fs@ubuntu:~/qiang/makefile/makefileTest/f2$ vi makefile  

內容如下:

[cpp] view plain copy
  1. ../$(OBJS_DIR)/f2.o:f2.c                                                                                         
  2.     $(CC) -c $^ -o $@   

 5、進入main目錄

[cpp] view plain copy
  1. <a target=_blank href="mailto:fs@ubuntu:~/qiang/makefile/makefileTest/f2$"><span style="color:#000000;">fs@ubuntu:~/qiang/makefile/makefileTest/f2$</span></a> cd ../main  
  2. fs@ubuntu:~/qiang/makefile/makefileTest/main$ vi main.c  

內容如下:

[cpp] view plain copy
  1. #include <stdio.h>                                                                                               
  2.   
  3. int main()    
  4. {    
  5.     print1();    
  6.     print2();    
  7.   
  8.     return 0;   
  9. }    
[cpp] view plain copy
  1. fs@ubuntu:~/qiang/makefile/makefileTest/main$ vi Makefile  

內容如下:

[cpp] view plain copy
  1. ../$(OBJS_DIR)/main.o:main.c                                                                                     
  2.     $(CC) -c $^ -o $@    

6、進入obj目錄

[cpp] view plain copy
  1. fs@ubuntu:~/qiang/makefile/makefileTest$ cd obj  
  2. fs@ubuntu:~/qiang/makefile/makefileTest/obj$ ls  
  3. fs@ubuntu:~/qiang/makefile/makefileTest/obj$ vi Makefile  

內容如下:

[cpp] view plain copy
  1. ../$(BIN_DIR)/$(BIN) : $(OBJS)  
  2.     $(CC) -o $@ $^  

這樣我們總體架構就完成了,我們看一下樹狀圖:

我們執行一下:

[cpp] view plain copy
  1. fs@ubuntu:~/qiang/makefile/makefileTest$ make  
  2. Makefile:3: *** commands commence before first target.  Stop.  

makefile時常遇到這樣的問題,彙總網上的原因如下:
1. 上一行換行符號 \ 後面有空格
2. 本行前面的空白有非法字符
1)Makefile可能是以命令行開始:以[Tab]字符開始,但不是一個合法的命令行(例如,一個變量的賦值)。命令行必須和規則一一對應。
2)第二種原因可能是一行的第一個非空字符爲分號,make會認爲此處遺漏了規則的“target: prerequisite”部分。

這兒我們的原因是第一個

改正後編譯:

[cpp] view plain copy
  1. fs@ubuntu:~/qiang/makefile/makefileTest$ make  
  2. mkdir -p bin  
  3. f1 f2 main obj  
  4. begin compile  
  5. make -C f1  
  6. make[1]: Entering directory `/home/fs/qiang/makefile/makefileTest/f1'  
  7. gcc -c f1.c -o ../obj/f1.o  
  8. make[1]: Leaving directory `/home/fs/qiang/makefile/makefileTest/f1'  
  9. make -C f2  
  10. make[1]: Entering directory `/home/fs/qiang/makefile/makefileTest/f2'  
  11. gcc -c f2.c -o ../obj/f2.o   
  12. make[1]: Leaving directory `/home/fs/qiang/makefile/makefileTest/f2'  
  13. make -C main  
  14. make[1]: Entering directory `/home/fs/qiang/makefile/makefileTest/main'  
  15. gcc -c main.c -o ../obj/main.o    
  16. make[1]: Leaving directory `/home/fs/qiang/makefile/makefileTest/main'  
  17. make -C obj  
  18. make[1]: Entering directory `/home/fs/qiang/makefile/makefileTest/obj'  
  19. gcc -o ../bin/myapp f1.o f2.o main.o  
  20. make[1]: Leaving directory `/home/fs/qiang/makefile/makefileTest/obj'  

執行一下:

[cpp] view plain copy
  1. fs@ubuntu:~/qiang/makefile/makefileTest$ cd bin/  
  2. fs@ubuntu:~/qiang/makefile/makefileTest/bin$ ls  
  3. myapp  
  4. fs@ubuntu:~/qiang/makefile/makefileTest/bin$ ./myapp  
  5. Message f1.c  
  6. Message f2.c  
  7. fs@ubuntu:~/qiang/makefile/makefileTest/bin$   
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章