用 mise 统一管理所有语言的版本

2024年6月16日星期日 / , , , ,
摘要:
nvm、pyenv、rustup、Homebrew 里的 openjdk,每种语言都有自己的一套版本管理。最近试了 mise,把它们全部换成了一个工具,`.zshrc` 也干净了不少。

我电脑上的编程语言,一直是各管各的。例如 Node 用 nvm,Python 用 pyenv,Rust 用 rustup,Go 和 Java 则直接用 Homebrew 装。每一个都有自己的命令、自己的安装目录、自己要往 .zshrc 里加的初始化代码。

随着时间一长,.zshrc 里就攒下了这么一堆东西:

Shell
zstyle ':omz:plugins:nvm' lazy yes
plugins=(git nvm history fzf-tab)

export PATH="/opt/homebrew/opt/openjdk@17/bin:$PATH"
export JAVA_HOME="/opt/homebrew/Cellar/openjdk@17/17.0.7/libexec/openjdk.jdk/Contents/Home"
alias gradle7='/opt/homebrew/Cellar/gradle@7/7.6.1/bin/gradle'

export PYENV_ROOT="$HOME/.pyenv"
command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

export PATH=$PATH:$HOME/go/bin

由于 nvm 是出了名的慢,所以我用了 oh-my-zsh 的 nvm 插件,还打开了 lazy 模式,等第一次用到 node 的时候再加载。pyenv 的两行 eval 同样每次打开终端都要跑一遍。

而最难受的是 Java。由于有一些项目需要 Java 17 和 Gradle 7,我就用 Homebrew 装了 openjdk@17 和 gradle@7,然后 JAVA_HOME 只能写死成 Homebrew Cellar 里的路径,路径里还带着具体的版本号 17.0.7。所以只要 brew upgrade 把它升了一个小版本,这个路径就失效了,又得回来手动改。

mise

直到我发现了 mise。它以前叫 rtx,今年年初才改名叫 mise,用 Rust 写的,可以理解成一个更快的 asdf。一个工具就能管理 Node、Python、Go、Rust、Java 这些语言的版本,还能装一些开发时用的命令行工具。

先比 nvm、pyenv 这类工具,mise 有以下几个优势:

  • 一个工具管所有语言。命令都是一样的,mise use、mise install、mise ls,不用再记每个工具各自的用法。
  • 快。它默认不用 shim,而是在每次显示提示符之前,根据当前目录算出要用哪些版本,直接改 PATH。执行命令的时候没有任何额外的开销,这一步本身也非常快。
  • 按目录切换版本。在项目目录里放一个配置文件,进入目录的时候版本就自动切过去,离开的时候再切回来。
  • 兼容旧的版本文件。项目里已经有的 .nvmrc、.python-version、.tool-versions,mise 都能直接读。

安装

我用 Homebrew 安装。

Shell
brew install mise

然后在 ~/.zshrc 里激活它,顺便把补全也加上。

Shell
eval "$(mise activate zsh)"
# mise completion
source <(mise completion zsh)

mise activate 会在 zsh 里注册一个 hook,每次提示符出现之前,它都会检查一下当前目录,然后把对应版本的路径放进 PATH。

全局版本

全局要用的版本,写在 ~/.config/mise/config.toml 里。可以直接编辑这个文件,也可以用 mise use -g,mise 会自动安装并写进配置。

Shell
mise use -g node@lts
mise use -g python@3.12
mise use -g go@latest
mise use -g rust@stable
mise use -g java@17

最后的配置文件大概是这样的。

TOML
[tools]
node = "lts"
python = "3.12"
go = "latest"
rust = "stable"
java = "17"
gradle = "7"
bun = "latest"
pnpm = "9"
"npm:wrangler" = "3"

这里的版本可以写得很具体,比如 3.12.4,也可以只写一个前缀,3.12 就表示 3.12 里最新的那个,latest 则是最新版。例如 Rust 写 stable 就会自动使用稳定版。

Java 也是 mise 管理的,所以不用再手写 JAVA_HOME 了,mise 会在切换 Java 版本的时候自动设置好。Gradle 也一样,直接写进配置里,那个指向 Cellar 的 gradle7 别名也可以删掉了。

除了语言本身,一些开发时用的工具也可以交给 mise。比如 pnpm 和 bun,以及 npm: 开头的这种写法,表示用 npm 安装的命令行工具,Cloudflare 的 wrangler 我就是这么装的。这样这些工具的版本也写进了同一个文件,不再需要 npm install -g。

项目里的版本

项目需要特定版本的时候,在项目目录下运行 mise use,不带 -g。

Shell
cd some-project
mise use node@18

它会在项目目录下生成一个 .mise.toml。

TOML
[tools]
node = "18"

之后只要进入这个目录,node 就是 18,出了这个目录就会恢复成全局的版本。而且这个文件也可以提交进仓库,别人用 mise 的话,进来运行一下 mise install 就能装好一样的版本。

如果项目里本来就有 .nvmrc 或者 .python-version,那就什么都不用做,mise 会直接按照里面的版本来。

环境变量

.mise.toml 里还能设置环境变量,进入目录的时候自动设置,离开的时候自动取消,有点像 direnv。

TOML
[env]
NODE_ENV = "development"
DATABASE_URL = "postgres://localhost/dev"

在编辑器里用

mise activate 是靠 shell 的 hook 工作的,所以只在交互式的终端里生效。如果是从 Dock 打开的编辑器,或者一些不经过 shell 的程序,它们就拿不到 mise 设置的 PATH。

这种情况可以用 shim 模式,在 ~/.zprofile 里加上这一行。

Shell
eval "$(mise activate zsh --shims)"

它会把 ~/.local/share/mise/shims 加进 PATH,里面是一个个和命令同名的小程序,调用的时候再由 mise 决定用哪个版本。不过目前我主要是在终端里打开编辑器,所以暂时还没开。

清理旧的工具

全部换成 mise 之后,原来那些东西就可以删掉了。

Shell
brew uninstall nvm
rm -rf ~/.nvm

.zshrc 里 nvm 的插件、pyenv 的初始化、写死的 JAVA_HOME 和 gradle7,也都删掉了,只剩下 mise activate 那一行。

装好之后可以用 mise ls 看一下当前装了哪些版本,哪个是正在用的。

Shell
mise ls

想更新的话,mise upgrade 会把配置里写着 latest、lts 或者前缀版本的工具,升级到符合条件的最新版。遇到奇怪的问题,可以先跑一下 mise doctor,它会检查 mise 有没有激活、配置文件有没有问题。

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...