⚒️

バージョン管理 anyenv -> asdf な話

2021/12/12に公開

記事を書くのは久しぶりです
okamosです

はじめに

Manage multiple runtime versions with a single CLI tool

冒頭から引用ですが asdf はバージョン管理のツールです
私は今まで anyenv を使っておりました
良さそうと思ってasdfに飛びついたわけですがそのときの苦痛と学びを共有して、もし使いたいという方がいましたら是非使ってみてくださいという記事です
asdfを使うことのメリットについては ここ に書いておりますが私は特に以下の点で優位性があるなと思います

  • consistent commands to manage all your languages: コマンドは統一されています。rbenvとtfenvのサブコマンドの違いに悩まされることはなくなります
  • single .tool-versions config file per project: 複数のランタイムを使用しているプロジェクトでも1つのファイルで管理可能です。他の人がanyenvを使っていても構いません対応する設定があります
  • 各ランタイム・コマンドはasdf-pluginによって追加しますがanyenvに比べて数が豊富です。プラグイン一覧

Getting Started

ドキュメント

curlとgitをインストールして

# macOSなどでhomebrewが入っていればOK
apt install curl git # ubuntu

asdfをダウンロードして

brew install asdf
# or 
# git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.9.0 # 0.9.0はバージョンなのでお好きなものを選んでください

asdfをインストールします

echo -e "\n. $(brew --prefix asdf)/libexec/asdf.sh" >> ${ZDOTDIR:-~}/.zshrc # これは ZSH & Homebrew

Plugins

ドキュメントプラグイン一覧(再掲)
好きなものをインストールしましょう
後述しますがdirenvは入れておいた方が良いです
こちらも後述しますが逆にGitなどよく使うコマンドはasdfの管理下に置かないほうが良いかもしれません

Nodeを入れたい

asdf plugin add nodejs
asdf install nodejs latest # 最新バージョンはlatestが使える
asdf global nodejs latest # 普段は最新バージョンを使います

手動でいっぱい入れるのが面倒ならこんなスクリプト(Bash)を実行するだけで大丈夫です
最初に述べた通りコマンドが一緒なのでなんのプラグインを入れるかは意識しなくて良いですね

#!/bin/bash
local -a plugins=(
  'direnv'
# ... 中略 ...
  'yarn'
)

for index in ${!plugins[*]}
do
  local plugin=`echo ${plugins[$index]} | cut -d' ' -f 1`
  asdf plugin add ${plugin}
  if [ $? -eq 2 ]; then
    continue
  fi

  asdf install ${plugin} latest
  asdf global ${plugin} latest
done

echo "$(tput setaf 2)Install asdf plugins complete. ✔︎$(tput sgr0)"

Migration

実は .node-version.ruby-version といったファイルに対応することもできます
以下の設定を入れるだけで有効化されます

~/.asdfrc
legacy_version_file = yes

ところでtfenvではこのような設定が可能です

.terraform-version
latest:^0.15

asdfもしっかり認識できています

okamoto_shinichi[test] asdf install terraform 'latest:^0.15'
terraform 0.15.5 is already installed

かと思ったら実行しようとするとそんなのないよと言われるので

okamoto_shinichi[test] terraform --version
No preset version installed for command terraform
Please install a version by running one of the following:

asdf install terraform latest:^0.15

今のところ私は tool-versions を使い明示しています

.tool-versions
terraform 0.15.5

Faster than anyenv?

実はZSHの初期処理の際にanyenvが0.5秒ほどかかっているのが気になっておりました
私は作業時にtmuxのペインをよく切るのでその度にZSHが立ち上がっておりました
どのくらい差があるかって0.6秒くらい違いました(のちに判明しますがanyenvが特別に遅いとかそういう理由ではないようです。この辺の早さのハックはかなり難しいのだと思います)

# anyenv
okamoto_shinichi[~] time zsh -i -c exit
num  calls                time                       self            name
-----------------------------------------------------------------------------------
 1)    1           1.01     1.01   63.04%      1.01     1.01   63.04%  colors
 2)    1           0.44     0.44   27.21%      0.44     0.44   27.21%  bashcompinit
 3)    1           0.16     0.16    9.75%      0.16     0.16    9.75%  has
-----------------------------------------------------------------------------------

zsh -i -c exit  0.41s user 0.56s system 104% cpu 0.929 total

okamoto_shinichi[~] cat ~/.zsh/.zshrc | grep anyenv
export PATH="$HOME/.anyenv/bin:$PATH"
eval "$(anyenv init -)"

# asdf
okamoto_shinichi[~] time zsh -i -c exit
num  calls                time                       self            name
-----------------------------------------------------------------------------------
 1)    1           0.85     0.85   61.97%      0.85     0.85   61.97%  colors
 2)    1           0.39     0.39   28.39%      0.39     0.39   28.39%  bashcompinit
 3)    1           0.13     0.13    9.64%      0.13     0.13    9.64%  has

-----------------------------------------------------------------------------------
zsh -i -c exit  0.14s user 0.25s system 136% cpu 0.289 total
okamoto_shinichi[~] cat ~/.zsh/.zshrc | grep asdf
. /usr/local/opt/asdf/libexec/asdf.sh
eval "$(asdf exec direnv hook bash)"

1日程度使ってみて思ったわけです。ZSHの初期化は早くなっているんだけどなんかモリモリ使うtig(gitコマンドを内部で使っていそう)とかディレクトリ移動 cd $(ghq list -p | fzf) とかが異常に遅いなと...
すぐに問題は判明しました

# with asdf
okamoto_shinichi[~] time git --version
git version 2.34.1
git --version  0.13s user 0.25s system 144% cpu 0.262 total

# without asdf
okamoto_shinichi[~] time git --version
git version 2.34.1
git --version  0.00s user 0.00s system 34% cpu 0.014 total

20倍くらい遅いじゃないか...

okamoto_shinichi[~] which git
/Users/okamoto_shinichi/.asdf/shims/git

okamoto_shinichi[~] time /Users/okamoto_shinichi/.asdf/shims/git --version
git version 2.34.1
/Users/okamoto_shinichi/.asdf/shims/git --version  0.12s user 0.23s system 142% cpu 0.250 total

okamoto_shinichi[~] asdf which git
/Users/okamoto_shinichi/.asdf/installs/git/2.34.1/bin/git

okamoto_shinichi[~] time /Users/okamoto_shinichi/.asdf/installs/git/2.34.1/bin/git --version
git version 2.34.1
/Users/okamoto_shinichi/.asdf/installs/git/2.34.1/bin/git --version  0.00s user 0.00s system 67% cpu 0.005 total

なるほど?
これがGitなどの多用するコマンドは管理に入れないほうが良いかもと言っていた理由となります
この問題はイシュー #290 にて議論されており執筆時(2021/12/12)asdfのコアパッケージで解決されておりません

これを解決するには下記のようにdirenvでasdfの設定を読み込む設定を入れます

~/.bashrc
# Hook direnv into your shell.
eval "$(asdf exec direnv hook bash)"

あるいはdirenvのグローバル設定に

~/.config/direnv/direnvrc
source "$(asdf direnv hook asdf)"

と入れておきプロジェクトごとに

PROJECT_ROOT/.tool-versions
python 3.9.9
terraform 1.1.0
PROJECT_ROOT/.envrc
use asdf

のように設定を入れておきます。全てのasdf管理下のバージョンを解決するのでdirenv allowすると若干時間がかかりますが変更しない限りはキャッシュを使ってくれるので高速です

結局初期処理には時間はかかりませんがプロジェクトのディレクトリに行くごとに設定を読み込むのでキャッシュを使っている時でも差はほとんどないかasdfの方が管理するプラグインが多いので遅いくらいになりました
結局のところ普段打つコマンドの反応が早いことの方が大事だと気付かされました
冒頭に述べましたが早さ以外にもメリットは大きく感じますので私はasdfを使っていこうと思います

Ansible + Python

Ansibleで利用するPythonはvirtualenvで入ります
Pythonはどこ?ここ?
環境をactivateしてから必要なパッケージをインストールしましょう

(venv) okamoto_shinichi[ansible] ansible --version
ansible 2.10.16
  config file = /Users/okamoto_shinichi/dev/github.com/samurai-engineer-juku/playbooks/ansible/ansible.cfg
  configured module search path = ['/Users/okamoto_shinichi/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /Users/okamoto_shinichi/.asdf/installs/ansible-base/2.10.16/venv/lib/python3.9/site-packages/ansible
  executable location = /Users/okamoto_shinichi/.asdf/installs/ansible-base/2.10.16/venv/bin/ansible
  python version = 3.9.9 (main, Nov 21 2021, 03:23:42) [Clang 13.0.0 (clang-1300.0.29.3)]
PROJECT_ROOT/.envrc
use asdf
source ~/.asdf/installs/ansible-base/2.10.16/venv/bin/activate # venv/bin下にいる。これはZSHの場合で読み込むものはシェルにより違うので注意してください
okamoto_shinichi[ansible] pip --version
pip 21.3.1 from /Users/okamoto_shinichi/.asdf/installs/ansible-base/2.10.16/venv/lib/python3.9/site-packages/pip (python 3.9)
okamoto_shinichi[~] pip freeze | grep boto

okamoto_shinichi[ansible] ansible-playbook playbooks/wordpress.yml --list-hosts
[WARNING]:  * Failed to parse /Users/okamoto_shinichi/dev/github.com/samurai-engineer-juku/playbooks/ansible/inventories/aws_ec2.yml with auto plugin: The ec2 dynamic inventory plugin requires boto3 and botocore.
[WARNING]:  * Failed to parse /Users/okamoto_shinichi/dev/github.com/samurai-engineer-juku/playbooks/ansible/inventories/aws_ec2.yml with yaml plugin: Plugin configuration YAML file, not YAML inventory
# ... 省略 ...
Note that the implicit localhost does not match 'all'

playbook: playbooks/wordpress.yml

  play #1 (all): WordPress server       TAGS: []
    pattern: ['all']
    hosts (0):
    
okamoto_shinichi[ansible] pip install boto3 botocore
Collecting boto3
  Using cached boto3-1.20.23-py3-none-any.whl (131 kB)
Collecting botocore
  Using cached botocore-1.23.23-py3-none-any.whl (8.4 MB)
# ... 省略 ...
Successfully installed boto3-1.20.23 botocore-1.23.23

okamoto_shinichi[ansible] ansible-playbook playbooks/wordpress.yml -l slack --list-hosts

playbook: playbooks/wordpress.yml

  play #1 (all): WordPress server       TAGS: []
    pattern: ['all']
    hosts (1):
      slack

okamoto_shinichi[ansible] cd ~
direnv: unloading
okamoto_shinichi[~] pip --version
pip 21.2.3 from /Users/okamoto_shinichi/.asdf/installs/python/3.10.0/lib/python3.10/site-packages/pip (python 3.10) # direnv設定外の箇所へ抜けるとグローバルに戻る

Breaking shims

asdfのアップグレードをした際に以下のようなメッセージが出るようになりました

okamoto_shinichi[~] nvim
/Users/okamoto_shinichi/.asdf/shims/nvim: line 4: /opt/homebrew/Cellar/asdf/0.9.0/libexec/bin/asdf: No such file or directory
/Users/okamoto_shinichi/.asdf/shims/nvim: line 4: exec: /opt/homebrew/Cellar/asdf/0.9.0/libexec/bin/asdf: cannot execute: No such file or directory

asdf reshimで直るかと思いましたが直らず以下のissueのコメントよりshimsのディレクトリを削除後にreshimすることで回復できました
https://github.com/asdf-vm/asdf/issues/418#issuecomment-1042557031

rm -rf ~/.asdf/shims; asdf reshim

ちなみにshimsにあるファイルは以下のようにasdf経由で各種コマンドを実行するようになっています

#!/usr/bin/env bash
# asdf-plugin: ghq 1.3.0
exec /opt/homebrew/opt/asdf/libexec/bin/asdf exec "ghq" "$@" # asdf_allow: ' asdf '

現在は /opt/homebrew/opt/asdf/libexec 配下にhomebrewでlinkしたバージョンのファイル群が配置されるようになっており昔のようにasdfのバージョンごとに切られたディレクトリ配下ではなくなっているので今後は同じ問題は発生しないかと思います

おわりに

いかがでしたでしょうか
私がasdfを1週間程度使ってみてハマったところなりを自分なりに整理してみました
anyenvはアンインストール済みだしGitなどはasdf管理下に入れない方が良いかもといいつつ私は管理下に置いています。どんどん問題にハマっていって解決しようと思います
私の管理しているdotfilesの commit があるのでasdf以外の変更点も混ぜ込んでいて綺麗なものではないですが参考にしてみてください

Discussion