Makefile


一個簡單的例子:

ab.out : a.o b.o
    g++ -o ab.out o/a.o o/b.o

a.o : a.cpp
    g++ -c a.cpp -o o/a.o

b.o : b.cpp
    g++ -c b.cpp -o o/b.o

clean:
    rm -f o/a.o o/b.o

 

再看下面一個:

simple_server_objects = socket/ServerSocket.o socket/Socket.o server.o
simple_client_objects = socket/ClientSocket.o socket/Socket.o client.o

all : server client

server: $(simple_server_objects)
    g++ -o server $(simple_server_objects) -lmysqlpp -lmemcached -lmysqlclient -L/usr/local/lib

client: $(simple_client_objects)
    g++ -o client $(simple_client_objects)

Socket.o: socket/Socket.cpp
    g++ -c socket/Socket.cpp -o socket/Socket.o
ServerSocket.o: socket/ServerSocket.cpp
    g++ -c socket/ServerSocket.cpp -o socket/ServerSocket.o
ClientSocket.o: socket/ClientSocket.cpp
    g++ -c socket/ClientSocket.cpp -o socket/ClientSocket.o
server.o: server.cpp
    g++ -c server.cpp -o server.o -I/usr/local/mysql/include
simple_client_main.o: simple_client_main.cpp
    g++ -c client.cpp  -o client.o

clean:
    rm -f *.o socket/*.o server client

 

通用步驟:
編譯時,可以不指定頭文件,如果指定頭文件,其作用是當此頭文件變化時,重新生成該規則。(但如果不指定頭文件,則當頭文件改動時,只能先 clean 再 make了,下面的通用思路也是如此)
編譯時,目標文件如果為 dir/%.o ,表示遞歸查找目錄 dir 下的所有 .o 文件,同樣的,依賴文件中如果也相應的為 %.cpp ,則表示查找匹配目標文件中的文件名的CPP文件。
編譯命令:$(CC) $(CFLAGS) -c $< -o $@  中,$< 表示依賴集,$@ 表示目標集,編譯命令一般情況下可以省略。
總結下最簡單的通用MAKEFILE思路:
1、查找項目目錄下的所有 .cpp 文件
2、將上面找到的變量集后綴名由 .cpp 替換成 .o
3、將第2步中的變量集,逐一加上 debug/ 或 release/ 前綴,可設為 $(OUTPUT_DIR),該步的目標是把所有 .o 文件單獨放到一個文件夾中集中管理,避免 .o 文件凌亂分布。如變量為 OUTPUT_OBJS
4、鏈接:
target : $(OUTPUT_OBJS)
  $(LINK) $(OUTPUT_OBJS) $(CFLAGS) -o target $(LIB_PATH) $(LIBS)
5、編譯:
$(OUTPUT_DIR)/%.o : %.cpp
  $(CC) $(CFLAGS) $(INCLUDE_PATH) -c $< -o $@
 
附實例:
 
 1 #定義編譯選項
 2 CC = g++
 3 LINK = g++
 4 CFLAGS = -Wall
 5 
 6 #定義頭文件目錄,鏈接庫目錄,鏈接文件
 7 INCLUDE_PATH = -Iinclude
 8 LIB_PATH = -Llib -L/usr/local/lib -Lsrc/lib
 9 LIBS = -lboost_thread -lboost_system -Llib #-ljsoncpp
10 
11 #定義項目代碼根目錄及所有文件夾目錄
12 SRC_DIR = src
13 VPATH =  $(SRC_DIR)
14 VPATH += $(SRC_DIR)/base
15 VPATH += $(SRC_DIR)/data
16 VPATH += $(SRC_DIR)/include
17 VPATH += $(SRC_DIR)/operate
18 VPATH += $(SRC_DIR)/operate/detail
19 
20 #找出所有 .cpp 文件和相應的 .o 文件(帶目錄)
21 SRC_FILES = $(foreach n, $(VPATH),$(wildcard $(n)/*.cpp))
22 OBJ_FILES = $(SRC_FILES:.cpp=.o)
23 
24 #把所有的 .o 文件放到定義好的輸出文件夾中統一管理
25 OUTPUT_DIR := debug
26 OUTPUT_OBJS = $(addprefix $(OUTPUT_DIR)/,$(subst $(SRC_DIR)/, ,$(OBJ_FILES)))
27 
28 #創建存放 .o 文件的目錄結構
29 $(shell mkdir -p "$(OUTPUT_DIR)")
30 $(shell mkdir -p "$(OUTPUT_DIR)/base")
31 $(shell mkdir -p "$(OUTPUT_DIR)/data")
32 $(shell mkdir -p "$(OUTPUT_DIR)/operate")
33 $(shell mkdir -p "$(OUTPUT_DIR)/operate/detail")
34 
35 #更新 ctags
36 #$(shell ctags -R --c++-kinds=+p --fields+iaS --extra=+q .)
37 
38 #定義輸出目標名
39 TARGET = server
40 
41 #鏈接
42 $(TARGET) : $(OUTPUT_OBJS)
43     $(LINK) $(OUTPUT_OBJS) -o $@ $(LIB_PATH) $(LIBS)
44 
45 #編譯
46 $(OUTPUT_DIR)/%.o : %.cpp
47     $(CC) -c $< -o $@
48 
49 #清除
50 .PHONY:clean
51 clean:
52     -rm -rf $(OUTPUT_DIR)/*
53     -rm -rf $(TARGET)     

 

備注:大體的步驟如上,不過需要提前生成存放 .o 文件的目錄結構,可以通過 $(shell mkdir -p "$(OUTPUT_DIR)")  等一系列命令來生成,另外需要事先定義好一些變量,如頭文件目錄,鏈接庫目錄,鏈接庫名稱等。該通用步驟可以使MAKEFILE寫起來很簡單,不過感覺效率不高,太多依賴自動查找,另外頭文件改動后,也要先 clean 再 make,這一點才是最致命的。待續。

 

GCC頭文件的環境變量是 export CPLUS_INCLUDE_PATH=/root/new_s3/src/include

加入到GCC環境變量相比使用 -I 來包含頭文件的區別是,在使用 -MM 生成依賴關系時,前者會忽略生成指定路徑的頭文件的依賴關系。

 

二、自動依賴

#定義編譯選項
CC = g++
LINK = g++
CFLAGS = -g -Wall -O0

#定義頭文件目錄,鏈接庫目錄,鏈接文件
#INCLUDE_PATH = -Isrc/include -Isrc/include/mysql 
LIB_PATH = -Lsrc/lib 
LIBS = -lboost_thread -lboost_system -ljsoncpp -lmysqlpp -lmysqlclient

#定義項目代碼根目錄及所有文件夾目錄
SRC_DIR = src/Server
VPATH =  $(SRC_DIR)
VPATH += $(SRC_DIR)/base
VPATH += $(SRC_DIR)/data
VPATH += $(SRC_DIR)/include
VPATH += $(SRC_DIR)/operate

#設置GCC編譯時的查找的頭文件目錄。如果在 -MM 時用-I來指定,那么會生成依賴關系,如BOOST庫等,但一般情況下BOOST庫是不會修改的。
export CPLUS_INCLUDE_PATH=src/include:src/include/mysql

#找出所有 .cpp 文件和相應的 .o 文件(帶目錄)
SRC_FILES = $(foreach n, $(VPATH),$(wildcard $(n)/*.cpp))
ALL_FILES = $(foreach n, $(VPATH),$(wildcard $(n)/*.h $(n)/*.cpp))
OBJ_FILES = $(notdir $(SRC_FILES:.cpp=.o))

.PHONY:all
all : depend server

#生成依賴關系文件,這里不用偽目標,這樣只有當代碼被改動時,依賴關系文件才會被重新生成
#.PHONY:depend
depend:$(ALL_FILES)
    #@export CPLUS_INCLUDE_PATH=src/include:src/include/mysql && 
    $(CC) $(CFLAGS) -MM $(SRC_FILES) > depend 
-include depend

#鏈接
server : $(OBJ_FILES)
    $(LINK) $(OBJ_FILES) -o $@ $(LIB_PATH) $(LIBS)

#清除
.PHONY:clean
clean:
    -rm -rf $(TARGET)
    -rm -rf *.o 

 

該方法缺點:每次都需要生成所有源文件的依賴關系文件,即使是改動一個源文件的情況下。

time make -j4   使用4線程來編譯程序

 

通配符可以使用在變量中,如 objects = *.o  ,不過 *.o 並不會展開,Makefile 中的變量,就是C/C++中的宏,如果需要展開的話,即 objects 的值是所有 .o 文件的集合,可以:

objects = $(wildcard *.o)

Makefile 默認只在當前目錄下尋找依賴文件和目標文件,如果定義了VPATH變量,則會到變量中所指示的目錄中去尋找,VPATH變量的值,應該用空格或冒號分開,(在WINDOWS下是用空格或分號),與之類似的還有一個vpath關鍵字,它靈活,可以指定在不同的目錄里搜索不同類型的文件,使用方法有三種:

vpath <pattern> <dir>    #為模式指定目錄

vpath <pattern>       #清除某種模式的搜索目錄

vpath           #清除所有模式的搜索目錄

<pattern>中使用 % ,來匹配0或若干個字符。如:vpath %.h src/include

 

偽目標也可以做為依賴:

.PHONY : cleanall cleanobj cleandiff
cleanall : cleanobj cleandiff
  rm program
cleanobj :
  rm *.o
cleandiff :
  rm *.diff

 

files = a.o b.o c.i

$(filter %.o,$(files)) : %.o : %.cpp

感覺 $(filter %.o,$(files)) : %.cpp 與 $(file) : %.o : %.cpp 效果一樣。

$(files: .o = .d)  #把變量$(files) 中的所有以 .o 結尾(后面是空格或結束符)字符串換成 .d

 

gcc 的 -M 和 -MM 選項,可以生成依賴關系,前者會包括標准庫的頭文件,后者不帶。

 

命令行前如果使用 @ 符號,則這個命令不會被 make 顯示出來,如 @echo 正在編譯……,會輸出“正在編譯……”

可以通過 make -n 或 make --just-print 來查看而不執行命令。 make -s 或 make --slient 是全面禁止顯示命令。
如果有多條命令,后面的命令是在前面的命令的基礎上執行的,比如 cd src;pwd ,則應該把這兩條命令寫在同一行上,使用分號隔開,而不能寫成兩行。
在命令前可以加上 - 號,表示命令出錯也仍然繼續,或者使用 make -i 或 make --ignore-errors,表示忽略所有的錯誤。
 
《跟我一起寫Makefile》書寫命令一章中“嵌套執行make” 和 "定義命令包" 兩小節在使用到時再看看吧。
順便提一下,可以使用 info make 來查看更多信息,關於 man 和 info,man,即 manunal,是 UNIX 系統手冊的電子版本; Linux 中的大多數軟件開發工具都是來自自由軟件基金會的 GNU 項目,這些工具軟件件的在線文檔都以 info 文件的形式存在。info 程序是 GNU 的超文本幫助系統。info 文檔一般保存在 /usr/info 目錄下,使用 info 命令查看 info 文檔。
 
 
Makefile變量的值有四種方式可以定義,一種是使用=,一種是使用 := ,前者可以使用后面定義的值,后者不可以。
描述一個空格的方法:
nullstring :=
space := $(nullstring) #end of the line
注意,如果一個變量指明一個路徑: dir = src/include   #end of the line      
但這樣其實后面還帶有空格,如果使用這個變量去拼接路徑的話,會出問題。
第三種方式是使用 ?= ,表示如果該變量之前定義過,則無效;如果沒有定義過,則定義。
DIR ?= src/include
等同於:
ifeq($(origin DIR), undefined)
  DIR = src/include
endif
第四種是 += ,用來追加變量值。如果該變量之前沒有定義過,會自動變成=
 
關於 override 指示符和使用define關鍵字產生多行變量,忽略。
 
目標變量:根據不同的目標,使用不同的變量值,相當於局部變量。
如:
server : CXXFLAGS=-g
類似的還有模式變量,如: %.o : CXXFLAGS=-g
 
Makefile 里的條件判斷:ifeq(param1,param2)  ... else ... endif
除了 ifeq 外還有 ifneq\ifdef\ifndef
如 ifdef(param) ,param值為非空,則成立。
 
字符串函數:
$(subst <from>,<to>,<text>)    #字符串替換,返回處理后的字符串
$(patsubst <from>,<to><text>)      #模式字符串替換,相當於 $(var:%.c=%.o),返回處理后的字符串
$(strip <string>)    #去首尾空格,返回處理后的字符串
$(findstring <find>,<text>)    #查找字符串,找到則返回<find>,否則返回空字符串
$(filter <pattern>,<text>)    #過濾出text中符合模式的字符串,如:$(filter %.c %.cpp,$(source))
$(filter-out <pattern>,<text>)   #和上面相反,返回不符合模式的字串,同樣可以有多個模式
$(sort <text>)    #給字符串中的單詞排序,注意它會去掉重復的單詞
$(word <n>,<text>)     #取出第n個單詞
$(wordlist <s>,<e>,<text>)   #取出第s到e個單詞,包括第s和第e的單詞
$(words <text>)   #統計單詞個數
$(firstword <text>)   #返回第一個單詞,相當於 $(word 1,<text>)

 

文件函數:

$(dir <names>)   #返回文件名序列的目錄部分
$(nodir <names>)   #返回文件名序列的文件部分
$(suffix <names>)   #返回文件名序列的后綴部分
$(basename <names>)   #返回文件名序列的前綴部分(返回最后一個小數點前的字串)
$(addsuffix <suffix>,<names>)   #為文件名序列中每個單詞加后綴
$(addprefix <prefix>,<names>)   #為文件名序列中每個單詞加前綴
$(join <list1>,<list2>)   #連接單詞,如 $(join aaa bbb,111 222 333) 返回 aaa111 bbb222 333

 

$(foreach <var>,<list>,<cmd>)   把<list>中的單詞逐一取出來,放到<var>中,然后執行<cmd>,如:

names := a b c dfiles := $(foreach n,$(names),$(n).o)

 
$(if <condition>,<then>)   或 $(if <condition>,<then>,<else>)
 
$(call <expression>,<param1>,<param2>,...)  為表達式傳遞參數,如:
files = $(2) $(1)
$(call files,a,b)   則返回 b a
 
$(origin  <var>)   判斷一個變量的出處,如是否定義過,是否是環境變量等
 
$(shell <shell cmd>)   執行shell命令
 
$(wildcard <pattern-text>)   展開符合模式的字符串,如:SRC_FILES = $(foreach n, $(VPATH),$(wildcard $(n)/*.cpp))

 
自動化變量:
$@  目標文件集  
$<   第一個依賴項
$?   所有比目標新的依賴項
$^    所有依賴集合
$+   同上,只是不去重復
另外,加上D或F,分別表示目錄部分或文件部分,如 $(@F)
當一個模式匹配包含有斜杠(實際也不經常包含)的文件時,那么在進行模式匹配時,目
錄部分會首先被移開,然后進行匹配,成功后,再把目錄加回去。在進行""的傳遞時,我
們需要知道這個步驟。例如有一個模式"e%t",文件"src/eat"匹配於該模式

 

生成動態鏈接庫與靜態鏈接庫的方法(先生成 *.o 文件,直接通過源文件生成亦可):

靜態鏈接庫其實就可以看成是 *.o 文件的簡單打包:

ar -crv libtest.a test.o      或: ar -r libtest.a test.cpp     -r選項是必須的,表示插入到備份文件,並且有則替換  -c表示創建備份文件  -v表示顯示詳情

動態鏈接庫優於靜態鏈接庫,無論是在內存使用和磁盤使用上(多進程時),既可以使用源碼文件直接生成,也可以使用 *.o 文件生成(*.o 文件的生成必須使用 -fPIC 參數,而從*.o 文件生成動態鏈接庫則不用再重復此參數):

gcc -c -fPIC test.cpp && gcc test.o -o libtest.so -shared

或: gcc test.cpp -o libtest.so -shared -fPIC

-shared 該選項指定生成動態連接庫(讓連接器生成T類型的導出符號表,有時候也生成弱連接W類型的導出符號),不用該標志外部程序無法連接。相當於一個可執行文件
-fPIC 表示編譯為位置獨立的代碼,不用此選項的話編譯后的代碼是位置相關的所以動態載入時是通過代碼拷貝的方式來滿足不同進程的需要,而不能達到真正代碼段共享的目的。

 

 
預編譯頭:
1、預編譯這個頭文件:
g++ -x c++-header -c stdc++.h   -o stdc++.h.gch
使用-x c++-header來說明這個文件作為C++的預編譯頭文件。注意:C和C++的處理方式不一樣,C要使用-x c-header選項。
2、在源文件中使用 #include <bits/stdc++.h>
3、編譯器在引入 stdc++.h 的時候,會檢查 stdc++.h 的同一目錄下是否有 stdc++.h.gch,如果有,再檢查GCC的編譯選項(及其編譯命令行中定義的宏)與預編譯頭文件時候的參數是否一致。僅僅只在編譯參數完全一致(順序無關)的情況下,預編譯的gch文件才會生效。
4、可以在 GCC 的命令行中加入 -Winvalid-pch 參數,當 .gch 文件無效的時候可以給出警告。通過 -H 參數,也可以查看使用了哪個預編譯頭。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM