make和makefile自動化構建
- make和makefile自動化構建
- github地址
- 前言
- 背景介紹
- 為什么需要make和makefile?
- make和makefile解析
- 什么是make和makefile
- 依賴關系和依賴方法
- 核心語法結構
- 簡單演示
- 編譯
- 清理
- 多階段編譯示例
- make時執行的順序
- 場景1:clean目標在前(特殊情況)
- 場景2:總構建目標在前(一般情況)
- make如何實現不編譯未被修改的文件
- 場景引入
- make實現原理
- stat命令查看文件的屬性
- 三種時間:
- 1. Access Time (atime)
- 2. Modify Time (mtime)
- 3. Change Time (ctime)
- **對比總結**
- **實際應用場景**
- 文件 = 文件屬性 + 文件內容
- touch命令會修改相關的時間
- **操作時間戳的命令**
- 項目清理
- 編寫清理規則
- makefile中的特殊符號
- 總結make的構建原理
- 結語
make和makefile自動化構建
github地址
有夢想的電信狗
前言
在Linux環境下開發中,make
和makefile
是構建復雜項目的核心工具。本文將深入解析其工作原理,并通過實例演示如何編寫高效的自動化構建規則。
背景介紹
為什么需要make和makefile?
回顧我們編譯單個文件時,例如main.c
,使用如下命令:
gcc main.c -o main
單個文件,我們如此編譯尚且可以。
但是,當一個項目中有成百上千個源文件時,這樣子手動編譯難免有些不夠優雅。
手動輸入命令容易顯得有些繁瑣,且容易出錯
make
和makefile
就是為了解決這樣的問題的。
make
和makefile
的功能和優勢:
- 大型項目管理:當工程包含數百個源文件時,手動編譯和鏈接效率極低。
- 自動化編譯:通過定義規則明確編譯順序和依賴關系,
make
命令可一鍵完成全量構建。 - 跨平臺協作:
makefile
是Unix/Linux
世界的通用構建語言,被所有主流IDE
支持。
make和makefile解析
什么是make和makefile
-
make
是一個命令工具,是一個解釋makefile
中指令的命令工具,一般來說,大多數的IDE
都有這個命令,比如:Delphi
的make
,Visual C++
的nmake
,Linux
下GNU
的make
。可見,makefile
都成為了一種在工程方面的自動化構建方法。 -
makefile
是一個文件,是一個按照特定規則編寫的文件,當makefile
編寫完成后,make
可以識別文件中的內容進行相應的操作。 -
make
是一條命令,是一個可執行程序。 -
makefile
是一個當前目錄下的文件,和make
搭配使用,完成項目自動化構建。其中makefile
命名為makefile
和Makefile
均可,首字母M/m
不區分大小寫。
依賴關系和依賴方法
核心語法結構
target1: prerequisitesrecipe
target1: prerequisitesrecipe
target1: prerequisitesrecipe
# ......
targetn: prerequisitesrecipe
-
目標(Target):要構建的目標
- 最終產物(可執行文件)或中間產物(
.o
文件) - 支持偽目標(
.PHONY target
)用于執行操作 - 示例:
.PHONY
修飾clean
,install
等非文件目標
- 最終產物(可執行文件)或中間產物(
-
依賴項(Prerequisites):構建目標所需的依賴文件項
- 構建目標所需的文件(如
.c
、.o
文件)
- 顯式聲明:直接列出的依賴文件,支持使用通配符
*
- 隱式依賴:通過變量或通配符自動推導
- 順序敏感:依賴列表的順序影響構建優先級
- 構建目標所需的文件(如
-
配方(Recipe):生成目標所用到的
shell
命令。- 必須以
Tab
開頭(4空格會導致語法錯誤) - 每條命令在獨立
shell
中執行,每條當前形成當前target
所需要的方法,稱為依賴方法。該方法一般是Linux
下的命令。 - 使用
@
前綴抑制命令回顯
- 必須以
簡單演示
編譯
有以下源程序mycode.c
輸入以下指令:我們的單個源程序就跑起來了。
make
./mycode
可以看到:
- 我們不需要輸入繁瑣的
gcc
編譯指令,只需要輸入make
就自動可以幫助我們編譯完成。 - 輸入
make
的本質還是執行了gcc
編譯指令。
清理
- 當
makefile
文件中定義了clean
目標的依賴方法時,輸入make clean
即可完成程序文件的清理。
多階段編譯示例
有如下makefile
- 依賴鏈:
mycode.c → mycode.i → mycode.s → mycode.o → mycode
- 編譯流程:預處理→編譯→匯編→鏈接
make時執行的順序
$ make
- 從終極目標
mycode
開始逆向解析 - 檢查
mycode
是否存在或需要更新 - 遞歸檢查
mycode.s
→mycode.i
→mycode.c
的依賴關系 - 按依賴鏈順序執行編譯命令
場景1:clean目標在前(特殊情況)
場景2:總構建目標在前(一般情況)
觀察以上結果,可以看到,只執行make
時,卻執行了clean
。
執行make mycode
,又得到了正確的編譯結果。這是為什么呢?
由上我們可以總結歸納出:
make
后面要跟上構建的target
名。make
后面什么都不跟時,默認尋找Makefile
中第一個target
的依賴關系,執行其依賴方法。make targetName
:make
后面跟上target
的名字時,該命令可以無視makefile
中的出現次序,構建指定為targeName
的目標文件- 建議:
- 建議將要生成的可執行程序作為第一個依賴關系,這樣只輸入
make
即可完成構建 - 建議將
clean
放在最后,防止他人的誤操作。
- 建議將要生成的可執行程序作為第一個依賴關系,這樣只輸入
make如何實現不編譯未被修改的文件
場景引入
觀察一下場景:
- 我們在
make
一次之后,如果沒有對源文件進行修改,就無法再次make
了。
這是因為沒有必要 - 如果源文件沒有被修改,再次編譯得到的結果還是一樣的。
make
設計出這樣的功能,是為了提高編譯效率,節省編譯的時間開銷。
那么,以上功能是如何實現的呢?
make實現原理
可以思考下,如果讓我們自己實現這樣的功能,我們的思路會是怎么樣的?
思路大致如下:
-
通常情況下,我們一定是由源文件編譯形成可執行文件。先有源文件,才會有可執行文件。
-
因此,一般而言(
不通過命令改變文件的修改時間
),源文件的最近修改時間,一定比可執行文件的最近修改時間要更老!!! -
因此,只需要比較可執行程序的最近修改時間和源文件的最近修改時間即可。
-
(此處用.exe代替可執行程序,Linux下是編譯形成的有可執行權限的文件)
-
最近修改時間,
.exe
新于.c
,說明源文件時老的,不需要重新編譯 -
最近修改時間,
.exe
老于.c
,說明源文件時新的,需要重新編譯 -
而一般而言,
.exe
和.c
的最近修改時間是不會一樣的,除非通過命令來統一修改。
-
-
因此,如果我們更改了源文件,歷史上曾經還有可執行文件,那么源文件的最近修改時間,一定比可執行程序要新!
而文件的最近修改時間屬于文件屬性的一部分。
stat命令查看文件的屬性
**我們今天先只關注文件屬性中的時間,其他的屬性暫不展開敘述。**以下是簡介:
在 Linux/Unix
文件系統中,每個文件都有三個與時間相關的重要屬性,可以通過 stat
命令查看。以下是它們的詳細解釋:
三種時間:
1. Access Time (atime)
? 定義:文件最后一次被 訪問(讀取)的時間
? 觸發場景:
? 用 cat
、less
查看文件內容
? 用 grep
搜索文件內容
? 程序讀取文件(如腳本執行、軟件加載配置文件)
? 例外:如果使用 mount -o noatime
掛載文件系統,訪問時間不會更新(提高性能)
2. Modify Time (mtime)
? 定義:文件內容最后一次被 修改 的時間
? 觸發場景:
? 用編輯器修改文件內容
? 重定向輸出到文件(如 echo "text" > file
)
? 文件被程序寫入新數據
? 注意:修改文件內容會同時更新 change time(見下文)
示例:
echo "new content" > myfile.txt # 修改時間和change time均更新
3. Change Time (ctime)
? 定義:文件 元數據(metadata)(屬性)最后一次被修改的時間
? 觸發場景:
? 修改文件權限(chmod
)
? 修改文件所有者(chown
)
? 創建硬鏈接
? 重命名文件
? 修改文件內容(因為文件大小等元數據變化)
? 注意:ctime
不可通過 touch
直接修改,只能通過元數據操作間接更新
示例:
chmod 644 myfile.txt # change time更新
對比總結
時間類型 | 簡稱 | 觸發操作 | 查看命令 |
---|---|---|---|
Access | atime | 讀取文件內容 | stat -c %x filename |
Modify | mtime | 修改文件內容 | stat -c %y filename |
Change | ctime | 修改文件屬性 | stat -c %z filename |
實際應用場景
- 備份系統:通過
mtime
判斷文件內容是否變化,決定是否需要備份。 - 日志分析:通過
atime
追蹤可疑的文件訪問記錄。 - 調試問題:用
ctime
確認文件權限或所有權是否被意外修改。 - 恢復文件:結合三個時間戳分析文件歷史操作。
文件 = 文件屬性 + 文件內容
我們之前提到過,文件 = 文件屬性 + 文件內容,由上可以總結為:
- 修改文件內容后,
Modify
時間會改變 - 修改文件屬性后,
Change
時間會改變 - 一項操作可能同時改變三種時間中的多個
三種時間,可以用acm
來簡化記憶。
touch命令會修改相關的時間
touch
會修改文件屬性,修改后make
便可再次編譯make
通過比較源文件和可執行文件最近修改時間的新舊來判定要不要進行編譯- 比較時將屬性中的時間轉化為
Linux時間戳
進行比較
操作時間戳的命令
? 查看時間戳:
stat filename # 查看文件的屬性,屬性中包含有時間,filename代表系統中文件的名字
? 修改時間戳:
touch -a filename # 僅修改atime 即為Access時間
touch -m filename # 僅修改mtime 即為Modify時間
touch -t 202301010000 filename # 指定時間(格式:[[CC]YY]MMDDhhmm[.ss])
- 高級知識
? relatime:現代 Linux 默認使用 relatime
(僅當 atime
比 mtime
或 ctime
舊時才更新,平衡性能與準確性)
? ext4 的納秒精度:ext4
文件系統支持時間戳的納秒級記錄
- 注意:
touch
命令可通過-a ``-m
選項單獨修改一個文件的Access
時間和Modify
,但無法單獨修改文件的change
時間 touch filename
指令:無參數的touch
命令會修改文件的三個時間。
項目清理
編寫清理規則
- 實際上
makefile
的清理命令make clean
我們已經一直在使用了。
.PHONY: clean
clean:rm -f mycode
clean
是我們的target
,rm -f mycode
是目標的依賴方法,clean的依賴方法也可以是其他的命令。
前文我們提到,make
通過比較源文件和可執行文件最近修改時間的新舊來判定要不要進行編譯,但如果我就是想讓一個target
在每次make
時都重新進行編譯,該怎么辦呢?這便是.PHONY
的作用。
- .PHONY:聲明偽目標,避免與同名文件沖突
- 強制執行:無論是否存在
clean
文件,make clean
都會執行 .PHONY
修飾的目標文件,只要輸入make
,總是會被執行
- 實際上不建議用
.PHONY
修飾總的目標文件,簡直用.PHONY
修飾clean
makefile中的特殊符號
makefile中有很多特殊符號,本文只介紹三種特殊符號:@
,$^
,$@
@
:@用于修飾依賴方法,使make
之后,執行的命令不在終端中回顯。$^
和$@
常用在依賴方法中:$@
用來表示依賴關系冒號
左邊的內容。$^
用來表示依賴關系冒號
右邊邊的內容。
總結make的構建原理
- 查找Makefile:優先讀取
Makefile
或makefile
- 確定終極目標:解析第一個目標(示例中的
hello
) - 依賴樹分析:遞歸檢查所有依賴項是否存在
- 時間戳比對:若依賴文件比目標文件新,則觸發重新編譯
- 增量構建:僅重新編譯修改過的文件及其依賴鏈
結語
通過合理設計makefile
,開發者可以:
- 實現一鍵式編譯,提升開發效率
- 利用增量編譯節省構建時間
- 規范項目的構建和清理流程
如果本文對你有幫助,歡迎點贊收藏,技術問題歡迎在評論區交流!
一鍵三連,好運連連!