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 的用武之地——它將節省你的時間!
一個 make 檔案作為一個簡單的依賴樹工作——它編譯過時的內容,然後將你的軟體連結在一起。你將不得不指定編譯選項,但它們不會再對你那麼苛刻了。一個用於編譯應用程式的簡單 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,它處理將多個原始檔(並建立一個 bunch of ".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 指令碼)會設定 clean 步驟以刪除 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 C 程式設計教程 - 編寫 makefile
- Makefile 教程:如何編寫 Makefile
- OPUS Makefile 教程
- C、gcc 和 gmake 的 Makefile 教程
- 使用 NMake
- 關於 GNU Make 的“詢問 Make 先生”系列文章
- make 有什麼問題?
- GNU make 有什麼問題?
- Peter Miller. "遞迴 Make 被認為是有害的". (曾經是 遞迴 Make 被認為是有害的)
- 高階自動依賴項生成.
- "Kleknev:構建系統的粗粒度分析器". 有時候“make”用於構建極其龐大複雜的系統;Kleknev 系統可以幫助人們找出在構建過程中哪些專案消耗了最多的時間。