這篇文章并不是介紹怎編寫Makefile, 是理解 “基于目標(biāo)分層” 的方式理解來理解一個工具,定義一個設(shè)計或者部署一套代碼的。
說明一下,Makefile最初是用來解決C語言的編譯問題的,所以和C的關(guān)系特別密切,但并不是說Makefile只能用來解決C的編譯問題。你用來處理Java一點(diǎn)問題沒有,但對于Java,顯然ant比Makefile處理得更好。但是那是細(xì)節(jié),你理解了Makefile,理解ant就沒有什么難度了。Makefile本身的格式也不是什么標(biāo)準(zhǔn),不同的make工具對Makefile本身怎么寫細(xì)節(jié)是不一樣的。本文介紹的是這個工具的思想原理,細(xì)節(jié)你要自己看對應(yīng)的手冊。
Makefile解決的是編譯的問題。編譯有什么問題呢?比如說,你有3個C文件。foo.c, bar.c, main.c三個C文件,你要編譯成一個app.exe,則會有這樣的命令:
gcc -Wall -c foo.c -o foo.o
gcc -Wall -c bar.c -o bar.o
gcc -Wall -c main.c -o main.o
gcc main.o foo.o bar.o -lpthread -o app.exe
按Unix程序員的慣例,凡是要一次次重新執(zhí)行的命令,都應(yīng)該寫成腳本,變成“一個動作”。所以,你會把上面這個命令序列寫成一個build.sh,每次編譯你只要執(zhí)行這個腳本問題就解決了。
但是,這個腳本依然有問題,假設(shè)我修改了foo.c,但我沒有修改bar.c和main.c,那么執(zhí)行這個腳本是很浪費(fèi)的,因為它會無條件也重新編譯bar.c和main.c。
所以,這個腳本更合理的寫法應(yīng)該是這樣的:
[ foo.o -ot foo.c ] && gcc -Wall -c foo.c -o foo.o
[ bar.o -ot bar.c ] && gcc -Wall -c bar.c -o bar.o
[ main.o -ot main.o] && gcc -Wall -c main.c -o main.o
[ app.executable -ot main.o ] && [ app.executable -ot foo.o ] && [ app.executable -ot bar.o ] && gcc main.o foo.o bar.o -lpthread -o app.executable
這很復(fù)雜是不是?同樣按Unix程序員的一般作風(fēng),如果你面對一個問題,不要嘗試重新去定義這個問題,而是看它和原來的問題相比,多出來的問題是什么,嘗試解決那個多出來的問題就好了。那么這里,多出來的問題就是文件修改時間比較。這個就是Makefile要解決的基本問題了。我們定義一種新的“腳本語言”(只是不用sh/bash/tch來解釋,而是用make來解釋),可以用很簡單的方法來說明我們需要做的文件比較。這樣上面的腳本就可以寫成這個樣子了:
foo.o: foo.c
gcc -Wall -c foo.c -o foo.o
bar.o: bar.c
gcc -Wall -c bar.c -o woo.o
main.o: main.c
gcc -Wall -c main.c -o main.o
app.executable: foo.o bar.o main.o
這就是Makefile解決的原始問題。Makefile不是必須的,但它能減少你很多麻煩。
Make工具需要一個稱為makefile的文件來告訴Make如何工作。一般來說,makefile告訴Make如何去編譯并且鏈接一個程序。
了解以上四個原則是很有必要的,當(dāng)我們編寫makefile的時候,應(yīng)該依照以上原則編寫。
target... : prerequisites...
recipe
...
...
每條規(guī)則一般都由一個或者多個target(目標(biāo))、prerequisites(依賴)以及recipe(處方)組成。當(dāng)Make通過比較target和prerequisites的新舊來決定是否執(zhí)行本條規(guī)則內(nèi)的recipe,如果prerequisites比target的修改日期更新,則recipe被執(zhí)行,否則不執(zhí)行規(guī)則內(nèi)的recipe。有一條值得注意:每一個recipe前面一定要先加一個tab制表符.
1、在目標(biāo)為edit的規(guī)則中,使用了''將一個長行分隔為兩行以方便閱讀。
2、默認(rèn)情況下,Make默認(rèn)從Makefile的第一條規(guī)則開始執(zhí)行,也就是說,當(dāng)我們在shell中只敲下"make"并回車,make將會自動尋址當(dāng)前目錄下的Makefile并從其中的第一條規(guī)則開始執(zhí)行。當(dāng)然,我們也可以指定開始執(zhí)行的規(guī)則
Example: make clean //從目標(biāo)為clean的規(guī)則開始執(zhí)行
3、在默認(rèn)開始執(zhí)行第一條規(guī)則edit時,由于目標(biāo)依賴一些.o文件,而這些.o文件又有自己的更新規(guī)則,于是會先觸發(fā)執(zhí)行.o文件自己的更新規(guī)則,最后再回過頭來執(zhí)行edit。你可以將它理解為一個遞歸的過程。
4、很明顯,由于clean這個目標(biāo)并不是任何其他目標(biāo)的依賴同時也不是第一條規(guī)則的目標(biāo),那么除非指定從這個規(guī)則開始執(zhí)行,否則這個規(guī)則永遠(yuǎn)不會被執(zhí)行;同樣的,由于clean的規(guī)則沒有任何依賴,這個規(guī)則執(zhí)行時永遠(yuǎn)不會觸發(fā)其他規(guī)則的執(zhí)行。
規(guī)則是Makefile的重要組成部分,Make通過讀取Makefile中的規(guī)則來決定如何更新工程文件。一般來說,一條規(guī)則分為三個部分:target、prerequisites和recipe.
如果沒有特別指定從哪條規(guī)則開始執(zhí)行,Make總是執(zhí)行文件中的第一條規(guī)則,除此之外,其他規(guī)則的書寫順序?qū)?zhí)行的先后沒有影響(Make只在需要的時候執(zhí)行需要的規(guī)則,而不是按書寫順序的先后執(zhí)行).
雖然Makefile中的第一條規(guī)則是最先執(zhí)行的,卻往往是最后執(zhí)行完的,因為第一條規(guī)則中的依賴部分經(jīng)常有自己的更新規(guī)則,而那更新規(guī)則里的依賴又有對應(yīng)的更新規(guī)則,這樣層層遞歸,第一條規(guī)則觸發(fā)了其他規(guī)則的執(zhí)行,其他規(guī)則執(zhí)行完后,再轉(zhuǎn)回第一條規(guī)則繼續(xù)執(zhí)行。我們編譯一份程序的時候也是這樣,總是先編譯每個子文件,再回過頭將它們連接起來組成程序,因此,Makefile中的第一條規(guī)則一般是用于觸發(fā)整個程序的編譯(并且第一條規(guī)則的目標(biāo)一般起名為:all,意指更新整個工程)
toolchain=gcc
OBJS=main.o modbus_cmd_generation.o software_crc.o
all:$(OBJS)
gcc modbus_cmd_generation.o main.o software_crc.o -o app
modbus_cmd_generation.o:modbus_cmd_generation.c modbus_cmd_generation.h software_crc.o
gcc modbus_cmd_generation.c -c
software_crc.o:software_crc.c software_crc.h
gcc software_crc.c -c
main.o:main.c
@echo
@echo compile the toolchain: $(toolchain)
@echo
gcc main.c -c
clean:
rm $(OBJS) app
在規(guī)則中,可以通過'$'來調(diào)用定義好的變量(如果希望真的打一個'$'而不是做變量引用,需要打"$$".
每條規(guī)則都有自己的recipes,其由一條或者多條shell指令組成,按照書寫順序執(zhí)行。通常執(zhí)行的結(jié)果是使得target被更新,默認(rèn)調(diào)用/bin/sh來執(zhí)行shell指令,可以手動更改.
也就是說,在Makefile中既有shell的指令,又有makefile自己的語句,所以說makefile遵循兩種語法:在recipes中使用shell的語法,在其他部分遵循makefile自己的語法。make對于makefile中的recipes只進(jìn)行有限的處理,然后就會丟給shell執(zhí)行。
每個recipe一般用[TAB]字符開頭,當(dāng)然還有其他寫法,不過我覺得目前知道這個就夠用了
以[TAB]開頭的空行同樣算一條recipe,稱為 empty recipe
在recipe中‘#'后的內(nèi)容不會被作為注釋,而是會原封不動的傳遞給shell
以[TAB]開頭,在recipe中定義的變量同樣會被當(dāng)作shell變量原封不動傳遞給shell,而不是作為makefile的變量
今天的分享就到這里啦,EBYTE人每一天都致力于更好的助力物聯(lián)化、智能化、自動化的發(fā)展,提升資源利用率,更多產(chǎn)品更多資料,感興趣的小伙伴可以登錄我們的億佰特官網(wǎng)進(jìn)行了解,還有客服小姐姐在線答疑哦!