Makefile基礎 4. 自動處理標頭檔案的依賴關係
現在我們的Makefile寫成這樣:
all: main main: main.o stack.o maze.o gcc $^ -o $@ main.o: main.h stack.h maze.h stack.o: stack.h main.h maze.o: maze.h main.h clean: -rm main *.o .PHONY: clean
按照慣例,用all
做預設目標。現在還有一點比較麻煩,在寫main.o
、stack.o
和maze.o
這三個目標的規則時要檢視原始碼,找出它們依賴於哪些標頭檔案,這很容易出錯,一是因為有的標頭檔案包含在另一個標頭檔案中,在寫規則時很容易遺漏,二是如果以後修改原始碼改變了依賴關係,很可能忘記修改Makefile的規則。為了解決這個問題,可以用gcc
的-M
選項自動生成目標檔案和原始檔的依賴關係:
$ gcc -M main.c main.o: main.c /usr/include/stdio.h /usr/include/features.h \ /usr/include/sys/cdefs.h /usr/include/bits/wordsize.h \ /usr/include/gnu/stubs.h /usr/include/gnu/stubs-32.h \ /usr/lib/gcc/i486-linux-gnu/4.3.2/include/stddef.h \ /usr/include/bits/types.h /usr/include/bits/typesizes.h \ /usr/include/libio.h /usr/include/_G_config.h /usr/include/wchar.h \ /usr/lib/gcc/i486-linux-gnu/4.3.2/include/stdarg.h \ /usr/include/bits/stdio_lim.h /usr/include/bits/sys_errlist.h main.h \ stack.h maze.h
-M
選項把stdio.h
以及它所包含的系統標頭檔案也找出來了,如果我們不需要輸出系統標頭檔案的依賴關係,可以用-MM
選項:
$ gcc -MM *.c main.o: main.c main.h stack.h maze.h maze.o: maze.c maze.h main.h stack.o: stack.c stack.h main.h
接下來的問題是怎麼把這些規則包含到Makefile中,GNU make
的官方手冊建議這樣寫:
all: main main: main.o stack.o maze.o gcc $^ -o $@ clean: -rm main *.o .PHONY: clean sources = main.c stack.c maze.c include $(sources:.c=.d) %.d: %.c set -e; rm -f $@; \ $(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ rm -f $@.$$$$
sources
變數包含我們要編譯的所有.c
檔案,$(sources:.c=.d)
是一個變數替換語法,把sources
變數中每一項的.c
替換成.d
,所以include
這一句相當於:
include main.d stack.d maze.d
類似於C語言的#include
指示,這裡的include
表示包含三個檔案main.d
、stack.d
和maze.d
,這三個檔案也應該符合Makefile的語法。如果現在你的工作目錄是乾淨的,只有.c
檔案、.h
檔案和Makefile
,執行make
的結果是:
$ make Makefile:13: main.d: No such file or directory Makefile:13: stack.d: No such file or directory Makefile:13: maze.d: No such file or directory set -e; rm -f maze.d; \ cc -MM maze.c > maze.d.$$; \ sed 's,\(maze\)\.o[ :]*,\1.o maze.d : ,g' < maze.d.$$ > maze.d; \ rm -f maze.d.$$ set -e; rm -f stack.d; \ cc -MM stack.c > stack.d.$$; \ sed 's,\(stack\)\.o[ :]*,\1.o stack.d : ,g' < stack.d.$$ > stack.d; \ rm -f stack.d.$$ set -e; rm -f main.d; \ cc -MM main.c > main.d.$$; \ sed 's,\(main\)\.o[ :]*,\1.o main.d : ,g' < main.d.$$ > main.d; \ rm -f main.d.$$ cc -c -o main.o main.c cc -c -o stack.o stack.c cc -c -o maze.o maze.c gcc main.o stack.o maze.o -o main
一開始找不到.d
檔案,所以make
會報警告。但是make
會把include
的檔名也當作目標來嘗試更新,而這些目標適用模式規則%.d:
%c
,所以執行它的命令列表,比如生成maze.d
的命令:
set -e; rm -f maze.d; \ cc -MM maze.c > maze.d.$$; \ sed 's,\(maze\)\.o[ :]*,\1.o maze.d : ,g' < maze.d.$$ > maze.d; \ rm -f maze.d.$$
注意,雖然在Makefile中這個命令寫了四行,但其實是一條命令,make
只建立一個Shell程式執行這條命令,這條命令分為5個子命令,用;
號隔開,並且為了美觀,用續行符\
拆成四行來寫。執行步驟為:
-
set -e
命令設定當前Shell程式為這樣的狀態:如果它執行的任何一條命令的退出狀態非零則立刻終止,不再執行後續命令。 -
把原來的
maze.d
刪掉。 -
重新生成
maze.c
的依賴關係,儲存成檔案maze.d.1234
(假設當前Shell程式的id是1234)。注意,在Makefile中$
有特殊含義,如果要表示它的字面意思則需要寫兩個$,所以Makefile中的四個$傳給Shell變成兩個$,兩個$在Shell中表示當前程式的id,一般用它給臨時檔案起名,以保證檔名唯一。 -
這個
sed
命令比較複雜,就不細講了,主要作用是查詢替換。maze.d.1234
的內容應該是maze.o: maze.c maze.h main.h
,經過sed
處理之後存為maze.d
,其內容是maze.o maze.d: maze.c maze.h main.h
。 -
最後把臨時檔案
maze.d.1234
刪掉。
不管是Makefile本身還是被它包含的檔案,只要有一個檔案在make
過程中被更新了,make
就會重新讀取整個Makefile以及被它包含的所有檔案,現在main.d
、stack.d
和maze.d
都生成了,就可以正常包含進來了(假如這時還沒有生成,make
就要報錯而不是報警告了),相當於在Makefile中添了三條規則:
main.o main.d: main.c main.h stack.h maze.h maze.o maze.d: maze.c maze.h main.h stack.o stack.d: stack.c stack.h main.h
如果我在main.c
中加了一行#include "foo.h"
,那麼:
1、main.c
的修改日期變了,根據規則main.o main.d: main.c main.h stack.h maze.h
要重新生成main.o
和main.d
。生成main.o
的規則有兩條:
main.o: main.c main.h stack.h maze.h %.o: %.c # commands to execute (built-in): $(COMPILE.c) $(OUTPUT_OPTION) $<
第一條是把規則main.o main.d: main.c main.h stack.h maze.h
拆開寫得到的,第二條是隱含規則,因此執行cc
命令重新編譯main.o
。生成main.d
的規則也有兩條:
main.d: main.c main.h stack.h maze.h %.d: %.c set -e; rm -f $@; \ $(CC) -MM $(CPPFLAGS) $< > $@.$$$$; \ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \ rm -f $@.$$$$
因此main.d
的內容被更新為main.o main.d: main.c main.h stack.h maze.h foo.h
。
2、由於main.d
被Makefile包含,main.d
被更新又導致make
重新讀取整個Makefile,把新的main.d
包含進來,於是新的依賴關係生效了。
相關文章
- Makefile 自動生成標頭檔案的依賴關係
- gcc 標頭檔案依賴關係 分析工具GC
- openGauss libpq使用依賴的標頭檔案
- Java基礎:如何在IDEA中檢視依賴關係JavaIdea
- Maven 依賴關係Maven
- Android專案的依賴關係解析Android
- 介面自動化如何處理介面依賴問題
- 自動部署模組所需環境依賴--bat批處理BAT
- 關聯關係與依賴關係的區別
- build task依賴關係UI
- .NET Core新增專案之間的依賴關係
- ASP.NET Core基礎知識(三)【依賴關係注入(服務)】ASP.NET
- OSGI中的service依賴關係管理
- 介面自動化測試:apiAutoTest使用re 處理資料依賴API
- Maven中如何管理多模組專案的依賴關係Maven
- 如何管理前端專案中的複雜依賴關係前端
- Oracle 查詢鎖之間依賴關係的最源頭SIDOracle
- Spring Ioc原始碼分析系列--自動注入迴圈依賴的處理Spring原始碼
- Spring框架模組依賴關係Spring框架
- mysql主外來鍵依賴關係MySql
- depmod解決模組依賴關係
- [譯] Node.js 基礎知識:沒有依賴關係的 Web 伺服器Node.jsWeb伺服器
- 【譯】Gradle 的依賴關係處理不當,可能導致你編譯異常Gradle編譯
- 【物件導向依賴關係概念總結】物件導向程式設計的五種依賴關係物件程式設計
- C++ 字元處理函式(cctype標頭檔案)C++字元函式
- 介面自動化測試-apiAutoTest 優化之資料依賴處理API優化
- Maven依賴衝突處理Maven
- 分析資料庫的依賴關係(轉)資料庫
- 什麼是專案管理中的任務依賴關係專案管理
- 新版的Django Docker部署方案,多階段構建、自動處理前端依賴DjangoDocker前端
- Oracle啟動後對控制檔案的依賴Oracle
- 專案管理中,如何識別和管理依賴關係?專案管理
- C++ 字串 cctype 標頭檔案標準庫處理函式C++字串函式
- 【python】【安裝包依賴關係】Python
- 解決rpm包依賴關係
- saltstack對遞迴依賴條件(死迴圈依賴)的處理遞迴
- maven基礎:依賴範圍Maven
- PHP 依賴注入基礎理解PHP依賴注入