make scripts

如何使用 make 來達到像是 「npm scripts」或是「composer scripts」的功能

基本概念

撰寫「Makefile」

撰寫「Makefile」內容如下

.PHONY: help status deploy

help:
    echo make [command]
    echo ====================
    echo make help
    echo make status
    echo make deploy

status:
    echo show status

deploy:
    echo deploying...

完整範例

關於「.PHONY」的用法,請參考「GNU make / 4.6 Phony Targets

執行指令

執行

$ make

顯示

echo make [command]
make [command]
echo ====================
====================
echo make help
make help
echo make status
make status
echo make deploy
make deploy

執行

$ make help

顯示

echo make [command]
make [command]
echo ====================
====================
echo make help
make help
echo make status
make status
echo make deploy
make deploy

這個結果跟上面直接下「make」是一樣的,因為若沒給第一個參數,就會找第一個Rule,「help」在這個範例是放在第一個,所以「make」和「make help」結果一樣。

執行

$ make status

顯示

echo show status
show status

執行

$ make deploy

顯示

echo deploying...
deploying...

執行

$ make deploy -s

顯示

deploying...

這裡加上「-s」,就不會把指令也印出來,請參考「GNU make / 5.2 Recipe Echoing」。

或是改寫「Makefile」,在指令前面加上「@」。

.PHONY: help status deploy

help:
    @echo make [command]
    @echo ====================
    @echo make help
    @echo make status
    @echo make deploy

status:
    @echo show status

deploy:
    @echo deploying...

完整範例

執行

$ make

顯示

make [command]
====================
make help
make status
make deploy

執行

$ make deploy

顯示

deploying...

設定預設「Rule」

或是改寫「Makefile」, 最前面加上一個「Rule」名稱是「default」,然後相依「help」這個「Rule」, 這樣就可以指定預設的「Rule」。

注意:「default」可以任意命名,只要放在第一個「Rule」就行了。

.PHONY: default help status deploy

default: help

status:
    @echo show status

deploy:
    @echo deploying...

help:
    @echo make [command]
    @echo ====================
    @echo make help
    @echo make status
    @echo make deploy

完整範例

分散「.PHONY」

或是改寫「Makefile」, 將「.PHONY」分散,寫到各個「Rule」底下, 這樣的寫法除了可以防止「.PHONY」後面不會接了一長串之外,然後漏寫, 分散到各個「Rule」底下,除了容易增加,也容易刪除。

default: help
.PHONY: default

status:
    @echo show status
.PHONY: status

deploy:
    @echo deploying...
.PHONY: deploy

help:
    @echo make [command]
    @echo ====================
    @echo make help
    @echo make status
    @echo make deploy
.PHONY: help

完整範例

把指令分散到「Shell Script」

上面的寫法,只需要一個「Makefile」搞定。

而我個人慣用把這些指令,拆解到個別的「Shell Script」去,就會如同下面的結構。

./
├── bin
│   ├── deploy.sh
│   ├── status.sh
│   └── help.sh
└── Makefile

1 directory, 4 files

撰寫「Makefile」,內容如下

THE_MAKEFILE_FILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST)))
THE_BASE_DIR_PATH := $(abspath $(dir $(THE_MAKEFILE_FILE_PATH)))
THE_BIN_DIR_PATH := $(THE_BASE_DIR_PATH)/bin


default: help
.PHONY: default

help:
    @$(THE_BIN_DIR_PATH)/help.sh
.PHONY: help

status:
    @$(THE_BIN_DIR_PATH)/status.sh
.PHONY: status

deploy:
    @$(THE_BIN_DIR_PATH)/deploy.sh
.PHONY: deploy

完整範例

撰寫「bin/help.sh」,內容如下

#!/usr/bin/env bash

THE_BASE_DIR_PATH=$(cd -P -- "$(dirname -- "$0")" && pwd -P)

usage()
{
    echo ""
    echo "Usage: make [command]"
    echo
    cat <<EOF
Ex:
$ make
$ make help
$ make status
$ make deploy
EOF
}

usage

撰寫「bin/status.sh」,內容如下

#!/usr/bin/env bash

echo show status

撰寫「bin/deploy.sh」,內容如下

#!/usr/bin/env bash

echo deploying...

執行指令

執行

$ make

或是執行

$ make help

顯示

Usage: make [command]

Ex:
$ make
$ make help
$ make status
$ make deploy

執行

$ make status

顯示

show status

執行

$ make deploy

顯示

deploying...

設定「PATH」

撰寫「Makefile」,內容如下

THE_MAKEFILE_FILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST)))
THE_BASE_DIR_PATH := $(abspath $(dir $(THE_MAKEFILE_FILE_PATH)))
THE_BIN_DIR_PATH := $(THE_BASE_DIR_PATH)/bin

PATH := $(THE_BIN_DIR_PATH):$(PATH)

default: help
.PHONY: default

help:
    @help.sh
.PHONY: help

status:
    @status.sh
.PHONY: status

deploy:
    @deploy.sh
.PHONY: deploy

test:
    @firefox
.PHONY: test

dump-path:
    @echo $(PATH)

完整範例

上面加上下面這一行,來設定「PATH」這個「環境變數」。

PATH := $(THE_BIN_DIR_PATH):$(PATH)

原本的

    @$(THE_BIN_DIR_PATH)/help.sh

就可以改寫成

    @help.sh

注意「$(THE_BIN_DIR_PATH)」要寫在「$(PATH)」前面,這樣就會先尋找「$(THE_BIN_DIR_PATH)」這個資料夾裡面的資料。

執行

make test

顯示

firefox starting...

並不會真的啟動「firefox」,請看「bin/firefox」裡面的內容。

若是改寫成下面這一行

PATH := $(PATH):$(THE_BIN_DIR_PATH)

若執行「make test」, 會執行「firefox」這個指令, 不過不是「bin/firefox」裡面這個檔, 而是系統的「firefox」,通常路徑是「/usr/bin/firefox」,所以會啟動「Firfox」。

另外有一個「Rule」是「dump-path」

可以執行

$ make dump-path

就可以看到「PATH」的內容了。

include

然後將剛剛的某部份寫到「mak/debug.mk」這個檔裡。

test:
    @firefox
.PHONY: test

dump-path:
    @echo $(PATH)

使用「include $(THE_MAK_DIR_PATH)/debug.mk」來「include」, 撰寫「Makefile」,內容如下

THE_MAKEFILE_FILE_PATH := $(abspath $(lastword $(MAKEFILE_LIST)))
THE_BASE_DIR_PATH := $(abspath $(dir $(THE_MAKEFILE_FILE_PATH)))
THE_BIN_DIR_PATH := $(THE_BASE_DIR_PATH)/bin
THE_MAK_DIR_PATH := $(THE_BASE_DIR_PATH)/mak

PATH := $(THE_BIN_DIR_PATH):$(PATH)

default: help
.PHONY: default

help:
    @help.sh
.PHONY: help

status:
    @status.sh
.PHONY: status

deploy:
    @deploy.sh
.PHONY: deploy

include $(THE_MAK_DIR_PATH)/debug.mk

完整範例

一樣可以執行

$ make test

顯示

firefox starting...

執行

$ make dump-path

關於「include」的用法,請參考「GNU make / 3.3 Including Other Makefiles」。

傳參數

尚未研究傳參數的方式,因為在工作流程上,就是要把一些常用的動作包在一起,所以就直接把參數寫死在「script」上了。

只需要下「make [command]」就行了。

更多參考