Makefile 入門
掃描二維碼
隨時(shí)隨地手機(jī)看文章
點(diǎn)擊上方藍(lán)字關(guān)注我哦~
01
前言
今天抽空研究了下 Makefile,在這里整理一下各處搜到的資料,以備將來復(fù)習(xí)時(shí)快速上手,同時(shí)也幫助和我一樣的初學(xué)者們節(jié)約時(shí)間。
02
準(zhǔn)備工作
首先,假設(shè)我們有如下幾個(gè)代碼文件:main.cpp functions.h function1.cpp function2.cpp
--- functions.h ---
// functions.h
void print_hello();
int factorial(int n);
--- function1.cpp ---
// function1.cpp
int factorial(int n){
if (n!=1)
return n*factorial(n-1);
else return 1;
}
--- function2.cpp ---
//function2.cpp
void print_hello()
{
std::cout << "hello world" << std::endl;
}
--- main.cpp ---
//main.cpp
int main(){
print_hello();
std::cout << "this is main" << std::endl;
std::cout << "The factorial of 5 is " << factorial(5) << std::endl;
return 0;
}
03
不用makefile如何編譯
如果不用 makefile,則需要按照下面的方式編譯上述代碼:
g++ -c function1.cpp
g++ -c function2.cpp
g++ -c main.cpp
g++ -o hello main.o function1.o function2.o
其中,g++ -c function1.cpp 會(huì)將源碼編譯成名為 function1.o 對(duì)象文件。如果不想采用默認(rèn)的命名,也可以自定義文件名,例如:
g++ -c function1.cpp -o fun1.o
也可以用一行命令整合編譯、鏈接的步驟:
g++ -o hello main.cpp function1.cpp function2.cpp
這種方式有很多弊端,例如:
每次編譯、鏈接都需要手動(dòng)敲的很多命令。
當(dāng)工程量很大時(shí),編譯整個(gè)工程需要花很久。而我們往往并不是每次都修改了所有源文件,因此希望程序自動(dòng)編譯那些被修改的源碼,而沒被修改的部分不要浪費(fèi)時(shí)間重新編譯。
為了解決上述第一個(gè)問題,我們可以把所有編譯需要的命令保存到文件中,編譯時(shí)一鍵執(zhí)行。針對(duì)第二個(gè)問題,我們希望有一個(gè)軟件,自動(dòng)檢測哪些源文件被修改過,然后自動(dòng)把它們挑出來選擇性地編譯。而 make 命令通過檢測代碼文件的時(shí)間戳,決定是否編譯它。
04
使用Makefile編譯
第一版Makefile
首先需要確定 Makefile 的名字,需要設(shè)置成 Makefile 或者 makefile,而不能是其它版本(MakeFile, Make_file, makeFile,... )。其次,需要注意的是 Makefile 是縮進(jìn)敏感的,在行首一定不能隨便打空格。下面我們看一下第一版 Makefile。
# Makefile (#號(hào)為注釋)
all:
g++ -o hello main.cpp function1.cpp function2.cpp
clean:
rm -rf *.o hello
(注意上面代碼片段的縮進(jìn),是一個(gè)<tab>而不是4個(gè)或者8個(gè)空格。)
其中 all 、clean的術(shù)語為 target,我也可以隨意指定一個(gè)名字,例如 abc,真正執(zhí)行編譯的是它下面縮進(jìn)行的命令。我們可以看到,這個(gè)命令和我們?cè)诿钚兄惺謩?dòng)敲的沒有任何區(qū)別。因此,通過這個(gè)簡單的 Makefile,就可以省去了每次手動(dòng)敲命令的痛苦:只需要在命令行敲下 make 回車,即可完成編譯。
clean 表示清除編譯結(jié)果,它下方就是普通的命令行刪除文件命令。命令行輸入 make 將默認(rèn)執(zhí)行第一個(gè) target (即 all)下方的命令;如要執(zhí)行清理操作,則需要輸入 make clean,指定執(zhí)行 clean 這個(gè) target 下方的命令。
這個(gè) Makefile 雖然可以省去敲命令的痛苦,卻無法選擇性編譯源碼。因?yàn)槲覀儼阉性次募家还赡X塞進(jìn)了一條命令,每次都要編譯整個(gè)工程,很浪費(fèi)時(shí)間。第二版 Makefile 將解決這個(gè)問題。
第二版Makefile
既然我們希望能夠選擇性地編譯源文件,就不能像上一節(jié)那樣把所有源文件放在一條命令里編譯了,而是要分開寫:
all: hello
hello: main.o function1.o function2.o
g++ main.o function1.o function2.o -o hello
main.o: main.cpp
g++ -c main.cpp
function1.o: function1.cpp
g++ -c function1.cpp
function2.o: function2.cpp
g++ -c function2.cpp
clean:
rm -rf *.o hello
上面的 Makefile 包含了一條重要的語法:<target>:<dependencies>。即,目標(biāo):目標(biāo)依賴的文件。
順著代碼捋一下邏輯:
命令行輸入 make ,將默認(rèn)執(zhí)行 all 這個(gè) target;
而 all 這個(gè) target 依賴于 hello,hello 在當(dāng)前目錄下并不存在,于是程序開始往下讀取命令..……終于找到了 hello 這個(gè) target;
正待執(zhí)行 hello 這個(gè) target 的時(shí)候,卻發(fā)現(xiàn)它依賴于 main.o,function1.o,function2.o 這三個(gè)文件,而它們?cè)诋?dāng)前目錄下都不存在,于是程序繼續(xù)向下執(zhí)行;
-
遇到 main.o target,它依賴于 main.cpp。而 main.cpp 是當(dāng)前目錄下存在的文件,終于可以編譯了,生成 main.o 對(duì)象文件。后面兩個(gè)函數(shù)以此類推,都編譯好之后,再回到 hello target,連接各種二進(jìn)制文件,生成 hello 文件。
第一次編譯的時(shí)候,命令行會(huì)輸出:
g++ -c main.cpp
g++ -c function1.cpp
g++ -c function2.cpp
g++ main.o function1.o function2.o -o hello
證明所有的源碼都被編譯了一遍。假如我們對(duì) main.cpp 做一點(diǎn)修改,再重新 make(重新 make 前不要 make clean),則命令行只會(huì)顯示:
g++ -c main.cpp
g++ main.o function1.o function2.o -o hello
這樣,我們就發(fā)揮出 Makefile 選擇性編譯的功能了。下面,將介紹如何在 Makefile 中聲明變量(declare variable)
第三版Makefile
我們希望將需要反復(fù)輸入的命令整合成變量,用到它們時(shí)直接用對(duì)應(yīng)的變量替代,這樣如果將來需要修改這些命令,則在定義它的位置改一行代碼即可。
CC = g++
CFLAGS = -c -Wall
LFLAGS = -Wall
all: hello
hello: main.o function1.o function2.o
$(CC) $(LFLAGS) main.o function1.o function2.o -o hello
main.o: main.cpp
$(CC) $(CFLAGS) main.cpp
function1.o: function1.cpp
$(CC) $(CFLAGS) function1.cpp
function2.o: function2.cpp
$(CC) $(CFLAGS) function2.cpp
clean:
rm -rf *.o hello
上面的 Makefile 中,開頭定義了三個(gè)變量:CC,CFLAGS,和 LFLAGS。其中 CC 表示選擇的編譯器(也可以改成 gcc);CFLAGS 表示編譯選項(xiàng),-c 即 g++ 中的 -c,-Wall 表示顯示編譯過程中遇到的所有 warning;LFLAGS 表示鏈接選項(xiàng),它就不加 -c 了。這些名字都是自定義的,真正起作用的是它們保存的內(nèi)容,因此只要后面的代碼正確引用,將它們定義成阿貓阿狗都沒問題。容易看出,引用變量名時(shí)需要用 $() 將其括起來,表示這是一個(gè)變量名。
第四版Makefile
第三版的 Makefile 還是不夠簡潔,例如我們的 dependencies 中的內(nèi)容,往往和 g++ 命令中的內(nèi)容重復(fù):
hello: main.o function1.o function2.o
$(CC) $(LFLAGS) main.o function1.o function2.o -o hello
我們不想敲那么多字,能不能善用 <target>:<dependencies> 中的內(nèi)容呢?這就需要引入下面幾個(gè)特殊符號(hào)了(也正是這些特殊符號(hào),把 Makefile 搞得像是天書,嚇退了很多初學(xué)者):
$@ ,$<,$^
例如我們有 target: dependencies 對(duì):all: library.cpp main.cpp
$@ 指代 all ,即 target
$< 指代 library.cpp, 即第一個(gè) dependency
$^ 指代 library.cpp 和 main.cpp,即所有的 dependencies
因此,本節(jié)開頭的 Makefile 片段可以改為:
hello: main.o function1.o function2.o
$(CC) $(LFLAGS) $^ -o $@
而第四版 Makefile 就是這樣的:
CC = g++
CFLAGS = -c -Wall
LFLAGS = -Wall
all: hello
hello: main.o function1.o function2.o
$(CC) $(LFLAGS) $^ -o $@
main.o: main.cpp
$(CC) $(CFLAGS) $<
function1.o: function1.cpp
$(CC) $(CFLAGS) $<
function2.o: function2.cpp
$(CC) $(CFLAGS) $<
clean:
rm -rf *.o hello
但是手動(dòng)敲文件名還是有點(diǎn)麻煩,能不能自動(dòng)檢測目錄下所有的 cpp 文件呢?此外 main.cpp 和 main.o 只差一個(gè)后綴,能不能自動(dòng)生成對(duì)象文件的名字,將其設(shè)置為源文件名字后綴換成 .o 的形式?
第五版Makefile
想要實(shí)現(xiàn)自動(dòng)檢測 cpp 文件,并且自動(dòng)替換文件名后綴,需要引入兩個(gè)新的命令:patsubst 和 wildcard。
wildcard
wildcard 用于獲取符合特定規(guī)則的文件名,例如下面的代碼:
SOURCE_DIR = . # 如果是當(dāng)前目錄,也可以不指定
SOURCE_FILE = $(wildcard $(SOURCE_DIR)/*.cpp)
target:
@echo $(SOURCE_FILE)
make 后發(fā)現(xiàn),輸出的為當(dāng)前目錄下所有的 .cpp 文件:
./function1.cpp ./function2.cpp ./main.cpp
其中 @echo 前加 @是為了避免命令回顯,上文中 make clean 調(diào)用了 rm -rf 會(huì)在 terminal 中輸出這行命令,如果在 rm 前加了 @ 則不會(huì)輸出了。
patsubst
patsubst 應(yīng)該是 pattern substitution 的縮寫。用它可以方便地將 .cpp 文件的后綴換成 .o。它的基本語法是:$(patsubst 原模式,目標(biāo)模式,文件列表)。運(yùn)行下面的示例:
SOURCES = main.cpp function1.cpp function2.cpp
OBJS = $(patsubst %.cpp, %.o, $(SOURCES))
target:
@echo $(SOURCES)
@echo $(OBJS)
輸出的結(jié)果為:
main.cpp function1.cpp function2.cpp
main.o function1.o function2.o
綜合上述兩個(gè)命令,我們可以升級(jí)到第五版 Makefile:
OBJS = $(patsubst %.cpp, %.o, $(wildcard *.cpp))
CC = g++
CFLAGS = -c -Wall
LFLAGS = -Wall
all: hello
hello: $(OBJS)
$(CC) $(LFLAGS) $^ -o $@
main.o: main.cpp
$(CC) $(CFLAGS) $< -o $@
function1.o: function1.cpp
$(CC) $(CFLAGS) $< -o $@
function2.o: function2.cpp
$(CC) $(CFLAGS) $< -o $@
clean:
rm -rf *.o hello
然而這一版的 Makefile 還有提升空間,它的 main.o,function1.o,function2.o 使用的都是同一套模板,不過換了個(gè)名字而已。第六版的 Makefile 將處理這個(gè)問題。
第六版Makefile
這里要用到 Static Pattern Rule,其語法為:
targets: target-pattern: prereq-patterns
其中 targets 不再是一個(gè)目標(biāo)文件了,而是一組目標(biāo)文件。而 target-pattern 則表示目標(biāo)文件的特征。例如目標(biāo)文件都是 .o 結(jié)尾的,那么就將其表示為 %.o,prereq-patterns (prerequisites) 表示依賴文件的特征,例如依賴文件都是 .cpp 結(jié)尾的,那么就將其表示為 %.cpp。
通過上面的方式,可以對(duì) targets 列表中任何一個(gè)元素,找到它對(duì)應(yīng)的依賴文件,例如通過 targets 中的 main.o,可以鎖定到 main.cpp。
下面是第六版的 Makefile:
OBJS = $(patsubst %.cpp, %.o, $(wildcard *.cpp))
CC = g++
CFLAGS = -c -Wall
LFLAGS = -Wall
all: hello
hello: $(OBJS)
$(CC) $(LFLAGS) $^ -o $@
$(OBJS):%.o:%.cpp
$(CC) $(CFLAGS) $< -o $@
clean:
rm -rf *.o hello
05
其它
看到有的 Makefile 設(shè)置了 -lm 的 flag,查閱資料發(fā)現(xiàn)表示連街 math 庫,因?yàn)榇a中可能
例如
g++ -o out fun.cpp -lm
CC = g++
LIBS = -lm
out: fun.cpp
$(CC) -o $@ $^ $(LIBS)
/ The End /
本文介紹了如何寫 Makefile,主要的知識(shí)點(diǎn)有:
在 Makefile 中定義變量并引用
$^,$@,$< 的含義
wildcard,patsubst 的用法
-
static pattern rule:targets: target-pattern: prereq-patterns
公眾號(hào)回復(fù):"Makefile" 獲取本文源文件下載鏈接。
推薦閱讀:
免責(zé)聲明:本文轉(zhuǎn)載自知乎,版權(quán)歸原作者所有。如涉及作品版權(quán)問題,請(qǐng)與我聯(lián)系刪除。
掃碼關(guān)注我們
看更多嵌入式案例
喜歡本篇內(nèi)容請(qǐng)給我們點(diǎn)個(gè)在看
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場,如有問題,請(qǐng)聯(lián)系我們,謝謝!