本文參考文章:
Makefile由淺入深–教程、乾貨
Makefile文件是什麼?
1. Makefile文件是什麼?
Makefile 可以簡單的認爲是一個工程文件的編譯規則,描述了整個工程的編譯和鏈接等規則。其中包含了那些文件需要編譯,那些文件不需要編譯,那些文件需要先編譯,那些文件需要後編譯,那些文件需要重建等等。編譯整個工程需要涉及到的,我們在 Makefile 中都可以進行描述。換句話說,有了 Makefile 可以使得我們的項目工程的編譯變得自動化,不用每次都手動輸入一堆源文件和參數。
2. 一個簡單的Makefile demo
需要說明一下,文檔目錄結構是用戶目錄HOME下有src incl bin lib。
src:源碼
incl:頭文件
bin:執行碼
lib:靜態/動態庫
這是大家最常見的Linux編程目錄結構,以下代碼編譯都是依據這個結構。
Makefile代碼:
hello:hello.c
gcc -I${HOME}/incl -c hello.c
gcc -o hello hello.o
rm -f hello.o
mv hello ${HOME}/bin
沒接觸過Makefile的同學肯定能看出,這段代碼不就是把編譯、鏈接、刪除、移動寫成shell腳本執行嗎?沒錯的,把第一行去掉,其他代碼粘貼到shell腳本里同樣可以編譯成功,一點問題都沒有。
3. Makefile結構說明
Makefile裏主要包含了五個東西:變量定義、顯式規則、隱晦規則、文件指示和註釋。
1、變量的定義。在Makefile中我們要定義一系列的變量,變量一般都是字符串,這個有點像C語言中的宏,當Makefile被執行時,其中的變量都會被擴展到相應的引用位置上。
2、顯式規則。顯式規則說明了,如何生成一個或多的的目標文件。這是由Makefile的書寫者明顯指出,要生成的文件,文件的依賴文件,生成的命令。 剛纔寫的疑似shell腳本的Makefile全部都是顯示規則。
3、隱晦規則。由於我們的make有自動推導的功能,所以隱晦的規則可以讓我們比較粗糙地簡略地書寫Makefile,這是由make所支持的。
4、文件指示。其包括了三個部分,一個是在一個Makefile中引用另一個Makefile,就像C語言中的include一樣。
5、註釋。Makefile中只有行註釋,和UNIX的Shell腳本一樣,其註釋是用“#”字符,這個就像C/C++中的“//”一樣。如果你要在你的Makefile中使用“#”字符。
4. 複雜一些的Makefile
根據上面的結構說明,我們對Makefile一層一層的改寫,首先是隱晦規則,告訴大家其中一種用法:
.SUFFIXES: .cpp .c
.cpp.o:
g++ ${INCL} -c $<
.c.o:
gcc ${INCL} -c $<
這個隱晦規則其實就是告訴大家,後綴爲cpp的文件怎麼編譯成.o,後綴爲c的文件怎麼編譯成.o。
到目前爲止的Makefile已經有模有樣了,Makefile代碼如下。
#隱含規則
INCL=-I${HOME}/incl
.SUFFIXES: .cpp .c
.cpp.o:
g++ ${INCL} -c $<
.c.o:
gcc ${INCL} -c $<
#C++編譯
hellocpp:hellocpp.o
echo "開始編譯"
g++ -o hellocpp hellocpp.o
rm -f hellocpp.o
mv hellocpp ${HOME}/bin
echo "編譯結束"
#C編譯
hello:hello.o
echo "開始編譯"
gcc -o hello hello.o
rm -f hello.o
mv hello ${HOME}/bin
echo "編譯結束"
細心的同學會發現剛纔有個“$<”,如果看的有些蒙圈,那一定要了解預定義變量,下面這些是常用的預定義變量。
$* 不包含擴展名的目標文件名稱。
$+ 所有的依賴文件,以空格分開,並以出現的先後爲序,可能包含重複的依賴文件。
$< 第一個依賴文件的名稱。
$? 所有的依賴文件,以空格分開,這些依賴文件的修改日期比目標的創建日期晚。
$@ 目標的完整名稱。
$^ 所有的依賴文件,以空格分開,不包含重複的依賴文件。
$% 如果目標是歸檔成員,則該變量表示目標的歸檔成員名稱。
Makefile的最終展現
實際編譯結果
看着挺亂的吧,稍微改一改
#最後形成的Makefile
INCL=-I${HOME}/incl
BIN=$(HOME)/bin
OBJ1=hellocpp.o
OBJ2=hello.o
.SUFFIXES: .cpp .c
.cpp.o:
g++ ${INCL} -c $<
.c.o:
gcc ${INCL} -c $<
all: hellocpp hello
#C++編譯
hellocpp:${OBJ1}
@echo "============開始編譯============"
g++ -o $@ $?
@rm -f ${OBJ1}
@mv $@ ${BIN}
@echo "============編譯結束============"
@echo ""
#C編譯
hello:${OBJ2}
@echo "============開始編譯============"
gcc -o $@ $?
@rm -f ${OBJ2}
@mv $@ ${BIN}
@echo "============編譯結束============"
@echo ""
命令前加@,表示當前命令不顯示,最後編譯結果是這樣。