サーバ上でアプリケーションを動作させたり、バイナリをビルドするために、特定のバージョンのプログラム言語プラットフォームを必要とすることがある。
あらかじめ Docker で動くように設計されているのであれば Docker で十分だが、全てが対応しているわけではない。
そんな時良く使われるのが、***env
系のバージョン管理ツール。
env 系ツール
元々は rbenv が始まりらしいが、今では様々な言語向けに移植されている。
できることは大体同じで、
- 特定のバーションを取得 (ビルド)
- グローバルに利用するバージョンの管理
- フォルダ毎にバージョンを固定
- ローカルに何かしらの dotfile を置き、そこに設定する
- 環境変数で切り替える
- shims の管理
ただ、言語ごとに **env
をいちいち取ってきてシェルの設定するのが面倒なので、その場合は **env
を管理する anyenv がよく使われる。
とはいえ、**env
系はメンテナンスが止まっているものもあったりと、不満もあった。
そこで、自分は asdf というツールに乗り換えた。
asdf
asdf は、様々なプログラム言語やツールの実行環境・SDK のバージョン管理を行う為のツール。
https://github.com/asdf-vm/asdf
管理できる対象は以下で一覧できる。
https://github.com/asdf-vm/asdf-plugins#plugin-list
一覧を見れば分かるが、プログラム言語の管理だけを目的としているわけではない。
kubectl や bazel などの CLI ツールや、Elasticsearch や MongoDB などのミドルウェアも何故か扱える。
が、今回はプログラム言語プラットフォームだけを対象とする。
対象言語
自分が知っている言語を抜き出してみた。
Language | Repository |
---|---|
Clojure | halcyon/asdf-clojure |
Coq | gingerhot/asdf-coq |
Crystal | marciogm/asdf-crystal |
D (DMD) | sylph01/asdf-dmd |
Elixir | asdf-vm/asdf-elixir |
Elm | vic/asdf-elm |
Erlang | asdf-vm/asdf-erlang |
Go | kennyp/asdf-golang |
GraalVM | vic/asdf-graalvm |
Haskell | vic/asdf-haskell |
Idris | vic/asdf-idris |
Java | skotchpine/asdf-java |
Julia | rkyleg/asdf-julia |
Kotlin | missingcharacter/asdf-kotlin |
Logtalk | LogtalkDotOrg/asdf-logtalk |
Lua | Stratus3D/asdf-lua |
LuaJIT | smashedtoatoms/asdf-luaJIT |
Nim | rfrancis/asdf-nim |
Node.js | asdf-vm/asdf-nodejs |
OCaml | vic/asdf-ocaml |
PHP | odarriba/asdf-php |
Python | danhper/asdf-python |
R | iroddis/asdf-R |
Racket | vic/asdf-racket |
Ruby | asdf-vm/asdf-ruby |
Rust | code-lever/asdf-rust |
Scala | mtatheonly/asdf-scala |
.Net Core | emersonsoares/asdf-dotnet-core |
2018/12 現在、メンテナンスもアクティブになされており、新しい言語にも対応していきそうだ。
新し目で言うと、GraalVM
なんかもある。
インストール
手順は簡単
$ git clone https://github.com/asdf-vm/asdf.git ~/.asdf
# ついでに、プラグインでよく使われるパッケージをインストールしておく
$ sudo apt-get install -y automake autoconf libreadline-dev libncurses-dev libssl-dev libyaml-dev libxslt-dev libffi-dev libtool unixodbc-dev
あとは、お使いの Shell に設定追加するだけ。私は fisher です。
$ echo 'source ~/.asdf/asdf.fish' >> ~/.config/fish/config.fish
$ mkdir -p ~/.config/fish/completions; and cp ~/.asdf/completions/asdf.fish ~/.config/fish/completions
$ source ~/.asdf/asdf.fish
これで終わり。
$ asdf --version
# v0.6.2
使ってみる
Node.js
まずは、分かりやすく Node.js で挑戦。
$ asdf plugin-add nodejs
# 既に入っているなら、アップデート
$ asdf plugin-update nodejs
# バージョンリストを取得
$ asdf list-all nodejs
# 0.10.0
# 0.10.1
# ....
# インストール
$ asdf install nodejs 10.10.0
$ asdf install nodejs 11.3.0
# 不要ならアンインストール
$ asdf uninstall nodejs 9.11.0
グローバルに選択してみる
$ asdf global nodejs 11.3.0
$ node -v
# v11.3.0
# 現在のグローバルなバージョン
$ asdf current nodejs
# 11.3.0 (set by /home/username/.tool-versions)
# インストールされたリストを一覧
$ asdf list nodejs
# 10.10.0
# 11.3.0
今度はプロジェクト毎に選択してみる
$ cd ./some_project_folder
$ asdf local nodejs 10.10.0
$ node -v
# v10.10.0
# ローカル設定は .tool-versions に保存
$ cat .tool-versions
# nodejs 10.10.0
# ディレクトリを抜ければ、もとに戻る
$ cd ..
$ node -v
# v11.3.0
ちなみに、shims はどうなってるかと言うと、
$ which node
# /home/username/.asdf/shims/node
$ cat /home/username/.asdf/shims/node
# #!/usr/bin/env bash
# # asdf-plugin: nodejs
# # asdf-plugin-version: 11.3.0
# # asdf-plugin-version: 10.10.0
# exec /home/username/.asdf/bin/private/asdf-exec nodejs bin/node "$@"
# 元のフォルダの位置
$ asdf where nodejs
# /home/username/.asdf/installs/nodejs/11.3.0
# 貼り直し
$ asdf reshim nodejs
これだけできれば十分。
Go
次は、Go でやってみる。
$ asdf plugin-add golang
# バージョンリストを取得
$ asdf list-all golang
# 1.2.2
# 1.3
# ....
# インストール
$ asdf install golang 1.11.2
$ asdf global nodejs 1.11.2
$ go version
# go version go1.11.2 linux/amd64
$ go env
# GOARCH="amd64"
# GOBIN=""
# GOCACHE="/home/username/.cache/go-build"
# GOEXE=""
# GOFLAGS=""
# GOHOSTARCH="amd64"
# GOHOSTOS="linux"
# GOOS="linux"
# GOPATH="/home/username/.asdf/installs/golang/1.11.2/packages"
# GOPROXY=""
# GORACE=""
# GOROOT="/home/username/.asdf/installs/golang/1.11.2/go"
# GOTMPDIR=""
# GOTOOLDIR="/home/username/.asdf/installs/golang/1.11.2/go/pkg/tool/linux_amd64"
# GCCGO="gccgo"
# CC="gcc"
# CXX="g++"
# CGO_ENABLED="1"
# GOMOD=""
# CGO_CFLAGS="-g -O2"
# CGO_CPPFLAGS=""
# CGO_CXXFLAGS="-g -O2"
# CGO_FFLAGS="-g -O2"
# CGO_LDFLAGS="-g -O2"
# PKG_CONFIG="pkg-config"
# GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build209186154=/tmp/go-build -gno-record-gcc-switches"#
GOROOT
や GOPATH
が存在しない場合、shims の中の asdf-exec
実行時に、$ASDF_INSTALL_PATH/go
と $ASDF_INSTALL_PATH/packages
に設定される。
https://github.com/kennyp/asdf-golang/blob/master/bin/exec-env
実際にやってみると、$ASDF_INSTALL_PATH
は /home/username/.asdf/installs/golang/1.11.2/
になった。
普通に set -x GOPATH $HOME/go
と設定すればそっちが優先される。
Python
Python は、内部では pyenv を利用し、asdf のインターフェイスに合わせただけのようだ
Java (JDK)
Java は、list-all
で表示するバージョンが 固定となっており、選べる OpenJDK は、openjdk-10.0.2
openjdk-11
openjdk-11.0.1
だけとなっている。
まぁ、サポート期間を考えて 10 未満を切り捨てているのであれば仕方ないのかも知れないが。
ちなみに、OracleJDK ではまだ 8 が存在している。
Rust や Haskell はどうすべきか問題
例えば、Rust には rustup があり、Haskell には stack がある。
これらは、
- 公式 or デファクトスタンダードなプラットフォームバージョン管理ツール
- ツールは安定し、使いやすく、メンテナンスもアクティブに続いている
- ネット上の技術情報がそのツール利用を前提とするものが多い
- バージョン管理以外にも、重要な役割が存在する
という状況で、無理をして asdf のような外部のツールで管理する必要はあるか。
ということで、Rust と Haskell は大人しく rustup と stack を使っている。
サードパーティ製プラグイン
好きな言語が対応していない場合には、自分でプラグインを書いて追加もできる。
$ asdf plugin-add <name> <git-url>
最低限、list-all
と install
があれば動くので、サクッと作れる。
感想
使っていて、手に馴染む感覚はある。
バージョン情報のコンプリーションもしっかりしてくれるし、対応言語も多い。
そういえば、scoop に使い心地も似ているのもあるかも。
あとがき
※ この記事は個人の見解であり、所属する組織を代表するものではありません。