Makefile

tenzzZ發表於2024-05-02

編譯工具及構建工具介紹

在之前的課程中,都是直接使用gcc對程式碼進行編譯,這對簡單的工程是可以的,但當我們遇到複雜的工程時,每次用gcc等編譯工具去操作就會顯得很低效。因此make工具就出現了, make的出現是為了解決手動編譯和連結大型工程的問題,它可以避免重複的工作,提高效率,保證正確性。make工具就根據makefile中的命令進行編譯和連結的。但是當工程非常大的時候,手寫makefile也是非常麻煩的,如果換了個平臺makefile又要重新修改,因此更高階的一些構建系統或者工具工具像cmake、qmake、ninja和auto make就出現了,它們可以根據一些配置檔案來自動化編譯和連結軟體專案。

cmake:是一個跨平臺的構建系統,它可以根據CMakeLists.txt中的指令來生成不同平臺和工具的工程檔案,例如Makefile、Visual Studio解決方案、Ninja檔案等。cmake可以支援多種語言和多種架構,它還提供了一些高階功能,如測試、打包、安裝等。

qmake:是一個用於Qt專案的構建系統,它可以根據.pro或.pri中的指令來生成Makefile或其他形式的工程檔案。

ninja:是一個小巧而快速的構建工具,它可以根據ninja.build中的規則來執行編譯和連結命令。ninja主要關注於效能和效率,它可以利用多核處理器和並行處理來加速構建過程。ninja通常不需要使用者直接編寫
配置檔案,而是由其他構建系統(如cmake)來生成

auto make:是一個用於生成Makefile.in檔案的工具,Makefile.in是一種用於auto conf的配置檔案格式,auto conf是一個用於生成configure指令碼的工具。configure指令碼是一個用於檢測系統環境並生成最終的Makefile檔案的指令碼Makefile.am是一種用於auto make的配置檔案格式,它包含了一些指令和變數,用於定義程式或庫的原始檔、目標檔案、依賴關係和編譯選項等。

make:是一個經典而通用的構建工具,它可以根據Makefile中的規則來執行編譯和連結命令。make可以支援多種平臺和工具,它還提供了一些高階功能,如條件判斷、函式呼叫、模式匹配。

Makefile的使用

編譯的四個階段

編譯的四個過程:預處理(Pre-Processing)、編譯(Compiling)、彙編 (Assembliang)、連結(Linking)

Makefile的規則

target ... : prerequisites ...
<tab縮排>command
<tab縮排>...
<tab縮排>...
  • target:也就是一個目標檔案,可以是 Object File,也可以是執行檔案。還可以是一個標籤( Label),
    對於標籤這種特性,在後續的講“偽目標”中會有敘述。
  • prerequisites:要生成那個 target 所需要的檔案或是目標
  • command :也就是 make 需要執行的任意shell命令。

Makefile一個示例:

debug:
	@echo "hello world"		# @的作用:是否輸出執行的命令

注意:在makefile中使用#號註釋

如果,我們要編譯下面這個最簡單的例子:

#include <stdio.h>

int main(int argc, char *argv[])
{
	printf("hello world!\n");
	return 0;
}

Makefile修改如下:

debug:
	@echo "hello world"
	
test:
	gcc -o hello hello.c

執行命令make test 可以生成 hello檔案, 執行make debug可以輸出“hello world”:

注意:如果make後面不跟目標名,預設執行第一個目標,上述Makefile中也即執行debug目標。

也可以在Makefile中加入清理工程的規則:如下所示

debug:
	@echo "hello world"
test:
	gcc -o hello hello.c
clean:
	rm hello

偽目標

如果一個目標和一個實際檔案同名,那麼make會認為該目標已經是最新的,不需要重新生成,也不會執
行其命令。透過將目標宣告為偽目標.PHONY: debug,可以避免這種情況,強制執行其命令

示例:在檔案中先建立了一個與目標clean同名的檔案,執行make clean時提示已是最新,導致clean目標下的shell命令沒有執行。

debug:
	@echo "hello world"
test:
	gcc -o hello hello.c
clean:
	rm hello

修改Makefile檔案,將clean說明為偽目標。會強制執行clean目標下的命令,最終輸出hello檔案

debug:
	@echo "hello world"
test:
	gcc -o hello hello.c
clean:
	rm hello
.PHONY: clean

基本語法

變數賦值和預定義變數

Makefile中的變數賦值運算子有四種,分別是=:=?=+=$符號表示取變數的值,當變數名多於一個字元時,使用"( )"

  • = 表示延遲展開賦值,即變數的值是在使用時才確定,可能會受到後面的賦值影響。例如,VAR_A = A,VAR_B = $(VAR_A) B,VAR_A = AA,那麼最後VAR_B的值是AA B,而不是A B

    VAR_A = A
    VAR_B = $(VAR_A) B
    VAR_A = AA
    
    debug:
    	@echo $(VAR_B)
    
    ten@ten-virtual-machine:~/H616/demo$ make
    AA B
    
  • := 表示直接賦值,即變數的值是在定義時就確定,不會受到後面的賦值影響。例如,VAR_A := A, VAR_B := $(VAR_A) B,VAR_A := AA,那麼最後VAR_B的值是A B,而不是AA B。

    VAR_A := A
    VAR_B := $(VAR_A) B
    VAR_A := AA
    
    debug:
    	@echo $(VAR_B)
    
    ten@ten-virtual-machine:~/H616/demo$ make
    A B
    
  • ?=表示條件賦值,即只有當變數沒有被賦值時,才使用等號後面的值作為變數的值。例如,VAR ?= new_value,如果VAR在之前沒有被賦值,那麼VAR的值就為new_value,否則保持原來的值不變。

    VAR ?= new_value
    
    debug:
    	@echo $(VAR)
    
    ten@ten-virtual-machine:~/H616/demo$ make
    new_value
    

    變數VAR前面已經賦值

    VAR = old_value
    VAR ?= new_value
    
    debug:
    	@echo $(VAR)
    
    ten@ten-virtual-machine:~/H616/demo$ make
    old_value
    
  • += 表示追加賦值,即將等號後面的值追加到變數原來的值之後,形成一個新的值。例如,VAR += new_value,如果VAR在之前沒有被賦值,那麼VAR的值就為new_value,如果VAR在之前被賦值為
    old_value,那麼VAR的值就為old_value new_value

    VAR = old_value
    VAR += new_value
    
    debug:
    	@echo $(VAR)
    
    ten@ten-virtual-machine:~/H616/demo$ make
    old_value new_value
    

$符的其他用法:

  • $^:表示所有的依賴檔案

  • $@:表示生成的目標檔案

  • $<:代表第一個依賴檔案

註釋和換行符

採用#進行一行註釋

採用\作為續行符

變數的替換引用

語法格式:

$(var:a=b)或${var:a=b}		#表示把變數var的值中的a字尾替換成b字尾

示例:把變數src的值中的.c字尾替換成.o字尾,賦值給變數obj。

src := a.c b.c c.c
obj := $(src:c=o)

debug:
	@echo $(obj) 
ten@ten-virtual-machine:~/H616/demo$ make
a.o b.o c.o

總結示例

# 這是一個Makefile的註釋
TARGET = hello #TARGET延遲賦值hello
CC := gcc #CC立即賦值gcc
CC += -g #CC追加賦值-g, gcc -g表示新增除錯資訊,可用於gdb的除錯

SRC = hello.c
OBJ = $(SRC:.c=.o) #變數的替換引用,把hello.c的.c替換成.o

debug :
	@echo "hello world"
	echo $(SRC)
	echo $(OBJ)
	
$(TARGET): $(SRC)
	$(CC) -o $@ $<		# $(CC) -o ${TARGET} hello.c

compile: $(TARGET)

clean:
	@rm hello hello.o -r
	
.PHONY: clean compile

Makefile的函式

基本格式:$(<function> <arguments>)或者是${<function> <arguments>}。function:是函式名,arguments:是函式的引數,引數之間要用逗號分隔開,引數和函式名之間使用空格分開。呼叫函式的時候要使用字元“$”,後面可以跟小括號或者大括號。

wildcard

用於擴充套件萬用字元,返回與萬用字元匹配的檔案列表。格式如下

$(wildcard arguments)

萬用字元

是一種特殊的字元,可以表示多個檔名或目錄名,常見的萬用字元有 *?,分別表示任意長度的任意字元和單個任意字元。

比如:*.c 表示所有以 .c 結尾的檔名。a?.txt 表示所有以 a 開頭,中間有一個任意字元,以 .txt 結尾的檔名。

示例:表示查詢並返回src目錄下所有的.c檔案, *表示萬用字元, 匹配一個或者多個任意字元。

注意:該函式不會遞迴查詢。只能查當前目下的檔案

SRC = $(wildcard src/*.c)

debug:
	@echo $(SRC)
ten@ten-virtual-machine:~/H616/demo$ ls
clean  hello2.c  hello3.c  hello.c  Makefile  test
ten@ten-virtual-machine:~/H616/demo$ make
./hello2.c ./hello3.c ./hello.c

shell

用於執行shell命令

$(shell <cmd> <args>)

返回值:返回命令執行結果

引數介紹:

  • cmd:需要執行的命令
  • args:引數列表

示例:表示查詢當前目錄及子目錄下的所有.c檔案結尾的程式碼原始檔

SRC := $(shell find ./ -name '*.c')

debug:
	@echo $(SRC)
ten@ten-virtual-machine:~/H616/demo$ ls
clean  hello2.c  hello3.c  hello.c  Makefile  test
ten@ten-virtual-machine:~/H616/demo$ cd ./test
ten@ten-virtual-machine:~/H616/demo/test$ ls
hello4.c
ten@ten-virtual-machine:~/H616/demo/test$ cd ../
ten@ten-virtual-machine:~/H616/demo$ make
./test/hello4.c ./hello.c ./hello2.c ./hello3.c

patsubst

用於替換文字中的內容。

$(patsubst pattern,replacement,text)

返回值:patsubst 函式會在 text 中找到所有符合 pattern 的單詞,並用 replacement 替換它們,然後
返回替換後的文字。

引數介紹:

  • pattern:字串。被替換的字串,可以包含萬用字元 % 的模式,表示匹配任意長度的任意字元。
  • replacement:字串。替換字串,也可以包含 %,表示用 pattern 中匹配的字元替換。
  • text:是一個要處理的文字,可以包含多個以空格分隔的單詞。

示例:

#包含萬用字元
SRC = a.c b.c c.c
OBJ = $(patsubst %.c,%.o,$(SRC))

debug:
	@echo $(OBJ)
ten@ten-virtual-machine:~/H616/demo$ make
a.o b.o c.o
#不包含萬用字元
SRC = hello world
OBJ = $(patsubst world,ten,$(SRC))

debug:
	@echo $(OBJ)
ten@ten-virtual-machine:~/H616/demo$ make
hello ten

subst

用於替換文字中的內容。

$(subst from,to,text)

返回值:subst 函式會在 text 中找到所有的 from,並用 to 替換它們,然後返回替換後的字串。

引數介紹:

  • from:是要被替換的字元或單詞
  • to:是替換後的字元或單詞
  • text:是要處理的字串。

示例

OBJ = $(subst e,E,hello ten)

debug:
	@echo $(OBJ)
ten@ten-virtual-machine:~/H616/demo$ make
hEllo tEn

綜合示例

test@test:~/makefiletest$ tree
.
├── Makefile
└── src
	└── test.c

#Makefile內容
CC = gcc
CC += -g
SRC := $(shell find . -name *.c)
TARGET := $(patsubst %.c, %,$(subst src,obj, $(SRC)))

debug:
	@echo "hello world"
	echo $(SRC)
	echo $(TARGET)
$(TARGET): $(SRC)
	mkdir -p obj
	$(CC) -o $@ $<

compile: $(TARGET)
clean:
	@rm obj -r
	
.PHONY: clean compile

/*test.c內容*/
#include <stdio.h>

int main()
{
	printf("hello world\n");
	return 0;
}

執行make compile,生成生成obj/test

pg@pg-Default-string:~/makefiletest$ tree -a
.
├── Makefile
├── obj
│ 	└── test
└── src
	└── test.c

dir

dir 函式是一個用於從檔名序列中提取目錄部分的函式

$(dir NAMES...)

最佳化上述Makefile

CC = gcc
CC += -g
SRC := $(shell find . -name *.c)
TARGET := $(patsubst %.c, %,$(subst src,obj, $(SRC)))

debug:
	@echo "hello world"
	echo $(SRC)
	echo $(TARGET)
	
$(TARGET): $(SRC)
	mkdir -p $(dir $(TARGET))
	$(CC) -o $@ $<
	
compile: $(TARGET)
clean:
	@rm $(dir $(TARGET)) -r

.PHONY: clean compile

suffix

從檔名序列中取出各個檔名的字尾。

$(suffix <names...>)

示例

OBJ = $(suffix hello.c test/hello4.txt ten)

debug:
	@echo $(OBJ)
ten@ten-virtual-machine:~/H616/demo$ make
.c .txt

basename

從檔名序列中取出各個檔名的字首部分。返回檔名序列的字首序列,如果檔案沒有字首,則返回空字串。

$(basename <names...>)

示例

OBJ = $(basename hello.c test/hello4.txt ten)

debug:
	@echo $(OBJ)
ten@ten-virtual-machine:~/H616/demo$ make
hello test/hello4 ten

addsuffix

把字尾加到檔案序列中的每個單詞後面。返回加過字尾的檔名序列。

$(addsuffix .c,foo bar)

示例

OBJ = $(addsuffix .c,foo bar)

debug:
	@echo $(OBJ)
ten@ten-virtual-machine:~/H616/demo$ make
foo.c bar.c

addprefix

把字首加到中的每個單詞後面。返回加過字首的檔名序列。

$(addprefix src/,foo bar)

示例

OBJ = $(addprefix src/,foo bar)

debug:
	@echo $(OBJ)
ten@ten-virtual-machine:~/H616/demo$ make
src/foo src/bar

foreach

把list中使用空格分割的單詞依次取出並賦值給變數var, 然後執行text表示式

$(foreach <var>,<list>,<text>)

示例

files = ten zzz nb
OBJ = $(foreach file,$(files),$(file).c)

debug:
	@echo $(OBJ)
ten@ten-virtual-machine:~/H616/demo$ make
ten.c zzz.c nb.c

條件判斷

ifeq/ifneq

ifeq語句 : 判斷引數 是否相等,相等為 true, 否則是 false。可以沒有else

ifeq (arg1, arg2)
	#arg1 arg2 相等執行這裡的語句
else
	#arg1 arg2 不相等執行這裡的語句
endif

ifneq語句:判斷引數 是否不等,不等為 true, 否則為 false。可以沒有else

ifneq (arg1, arg2)
	#arg1 arg2 不相等執行這裡的語句
else
	#arg1 arg2 相等執行這裡的語句
endif

ifdef/ifndef

ifdef 語句: 判斷引數 是否有值 ,有值為 true, 否則是 false。可以沒有else

ifdef var
	#如果定義了var,執行這裡的內容
else
	#如果沒定義var,執行這裡的內容
endif

ifndef : 判斷引數 是否沒有值 ,沒有值為 true, 否則為 false。可以沒有else

infdef var
	#如果沒定義var,執行這裡的內容
else
	#如果定義var,執行這裡的內容
endif

相關文章