👨‍🍳

mise ではじめる開発環境構築

2024/01/10に公開

mise とは

発音は ミーズ らしいです。
最近まで rtx という名称でしたが、改名されて mise になりました。

確かに rtx だと某謎の半導体メーカの製品名と被るため、ググラビリティが低かったです。


mise は次世代 asdf という認識が強く、複数のランタイムのバージョン管理という印象が広まっていますが、公式では 開発環境のセットアップツール と称しています。
勿論、複数のランタイムのバージョン管理をする機能が目玉ではあるのですが、mise が管理するのはそれより広い意味である 開発環境 を管理するのだという主張が受け取れます。

自分の周りでは anyenvasdf から乗り換える人を最近よくみます。
そこで自分なりに mise にはどんな機能があるのか、どうやって使っていくのかを書いてみたいと思います。

また、ドキュメントがかなり充実しているため、読んでみることをオススメします。

導入

ドキュメントで環境毎に詳しく説明されています。

自分は Mac のため以下のような手順で導入しています。

brew install mise

echo 'eval "$(~/.local/bin/mise activate zsh)"' >> ~/.zshrc

主な機能

主な機能を紹介する前に、mise の様々な機能は mise のコンフィグファイルにて設定することが可能です。(勿論、都度コマンドを叩いて設定することもできます)

[tools]
node = '18'
go = ['1.21', '1.18']
watchexec = 'latest'

このようなファイルを例えば ~/.config/mise/config.toml として配置することでグローバルに反映することができます。
また、後述しますがこれらの設定はディレクトリごとに配置可能なため、特定のリポジトリで使いたい設定をまとめて書いておくだけで、そのリポジトリ用の mise ファイルが設定できます。

それでは、mise の機能をみていきましょう。主に3種類あります。

Go の複数バージョン管理について

本記事ではランタイムの複数バージョン管理の例として Go を題材にしていますが、現在の Go に複数のバージョン管理の必要性はほぼないと考えています。
(詳細は割愛します)

ご自身の環境で複数バージョンを管理したいランタイムに置き換えて読むようお願いします。

1. ツールの管理

複数のバージョンのランタイムなどを mise 経由でインストールし、管理することができます。

Examples:
  $ mise install node@20.0.0  # install specific node version
  $ mise install node@20      # install fuzzy node version
  $ mise install node         # install version specified in .tool-versions or .mise.toml
  $ mise install              # installs everything specified in .tool-versions or .mise.toml

CLI Reference

所謂 asdfanyenv で導入していたツールができることを指します。

mise で管理するには、そのツールのプラグインが必要です。
プラグインは2種類あります。

  1. mise 内で元からある組み込みのプラグイン
  2. コミュニティが作成したプラグイン

組み込みのプラグインは現在(2024年1月)時点で

  • Bun
  • Deno
  • Go
  • Java
  • NodeJS
  • Python
  • Ruby

に対応しています。

コミュニティが作成したプラグインは、Registry の README から確認できます。
これらは asdf のプラグインと互換があるため、asdf の資産を生かせます。

とはいえこれら2種類のプラグインの違いは、使う側からするとほぼ意識することはありません。
強いていうとインストールする際に、コミュニティ製のものだと確認が挟まれることぐらいです。

コミュニティ製ツールのインストール時のみ確認が挟まれる

ちなみに先程の例のような設定ファイルを記載した場合、

[tools]
node = '18'
go = ['1.21', '1.18']
watchexec = 'latest'
mise i

とすることで、設定ファイルに記載された内容を元にインストールできます。

2. 環境の管理

プロジェクトや環境によって、mise の設定や環境変数などを切り替えることができます。

特定のディレクトリ以下でのみ mise を設定する

特定のリポジトリで必要なツールを列挙するのに使えます。

例えば、あるリポジトリのルートに mise の設定ファイルを以下のように配置します。

.mise.toml
[tools]
go = "1.18"
pre-commit = "latest"

mise でグローバルにバージョン 1.21 をインストールしていたとしても、このディレクトリ以下では .mise.toml に記載されたバージョンである 1.18 が使われます。

ただしインストールが必要なので、そのディレクトリ内で

mise i

を実行して足りないものをインストールする必要はあります。

また、複数の mise 設定ファイルを用意して、環境によって切り替えるなども可能です。
設定ファイルの命名を .mise.{MISE_ENV}.toml などとします。
環境変数 MISE_ENV の値によって、使う設定ファイルが切り替わります。

特定のディレクトリ以下に環境変数を設定する

direnv のようなこと、つまりディレクトリによって環境変数を設定させることができます。

設定ファイルの env ブロックに記載します。

.mise.toml
[env]
ENV_NAME = "develop"

リポジトリのルートにこのファイルを配置することで、そのリポジトリ内では ENV_NAME には develop が設定されます。

echo $ENV_NAME
> develop

また、.env ファイルのような dotenv にも対応しており、env ブロック内からインポートすることができます。

.mise.toml
[env]
mise.file = ".env"
.env
SUPER_SECRET=supersecretvalue
echo $SUPER_SECRET
> supersecretvalue

ちなみに、設定ファイル自体に環境変数を設定することと、.env のようなファイルをインポートすることは併用できます。
そのため、開発環境で強制したいような環境変数は設定ファイル(.mise.toml)に記載し、リポジトリに残したくない秘匿な値は .env に記載して git で追跡させない使い方もできます。

.mise.toml
[env]
ENV_NAME = "develop"
mise.file = ".env"

3. タスク機能


この機能ではタスクランナー的なことができます。Makefile にコマンドをまとめるようなアレです。

.mise.toml
[tasks.build]
description = "Build"
run = "go build -ldflags='-s -w' -trimpath -o mise-sample main.go"

[tasks.<コマンド名>] というブロックに内容を書きます。
追加したタスクは mise run コマンドで実行できます。

mise run build

複数のタスクをまとめたタスクを作ることもできます。

.mise.toml
[tasks.test]
description = "Run tests"
run = "go test ./..."

[tasks.fmt]
description = "Run go fmt"
run = "go fmt"

[tasks.ci]
description = "Run CI tasks"
depends = ["test", "fmt"]
# test と fmt が実行される
mise run ci

外部のファイルも実行できるため、設定ファイルに書ききれない内容であれば使うのがよいでしょう。

.mise.toml
[tasks.deploy]
description = "Deploy app"
file = "scripts/deploy.sh"

コードが変更されたときに自動でタスクを実行する watch 機能もあります。

mise watch -t watch

ただしこちらは watchexec が必要になるため、実行時に存在しない場合は mise 経由でのインストールを促されます。

どう使っていくか

見ての通り沢山の機能があります。
従来だと開発環境を構築するには様々なツールが必要で、またランタイムごとにそれらが変わりがちでした。
共通する機能をもったツールは全て mise でまとめてしまおうというような、そういった思想が感じられます。そのためこれからもどんどん機能追加がされそうです。

mise には大きく分けて2種類の使い方があると思っています。

1. グローバルに使うツールの管理
2. リポジトリ毎のセットアップ

1 は従来の anyenv や asdf が担っていた機能です。
同じランタイムの中で複数のバージョンを使い分けたい状況にかなり有用です。
予め自分が使うランタイムのバージョンをそれぞれインストールしておき、リポジトリ直下にある .node-version のようなバージョンを判別するファイルを読み取り、切り替えるといった流れです。(もしくは手動で切り替えるなど)
ランタイムが同じ形式で管理できるため便利です。

2 は従来では様々なツール・手法が担当していた機能ではないでしょうか。

  • 開発に必要なツール
    • => 独自のインストールスクリプト
    • => ドキュメントでゴリ押し解決
  • 環境変数のセット
    • => dotenv や direnv など
  • タスクランナー
    • => Makefile など

これらは全て mise で解決できることになります。
冒頭で書いた mise が自身を 開発環境のセットアップツール と称している、というのはこういった機能から伺えますね。
とはいえこの機能を使っていくことは、他の開発者にも影響することだと思いますので、プロジェクトの方針次第で決めていくのが良いでしょう。

実際の例

今回、上記で挙げた使い方の リポジトリ毎のセットアップ がどんなものか、説明するためのリポジトリを作ってみました。

  • 適当な Go のプロジェクトで各種テストコマンドなどがある
  • Go のバージョンは 1.18 を使う
  • pre-commit を導入しており、コミット時にローカルでいくつかのフックを発火させたい
  • ある環境変数を設定したい
.mise.toml
[env]
# インラインで環境変数を定義可能
ENV_NAME = "develop"

 # .env に設定された環境変数を反映する
mise.file = ".env"

[tools]
go = "1.18"
pre-commit = "latest"
shellcheck = "latest"

[tasks.setup]
description = "Setup project"
run = "mise i && pre-commit install && go mod download"

[tasks.build]
description = "Build"
run = "go build -ldflags='-s -w' -trimpath -o mise-sample main.go"

[tasks.test]
description = "Run tests"
run = "go test ./..."

[tasks.fmt]
description = "Run go fmt"
run = "go fmt"

[tasks.ci]
description = "Run CI tasks"
depends = ["test", "fmt"]

[tasks.deploy]
description = "Deploy app"
file = "scripts/deploy.sh"

順にみていきます。

.mise.toml
[env]
# インラインで環境変数を定義可能
ENV_NAME = "develop"

 # .env に設定された環境変数を反映する
mise.file = ".env"

まずは環境変数を設定しています。
秘匿情報などがある環境変数向けに .env ファイルを指定しています。こちらは git の管理外のため、clone したら真っ先に追加する必要があります。そうしないと mise 側でエラーが出てしまいます。(ここは何とかならないかなと思ってて、理想は clone したら mise tun setup すれば一発で環境が整う状態です)


必要なツールを記述しています。

.mise.toml
[tools]
go = "1.18"
pre-commit = "latest"
shellcheck = "latest"

pre-commitshellcheck などの開発を補助するツールは latest で指定することが多いです。


タスクランナー用の設定です。

.mise.toml
[tasks.setup]
description = "Setup project"
run = "mise i && pre-commit install && go mod download"

[tasks.build]
description = "Build"
run = "go build -ldflags='-s -w' -trimpath -o mise-sample main.go"

setup というタスクを登録しています。

mise run setup

これにより、mise で定義されている Go と pre-commit、shellcheck のインストールが完了。
さらに pre-commit の初期セットアップと Go の依存モジュールのダウンロードも完了します。

元がかなりシンプルなリポジトリのため、呆気無いですがこれでセットアップは完了です。
あとはコードを書きながら、各種タスクを mise で実行することができます。
タスクの description はかなり雑に書いていますが、実務だともっと詳しく書いて .mise.toml を読めばなんとなく分かる状態を目指したいですね。

pre-commit は setup コマンド実行時に有効になっているため、そのままコミットをすることで発火されます。

Docker との住み分け

プロジェクトにおいて、他のメンバーにツールの導入や環境変数の設定などを強制したい場合、今日であれば Docker に開発環境を内包するのがベターな方法かと思います。
自分もそう思っていましたが、Docker 外で管理させたいツールなどがあったりします。
上記の例で挙げた pre-commit などはそれに該当します。pre-commit によってコミット前に何らかのフックを発火させて、リンターやフォーマッターを実行させる設定があったとしましょう。
このときのリンターやフォーマッター、また pre-commit などのツールそのものは Docker 外で管理させたいものです。
従来だと README にずらずらと これをインストールしておいて下さい などを記載していましたが、mise で管理すると、$ mise i を実行して下さい で済むことになります。

また、Docker 化するほどではないリポジトリなどでも、組織内で mise を使うような共通認識があれば、mise の設定ファイルを配置するだけで意図が伝わります。

なので Docker か mise かというわけではなく、 Docker の手の届きにくい箇所で真価を発揮するのだと思っています。

おわり

開発環境のセットアップツールである mise の紹介をしました。

今でこそ慣れてしまいましたが、開発に必要なツールというのは膨大で、またプロジェクトによって異なるものです。
いつの日か、このようなツールがデファクトになることで、環境構築をすることのマイナスなイメージが軽減されるといいですね。

Discussion