《讓makefile變得簡單》系列--第一章

引言

makefile是一種文件類型,它指導怎麼編譯並且鏈接程序。所以,makefile文件只是告訴make怎麼去執行。
在本章節中,我們討論一個簡單的makefile,它描述了怎麼編譯和鏈接text editor。texteditor包含了8個C源文件和3個H文件。它還可以在makefile顯式指定了命令時執行更加複雜的命令。
當make根據makefile編譯文件時,如果文件發生了變動,那麼文件需要重新編譯。如果時H文件發生了變動,那麼所有依賴該H的C文件需要重新編譯。每個C文件都對應編譯出一個O文件。任何一個C源文件重新編譯生成O文件後,都需要把所有的O文件重新鏈接一次生成texteditor。

C文件是C源文件.c,H文件是頭文件.h,O文件是object文件.o

makefile長啥樣?

makefile的規則基本格式如下:

target ... : prerequisites ...
	recipe
	...

target,文件名,通常是object文件名或者是可執行程序的文件名,也可以是一個動作(action)的名字。例如makefile文件中通常會有一個clean動作,用於清理臨時或者過程中文件。
prerequisites,依賴文件,作爲輸入文件,用於產生左邊的target。但是它也不是必須的。
recipe,動作與規則,即按照規則把依賴文件作爲輸入文件去生成輸出文件target。
上面的格式中有幾點需要注意:

  1. 如果target是文件列表,那麼文件之間使用空格隔開,多餘空格被忽略
  2. 如果依賴文件是多個文件,那麼文件之間使用空格隔開,多餘空格被忽略
  3. 冒號前後建議使用一個空格
  4. recipe前是tab而不是空格。默認情況下是tab符合後再跟這recipe,這樣make在讀makefile文件時,就知道這一行是動作。如果你不喜歡tab,那麼makefile有一個特殊變量.RECIPEPREFIX,可以用你喜歡的符合設定這個變量。
  5. recipe可以有多個。在每個recipe獨佔一行時,每行的開始都要使用一個tab佔位。
    通常情況下,只要依賴文件發生了變動,recipe就會按照規則生成targe。在recipe中,規則顯式或者隱式地明確什麼時候以及怎麼樣把依賴文件編譯並鏈接爲對應的target。

一個簡單的makefile例子

一個edit程序依賴8個C文件,3個H文件。

edit : main.o kbd.o command.o display.o \
       insert.o search.o files.o utils.o
	cc -o edit main.o kbd.o command.o display.o \
          insert.o search.o files.o utils.o
main.o : main.c defs.h
	cc -c main.c
kbd.o : kbd.c defs.h command.h
	cc -c kbd.c
command.o : command.c defs.h command.h
	cc -c command.c
display.o : display.c defs.h buffer.h
	cc -c display.c
insert.o : insert.c defs.h buffer.h
	cc -c insert.c
search.o : search.c defs.h buffer.h
	cc -c search.c
files.o : files.c defs.h buffer.h command.h
	cc -c files.c
utils.o : utils.c defs.h
	cc -c utils.c
clean :
	rm edit main.o kbd.o command.o display.o \
       insert.o search.o files.o utils.o

我們先分析這個makefile:

  1. 在makefile文件中,如果某一行很長,可以使用\換行,這表示\前後的2行是同一個邏輯行。在makefile語法中,\前後所有的空格都會被壓縮成一個空格。
 edit : main.o kbd.o command.o display.o \
       insert.o search.o files.o utils.o

make在讀makefile文件時,其實看到的是

 edit : main.o kbd.o command.o display.o insert.o search.o files.o utils.o
  1. 在linux環境下的命令行,切換到makefile文件的路徑下執命令make,生成edit可執行程序。執行命令make clean把edit和其他的object文件一起刪除
  2. edit作爲target,main.o等總共8個object文件是依賴文件,recipe是
cc -o edit main.o kbd.o command.o display.o \
          insert.o search.o files.o utils.o
  1. 如果target是文件時,如果依賴文件有更新,那麼target需要重新編譯和鏈接。例如main.c更新,那麼main.o需要先更新後再更新edit。
  2. clean不是文件,僅僅是一個動作名稱,它沒有任何依賴文件,只有recipe中描述了該動作的規則。
問題:如果你的工程文件中有個clean.c文件,那麼在makefile文件中就有clean這個target,那同時又要包含動作clean。make在閱讀makefile時,怎麼區分哪個clean是動作,哪個clean是target呢?

make是怎麼處理makefile文件的呢?

make逐行閱讀makefile,直到遇到第一個target,把它作爲默認target,並且在這個target生成之後停止編譯其他的target。例如上一小節中的例子,make逐行掃描makefile,它遇到的第一個target是edit,所以make按照recipe把依賴文件作爲輸入生成edit,但是依賴文件也是targe,所以先把依賴文件全部更新生成後再最終生成edit。我們把上面的makefile稍作改動:

main.o : main.c defs.h
	cc -c main.c
edit : main.o kbd.o command.o display.o \
       insert.o search.o files.o utils.o
	cc -o edit main.o kbd.o command.o display.o \
          insert.o search.o files.o utils.o
kbd.o : kbd.c defs.h command.h
	cc -c kbd.c
command.o : command.c defs.h command.h
	cc -c command.c
display.o : display.c defs.h buffer.h
	cc -c display.c
insert.o : insert.c defs.h buffer.h
	cc -c insert.c
search.o : search.c defs.h buffer.h
	cc -c search.c
files.o : files.c defs.h buffer.h command.h
	cc -c files.c
utils.o : utils.c defs.h
	cc -c utils.c
clean :
	rm edit main.o kbd.o command.o display.o \
       insert.o search.o files.o utils.o

那麼會發生什麼呢?沒錯,make執行完成第一個target,main.o後,停止。那麼edit不會生成,它依賴的其他的那些文件也不會生成。針對這個makefile,有2種辦法:
5. 直接執行命令make edit
6. 在該makefile文件中設定特殊變量.DEFAULT_GOAL。該變量設定後,make把它作爲默認目標,那麼直接執行make時,就不會在生成了main.o後停止。

更新規則

recipe中描述了什麼時候以及怎麼樣去生成target。什麼時候會觸發recipe中的動作呢?當target不存在或者是依賴文件中任何一個文件都比target更新時,就會觸發動作。注意,依賴文件本身也可以是target,比如edit依賴main.o,而main.o也是一個target。
例如上節中的例子,edit總共依賴8個O文件。假設除了main.o和kbd.o文件外,其他文件都存在且在上一次生成後再也沒有發生過變化。所以:

  1. 如果edit不存在,觸發recipe中動作
  2. 如果main.o或者kbd.o不存在,觸發recipe中動作
  3. 如果edit、main.o、kbd.o都存在,但是edit比它們舊,觸發
  4. 如果edit、main.o、kbd.o都存在,並且edit比它新,那麼會觸發嗎?因爲main.o和kbd.o都是target,所以它們也要檢查。
    其實真正的順序應該是這樣:edit依賴kbd.o,kbd.o依賴kbd.c、defs.h、command.h,按照上面描述的更新規則檢查kbd.o是否需要生成或者更新,檢查完成後再檢查edit與kbd.o。以此類推edit中其他的依賴文件。
    ##變量讓makefile更加簡潔
    不知道你注意了沒,上節中的makefile文件,把每一個C文件和H文件都列舉出來了,而且edit生成的規則中,依賴文件和recipe重複描述了很多object文件。在實際的工程項目中,少則幾百個源文件,多則上千個,你難道也一個一個列舉出來?太累了。
edit : main.o kbd.o command.o display.o \
       insert.o search.o files.o utils.o
	cc -o edit main.o kbd.o command.o display.o \
          insert.o search.o files.o utils.o

在makefile的語法中,標準的做法是,把目標文件用變量名objects、OBJECTS、objs、OBJS、obj、OBJ等表示。定義這樣一個變量

objects = main.o kbd.o command.o display.o \
          insert.o search.o files.o utils.o

我們把上節中的makefile重寫一次:

objects = main.o kbd.o command.o display.o \
          insert.o search.o files.o utils.o
edit : $(objects)
	cc -o edit $(objects)
main.o : main.c defs.h
	cc -c main.c
kbd.o : kbd.c defs.h command.h
	cc -c kbd.c
command.o : command.c defs.h command.h
	cc -c command.c
display.o : display.c defs.h buffer.h
	cc -c display.c
insert.o : insert.c defs.h buffer.h
	cc -c insert.c
search.o : search.c defs.h buffer.h
	cc -c search.c
files.o : files.c defs.h buffer.h command.h
	cc -c files.c
utils.o : utils.c defs.h
	cc -c utils.c
clean :
	rm edit main.o kbd.o command.o display.o \
       insert.o search.o files.o utils.o

問題只解決了一半。如果工程項目中源文件很多,那還是得一條一條規則寫出來並列出源文件名。

讓make自己推導規則

  1. 在依賴文件中不必列出源文件,根據target的名,make推導出源文件名
  2. 在recipe中不必列出規則動作,make使用默認動作"cc -c"
objects = main.o kbd.o command.o display.o \
          insert.o search.o files.o utils.o
edit : $(objects)
	cc -o edit $(objects)
main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h

.PHONY : clean
clean :
	rm edit $(objects)

例如,make需要編譯main.o這個target時,認爲存在main.c這個源文件。其他的target也是如此。

還有一種寫法,更加簡潔

objects = main.o kbd.o command.o display.o \
          insert.o search.o files.o utils.o
          
edit : $(objects)
	cc -o edit $(objects)
	
$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h

def.h是所有object共同的依賴文件。command.h只被kbd.o、command.o、files.o依賴。buffer.h只被display.o、insert.o、search.o、files.o依賴

清理

在上上節中,提出一個問題:如果你的工程文件中有個clean.c文件,那麼在makefile文件中就有clean這個target,那同時又要包含動作clean。make在閱讀makefile時,怎麼區分哪個clean是動作,哪個clean是target呢?
答案是.PHONY。
在makefile文件中,不會僅僅只描述怎麼生成program,還涉及到刪除目標文件的規則。所以會在makefile文中添加clean的規則,當makefile中存在clean作爲target,爲了避免讓make感到迷惑,使用.PHONY來修飾clean的動作

.PHONY : clean
clean :
	-rm edit $(objects)

並且clean規則通常只放在makefile中最後面,避免因爲正常的make動作而執行到。

targert1 : command.h
.PHONY : clean
clean:
	-rm target1 target2
target2 : def.h
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章