實際項目開發中,源碼文件數量都不會只有少數幾個。如果一個由上百個文件的代碼構成的項目,只有一個活少數幾個文件進行了修改,再從頭到尾將每一個文件都重新編譯是個比較繁瑣的過程。爲此,引入了Make工程管理器的概念,工程管理器指管理較多的文件,它是自動管理器能根據文件時間自動發現更新過的文件而減少編譯的工作量,同時通過讀入Makefile文件來執行大量的編譯工作。
關於官方make工程管理器更詳細的資料參考 https://www.gnu.org/software/make/manual/make.html 。
Makefile語法格式
target: dependency_files //目標項:依賴項 目標名隨便定義
TAB鍵 command //必須以tab鍵開頭,command編譯命令
注意:在寫command命令行的時候,必須要在前面按TAB鍵
Makefile編譯c程序示例
在前面使用到的兩個c文件編譯示例基礎上,使用Makefile編譯代碼。在兩個源碼所有的目錄中新建一個名字爲Makefile的文件,並且在其中編寫編譯規劃。
Makefile代碼清單:
main:main.o add.o
gcc -o main.exe main.o add.o
main.o:main.c
gcc -c main.c
add.o:add.c
gcc -c add.c
PHONY:clean
clean:
rm *.o main.exe
新建一個名爲Makefile 的文件,把上面的代碼寫在其中,存放在上一個工程源碼目錄中,然後,編譯就可以使用make命令進行編譯了。
具體操作過程
查看工程文件情況
[root@local Makefile] # ls
add.c main.c Makefile
編譯工程
[root@local Makefile]# make
gcc -c hello.c
gcc -c add.c
gcc -o main hello.o add.o
查看編譯後的工程放文件
[root@local Makefile] # ls
add.c add.o hello.c hello.o main.exe Makefile
運行可生成的可執行程序
[root@local Makefile] # ./main.exe
s:3
hello linux!
[root@local Makefile] #
說明:使用make編譯
對於該Makefile文件,程序make處理過程如下:
- make程序首先讀到第1行的目標文件main和它的兩個依賴文件main.o和add.o;然後比較文件main.exe和main.o/add.o的產生時間,如果main.exe比main.o/add.o舊的話,則執行第2條命令,以產生目標文件main。
- 在執行第2行的命令前,它首先會查看Makefile中的其他定義,看有沒有以第1行main.o和add.o爲目標文件的依賴文件,如果有的話,繼續按(1)、(2)兩步方式匹配下去。
- 根據(2)的匹配過程,make程序發現第3行有目標文件main.o依賴於main.c,則比較目main.o與它的依賴文件main.c的文件新舊,如果main.o比main.c舊,則執行第4行的命令以產生目標文件maino。在執行第4條命令時,main.c在文件Makefile不再有依賴文件的定義,make程序不再繼續往下匹配,而是執行第4條命令,產生目標文件main.o 。
- 目標文件add.o按照上面的同樣方式判斷產生。
- 執行(3)、(4)產生完main.o和add.o以後,則第2行的命令可以順利地執行了,最終產生了第1行的目標文件main。
Makefile中特殊處理與僞目標
.PHONY是Makefile文件的關鍵字,表示它後面列表中的目標均爲僞目標。
.PHONY:b
b:
echo ‘hello’ #通常用@echo “hello”
僞目標通常用在清理文件、強制重新編譯等情況下。
main:hello.o add.o
gcc -o main hello.o add.o
hello.o:hello.c
gcc -c hello.c
add.o:add.c
gcc -c add.c
PHONY:rebuild clean
rebuild:clean all #先執行清理,在執行 all
clean:
rm -rf *.o main *~
注意:和shell編程相同,在Makefile中,也用“#”號表示註釋
再執行下面的命令:
make 直接make,即從默認文件名(Makefile)的第一行開始執行
make clean 表示執行clean: 開始的命令段
make add.o 表示執行add.o: 開始的命令段
make rebuild 則先執行清除,再重新編譯連接
指定Makefile文件編譯
默認情況下 make 工具在會當前目錄查找Makefile 名字文件,然後按其中的規則進行編譯,我如果你不希望使用當前目錄的Makefile 名字文件,而是其他名字,可以通過使用 -f 參數指定。
複製一個其他名字的Makefile文件
變量、規則與函數
隨着軟件項目的變大、變複雜,源文件也越來越多,如果採用前面的方式寫Makefile文件,將會使Makefile也變得複雜而難於維護。通過make支持的變量定義、規則和內置函數,可以寫出通用性較強的Makefile文件,使得同一個Makefile文件能夠適應不同的項目。
Makefile中變量分類:用戶自定義變量,預定義變量,自動變量,環境變量。
自定義變量使用示例
變量:用來代替一個文本字符串
-
定義變量的3種方法:
變量名=變量值 遞規變量展開(幾個變量共享一個值) ,不常用
變量名:=變量值 簡單變量展開(類似於C語言賦值) ,是覆蓋之前的值, 通常採用這種形式
變量名?=變量值 如果變量沒有賦值才進行賦值, 這種方式一般用於可以通過make 變量名=新值 方式來替換Makefile中的定義的默認值。 -
使用變量的一般方法:
val=$(變量名) 引用 -
示例:= 和:= 賦值符號測試示例
把賦值代碼寫在Makefile文件中,然後終端輸入make
x=test
y = $(x) NOTE
x = csy
all:
@echo "x:$(x)"
@echo "y:$(y)"
測試:
[root@local Make_test]# make
x:csy
y:csy NOTE
說明:從結果可以看到,y的結果是是x展開後的結果連接上 NOTE。
EXE:=main
OBJS:=main.o add.o #main.o add.o
$(EXE): $(OBJS)
gcc -o $(EXE) $(OBJS)
main.o:main.c
gcc -c main.c
add.o:add.c
gcc -c add.c
PHONY:rebuild clean
rebuild:clean $(EXE) #先執行清理,在執行 all
clean:
rm -rf $(EXE) $(OBJS) *~
自動變量使用示例
自動變量:指在使用的時候,自動用特定的值替換。
用來改進makefile
EXE:=main
OBJS:=main.o add.o #main.o add.o
$(EXE):main.o add.o
gcc -o $@ $(OBJS)
@#輸出哪些依賴目標發生變化了,無實際意義,這裏只是用來說明一下 $?作用
echo $?
main.o:main.c
gcc -c main.c
add.o:add.c
gcc -c add.c
PHONY:rebuild clean
rebuild:clean $(EXE) #先執行清理,在執行 all
clean:
rm -rf $(EXE) $(OBJS) *~
預定義變量使用示例
預定義變量:內部事先定義好的變量,但是它的值是固定的,並且有些的值是爲空的。
AR:庫文件打包程序默認爲ar
AS:彙編程序,默認爲as
CC:c編譯器默認爲cc
CPP:c預編譯器,默認爲$(CC) -E
CXX:c++編譯器,默認爲g++
RM:刪除,默認爲rm -f
ARFLAGS:庫選項,無默認
ASFLAGS:彙編選項,無默認
CFLAGS:c編譯器選項,無默認
CPPFLAGS:c預編譯器選項,無默認
CXXFLAGS:c++編譯器選項
Makefile改進示例3:使用自動變量改進上面的Makefile (詳見04_makefile)
EXE:=main
OBJS:=hello.o add.o #hello.o add.o
CFLAGS:= -Wall -O2 -fpic #顯示所有警告信息,優化級別爲2
#爲觀察CC 這個預訂義變量,註釋它,其實預訂義值是 cc
#CC:=gcc
$(EXE):$(OBJS)
$(CC) -o $(EXE) $(OBJS)
@#爲觀察CC 這個預訂義變量,輸出它,其實預訂義值是 cc
@echo CC=$(CC)
hello.o:hello.c
$(CC) $(CFLAGS) -o $@ -c $^
add.o:add.c
$(CC) $(CFLAGS) -o $@ -c $^
PHONY:rebuild clean
rebuild:clean $(EXE) #先執行清理,在執行 all
clean:
@#爲觀察RM這個預訂義變量,使用它代替rm
$(RM) -r $(EXE) $(OBJS) *~
模式規則 %.o: %.c
模式規則:通過匹配模式找字符串, %匹配1或多個任意字符串 (Makefile_5)
%.o: %.c: 表示任何目標文件的依賴文件是與目標文件同名的並且擴展名爲.c的文件.
Makefile改進示例4:使用匹配模式改進上一版本Makefile
EXE:=main
OBJS:=main.o add.o #main.o add.o
CFLAGS:= -Wall -O2 -fpic #顯示所有警告信息,優化級別爲2
#爲觀察 CC 這個預訂義變量,註釋它,其實預訂義值是 cc
#CC:=gcc
(OBJS)
$(CC) -o $(EXE) KaTeX parse error: Expected 'EOF', got '#' at position 10: (OBJS)
@#̲爲觀察CC 這個預訂義變量,輸…(CC)
%.o: %.c
$(CC) $(CFLAGS) -o $@ -c $^
PHONY:rebuild clean
rebuild:clean $(EXE) #先執行清理,在執行 all
clean:
@#爲觀察RM這個預訂義變量,使用它代替rm
$(RM) -r $(EXE) $(OBJS) *~
使用shell函數優化Makefile
在上面的示例代碼中目標文件變量OBJS的文件列表還是需要人工修改,當工程文件增加,還需要再修改Makefile,可以使用shell函數的來遍歷指定目錄的c文件,然後把後綴名去掉,得到目標名文件列表,這樣就可以做到更通用的Makefile腳本了。常用幾個shell函數有 wildcard,patsubst,addprefix。
wildcard 擴展通配符函數
通配符會被自動展開。但在變量的定義和函數引用時,通配符將失效。$(wildcard PATTERN…) 。在Makefile中,它被展開爲已經存在的、使用空格分開的、匹配此模式的所有文件列表。如果不存在任何符合此模式的文件,函數會忽略模式字符並返回空。需要注意的是:這種情況下規則中通配符的展開和其他情況下匹配通配符的區別。
使用示例:
src= $(wildcard .c ./foo/.c)
搜索當前目錄及./foo/下所有以.c結尾的文件,生成一個以空格間隔的文件名列表,並賦值給src。當前目錄文件只有文件名,子目錄下的文件名包含路徑信息,比如 ./foor/bar.c。
patsubst替換通配符函數
patsubst是個模式替換函數。
語法:KaTeX parse error: Expected 'EOF', got '&' at position 33: …, 目標模式,文件列表 )
&̲emsp; 功能:查…(patsubst %.c,%.o,x.c.c bar.c)
把字串“x.c.c bar.c”符合模式[%.c]的單詞替換成[%.o],返回結果是“x.c.o bar.o”
addprefix 添加前綴函數
patsubst是個模式替換函數。
語法:(addprefix test,/dir1/a.c b.c ./d.c)
執行後src的結果是:test/dir1/a.c testb.c test./d.c
由執行結果可以看到是直接在後面的文件列表前面添加上前綴而已。
notdir去除路徑函數
notdir函數是刪除帶路徑的文件名前面的路徑字符串,
語法:取文件函數: $(notdir <names…>)
功能:從文件名序列 中取出非目錄部分
返回:文件名序列 中的非目錄部分
示例:src = $(notdir /home/a.c ./bb.c …/c.c d.c)
執行後src的值是:a.c bb.c c.c d.c
示例:改進後的Makefile
#可執行程序名
EXE:=main
#編譯器名
CC:=g++
#生成的可執行文件路徑
BINDIR:= ./bin
#生成的目標文件路徑
DIR := ./debug
#C文件編譯選項顯示所有警告信息,優化級別爲2
CFLAGS:= -Wall -O1 -fpic
#C++文件編譯選項顯示所有警告信息
CXXFLAGS:= -Wall -g
SRCS:= $($(wildcard *.c))
OC := $(patsubst %.c, $(DIR)/%.o, $(wildcard *.c))
OBJS:= $(OC)
$(BINDIR)/$(EXE): MKDIR $(OBJS)
$(CC) -o $@ $(OBJS)
MKDIR:
@mkdir -p $(DIR) $(BINDIR)
$(DIR)/%.o : %.c
$(CC) -c $(CFLAGS) $< -o $@
PHONY:rebuild clean
rebuild:clean $(EXE) #先執行清理,在執行 all
clean:
@#爲觀察RM這個預訂義變量,使用它代替rm
$(RM) -r $(BINDIR)/$(EXE) $(OBJS) *~
Makfile說明:
- wildcard搜索指定目錄(如果不指定表示當前目錄)下的文件名,展開成一列所有符合由其參數描述的文件名,文件間以空格間隔。SRCS = $(wildcard *.c)把當前目錄下所有 .c文件存入變量 SRCS裏。
- 字符串替換函數:$(patsubst要查找的子串,替換後的目標子串,源字符串)。將源字符串(以空格分隔)中的所有要查找的子串替換成目標子串。如OBJS = (SOURCES))
- 把SRCS中’.c’ 替換爲’.o’ ,然後把替換後的字符串存入變量OBJS。
- $(addprefix 前綴,源字符串)函數把第二個參數列表的每一項前綴上第一個參數值。
C/C++合用 Makefile 模板
下面是一個較爲通用的Makefile:
#可執行程序名
EXE:=main
#編譯器名
CC:=g++
#第三方庫名,不用加-l
LIBS :=
#生成的可執行文件路徑
BINDIR := ./bin
#生成的目標文件路徑
DIR := ./debug
#C文件編譯選項顯示所有警告信息,優化級別爲2
CFLAGS:= -Wall -O1 -fpic
#C++文件編譯選項顯示所有警告信息
CXXFLAGS:= -Wall -g
SRCS:= $($(wildcard *.c) wildcard *.cpp) $(wildcard *.cc)
OC := $(patsubst %.c, $(DIR)/%.co, $(wildcard *.c))
OCPP:= $(patsubst %.cpp, $(DIR)/%.o, $(wildcard *.cpp))
OCC := $(patsubst %.cc, $(DIR)/%.cco, $(wildcard *.cc))
OBJS:= $(OC) $(OCC) $(OCPP)
$(BINDIR)/$(EXE): MKDIR $(OBJS)
$(CC) -o $@ $(OBJS) $(addprefix -l,$(LIBS))
MKDIR:
@mkdir -p $(DIR) $(BINDIR)
$(DIR)/%.co : %.c
$(CC) -c $(CFLAGS) $< -o $@
$(DIR)/%.o : %.cpp
$(CC) -c $(CXXFLAGS) $< -o $@
$(DIR)/%.cco : %.cc
$(CC) -c $(CXXFLAGS) $< -o $@
PHONY:rebuild clean
rebuild:clean $(EXE) #先執行清理,在執行 all
clean:
@#爲觀察RM這個預訂義變量,使用它代替rm
$(RM) -r $(BINDIR)/$(EXE) $(OBJS) *~