3 6 Makefile - ianchen0119/AwesomeCS GitHub Wiki
如果讀者經常泡在 GitHub 上瀏覽他人的 C 語言專案,應該很常會看到名為 Makefile
的文件。
本文會介紹 Make 這套建構工具以及撰寫 Makefile 文件的技巧。
Make
Make 是一套自動化建構軟體,它會讀取名為 Makefile
的檔案,並根據使用者輸入的命令找到 target
後生成命令轉交給 shell 去執行,以生成開發者需要的可執行檔案。
不只如此, Make 還具有依賴關係的檢查系統,可以幫助開發者高效的進行測試與編譯工作。
Make 的歷史
該軟體最早由貝爾實驗室的 Stuart Feldman 開發,隨後也被各種 UNIX Like 的分支進行無數次的改良 (包含改良演算法與功能擴充),衍生出了多種版本:
- BSD Make
- GNU Make
- Microsoft nmake
撰寫 Makefile
要撰寫 Makefile ,我們需要先建立對 Target
、 Dependencies
與 Commands
的認知,讓我們先看看基本的 Makefile 文件:
output: main.o
gcc -o output main.o
main.o: main.c def.h
gcc -c main.c
在這個 Makefile 中:
- 有兩個 Target ,分別是
output
以及main.o
。 - 在 Target 的右方,我們可以看到它的依賴,以
output
為例: 它的依賴為main.o
。 假設output
這個 Target 被執行,Make 會去檢查 main.o 是否存在或是否被修改,如果有的話便會先執行main.o
target 底下的命令產生main.o
,再執行output
底下的命令產生output
檔案。 至於要如何執行 Target 呢?我們可以這樣做:
make output
或是:
make
如果不指定 target 的話,Make 會執行 Makefile 當中的第一個 Target 。
使用巨集
Make 也提供了巨集功能,可以幫助開發者寫出更精簡的 Makefile 文件。 以剛剛的 Makefile 為例,我們可以使用巨集對它稍作梳理:
OBJ = main.c def.h
output: main.o
gcc -o output main.o
main.o: $(OBJ)
gcc -c main.c
條件式
如果有撰寫 C 程式的經驗,想必都已經熟悉了前置處理器的使用。Make 讓我們能夠在 Makefile 中使用類似的條件式,以下內容取在 xv6-riscv 的 Makefile:
ifndef TOOLPREFIX
TOOLPREFIX := $(shell if riscv64-unknown-elf-objdump -i 2>&1 | grep 'elf64-big' >/dev/null 2>&1; \
then echo 'riscv64-unknown-elf-'; \
elif riscv64-linux-gnu-objdump -i 2>&1 | grep 'elf64-big' >/dev/null 2>&1; \
then echo 'riscv64-linux-gnu-'; \
elif riscv64-unknown-linux-gnu-objdump -i 2>&1 | grep 'elf64-big' >/dev/null 2>&1; \
then echo 'riscv64-unknown-linux-gnu-'; \
else echo "***" 1>&2; \
echo "*** Error: Couldn't find a riscv64 version of GCC/binutils." 1>&2; \
echo "*** To turn off this error, run 'gmake TOOLPREFIX= ...'." 1>&2; \
echo "***" 1>&2; exit 1; fi)
endif
包含上面使用的 ifndef
,Make 還提供了:
- ifdef
- ifndef
- ifeq
- ifneq
給開發者使用,使用這些條件式都必須在結尾處加上 endif
。
Shell Script
除了巨集功能,我們還可以將 Shell script 的條件式應用到 Makefile 中:
forfun:
@if [ "test" = "test" ]; then\
echo "Hello world";\
fi
需要注意的有以下幾點:
- 在 Shell script 中,使用 if 後需要接上 fi。
- 在上面的範例中,
;
與\
都是必要的。
接著,我們可以嘗試使用這個方法簡化版本推送的流程:
push:
@if [ "x$(MSG)" = 'x' ]; then\
echo "Commit message is necessary."; fi
@test "x$(MSG)" != 'x'
git commit -a -m "$(MSG)"
git push origin master
@if
幫助我們判斷 MSG 是否為空@test
如果 MSG 不為空,繼續執行,否則 make 會被暫停。 改寫腳本後,使用下列命令呼叫push
target:
MSG="init commit" make push