從頭開始寫專案Makefile(八):模式規則
上一節講到目錄建立成功,目標檔案沒有生產到對應目錄下,這裡我們先給目標檔案加上對應目錄,這樣的話產生對應的目標檔案會直接生成到對應目錄。我們先給庫檔案目標和可執行檔案目標加上路徑,如下:
lib : $(OBJDIR) $(LIBDIR)/$(SRC_LIB)
bin : $(OBJDIR) $(BINDIR)/$(SRC_BIN)
$(OBJDIR) :
>[email protected] " MKDIR $(notdir [email protected])..."
>[email protected] -p [email protected]
ifneq ($(SRC_BIN),)
$(BINDIR)/$(SRC_BIN) : $(SRC_OBJ)
>---$(CC) -o [email protected] $^ $(LDFLAGS)
endif
ifneq ($(SRC_LIB),)
$(LIBDIR)/$(SRC_LIB) : $(SRC_OBJ)
>---$(AR) rcs [email protected] $^
>---cp [email protected] $(SRC_BASE)/libs
endif
此時再執行make,完成後檢視build目錄樹:
build/ └── unix_dbg ├── bin │ └── target_bin ├── lib │ ├── libipc.a │ └── libtools.a └── obj ├── ipc ├── main └── tools
可以看到,生成的目標是在對應目錄下。我們乘勝追擊,把.o檔案也將其修改了。我們之前的每個模組Makefile大致是這樣寫的:
SRC_BASE = ../.. CFLAGS += CPPFLAGS += -I. -I./inc -I$(SRC_BASE)/include # SRC_OBJ = $(patsubst %.c, %.o, $(wildcard *.c)) SRC_FILES = $(wildcard src/*.c) SRC_OBJ = $(SRC_FILES:.c=.o) SRC_LIB = xx.a include $(SRC_BASE)/Makefile.rule
其中SRC_OBJ在此處給出,然後再在Makefile.rule中使用,此處的.o檔案會在.c檔案相同目錄下生成,所以我們現在需要將.o檔案加上路徑,由於取得路徑是在Makefile.rule裡面,所以我們可以統一在Makefile.rule裡面給變數SRC_OBJ賦值,大致如下:
SRC_OBJ = $(patsubst %.c, $(OBJDIR)/%.o, $(notdir $(SRC_FILES)))
這裡用到函式patsubst、notdir,關於函式會在後面講到。這樣.o檔案作為目標生成之後就會生成到相應目錄裡面了。
此時再編譯:
# make
make[1]: Entering directory `/home/Myprojects/example_make/version-2.9/src/ipc'
make[1]: *** No rule to make target `../../build/unix_dbg/obj/ipc/ipc.o', needed by `../../build/unix_dbg/lib/libipc.a'. Stop.
make[1]: Leaving directory `/home/Myprojects/example_make/version-2.9/src/ipc'
make: *** [ipc] Error 2
#
發現出錯了,並且是在生成目標檔案ipc.o時沒有成功,檢視build目錄樹也沒有生成.o檔案。為什麼會生成失敗呢?
我們沒有給出生成.o目標的規則,之前可以生成是因為make有通過隱含規則來自動推導的能力(這個之前有講到,連結過去)。在我們沒有修改之前,生成.o通過隱含規則來完成:
%.o: %.c
# commands to execute (built-in):
>---$(COMPILE.c) $(OUTPUT_OPTION) $<
因為所有的.o目標符合該規則,所以會自動推導生成.o檔案。我們現在在..o前面加上路徑後沒有符合生成.o的隱含模式規則了,所以就沒有生成該檔案,導致編譯出錯。那怎麼辦呢?沒有隱含模式規則,我們可以自己寫符合生成該目標的模式規則。模式規則類似於普通規則,只是在模式規則中,目標檔案是一個帶有模式字元“%”的檔案,使用模式來匹配目標檔案。在目標檔名中“%”匹配的部分稱為“莖”。使用模式規則時,目標檔案匹配之後得到“莖”,依賴根據“莖”產生對應的依賴檔案,這個依賴檔案必須是存在的或者可被建立的。
所以,我們增加一條模式規則如下:
$(OBJDIR)/%.o : %.c
>---$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o [email protected]
該模式規則中目標檔案是$(OBJDIR)/%.o,那麼現在有了符合生成我們需要的.o檔案的規則了,編譯一下:# make
make[1]: Entering directory `/home/Myprojects/example_make/version-2.9/src/ipc'
make[1]: *** No rule to make target `../../build/unix_dbg/obj/ipc/ipc.o', needed by `../../build/unix_dbg/lib/libipc.a'. Stop.
make[1]: Leaving directory `/home/Myprojects/example_make/version-2.9/src/ipc'
make: *** [ipc] Error 2
#
發現還是不對,不是已經增加了模式規則了嗎,為何還是沒有生成.o檔案。我們這裡先說說靜態模式規則:
一個規則中可以有多個目標,規則所定義的命令對所有的目標有效。一個具有多目標的規則相當於多個規則。 規則的命令對不同的目標的執行效果不同, 因為在規則的命令中可能使用了自動化變數 “[email protected]” 。 多目標規則意味著所有的目標具有相同的依賴檔案。多目標通常用在以下兩種情況:雖然在多目標的規則中, 可以根據不同的目標使用不同的命令 (在命令列中使用自動化變數 “[email protected]” )。但是, 多目標的規則並不能做到根據目標檔案自動改變依賴檔案 (像上邊例子中使用自動化變數“[email protected]”改變規則的命令一樣) 。需要實現這個目的是,要用到make的靜態模式。
靜態模式規則是這樣一個規則:規則存在多個目標, 並且不同的目標可以根據目標檔案的名字來自動構造出依賴檔案。靜態模式規則比多目標規則更通用, 它不需要多個目標具有相同的依賴。但是靜態模式規則中的依賴檔案必須是相類似的而不是完全相同
的。靜態模式規則語法如下:
<targets ...>: <target-pattern>: <prereq-patterns ...>
<commands>
....
比如下面是一個靜態模式規則:objects = foo.o bar.o
all: $(objects)
$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o [email protected]
該規則描述了所有的.o檔案的依賴檔案為對應的.c檔案,對於目標“foo.o” ,取其莖“foo”替代對應的依賴模式“%.c”中的模式字元“%”之後可得到目標的依賴檔案“foo.c”。這就是目標“foo.o”的依賴關係“foo.o: foo.c”,規則的命令列描述瞭如何完成由“foo.c”編譯生成目標“foo.o” 。命令列中“$<”和“[email protected]”是自動化變數,“$<” 表示規則中的第一個依賴檔案, “[email protected]” 表示規則中的目標檔案。上邊的這個規則描述了以下兩個具體的規則:foo.o : foo.c
>---$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
>---$(CC) -c $(CFLAGS) bar.c -o bar.o
(注:該示例與其相關描述摘抄於網際網路,描述很不錯,估計比我講的詳細)那靜態模式規則和普通的模式規則(非靜態模式規則)有什麼去區別呢?兩者都是用目標模式和依賴模式來構建目標的規則中的檔案依賴關係,兩者不同的地方是 make 在執行時使用它們的時機。
靜態模式規則只能用在規則中明確指出的那些檔案的重建過程中。不能用在除此之外的任何檔案的重建過程中,並且它對指定的每一個目標來說是唯一的。如果一個目標存在於兩個規則,並且這兩個規則都定義了命令, make 執行時就會提示錯誤。
非靜態模式規則可被用在任何和它相匹配的目標上,當一個目標檔案同時符合多個目標模式時,make將會把第一個目標匹配的模式規則作為重建它的規則。
那有沒有想過如果我們指定了模式規則後,那還有隱含規則呢,那怎麼選擇執行哪一個模式規則呢?Makefile中明確指定的模式規則會覆蓋隱含模式規則。就是說如果在Makefile中出現了一個對目標檔案合適可用的模式規則,那麼make就不會再為這個目標檔案尋找其它隱含規則,而直接使用在Makefile中出現的這個規則。在使用時,明確規則永遠優先於隱含規則。
我們繼續說之前的那個問題,我們定義了模式規則後還是沒有生成.o檔案,我們現在將其改為靜態規則再試試就看,如下:
$(SRC_OBJ) : $(OBJDIR)/%.o : %.c
>---$(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o [email protected]
執行後:# make
make[1]: Entering directory `/home/Myprojects/example_make/version-2.9/src/ipc'
make[1]: *** No rule to make target `ipc.c', needed by `../../build/unix_dbg/obj/ipc/ipc.o'. Stop.
make[1]: Leaving directory `/home/Myprojects/example_make/version-2.9/src/ipc'
make: *** [ipc] Error 2
#
發現提示沒有檔案ipc.c,這說明沒有生成.o的原因是沒有.c檔案,我很好奇的是為何使用非靜態模式為何不提示呢?(還沒搞懂,再研究研究,知道的可以給個提示哈~~)缺少依賴檔案,為何沒有*.c檔案,仔細想想我們的.o檔案沒有和.c檔案在同一目錄。在我們工程中,將原始碼和二進位制檔案(.o 檔案和可執行檔案)安排在不同的目錄來進行區分管理。這種情況下,我們可以使用 make 提供的目錄搜尋依賴檔案功能。該功能在下一節講述,這一節說的夠多了,有點累了。可惜最終還是沒有給出一個可用的Makefile,在下一節將會給出。
相關推薦
從頭開始寫專案Makefile(八):模式規則
【版權宣告:轉載請保留出處:blog.csdn.net/gentleliu。Mail:shallnew at 163 dot com】 上一節講到目錄建立成功,目標檔案沒有生產到對應目錄下,這裡我們先給目標檔案加上對應目錄,這樣的話產生對應的目標檔案會直接生成到對應
從頭開始寫專案Makefile(一):基本規則
【版權宣告:轉載請保留出處:blog.csdn.net/gentleliu。Mail:shallnew at 163 dot com】 一般一個稍大的linux專案會有很多個原始檔組成,最終的可執行程式也是由這許多個原始檔編譯連結而成的。編譯是把一個.c或.cpp檔案編譯成
從頭開始寫專案Makefile(六):引數傳遞、條件判斷、include
在多個Makefile巢狀呼叫時,有時我們需要傳遞一些引數給下一層Makefile。比如我們在頂層Makefile裡面定義的開啟除錯資訊變數DEBUG_SYMBOLS,我們希望在進入子目錄執行子Makefile時該變數仍然有效,這是需要將該變數傳遞給子Makefile,那怎麼傳遞呢?這裡有兩種方法:
從頭開始寫專案Makefile(五):巢狀執行(轉)
【版權宣告:轉載請保留出處:blog.csdn.net/gentleliu。Mail:shallnew at 163 dot com】 在大一些的專案裡面,所有原始碼不會只放在同一個目錄,一般各個功能模組的原始碼都是分開的,各自放在各自目錄下,並且標頭檔案
從頭開始寫專案Makefile(十):make內嵌函式及make命令顯示
【版權宣告:轉載請保留出處:blog.csdn.net/gentleliu。Mail:shallnew at 163 dot com】 這一節我們講一下make的函式,在之前的章節已經講到了幾個函式:wildcard、patsubst、notdir、shell
[Golang] 從零開始寫Socket Server(3): 對長、短連接的處理策略(模擬心跳)
microsoft ted 每次 range 點擊 關閉 ade 而在 href 通過前兩章,我們成功是寫出了一套湊合能用的Server和Client,並在二者之間實現了通過協議交流。這麽一來,一個簡易的socket通訊框架已經初具雛形了,那麽我們接下來做的
[Golang] 從零開始寫Socket Server(5):Server的解耦—通過Router+Controller實現邏輯分發
在實際的系統專案工程中中,我們在寫程式碼的時候要儘量避免不必要的耦合,否則你以後在更新和維護程式碼的時候會發現如同深陷泥潭,隨便改點東西整個系統都要變動的酸爽會讓你深切後悔自己當初為什麼非要把東西都寫到一塊去(我不會說我剛實習的時候就是這麼幹
[Golang] 從零開始寫Socket Server(4):將執行引數放入配置檔案(XML/YAML)
為了將我們寫好的Server釋出到伺服器上,就要將我們的程式碼進行build打包,這樣如果以後想要修改一些程式碼的話,需要重新給程式碼進行編譯打包並上傳到伺服器上。 顯然,這麼做過於繁瑣。。。因此常見的做法都是將Server執行中
[Golang] 從零開始寫Socket Server(2): 自定義通訊協議
在上一章我們做出來一個最基礎的demo後,已經可以初步實現Server和Client之間的資訊交流了~ 這一章我會介紹一下怎麼在Server和Client之間實現一個簡單的通訊協議,從而增強整個資訊交流過程的穩定性。  
[Golang] 從零開始寫Socket Server(1): Socket-Client框架
第一次跑到網際網路公司實習 。。感覺自己進步飛快啊~第一週剛寫了個HTTP伺服器用於微信公共號的點餐系統~ 第二週就直接開始一邊自學GO語言一邊寫用於Socket的伺服器了。。。 因為發現Golang這一塊資料挺少的,接下來我會在Blog裡把整個Server的Coding,還有遇到的坑都記錄
.Net Core 商城微服務專案系列(八):購物車
最近加班有點多,一週五天,四天加班到11點+,心很累。原因是我當前在的這個組比較特殊,相當於業務的架構組,要為其它的開發組提供服務和監控。所以最近更新的也少,不過這個元旦三天假應該會更新三篇。 這篇是介紹一下商城的購物車,程式碼就不詳細介紹了,因為技術點都已經再前面幾篇介紹過了,無非就是產品的增刪該查,當然
[Golang] 從零開始寫Socket Server(3): 對長、短連線的處理策略(模擬心跳)
通過前兩章,我們成功是寫出了一套湊合能用的Server和Client,並在二者之間實現了通過協議交流。這麼一來,一個簡易的socket通訊框架已經初具雛形了,那麼我們接下來做的,就是想辦法讓這
從零寫一個編譯器(八):語義分析之構造符號表
專案的完整程式碼在 C2j-Compiler 前言 在之前完成了描述符號表的資料結構,現在就可以正式構造符號表了。符號表的建立自然是要根據語法分析過程中走的,所以符號表的建立就在LRStateTableParser裡的takeActionForReduce方法 不過在此之前,當然還需要一個方便對這個符號表
我是如何學習寫一個作業系統(八):記憶體管理和段頁機制
前言 多程序和記憶體管理是緊密相連的兩個模組,因為執行程序也就是從記憶體中取指執行,建立程序首先要將程式和資料裝入記憶體。將使用者原程式變成可在記憶體中執行的程式,而這就涉及到了記憶體管理。 記憶體的裝入 絕對裝入。 在編譯時,如果知道程式將駐留在記憶體的某個位置,編譯程式將產生絕對地址的目的碼。絕對裝入程
makefile(二):普通規則
一.普通規則 規則是makefile的主要內容,它決定了編譯系統何時,如何來編譯指定的目標。規則的語法如下: 目標:依賴 指令 請特別注意,指令前面有一個tab空格。 同時指令可以直接寫在依賴後面,即同一行上,並且這種情況下指令前可以
Scala基礎教程(八):模式匹配、正則表示式
匹配使用case 類: case classes是用於模式匹配與case 表示式指定類。這些都是標準類具有特殊修飾:case。下面是一個簡單的模式使用case class匹配示例: object Test { def main(args: Array[String])
[Golang] 從零開始寫Socket Server(6)【完結】:日誌模組的設計與定時任務模組模組
好久沒寫文章啦。。。今天把golang挖的這個坑給補完吧~ 作為一個Server,日誌(Log)功能是必不可少的,一個設計良好的日誌模組,不論是開發Server時的除錯,還是執行時候的維護,都是非常有幫助的。 因為這裡寫的是一個比較簡化的Server框架,因此我選擇對Golang本
從零開始Vue專案實戰(三)-專案結構
現在在瀏覽器中輸入http://localhost:8083,可以看到初始的“Welcome to Your Vue.js App”頁面了 目錄結構 ├── README.md 專案介紹 ├── index.html 入口頁面 ├── build
從零開始Vue專案實戰(二)-搭建環境
1、下載node.js並安裝 下載地址:https://nodejs.org/en/download/。 下載.msi 格式的直接連續下一步就可以了。安裝完成後可以用 node -v 和 npm -v 檢視版本號。 2、安裝vue-cli 腳手架構建工具 在命令列中輸入npm ins
從零開始Vue專案實戰(一)-準備篇
從前參與過一個react專案的程式碼編寫,大神搭建的框架,我主要負責業務邏輯程式碼編寫,現在回想起來似乎又什麼都不會,現在為了鞏固前端知識,決定用Vue來做這個專案的移動端網站,我本人Vue是從零開始的,一邊學習一邊寫程式碼,在這裡記錄一下過程。 專案說明: 主要功能實現一個投資平臺,會員身份為融資人或投