一直以為 Makefile 是獨屬於 C++ 專案的,看到這個文件就比較頭大,因為少不了需要執行一堆 make 命令來等待編譯鏈接。
不過最近才了解到,make 命令其實並沒有那麼神秘,相反,它可以有效地組織開發專案中會使用到的各種命令、環境變數、shell 命令等。
除此之外,Makefile 的一大好處就是可以配置一些自動化的前置步驟,比如 python 專案的 pip install, node 專案的 npm install 等。
最後,Makefile 還可以依賴本地的文件,如果文件沒有更新,則與之相應的任務並不會執行,有點類似於本地的 CI 了。
接下來就認識一下 Makefile 的簡單編寫以及實例吧。
Makefile 的基礎語法#
.PHONY#
因為 Makefile 的設計初衷其實是監控本地的文件是否有更新,再去觸發一系列動作,所以單純的 shell 命令執行得使用.PHONY(虛假)來實現,比如
dev:
@echo "Hello, world!"
.PHONY: dev
依賴#
Makefile 是分步驟進行的,每一個步驟都會有其前置任務,其寫法一般是:
target1 [target2 ...]: [pre-req1 pre-req2 pre-req3 ...]
[recipes
...]
最簡單的舉例,比如我們想執行一個 next.js 專案的本地預覽,其前置條件必須是已經安裝了對應的 dependences,那麼就可以這樣寫:
dev: node_modules ## 開啟本地服務
@./node_modules/.bin/next dev
.PHONY: dev
node_modules: package.json
@yarn install
這裡就構成了一個依賴的執行順序:
- 執行 make dev,會檢查前置條件 node_modules(注意這是一個步驟的命名)
- node_modules 步驟依賴於 package.json,即首先會執行一次 yarn install,後續如果 package.json 如果更新了才會執行。
根據上述簡單的示例就可以看到,Makefile 可以讓我們清晰有序地管理一些常見的開發步驟,免去諸如修改 package.json 但是忘記執行 yarn install 的 “悲劇”。
幫助文檔#
可以看到在上面的示例中,有通過 ##
來編寫註釋。
此時如果在 Makefile 中加入下面這段:
help: ## Show this help
@echo "\nSpecify a command. The choices are:\n"
@grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[0;36m%-12s\033[m %s\n", $$1, $$2}'
@echo ""
.PHONY: help
就可以自動生成註釋了,即輸入 make help 自動打印幫助文檔,是不是非常神奇呢 hh。
簡單示例:使用 Makefile 來管理你的 hugo 博客#
hugo 管理博客有幾個最簡單的命令,如開啟伺服器,生成站點靜態文件等,我們可以將其都放在一個 Makefile 中。
all: help ## 默認打開幫助文檔
serve: ## 啟動伺服器
@echo "Starting Hugo server..."
hugo server
build: ## 構建站點
@echo "Building the site..."
hugo
clean: ## 清理生成的文件
@echo "Cleaning up the public directory..."
rm -rf public/
help: ## Show this help
@echo "\nSpecify a command. The choices are:\n"
@grep -E '^[0-9a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[0;36m%-12s\033[m %s\n", $$1, $$2}'
@echo ""
.PHONY: all serve build clean help
默認的命令 all
就是顯示幫助文檔,而幫助文檔的生成只需要寫清楚註釋即可自動完成,現在輸入 make 命令的效果如下:
$:~/develop$ make
Specify a command. The choices are:
all 默認打開幫助文檔
serve 啟動伺服器
build 構建站點
clean 清理生成的文件
help Show this help
當然這裡只是最簡單的展示,你還可以根據需要添加對應的依賴文件,以及 new post 的操作也都是可以集成進來的。
一步步使用 Makefile 來管理 python 專案#
簡單版本#
首先,假設我們的 python 專案入口為 app.py,那麼我們最終執行的目標就是
run: ## 記得加註釋,可以自動生成幫助文檔
python app.py
.PHONY run ## 因為並沒有依賴文件,所以需要使用.PHONY
其次,python 肯定有前置依賴需要安裝,一般依賴都會寫在 requirements.txt,於是我們有
setup: requirements.txt
pip install -r requirements.txt
當然,一般還會補充一個 clean 的命令,於是有
clean:
rm -rf __pycache__
進階版本#
由於每個人電腦上本地的 python 環境變數都不同,當然這個也可以用 docker 來解決,不過 python 本身提供了 venv 這種更輕量級的解決方案,於是可以
python3 -m venv venv
-m venv 代表使用 venv 命令,創建一個虛擬環境,位於 venv 目錄。
此時安裝依賴就可以使用
./venv/bin/pip install -r requirements.txt
因為此時 pip 是位於 ./venv/bin/ 目錄下的。
所以得到 Makefile
venv/bin/activate: requirements.txt
python3 -m venv venv
./venv/bin/pip install -r requirements.txt
但是每次都要寫 ./venv/bin/
比較麻煩,可以使用變數做一下簡化。
即聲明一個變數 VENV
它的值默認為 venv
目錄,但是你也可以通過命令行傳遞的方式來修改。
引用變數的方式則是 $(VENV)
,可以理解為簡單的字串替換,於是 Makefile 就可以修改為:
VENV = venv
PYTHON = $(VENV)/bin/python3
PIP = $(VENV)/bin/pip
run: $(VENV)/bin/activate
$(PYTHON) app.py
.PHONY run
$(VENV)/bin/activate: requirements.txt
python3 -m venv $(VENV)
$(PIP) install -r requirements.txt
clean:
rm -rf __pycache__
rm -rf $(VENV)
.PHONY clean
簡單總結#
有了 GPT 之後,其實 Makefile 完全可以讓 GPT 來代勞,對於其語法完全可以做一個簡單了解,能看懂就行了,所以我也只列舉出了最簡單的語法以及對應的示例。
不得不說有了 AI 之後,諸如此類的工作已經完全不用開發者操心了。從另一個方面來說,以前覺得一些麻煩的,不太想了解的技術細節,其實往往有很多意想不到的作用。
具體到 Makefile,完全可以將其當做一系列命令的封裝,以及一些繁瑣依賴人工記錄的步驟,甚至可以將其當做 README 文件來使用了。