Make 是甚麼以及它的用法
Make 是甚麼 ?
顧名思義,make 的意思就是做。譬如說,想做出一個文件 a.txt,那麼就可以執行下面的指令:
$ make a.txt
但是如果你直接在命令行輸入這個指令是不會起作用的,因為 make 本身不知道要怎麼做出 a.txt,需要其他人告訴 make 怎麼做,也就是下面會介紹到的 Makefile。
比如說,假設 a.txt 要由 b.txt 和 c.txt 合併來得到,實際上指令應該要像這樣:
a.txt: b.txt c.txt
cat b.txt c.txt > a.txt
我們來解析下上面的指令。其實 make a.txt 本身可以分為兩步。第一步(第一行),要先確認 b.txt 和 c.txt 存在。第二步(第二行),使用 cat 連接指令合併 b.txt 和 c.txt 並且輸出到 a.txt。
其實這就是本文的主角,Makefile。像上面告訴 make 指令要怎麼做的規則都寫在一個叫做 Makefile 的文件中。而我們可以指定當前的 make 指令要依照哪一個 Makefile 文件的規定去 make 東西,像下面這樣:
$ make -f rules.txt
// 或是
$ make --file=rules.txt
上面的代碼就是說,指定 make 依照 rules.txt 中的規則去做東西。
這邊先簡單總結下,其實 make 就是一個根據 Makefile 規則指示構建東西的工具,Makefile 中的規則也很簡單,說明要做的東西依賴甚麼東西,要怎麼做。
Makefile 怎麼寫 ?
文件格式
Makefile 文件其實就是一些規則的集合,而每條規則型式如下:
<target> : <prerequisites>
[tab] <command>
target 放的叫做"目標",prerequisites 放的叫做前置條件,第二行必須由一個 tab
開頭,這是規定,然後後面就跟著 command,也就是要執行的指令。
其中,除了 target 為必需的,其他都是可選的,而 prerequisites 和 command 則必須至少存在一個。
target
一個目標就構成一個規則。通常,目標可以是文件名,像是上面的 a.txt。除了文件名,還可以是某個操作的名字,這叫做偽目標 (phony target)。看看下面的例子:
clean:
rm *.o temp
像上面這樣,clean 就是一個偽目標,作用是刪除所有後綴為 .o 的文件。
$ make clean
但是,萬一我們的目錄下面已經有了一個叫 clean 的文件,那 make 就會認為已經存在 clean 這個目標,所以並不會去執行真正的 clean 規則,當然這不是我們所希望的。解決辦法就是可以顯示的聲明 clean 是一個偽目標,相當於告訴 make,當我執行 make clean
時,不用去檢查是否已經存在 clean 文件。寫法如下:
.PHONY: clean
clean:
rm *.o temp
這邊補充下,如果我們的命令這麼寫:
$ make
那 make 就會默認去執行 Makefile 文件中的第一個目標
。
prerequisites
前置條件通常是一些文件名,中間用空格分開,指定了"目標"是否重新構建的標準。只要有一個前置文件不存在或是被更新過,就會需要重新構建該"目標。來看看這個例子:
result.txt: source.txt
cp source.txt result.txt
上面的 Makefile 中,構建目標 result.txt 的前置條件就是要先有個 source.txt,如果有,make result.txt
就會將 source.txt 的內容 copy 到 result.txt 當中。如果沒有,那就要再寫一個生成 source.txt 的規則,還生成目標 source.txt,如下:
source.txt:
echo "This is source.txt" > source.txt
可以看到,source.txt 目標不依賴任何前置條件,只要 source.txt 不存在,每次執行 make source.txt
都會如期生成 source.txt,文件內容為 “This is source.txt”。
有了上述的兩個目標 result.txt 和 source.txt 後,可以來看看下面這個例子,更清楚一點:
make result.txt
make result.txt
連續執行了兩次 make result.txt
,第一次因為還沒有 source.txt,所以會先新建一個 source.txt,然後再新建一個 result.txt。第二次的 make result.txt
因為發現 source.txt 沒有變動,所以不會執行不會執行任何操作,當然,result.txt 也不會再生成一次。
這邊補充一個用法,如果要生成多個文件,往往採用下面的寫法:
generate: file1 file2 file3
上述的 generate
是一個偽目標,只有三個前置文件,然後如下指令:
$ make generate
就會做出三個文件,file1, file2, file3。
command
命令就是要表示要對文件做甚麼操作,由一行或多行 shell 命令組成。command 是構建目標的具體指令,通常,運行結果就是生成目標文件,或是執行完偽目標。
實際案例
有了基本的概念,那麼就來看看一個實際的案例吧。
這是某次作業的要求,在 c++ 文件中所有的打印部分都要由 nasm 彙編語言來實現,並且用 Makefile 來啟動整個項目。當然我們首先就需要兩個文件,main.cpp 和 my_print.asm。那麼就來看看 Makefile 文件怎麼寫:
// main.cpp
#include <stdio.h>
#include <string.h>
#include <string>
#include <iostream>
#include <stdlib.h>
using namespace std;
extern "C" {
void nasm_print(const char *, const int);
}
void myPrint(const char * p);
int main() {
const char* s = "Hello World!";
myPrint(s);
return 0;
}
void myPrint(const char * p) {
nasm_print(p, strlen(p));
}
; my_print.asm
SECTION .text
global nasm_print
nasm_print:
push ebp
mov ebp, esp
mov edx, [ebp+12]
mov ecx, [ebp+8]
mov ebx, 1
mov eax, 4
int 80h
pop ebp
ret
# Makefile
init: main.cpp my_print.asm
nasm -f elf32 my_print.asm # 用 nasm 編譯 my_print.asm
g++ -m32 main.cpp my_print.o -o main # 用 g++ 將 cpp 文件和 asm 文件 link
rm -rf my_print.o
clean:
rm -rf main
這邊的案例是將 cpp 和 asm 進行聯合編譯,若有不清楚,可以去參考 C++和NASM联合编译。
有了上面的 Makefile,就可以在命令行中直接執行以下命令:
$ make init
$ make clean
相信效果一目瞭然的 ! 這篇關於 Make 的分享就到這邊結束。