Make 在我們做linux 開發中是必不可少的一部分,它在我們編寫大型項目工程文件中起到非常大的作用。
???? Make工程管理器也就是個“自動編譯管理器”,這里的“自動”是指它能夠根據文件時間戳自動發現更新過的文件而減少編譯的工作量,同時,它通過讀入Makefile文件的內容來執行大量的編譯工作。Make將只編譯改動的代碼文件,而不用完全編譯。
??? 而Makefile是Make讀入的唯一配置文件,makefile關系到了整個工程的編譯規則。一個工程中的源文件不計數,其按類型、功能、模塊分別放在若干個目錄中,makefile定義了一系列的規則來指定,哪些文件需要先編譯,哪些文件需要后編譯,哪些文件需要重新編譯,甚至于進行更復雜的功能操作,因為makefile就像一個Shell腳本一樣,其中也可以執行操作系統的命令。
???下面我們通過兩個個實例來學習makefile的編寫:
一、Makefile編寫的基本規則
??? 我在文件夾下有如下文件
[cpp]?view plaincopy
fs@ubuntu:~/qiang/makefile/example1$?ls?? buffer.h??defs.h??files.c??main.c??makefile??utils.c?? fs@ubuntu:~/qiang/makefile/example1$???
這里的代碼就不展示了,我說一下文件包含關系:files.c需要buffer.h及defs.h ,? main.c需要defs.h? ,utils.c需要defs.h。
我把makefile文件內容展示出來:
[cpp]?view plaincopy
OBJS?=?main.o?files.o?utils.o?? CC?=?gcc?? CFLAGS?=?-c?? ?? Test:$(OBJS)?? ????$(CC)?-o?Test?$(OBJS)?? main.o:main.c?defs.h?? ????$(CC)?$(CFLAGS)?main.c?? files.o:files.c?buffer.h?defs.h?? ????$(CC)?$(CFLAGS)?files.c?? utils.o:utils.c??defs.h?? ????$(CC)?$(CFLAGS)?utils.c?? ?? .PHONY:clean?? clean:?? ????rm?-rf?*.o?Test?? ????@echo?"Clean?done!"??
我們開始學習makefile編寫規范:Makefile的根本任務是根據規則生成目標文件。
1、規則
一條規則包含三個:目標文件,目標文件依賴的文件,更新(或生成)目標文件的命令。
規則:???????????????
<目標文件>:<依賴文件>
???????? <更新目標的命令>
注意:命令行前面必須是一個“TAB鍵”,否則會編譯錯誤!
Example??????
[cpp]?view plaincopy
hello.o:?hello.c?hello.h?????? ????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 plaincopy
Test:$(OBJS)?? ????$(CC)?-o?Test?$(OBJS)??
我們這里就是終極目標
注意:終極目標必須放在第一個,其余的可以隨便放!
?
3、多規則目標
Makefile中,一個文件可以作為多個規則的目標,這種情形就是多規則目標。
多規則目標下,以這個文件為目標的所有規則的依賴文件將會被合并成此一個依賴文件列表,但是命令不會合并,而且實際上,這多個規則中至多只能有一個規則定義了更新命令。
all:hello.o?????????????
all:hello.h?????????????
等價于? all: hello.o hello.h
[cpp]?view plaincopy
main.o:main.c?defs.h??
我們這里就是多規則目標;
?
4、偽目標
一般情況下目標文件是一個具體的文件,但有時候我們只需要一個標簽,如目標clean。
聲明偽目標:???
.PHONY:? <偽目標>
偽目標只是一個標簽,這意味著偽目標的時間戳總是最新的,結果就是makefile每次都會去執行更新偽目標的命令。
[cpp]?view plaincopy
.PHONY:clean?? clean:?? ????rm?-rf?*.o?Test?? ????@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 plaincopy
OBJS?=?main.o?files.o?utils.o??
[cpp]?view plaincopy
Test:$(OBJS)?? ????$(CC)?-o?Test?$(OBJS)??
這里我們可以看到變量的創建與使用。
3)預定義變量
? CC? :C編譯器的名稱,默認值為cc 。CPP? C預編譯器的名稱,默認值為$(CC) -E。
[cpp]?view plaincopy
CC?=?gcc??
[cpp]?view plaincopy
Test:$(OBJS)?? ????$(CC)?-o?Test?$(OBJS)??
這里我們可以看到預定義變量CC的使用。
其他的預定義變量:
ARFLAGS??庫文件維護程序的選項,無默認值。
ASFALGS? 匯編程序的選項,無默認值。
CFALGS??? C編譯器的選項,無默認值。
....
[cpp]?view plaincopy
CFLAGS?=?-c??
[cpp]?view plaincopy
main.o:main.c?defs.h?? ????$(CC)?$(CFLAGS)?main.c??
這里我們可以看到CFLAGS的使用。
4)自動變量
$* 不包含擴展名的目標文件名稱
$< 第一個依賴文件的名稱
$@ 目標文件的完整名稱
$^ 所有不重復的目標依賴文件,以空格分開
...
??? 變量的使用有助于我們修改makefile,大大加快了我們的開發效率。
?
7、回顯問題
我們知道,makefile中的命令行都會被打印出來的,如果遇到我們不想打印的命令怎么辦?
[cpp]?view plaincopy
@echo?"Clean?done!"??
在命令行前面加 @ ,就是不回顯的意思,這樣"echo "Clean done!" "就不會打印。
?
我們來看看編譯效果:
[cpp]?view plaincopy
fs@ubuntu:~/qiang/makefile/example1$?ls?? buffer.h??defs.h??files.c??main.c??makefile??utils.c?? fs@ubuntu:~/qiang/makefile/example1$?make?? gcc?-c?main.c?? gcc?-c?files.c?? gcc?-c?utils.c?? gcc?-o?Test?main.o?files.o?utils.o?? fs@ubuntu:~/qiang/makefile/example1$?ls?? buffer.h??files.c??main.c??makefile??utils.c?? defs.h????files.o??main.o??Test??????utils.o?? fs@ubuntu:~/qiang/makefile/example1$?make?clean?? rm?-rf?*.o?Test?? Clean?done!?? fs@ubuntu:~/qiang/makefile/example1$?ls?? buffer.h??defs.h??files.c??main.c??makefile??utils.c?? 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 plaincopy
fs@ubuntu:~/qiang/makefile$?mkdir?makefileTest?? fs@ubuntu:~/qiang/makefile$?cd?makefileTest/?? fs@ubuntu:~/qiang/makefile/makefileTest$?mkdir?f1?f2?main?obj?include?? fs@ubuntu:~/qiang/makefile/makefileTest$?ls?? f1??f2??include??main??obj?? fs@ubuntu:~/qiang/makefile/makefileTest$???
在include文件夾中創建一個共用頭文件,在其中輸入#include <stdio.h>
[cpp]?view plaincopy
fs@ubuntu:~/qiang/makefile/makefileTest$?cd?include/?? fs@ubuntu:~/qiang/makefile/makefileTest/include$?vi?myinclude.h?? fs@ubuntu:~/qiang/makefile/makefileTest/include$?cat?myinclude.h??? #include?<stdio.h>?? fs@ubuntu:~/qiang/makefile/makefileTest/include$???
2、創建頂層Makefile文件
[cpp]?view plaincopy
fs@ubuntu:~/qiang/makefile/makefileTest$?vi?Makefile??
內容如下:
[cpp]?view plaincopy
CC?=?gcc??????????????????????????????????????????????????????????????????????????????????????????????????????????? SUBDIRS?=?f1?\?? ?????f2?\?? ?????main?\?? ?????obj???? OBJS?=?f1.o?f2.o?main.o?? BIN?=?myapp?? OBJS_DIR?=?obj?? BIN_DIR?=?bin?? export?CC?OBJS?BIN?OBJS_DIR?BIN_DIR?????? ?? all:CHECK_DIR?$(SUBDIRS)?? CHECK_DIR:?? ????mkdir?-p?$(BIN_DIR)?? $(SUBDIRS):ECHO?? ????make?-C?$@??? ?? ECHO:?? ????@echo?$(SUBDIRS)?? ????@echo?begin?compile?? ?? CLEAN:?? ????@$(RM)?$(OBJS_DIR)/*.o?? ????@rm?-rf?$(BIN_DIR)??
?3、進入在f1目錄下創建makefile
[cpp]?view plaincopy
fs@ubuntu:~/qiang/makefile/makefileTest$?cd?f1?? fs@ubuntu:~/qiang/makefile/makefileTest/f1$?vi?f1.c??
內容如下:
[cpp]?view plaincopy
#include?"../include/myinclude.h"???????????????????????????????????????????????????????????????????????????????? ?? void?print1()???? {???? ????printf("Message?f1.c\n");???? ????return;???? }???
[cpp]?view plaincopy
fs@ubuntu:~/qiang/makefile/makefileTest/f1$?vi?Makefile??
內容如下:
[cpp]?view plaincopy
../$(OBJS_DIR)/f1.o:f1.c????????????????????????????????????????????????????????????????????????????????????????? ????$(CC)?-c?$^?-o?$@????
?4、進入f2目錄
[cpp]?view plaincopy
fs@ubuntu:~/qiang/makefile/makefileTest/f1$?cd?../f2?? fs@ubuntu:~/qiang/makefile/makefileTest/f2$?vi?f2.c??
內容如下:
[cpp]?view plaincopy
#include?"../include/myinclude.h"???????????????????????????????????????????????????????????????????????????????? ?? void?print2()???? {???? ????printf("Message?f2.c\n");???? ????return;???? }???
[cpp]?view plaincopy
fs@ubuntu:~/qiang/makefile/makefileTest/f2$?vi?makefile??
內容如下:
[cpp]?view plaincopy
../$(OBJS_DIR)/f2.o:f2.c????????????????????????????????????????????????????????????????????????????????????????? ????$(CC)?-c?$^?-o?$@???
?5、進入main目錄
[cpp]?view plaincopy
<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?? fs@ubuntu:~/qiang/makefile/makefileTest/main$?vi?main.c??
內容如下:
[cpp]?view plaincopy
#include?<stdio.h>??????????????????????????????????????????????????????????????????????????????????????????????? ?? int?main()???? {???? ????print1();???? ????print2();???? ?? ????return?0;??? }????
[cpp]?view plaincopy
fs@ubuntu:~/qiang/makefile/makefileTest/main$?vi?Makefile??
內容如下:
[cpp]?view plaincopy
../$(OBJS_DIR)/main.o:main.c????????????????????????????????????????????????????????????????????????????????????? ????$(CC)?-c?$^?-o?$@????
6、進入obj目錄
[cpp]?view plaincopy
fs@ubuntu:~/qiang/makefile/makefileTest$?cd?obj?? fs@ubuntu:~/qiang/makefile/makefileTest/obj$?ls?? fs@ubuntu:~/qiang/makefile/makefileTest/obj$?vi?Makefile??
內容如下:
[cpp]?view plaincopy
../$(BIN_DIR)/$(BIN)?:?$(OBJS)?? ????$(CC)?-o?$@?$^??
這樣我們總體架構就完成了,我們看一下樹狀圖:
我們執行一下:
[cpp]?view plaincopy
fs@ubuntu:~/qiang/makefile/makefileTest$?make?? Makefile:3:?***?commands?commence?before?first?target.??Stop.??
makefile時常遇到這樣的問題,匯總網上的原因如下:
1. 上一行換行符號 \ 后面有空格
2. 本行前面的空白有非法字符
1)Makefile可能是以命令行開始:以[Tab]字符開始,但不是一個合法的命令行(例如,一個變量的賦值)。命令行必須和規則一一對應。
2)第二種原因可能是一行的第一個非空字符為分號,make會認為此處遺漏了規則的“target: prerequisite”部分。
這兒我們的原因是第一個
改正后編譯:
[cpp]?view plaincopy
fs@ubuntu:~/qiang/makefile/makefileTest$?make?? mkdir?-p?bin?? f1?f2?main?obj?? begin?compile?? make?-C?f1?? make[1]:?Entering?directory?`/home/fs/qiang/makefile/makefileTest/f1'?? gcc?-c?f1.c?-o?../obj/f1.o?? make[1]:?Leaving?directory?`/home/fs/qiang/makefile/makefileTest/f1'?? make?-C?f2?? make[1]:?Entering?directory?`/home/fs/qiang/makefile/makefileTest/f2'?? gcc?-c?f2.c?-o?../obj/f2.o??? make[1]:?Leaving?directory?`/home/fs/qiang/makefile/makefileTest/f2'?? make?-C?main?? make[1]:?Entering?directory?`/home/fs/qiang/makefile/makefileTest/main'?? gcc?-c?main.c?-o?../obj/main.o???? make[1]:?Leaving?directory?`/home/fs/qiang/makefile/makefileTest/main'?? make?-C?obj?? make[1]:?Entering?directory?`/home/fs/qiang/makefile/makefileTest/obj'?? gcc?-o?../bin/myapp?f1.o?f2.o?main.o?? make[1]:?Leaving?directory?`/home/fs/qiang/makefile/makefileTest/obj'??
執行一下:
[cpp]?view plaincopy
fs@ubuntu:~/qiang/makefile/makefileTest$?cd?bin/?? fs@ubuntu:~/qiang/makefile/makefileTest/bin$?ls?? myapp?? fs@ubuntu:~/qiang/makefile/makefileTest/bin$?./myapp?? Message?f1.c?? Message?f2.c?? fs@ubuntu:~/qiang/makefile/makefileTest/bin$?
總結
以上是生活随笔為你收集整理的Linux 应用---make及makefile的编写的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。