🚀

Makefile から Taskfile へ移行するモチベーションの言語化

2024/10/27に公開
1

はじめに

タスクランナーを Makefile から Taskfile へと移行したいと考え、社内の他の開発者へ説明する機会がありました。
そのときの資料の転記になります。

すでに以下の記事があり、内容を加筆したものになります。
gsy0911 さんの記事がなければここまでまとめようと思いませんでした。ありがとうございます。

https://zenn.dev/gsy0911/articles/0a8e0e2156579d

本記事ではタスクランナーとしての Makefile と Taskfile を比較し、そのメリット・デメリットを洗い出すことを目的としています。
Taskfile の機能の説明を網羅したものでない点にご注意ください。

先にまとめ

モチベーションをまとめるとこの2つ。

  • Makefile の潜在的なリスクを回避したい
  • Taskfile による豊富な機能を享受したい

Makefile にはタブインデントや OS の環境差異のリスクがあると考えています。

Makefile はビルドツールのため、タスクランナーとして使用しようとすると追加の複雑さが必要な印象を受けました。
Taskfile にはタスクランナーとしての豊富な機能を活用できるメリットがあると考えています。

また、CI で make コマンドを使用していると影響範囲がそれなりに広がります。
規模が大きいアプリケーションでは修正箇所の把握、段階的な移行が大切であると考えます。

メリット・デメリットの概要

この後のセクションには詳細を書いていますが、その全体像を以下に示します。

  • Makefile の課題感
    • タブインデント
    • .PHONY の指定
    • OS の環境差異
  • Taskfile のメリット
    • クロスプラットフォーム対応
    • Makefile にない豊富な機能
    • yaml 形式による視認性
      • 特に「Makefile にない機能」との相乗効果で、豊富な機能を享受しつつ視認性を確保できる
  • 移行コスト
    • Makefile から Taskfile.yaml へ書き換え
    • ローカルに task をインストール
    • CI で task をセットアップ
    • README.md、社内ドキュメントの maketask へ書き換え

Makefile の課題感

タブインデント

  • Makefile はタブインデントでないと動作しない
  • タブインデントが原因で実行エラーとなったとき、デバッグしづらい (気づきづらい)

ファイルを生成しないコマンドには .PHONY を指定する必要がある

タスクランナーとしてみたとき、 .PHONY の指定は不要な記述となります。

Makefile

Makefile
.PHONY: hello

hello:
	echo "Hello, World!"

Taskfile

Taskfile.yaml
version: '3'

tasks:
  hello:
    cmds:
      - echo "Hello, World!"

Windows 環境では make コマンドが動作しないパターンがある

Windows10環境でmakeコマンドを使用する方法【ハマリ回避】

  • Program Filesフォルダにインストールすると、makeコマンドのパスにスペースが入ってうまく動かない
  • MinGWをインストールする際にmsysを一緒にインストールするとmakeプログラムもインストールされるが、この方法でインストールされたmakeは一部の組み込み変数の値がおかしくなっている

Taskfile のメリット

クロスプラットフォーム対応

  • どのプラットフォームでも追加の設定などすることなく、動く

yaml 形式による視認性

特にファイルの内容が多くなってきたとき、構造化されていると認知不可が低い。

Makefile

Makefile
.PHONY: build test clean

build:
	echo "Building the project..."

test:
	echo "Running tests..."

clean:
	echo "Cleaning up..."

Taskfile

Taskfile.yaml
version: '3'

tasks:
  build:
    desc: "Build the project"
    cmds:
      - echo "Building the project..."

  test:
    desc: "Run tests"
    cmds:
      - echo "Running tests..."

  clean:
    desc: "Clean up"
    cmds:
      - echo "Cleaning up..."

Taskfile の機能と比較

説明

Makefile

Makefile
.PHONY: hello

hello: ## say hello
	echo "Hello, World!"

Taskfile

Taskfile.yaml
version: '3'

tasks:
  hello:
    # `desc` でタスクの説明を追加
    desc: say hello
    cmds:
      - echo "Hello, World!"

タスクリスト

Makefile

  • サポートなし

Taskfile

Taskfile.yaml
version: '3'

tasks:
  hello:
    desc: This task says Hello, World!
    cmds:
      - echo "Hello, World!"
# -l オプションでタスクリストを表示
$ task -l
task: Available tasks for this project:
* hello:       This task says Hello, World!
default と組み合わせて設定することもできる
Taskfile.yaml
version: '3'

tasks:
  default:
    cmds:
      - task -l --sort none

  hello:
    desc: This task says Hello, World!
    cmds:
      - echo "Hello, World!"
$ task
task: Available tasks for this project:
* hello:       This task says Hello, World!

引数

Makefile

Makefile
.PHONY: hello

hello:
	echo "Hello $(USER_NAME)"
$ USER_NAME=taro make hello
hello taro

Taskfile

Taskfile.yaml
version: '3'

tasks:
  hello:
    cmds:
      - echo "hello {{.CLI_ARGS}}"
$ task hello -- taro
hello taro

ワイルドカード

Makefile

Taskfile

Taskfile.yaml
version: '3'

tasks:
  echo-*-*:
    vars:
      ARG_1: '{{index .MATCH 0}}'
      ARG_2: '{{index .MATCH 1}}'
    cmds:
      - echo {{.ARG_1}} {{.ARG_2}}
$ task echo-foo-bar
foo bar

内部タスク

Makefile

  • サポートなし

Taskfile

Taskfile.yaml
version: '3'

tasks:
  build-image-1:
    desc: build image-1
    cmds:
      - task: build-image
        vars:
          DOCKER_IMAGE: image-1

  build-image:
    # 内部タスクとして指定。task XXX で実行不可。再利用可能な関数のように利用できる。
    internal: true
    desc: build image
    cmds:
      - docker build -t {{.DOCKER_IMAGE}} .
$ task -l            
task: Available tasks for this project:
* build-image-1:       build image-1

プラットフォーム固有のタスク

Makefile

  • サポートなし

Taskfile

Taskfile.yaml
version: '3'

tasks:
  build-windows:
    # windows でしか実行されない。他の OS ではスキップされる。
    platforms: [windows]
    cmds:
      - echo 'Running command on Windows'

依存タスクの並列実行

Makefile

  • サポートなし

Taskfile

Taskfile.yaml
version: '3'

tasks:
  build:
    deps: [assets]
    cmds:
      - go build -v -i main.go

  assets:
    cmds:
      - esbuild --bundle --minify css/index.css > public/bundle.css

依存タスクの直列実行

Makefile

Makefile
.PHONY: hello

task-to-be-called:
	echo "Task to be called"

another-task:
	echo "Another task"

hello: task-to-be-called another-task
	echo "Hello, World!"

Taskfile

Taskfile.yaml
version: '3'

tasks:
  main-task:
    cmds:
      - task: task-to-be-called
      - task: another-task
      - echo "Both done"

  task-to-be-called:
    cmds:
      - echo "Task to be called"

  another-task:
    cmds:
      - echo "Another task"

実行ディレクトリの指定

Makefile

Makefile
.PHONY: cd-ls

cd-ls:
	(cd example-dir && ls)

Taskfile

Taskfile.yaml
version: '3'

tasks:
  cd-ls:
    # 実行ディレクトリを指定
    dir: example-dir
    cmds:
      - ls

タスクごとの変数定義

Makefile

  • サポートなし

Taskfile

Taskfile.yaml
version: '3'

tasks:
  hello:
    desc: This task says Hello, World!
    cmds:
      - echo "{{.LOCAL_MESSAGE}}, World!"
    # タスクごとに定義
    vars: { LOCAL_MESSAGE: 'Hello' }

グローバルな変数定義

Makefile

Makefile
MESSAGE := Hello

.PHONY: hello

hello:
	echo "$(MESSAGE), World!"

Taskfile

Taskfile.yaml
version: '3'

vars: { MESSAGE: 'Hello' }

tasks:
  hello:
    desc: This task says Hello, World!
    cmds:
      - echo "{{.MESSAGE}}, World!"

動的な値の変数定義

Makefile

Makefile
$(eval AWS_ACCOUNT := $(shell aws sts get-caller-identity | jq -r .Account))

.PHONY: hello

hello:
	echo "$(AWS_ACCOUNT)"

Taskfile

Taskfile.yaml
version: '3'

vars:
  AWS_ACCOUNT:
    sh: aws sts get-caller-identity | jq -r .Account

tasks:
  hello:
    cmds:
      - echo "{{.AWS_ACCOUNT}}"

dotenv との統合

Makefile

  • サポートなし

Taskfile

local.env
USER_NAME=taro
Taskfile.yaml
version: '3'

tasks:
  hello:
    cmds:
      - echo "hello $USER_NAME!"
    dotenv: ['local.env']

設定ファイルの分割

Makefile

docker.mk
docker-build:
    echo "build"
Makefile
include docker.mk

.PHONY: hello

hello:
    echo "Hello, World!"
$ make docker-build
build

Taskfile

Taskfile.docker.yaml
version: '3'

tasks:
  build:
    desc: build image
    cmds:
      - echo "build"
Taskfile.yaml
version: '3'

includes:
  docker: ./Taskfile.docker.yml

tasks:
  hello:
    desc: This task says Hello, World!
    cmds:
      - echo "Hello, World!"
$ task -l
task: Available tasks for this project:
* hello:              This task says Hello, World!
* docker:build:       build image

$ task docker:build
build

移行コスト

移行コストについてもまとめようと思います。
タスクランナーを CI で使用している場合、思ったよりも影響範囲は大きいです。

  • ローカル
    • task のインストール
    • MakefileTaskfile.yaml へ書き換え
  • CI
    • task のインストール
    • make コマンドを task コマンドへ書き換え
  • ドキュメント
    • README.md などの書き換え

CLI インストール

mac

$ brew install go-task/tap/go-task
$ brew install go-task

npm

$ npm install -g @go-task/cli

go

$ go install github.com/go-task/task/v3/cmd/task@latest

GitHub Actions セットアップ

- name: Install Task
  uses: arduino/setup-task@v1
  with:
    version: 3.x
    repo-token: ${{ secrets.GITHUB_TOKEN }}

おわりに

いかがだったでしょうか。ぜひみなさんも Taskfile を触ってみてください。

参考文献

Discussion