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 ,我們需要先建立對 TargetDependenciesCommands 的認知,讓我們先看看基本的 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

需要注意的有以下幾點:

  1. 在 Shell script 中,使用 if 後需要接上 fi。
  2. 在上面的範例中, ;\ 都是必要的。

接著,我們可以嘗試使用這個方法簡化版本推送的流程:

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

Reference