make
make 是一種用於構建應用程式的實用程式。本教程將教你如何使用 Makefile 與此實用程式一起使用。本教程主要關注 GNU make。
你可能會認為 Make 僅僅是一個構建大型二進位制檔案或庫的工具(它確實如此,幾乎到了極致),但它遠不止於此。
Makefile 是機器可讀的文件,可以使你的工作流程可重現。
– Mike Bostock , "為什麼要使用 Make"[1]
在我們開始討論“make”之前,讓我們首先描述很久以前使用的構建過程:“手動編譯”,也稱為“手動構建”。如果你只有一個或兩個原始檔,這個過程並不算太糟糕。一個簡單的編譯過程可以透過手動輸入以下程式碼來完成
g++ -o file file.cpp
對於初學者來說,這可能需要一段時間才能習慣。作為替代方案,使用 make 命令將是
make file
這兩個命令都會做同樣的事情 - 獲取 file.cpp 原始檔,編譯它,然後將其連結到可執行檔案。
但問題在於,當用戶必須使用多個原始檔或依賴於庫時。建立一個 SDL 應用程式可能包含以下內容
g++ `sdl-config --cflags` -o file file.cpp
這越來越難記了,不是嗎?
讓我們新增一些最佳化
g++ -O3 -fomit-frame-pointer `sdl-config --cflags` -o file file.cpp
你真的想每次對原始檔進行最小的編輯時都輸入整個命令列嗎?這就是 make 的用武之地 - 它將節省你的時間!
一個 Makefile 就像一個簡單的依賴樹 - 它編譯過時的內容,然後將你的軟體連結在一起。你將必須指定編譯選項,但它們不會再讓你那麼費力了。一個用於編譯應用程式的簡單 Makefile
Makefile
default: myapp
myapp:
g++ -o file file.cpp
要編譯你的應用程式,你可以執行
make
或者
make myapp
好吧,你現在可能會問,這有什麼用?我自己可以為此編寫一個 shell 指令碼!所以現在讓我們處理多個檔案和目標檔案 - 你的應用程式足夠大了,可以連結了:它包含五個檔案。
全域性來看,存在三個函式
int main(); (file1.cpp) void file2function(std::string from); (file2.cpp) void file3function(std::string from); (file3.cpp)
File2 和 file3 有它們的標頭檔案,因此它們可以相互連結。實際的應用程式將如下所示
file1.cpp
#include "file2.h"
#include "file3.h"
int main()
{
file2function("main");
file3function("main");
}
file2.h
#include <string>
#include <iostream>
#include "file3.h"
void file2function(std::string source);
file2.cpp
#include "file2.h"
void file2function(std::string from)
{
std::cout << "File 2 function was called from " << from << std::endl;
file3function("file2");
}
file3.h
#include <string>
#include <iostream>
#include "file2.h"
void file3function(std::string source);
file3.cpp
#include "file3.h"
void file3function(std::string from)
{
std::cout << "File 3 function was called from " << from << std::endl;
}
那麼,我該如何將這一切連結起來呢?好吧,最基本的方法是手動完成所有操作
g++ -c -o file2.o file2.cpp
g++ -c -o file3.o file3.cpp
g++ -o file1 file1.cpp file2.o file3.o
但是,make 提供了一種更簡單的方法,**Makefile**
Makefile
default: testcase
testcase: file2.o file3.o
g++ -o file1 file1.cpp file2.o file3.o
非常不同,對吧?有一種不同的方法來做到這一點,這就是使 make 如此優秀的原因
Makefile
# Create the executable "file1" from all the source files.
COMPILER=g++
OBJS=file1.o file2.o file3.o
default: testcase
testcase: $(OBJS)
$(COMPILER) -o file1 $(OBJS)
現在擴充套件起來有多容易?只需將一個檔案新增到 OBJS 列表中,你就完成了!
這是一個完整的 Makefile,它處理將多個原始檔(並建立一堆“.o”檔案作為中間步驟)轉換為最終的“file1”可執行檔案的完整過程。
優秀的程式設計師會在 Makefile 中新增人類可讀的註釋(以“#”字元開頭的行),以描述編寫 Makefile 的原因以及 make 預期如何使用此 Makefile(這,唉,並不總是實際發生的情況)。
Makefile 中的一些語句可能非常長。為了使它們更容易閱讀,一些程式設計師將長語句分解成幾個更短、更容易閱讀的物理行。許多控制檯橫向顯示 80 個字元,這是某些程式設計師用於分解行和註釋的閾值。
簡而言之,程式設計師可能會認為編寫幾個短的物理行來組成一個語句看起來更漂亮,例如
some really long line \
with, like, \
a bajillion parameters and \
several file names \
and stuff
而不是將相同的語句塞進一行中
some really long line with, like, a bajillion parameters and several file names and stuff
當你在命令提示符下鍵入“make clean”時,“make”會刪除你指定的所有易於替換的檔案 - 即從你不可替換的手寫原始檔生成的中間檔案和輸出檔案。
要告訴“make”某個檔案是易於替換的檔案,請將其新增到 Makefile 中“clean”部分的“rm”行中,並且每次你在命令提示符下鍵入“make clean”時,該檔案都將被刪除。(如果你的 Makefile 中還沒有“clean”部分,請在 Makefile 的末尾新增一個如下所示的部分)
.PHONY: clean
clean:
rm *.o
rm file
在此示例中,我們告訴“make”所有中間目標檔案(“*.o”)和最終的可執行檔案(“file”)都是安全刪除、易於重新生成的。通常,人們(或 automake 指令碼)會設定清理步驟以刪除 Makefile 中的每個目標。[2]
通常,程式設計師會定期執行“make clean”,然後存檔目錄中的*每個*檔案,然後執行“make”以重新生成每個檔案。然後他測試程式可執行檔案,確信他正在測試的程式可以很容易地完全從存檔中的檔案重新生成。
許多 Makefile 包含一個“make check”目標,該目標執行自檢(如果有)。[3](make test 目標通常是 make check 的同義詞)。[4]
為了支援測試優先程式設計和迴歸測試,“make check”只執行測試,它不會重建程式;但通常 make all 首先重新構建程式,然後也執行“make check”。
“make”接受三種設定變數的方式。
- 遞迴擴充套件變數 透過使用
variable = value定義。這意味著如果一個變數 'y' 包含對另一個變數 'x' 的引用,並且 'x' 在 'y' 定義後發生更改,則 'y' 將包含對 'x' 進行的更改。
x = abc
y = $(x)ghi
x = abcdef
# x will be abcdef, while y will be abcdefghi
- 簡單擴充套件變數 透過使用
variable := value定義。這意味著如果一個變數 'x' 包含對另一個變數 'y' 的引用,則將對 'y' 變數進行一次且僅一次掃描。
x := abc
y := $(x)ghi
# x is 'abc' at this time
x := abcdef
# x will be abcdef, while y will be abcghi
- 如果你只想確保設定了變數,則可以使用
variable ?= value。如果變數已設定,則不會執行定義;否則,將 'variable' 設定為 'value'。[5]
許多程式設計師建議不要建立包含空格或美元符號的檔案,以避免“make”和許多其他實用程式中的“檔名中包含空格”錯誤。[6]
如果你必須處理檔名中包含空格或美元符號的檔案(例如檔案“my $0.02 program.c”),有時你可以使用雙反斜槓和雙美元符號轉義檔案的字面名稱 - 在 Makefile 中,該檔名可以表示為“my\\ $$0.02\\ program.c”。[7]
遺憾的是,當“make”與檔案列表一起工作時(這些檔案在內部表示為以空格分隔的檔名),雙斜槓轉義不起作用。一種解決方法是使用無空格的表示法(如“my+$$0.02+program.c”)來引用該檔名,然後在makefile中稍後使用$(subst)函式將該無空格表示法轉換回實際的磁碟檔名。[6] 遺憾的是,這種解決方法無法處理同時包含空格和加號的檔名。
也許最簡單的辦法是避免在“make”的輸入或輸出中使用包含空格的檔名。
建立新的makefile時,將其命名為“Makefile”(8個字元,無副檔名)。
原則上,您可以建立具有其他名稱的makefile,例如makefile或GNUmakefile,這些名稱(如Makefile)會被GNU版本的make實用程式自動找到,或者您可以將其他名稱與--file選項一起傳遞給make實用程式。[8]
- ↑ Mike Bostock "為什麼要使用Make".2013。
- ↑ "Makefile和RMarkdown". 2015。
- ↑ "使用者標準目標"。
- ↑ "實現`make check`或`make test`"。
- ↑ "GNU Make手冊" “6.2 兩種變數”章節。
- ↑ a b John Graham-Cumming. "GNU Make遇到包含空格的檔名". CM Crossroads 2007。
- ↑ John Graham-Cumming. "GNU Make轉義:狂野之旅" CM Crossroads 2007。
- ↑ "GNU Make手冊" “3.2 為Makefile指定什麼名稱”章節。
- "問答先生"系列關於GNU Make的文章
- make有什麼問題?
- GNU make有什麼問題?
- Peter Miller. "遞迴Make有害". (曾為 遞迴Make有害)
- 高階自動依賴項生成.
- "Kleknev:構建系統的粗粒度分析器". 有時“make”用於構建極其龐大而複雜的系統;Kleknev系統幫助人們找出構建過程中哪些專案消耗了最多的時間。