[Linux工具] Makefile


簡介
Linux的make程序用來自動化編譯大型源碼,很多時候,我們在Linux下編譯安裝軟件,只需要敲一個make就可以全自動完成,非常方便。
make能自動化完成這些工作,是因為項目提供了一個Makefile文件,它負責告訴make,應該如何編譯和鏈接程序。
Makefile相當于Java項目的pom.xml,Node工程的package.json,Rust項目的Cargo.toml,不同之處在于,make雖然最初是針對C語言開發,但它實際上并不限定C語言,而是可以應用到任意項目,甚至不是編程語言。此外,make主要用于Unix/Linux環境的自動化開發,掌握Makefile的寫法,可以更好地在Linux環境下做開發,也可以為后續開發Linux內核做好準備。
在本教程中,我們將由淺入深,一步一步學習如何編寫Makefile,完全針對零基礎小白,只需要提前掌握如何使用Linux命令。
安裝make
在Linux(Ubuntu)系統中,我們可以使用包管理工具直接安裝make以及GCC工具鏈。例如在Ubuntu下,使用以下命令:
$ sudo apt update
$ sudo apt install build-essential
安裝完成后,可以輸入以下命令驗證安裝是否成功:
$ make -v
GNU Make 4.3
$ gcc --version
gcc (Ubuntu ...) 11.4.0
這樣,我們就成功地安裝了make,以及GCC工具鏈。
Makefile基礎
在Linux環境下,當我們輸入make命令時,它就在當前目錄查找一個名為Makefile的文件,然后,根據這個文件定義的規則,自動化地執行任意命令,包括編譯命令。
Makefile由若干條規則(Rule)構成,每一條規則指出一個目標文件(Target),若干依賴文件(Prerequisites),以及生成目標文件的命令。規則的基本格式如下:
目標文件: 依賴文件1 依賴文件2 ...
命令
緊接著,以Tab開頭的是命令,用來生成目標文件。命令可以是任何Shell命令,如gcc、cp、mv等。以#開頭的是注釋,會被make命令忽略。
編譯C語言程序
下面是一個典型的Makefile,用于編譯C語言程序:
BIN=code # 目標文件
CC=gcc # 編譯器
SRC=$(wildcard *.c) # 所有的.c文件
OBJ=$(SRC:.c=.o) # 替換為.o文件
LFLAGS=-o
FLAGS=-c
RM=rm -f # 刪除方式
$(BIN): $(OBJ)
@$(CC) $(LFLAGS) $@ $^
%.o: %.c
@$(CC) $(FLAGS) $<
.PHONY: clean
clean:
$(RM) $(OBJ) $(BIN)
在這個Makefile中:
BIN=code:變量BIN的值是code,表示目標文件名。CC=gcc:變量CC的值是gcc,表示所使用的編譯器。SRC=$(wildcard *.c):SRC表示當前目錄下所有的.c文件,使用wildcard函數動態生成。OBJ=$(SRC:.c=.o):將SRC中的.c文件名替換為.o,表示目標文件對應的中間文件(對象文件)。LFLAGS=-o:鏈接選項,指定輸出文件名。FLAGS=-c:編譯選項,用于生成中間文件。RM=rm -f:刪除命令,用于清理目標文件和中間文件。$(wildcard *.c)用于匹配當前目錄下的所有.c文件。$(SRC:.c=.o)將所有的.c文件替換為.o文件。$@表示目標文件。$<表示第一個依賴文件。$^表示所有依賴文件。.PHONY聲明clean為偽目標,避免與同名文件沖突。
執行make即可編譯,執行make clean可清理生成的文件。
**注意:**如果在修改文件內容,但是其他文件沒有改變內容(Modify time)沒有更新,則未改變的文件不會進行重新編譯,可能會造成程序出現問題。所以當項目出現問題的時候可以對項目進行重新創建,以此解決問題。
使用隱式規則
Makefile支持許多內置的隱式規則,例如編譯C語言程序的規則:
%.o: %.c
$(CC) $(FLAGS) $<
含義:
- 將每個
.c文件編譯為對應的.o文件(對象文件)。 $<表示第一個依賴文件。
所以該規則會使*.c從第一個開始,逐個編譯為對應的.o文件。
小結:隱式規則讓Makefile更簡潔,很多情況下不需要顯式定義規則。
使用變量
賦值形式 =
這種賦值在解析時是延遲展開的。也就是說,右側的值在被使用時才會被計算,左值變為變量來使用。這種賦值方式是 GNU Make 的標準形式。
例如:
SRC=$(wildcard *.c)
變量可以提高Makefile的可讀性和可維護性。例如:
CC=gcc
CFLAGS=-Wall -g
SRC=main.c utils.c
OBJ=$(SRC:.c=.o)
program: $(OBJ)
$(CC) $(CFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -f $(OBJ) program
符號說明:
$(變量名):表示引用變量,例如$(CC)引用了變量CC,其值為gcc。
示例:
$@示例
objects = foo.o bar.o
$(objects): %.o: %.c
gcc -c $< -o $@
在這個例子中,$@ 將被替換為每個 .o 文件的目標名稱,如 foo.o 或 bar.o。
$@代表當前規則的目標文件名(即左側的文件)。- 它用于在命令中引用規則左側的目標。
$^示例
target: dependency1 dependency2
gcc -o $@ $^
在這個例子中,$^ 將被替換為 dependency1 dependency2。
$^代表當前規則中所有依賴文件的列表。- 它用于在命令中引用規則右側的所有依賴文件。
$<示例
%.o: %.c
gcc -c $< -o $@
在這個例子中,$< 將被替換為規則中的第一個依賴文件,如 main.c。
$<專門用來引用規則中列出的第一個依賴文件。
通過這些示例,可以清楚地理解這些符號的用途及功能。
使用模式規則
模式規則支持使用通配符定義一組目標文件的規則,例如:
%.o: %.c
$(CC) -c $< -o $@
符號說明:
%:通配符,表示任意文件名。例如,%.o匹配所有.o文件,%.c匹配所有.c文件。$<:表示當前規則中的第一個依賴文件。例如,main.c。$@:表示目標文件。例如,main.o。
通過模式規則,可以避免重復定義規則,適用于大規模項目中的批量操作。
自動生成依賴
對于大型項目,手動維護依賴關系容易出錯。我們可以使用gcc的-M選項自動生成依賴關系:
gcc -MM main.c > deps.d
然后在Makefile中包含生成的依賴文件:
include deps.d
自動生成依賴的意義:
- 動態更新依賴關系:當源碼文件發生變動時,重新運行依賴生成命令即可更新
deps.d。 - 減少人工維護的錯誤:避免手動書寫依賴關系帶來的遺漏問題。
注意:
- 包含依賴文件時,如果依賴文件不存在,Makefile可能會報錯。為此,可以使用如下方式:
-include deps.d
這表示如果deps.d不存在,Makefile將不會報錯。
示例:
- 自動生成依賴文件
%.d: %.c
@set -e; rm -f $@; \
$(CC) -MM $(CFLAGS) $< > $@.$$$$; \
mv $@.$$$$ $@
在這個規則中,依賴文件 .d 將自動根據對應的 .c 文件生成。
- 包含所有依賴
include $(SRC:.c=.d)
通過這條規則,可以動態包含所有的依賴文件。
完善Makefile
通過上述方法,我們可以逐步完善一個Makefile。以下是一個完整的示例:
BIN=program
CC=gcc
CFLAGS=-Wall -g
SRC=$(wildcard *.c)
OBJ=$(SRC:.c=.o)
$(BIN): $(OBJ)
$(CC) $(CFLAGS) -o $@ $^
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
.PHONY: clean
clean:
rm -f $(OBJ) $(BIN)
.PHONY: all
all: clean $(BIN)
-include deps.d
%.d: %.c
@set -e; rm -f $@; \
$(CC) -MM $(CFLAGS) $< > $@.$$$$; \
mv $@.$$$$ $@
通過以上內容的學習,已經足夠編寫寄出的Makefile~
本文來自博客園,作者:DevKevin,轉載請注明原文鏈接:http://www.rzrgm.cn/kevinbee/p/18678193

浙公網安備 33010602011771號