編譯工具及構建工具介紹
在之前的課程中,都是直接使用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 BVAR_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_valueVAR = 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