源代碼下載: Makefile-cn
Makefile 用於定義如何創建目標文件, 比如如何從源碼到可執行文件. 創建這一工具的目標是
減少不必要的編譯或者任務.是傳説中的 Stuart Feldman 在 1976 年花了一個週末寫出來的,
而今仍然使用廣泛, 特別是在 Unix 和 Linux 系統上.
雖然每個語言可能都有相應的或多或少提供 make 的功能, 比如 ruby 的 rake, node 的 gulp, broccoli
, scala 的 sbt 等等. 但是 make 的簡潔與高效, 和只做一件事並做到極致的風格, 使其至今仍是無可替代的,
甚至與其他構建工具一起使用也並無衝突.
儘管有許多的分支和變體, 這篇文章針對是標準的 GNU make.
# 這行表示註釋
# 文件名一定要叫 Makefile, 大小寫區分, 使用 `make <target>` 生成 target
# 如果想要取別的名字, 可以用 `make -f "filename" <target>`.
# 重要的事情 - 只認識 TAB, 空格是不認的, 但是在 GNU Make 3.82 之後, 可以通過
# 設置參數 .RECIPEPREFIX 進行修改
#-----------------------------------------------------------------------
# 初級
#-----------------------------------------------------------------------
# 創建一個 target 的規則非常簡單
# targets : prerequisites
# recipe
# …
# prerequisites(依賴) 是可選的, recipe(做法) 也可以多個或者不給.
# 下面這個任務沒有給 prerequisites, 只會在目標文件 file0.txt 文件不存在時執行
file0.txt:
echo "foo" > file0.txt
# 試試 `make file0.txt`
# 或者直接 `make`, 因為第一個任務是默認任務.
# 注意: 即使是這些註釋, 如果前面有 TAB, 也會發送給 shell, 注意看 `make file0.txt` 輸出
# 如果提供 prerequisites, 則只有 prerequisites 比 target 新時會執行
# 比如下面這個任務只有當 file0.txt 比 file1.txt 新時才會執行.
file1.txt: file0.txt
cat file0.txt > file1.txt
# 這裏跟shell裏的命令式一模一樣.
@cat file0.txt >> file1.txt
# @ 不會把命令打印到 stdout.
-@echo 'hello'
# - 意思是發生錯誤了也沒關係.
# 試試 `make file1.txt` 吧.
# targets 和 prerequisites 都可以是多個, 以空格分割
file2.txt file3.txt: file0.txt file1.txt
touch file2.txt
touch file3.txt
# 如果聲明重複的 target, make 會給一個 warning, 後面會覆蓋前面的
# 比如重複定義 file2.txt 會得到這樣的 warning
# Makefile:46: warning: overriding commands for target `file2.txt'
# Makefile:40: warning: ignoring old commands for target `file2.txt'
file2.txt: file0.txt
touch file2.txt
# 但是如果不定義任何 recipe, 就不會衝突, 只是多了依賴關係
file2.txt: file0.txt file3.txt
#-----------------------------------------------------------------------
# Phony(假的) Targets
#-----------------------------------------------------------------------
# phony targets 意思是 tagets 並不是文件, 可以想象成一個任務的名字而已.
# 因為不是文件, 無法比對是否有更新, 所以每次make都會執行.
all: maker process
# 依賴於 phony target 的 target 也會每次 make 都執行, 即使 target 是文件
ex0.txt ex1.txt: maker
# target 的聲明順序並不重要, 比如上面的 all 的依賴 maker 現在才聲明
maker:
touch ex0.txt ex1.txt
# 如果定義的 phony target 與文件名重名, 可以用 .PHONY 顯式地指明哪些 targets 是 phony
.PHONY: all maker process
# This is a special target. There are several others.
# 常用的 phony target 有: all clean install ...
#-----------------------------------------------------------------------
# 變量與通配符
#-----------------------------------------------------------------------
process: file*.txt | dir/a.foo.b # 可以用通配符匹配多個文件作為prerequisites
@echo $^ # $^ 是 prerequisites
@echo $@ # $@ 代表 target, 如果 target 為多個, $@ 代表當前執行的那個
@echo $< # $< prerequisite 中的第一個
@echo $? # $? 需要更新的 prerequisite 文件列表
@echo $+ # $+ 所有依賴, 包括重複的
@echo $| # $| 豎線後面的 order-only prerequisites
a.%.b:
@echo $* # $* match 的target % 那部分, 包括路徑, 比如 `make dir/a.foo.b` 會打出 `dir/foo`
# 即便分開定義依賴, $^ 依然能拿到
process: ex1.txt file0.txt
# 非常智能的, ex1.txt 會被找到, file0.txt 會被去重.
#-----------------------------------------------------------------------
# 模式匹配
#-----------------------------------------------------------------------
# 可以讓 make 知道如何轉換某些文件到其他格式
# 比如 從 svg 到 png
%.png: %.svg
inkscape --export-png $^
# 一旦有需要 foo.png 這個任務就會運行
# 路徑會被忽略, 所以上面的 target 能匹配所有 png
# 但是如果加了路徑, make 會找到最接近的匹配, 如果
# make small/foo.png (在這之前要先有 small/foo.svg 這個文件)
# 則會匹配下面這個規則
small/%.png: %.svg
inkscape --export-png --export-dpi 30 $^
%.png: %.svg
@echo 重複定義會覆蓋前面的, 現在 inkscape 沒用了
# make 已經有一些內置的規則, 比如從 *.c 到 *.o
#-----------------------------------------------------------------------
# 變量
#-----------------------------------------------------------------------
# 其實是宏 macro
# 變量都是字符串類型, 下面這倆是一樣一樣的
name = Ted
name2="Sarah"
echo:
@echo $(name)
@echo ${name2}
@echo $name # 這個會被蠢蠢的解析成 $(n)ame.
@echo \"$(name3)\" # 未聲明的變量會被處理成空字符串.
@echo $(name4)
@echo $(name5)
# 你可以通過4種方式設置變量.
# 按以下順序由高到低:
# 1: 命令行參數. 比如試試 `make echo name3=JICHAO`
# 2: Makefile 裏面的
# 3: shell 中的環境變量
# 4: make 預設的一些變量
name4 ?= Jean
# 問號意思是如果 name4 被設置過了, 就不設置了.
override name5 = David
# 用 override 可以防止命令行參數設置的覆蓋
name4 +=grey
# 用加號可以連接 (中間用空格分割).
# 在依賴的地方設置變量
echo: name2 = Sara2
# 還有一些內置的變量
echo_inbuilt:
echo $(CC)
echo ${CXX)}
echo $(FC)
echo ${CFLAGS)}
echo $(CPPFLAGS)
echo ${CXXFLAGS}
echo $(LDFLAGS)
echo ${LDLIBS}
#-----------------------------------------------------------------------
# 變量 2
#-----------------------------------------------------------------------
# 加個冒號可以聲明 Simply expanded variables 即時擴展變量, 即只在聲明時擴展一次
# 之前的等號聲明時 recursively expanded 遞歸擴展
var := hello
var2 := $(var) hello
# 這些變量會在其引用的順序求值
# 比如 var3 聲明時找不到 var4, var3 會擴展成 `and good luck`
var3 := $(var4) and good luck
# 但是一般的變量會在調用時遞歸擴展, 先擴展 var5, 再擴展 var4, 所以是正常的
var5 = $(var4) and good luck
var4 := good night
echoSEV:
@echo $(var)
@echo $(var2)
@echo $(var3)
@echo $(var4)
@echo $(var5)
#-----------------------------------------------------------------------
# 函數
#-----------------------------------------------------------------------
# make 自帶了一些函數.
# wildcard 會將後面的通配符變成一串文件路徑
all_markdown:
@echo $(wildcard *.markdown)
# patsubst 可以做替換, 比如下面會把所有 markdown
# 後綴的文件重命名為 md 後綴
substitue: *
@echo $(patsubst %.markdown,%.md,$* $^)
# 函數調用格式是 $(func arg0,arg1,arg2...)
# 試試
ls: *
@echo $(filter %.txt, $^)
@echo $(notdir $^)
@echo $(join $(dir $^),$(notdir $^))
#-----------------------------------------------------------------------
# Directives
#-----------------------------------------------------------------------
# 可以用 include 引入別的 Makefile 文件
# include foo.mk
sport = tennis
# 流程控制語句 (如if else 等等) 頂格寫
report:
ifeq ($(sport),tennis)
@echo 'game, set, match'
else
@echo "They think it's all over; it is now"
endif
# 還有 ifneq, ifdef, ifndef
foo = true
# 不只是 recipe, 還可以寫在外面喲
ifdef $(foo)
bar = 'bar'
endif
hellobar:
@echo bar
資源
- GNU Make 官方文檔 HTML PDF
- software carpentry tutorial
- learn C the hard way ex2 ex28
有建議?或者發現什麼錯誤?在Github上開一個issue,或者發起pull request!
原著Robert Steed,並由0個好心人修改。
© 2022 Robert Steed, Jichao Ouyang
Translated by: Jichao Ouyang
本作品採用 CC BY-SA 3.0 協議進行許可。