mise
mise-en-place 提供的开源软件 mise 为开发者提供了一个快速的环境就绪,免除了多语言开发工作者不要使用不同的软件版本管理工具来安装不同的开发语音的痛苦。网站上有一句话非常打动人心 You dev env, already prepped.
mise 使用Rust开发,能够管理 rust, python, ruby, node 等,将开发工具安装在用户的HOME目录,无需root权限,并且支持在不同项目中使用不同的开发语言版本,这对于需要维护不同环境的工作非常有利。
Docker环境不建议使用mise
我尝试在 Colima镜像 中融合了 mise 来安装我的开发环境,确实非常方便。不过,Dockerfile中不推荐 mise :
如果在云计算环境原生架构中,
mise的shims机制会切断 neovim 的LSP探测,也就是mise的环境加载脚本eval "$(mise activate bash)"可能会因为容器环境没有触发.bashrc而无法完成初始化。当然,交互情况下这个问题可以避免,但是部署到服务器无人操作时就存在不一致的隐患。mise在容器内运行时,其下载、编译、缓存和软链接的目录全部杂糅在/home/admin/.local/share/mise,当通过mise install XXX安装是,后台下载庞大的源码、编译临时文件、校验包。这些临时垃圾会和最终的编译器一起死死地固化在同一个 Docker 镜像层(Layer)中,导致镜像体积不可控地暴增数 GB。在Docker运行中推荐采用官方二进制包,这样每个
RUN语句控制一层,修订版本只需要秒级增量构建;如果使用mise则版本变更会导致整个mise状态机重新计算,构建效率极低
macOS工作室 建议使用mise
在MacBook Pro 本地系统上,项目五花八门,每个项目可能使用了不同的 Go / Python 版本,此时利用 mise 的 .mise.toml 能够控制随着项目目录自动、无感地切换环境版本,对于多项目并行开发非常优雅。
# Install Xcode Command Line Tools
xcode-select --install
# Install Homebrew and dependencies
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
echo 'export PATH="/opt/homebrew/bin:$PATH"' >> ~/.zshrc
source ~/.zshrc
brew install openssl@3 libyaml gmp rust
# Install Mise version manager
curl https://mise.run | sh
echo 'eval "$(~/.local/bin/mise activate)"' >> ~/.zshrc
source ~/.zshrc
mise代理
mise的代理设置才用了类似 curl 的环境变量方式,我在 Colima镜像 构建时是用了socks5h代理,所以可以采用环境变量来控制mise代理:
all_proxy=socks5h://192.168.5.2:1080
http_proxy=socks5h://192.168.5.2:1080
https_proxy=socks5h://192.168.5.2:1080
快速使用mise
执行以下命令快速完成 c/c++ , rust , go , python , ruby 以及 neovim 安装:
mise use --global cmake@latest && \
mise use --global rust@stable && \
mise use --global go@latest && \
mise use --global python@latest && \
mise use --global node@lts && \
mise use --global ruby@3 && \
mise use --global neovim@stable && \
mise use --global tmux@latest
gem install --no-document rails
为了快速完成 neovim 的开发环境设置,参考 alpine-dev Podman image 自动完成:
~/.config/nvim/init.lua-- =============================================================================
-- 1. 基础体验优化
-- =============================================================================
vim.g.mapleader = " " -- 设置 Leader 键为空格
vim.opt.number = true -- 显示行号
vim.opt.relativenumber = true -- 显示相对行号
vim.opt.termguicolors = true -- 开启 24 位真彩色支持
vim.opt.shiftwidth = 4 -- 缩进空格数
vim.opt.tabstop = 4
vim.opt.expandtab = true -- Tab 转空格
vim.opt.signcolumn = "yes" -- 始终显示左侧警告/报错标记列
vim.opt.mouse = "" -- 禁用VISUAL模式方便鼠标选择复制
-- =============================================================================
-- 2. 健壮的 lazy.nvim 自动安装逻辑
-- =============================================================================
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then
local lazyrepo = "https://github.com/folke/lazy.nvim.git"
local out = vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", lazyrepo, lazypath })
if vim.v.shell_error ~= 0 then
vim.api.nvim_echo({
{ "Failed to clone lazy.nvim:\n", "ErrorMsg" },
{ out, "WarningMsg" },
{ "\nPress any key to exit..." },
}, true, {})
vim.fn.getchar()
os.exit(1)
end
end
vim.opt.rtp:prepend(lazypath)
local status_ok, lazy = pcall(require, "lazy")
if not status_ok then return end
-- =============================================================================
-- 3. 插件配置列表
-- =============================================================================
lazy.setup({
-- 【主题】:Tokyo Night
{
"folke/tokyonight.nvim",
lazy = false,
priority = 1000,
config = function()
vim.cmd([[colorscheme tokyonight-night]])
end,
},
-- 【文件导航栏】:neo-tree
{
"nvim-neo-tree/neo-tree.nvim",
branch = "v3.x",
dependencies = {
"nvim-lua/plenary.nvim",
"nvim-tree/nvim-web-devicons", -- 文件图标支持
"MunifTanjim/nui.nvim",
},
config = function()
require("neo-tree").setup({
close_if_last_window = true, -- 如果只剩下文件树窗口,自动退出 nvim
filesystem = {
filtered_items = {
visible = true, -- 显示隐藏文件(如 .gitignore, .config 等)
},
follow_current_file = {
enabled = true, -- 打开文件时,文件树自动定位到当前文件
},
},
})
-- 快捷键设置:
-- 按 <Ctrl + n> 切换文件树显隐
vim.keymap.set("n", "<C-n>", ":Neotree toggle<CR>", { silent = true })
-- 按 <Leader + e>(即 空格 + e)也可以切换文件树
vim.keymap.set("n", "<leader>e", ":Neotree toggle<CR>", { silent = true })
end,
},
-- 【语法高亮】:Treesitter
{
"nvim-treesitter/nvim-treesitter",
build = ":TSUpdate",
config = function()
local ts_ok, ts_configs = pcall(require, "nvim-treesitter.configs")
if not ts_ok then return end
ts_configs.setup({
ensure_installed = { "c", "ruby", "python", "rust", "markdown", "markdown_inline", "bash", "lua" },
highlight = { enable = true },
})
end,
},
-- 【LSP 配置库】
"neovim/nvim-lspconfig",
-- 【自动补全引擎与 UI 弹窗】
{
"hrsh7th/nvim-cmp",
dependencies = {
"hrsh7th/cmp-nvim-lsp", -- LSP 补全源
"hrsh7th/cmp-buffer", -- 当前 Buffer 词汇补全
"hrsh7th/cmp-path", -- 文件路径补全
"L3MON4D3/LuaSnip", -- 代码片段引擎
},
config = function()
local cmp = require("cmp")
cmp.setup({
snippet = {
expand = function(args) require("luasnip").lsp_expand(args.body) end,
},
mapping = cmp.mapping.preset.insert({
["<C-k>"] = cmp.mapping.select_prev_item(),
["<C-j>"] = cmp.mapping.select_next_item(),
["<C-Space>"] = cmp.mapping.complete(),
["<CR>"] = cmp.mapping.confirm({ select = true }),
["<Tab>"] = cmp.mapping.select_next_item(),
}),
sources = cmp.config.sources({
{ name = "nvim_lsp" },
{ name = "path" },
{ name = "buffer" },
}),
})
end,
},
-- 【状态栏】
{
"nvim-lualine/lualine.nvim",
config = function() require("lualine").setup() end,
},
}, -- <-- 注意这里逗号隔开,把原本结束的括号挪到下面
{
rocks = {
enabled = false, -- 传递给 lazy 的全局配置
},
})
-- =============================================================================
-- 4. 绑定 Dockerfile 中内置的 LSP
-- =============================================================================
local capabilities = {}
local cmp_lsp_ok, cmp_nvim_lsp = pcall(require, "cmp_nvim_lsp")
if cmp_lsp_ok then
-- 修复:修改为全小写划线命名 default_capabilities()
capabilities = cmp_nvim_lsp.default_capabilities()
end
local on_attach = function(client, bufnr)
local opts = { buffer = bufnr, silent = true }
vim.keymap.set("n", "gd", vim.lsp.buf.definition, opts) -- 跳转定义
vim.keymap.set("n", "K", vim.lsp.buf.hover, opts) -- 文档悬停
vim.keymap.set("n", "gr", vim.lsp.buf.references, opts) -- 查找引用
vim.keymap.set("n", "<leader>rn", vim.lsp.buf.rename, opts) -- 变量重命名
vim.keymap.set("n", "<leader>ca", vim.lsp.buf.code_action, opts) -- 代码修复
end
local servers = { "clangd", "solargraph", "jedi_language_server", "ruff", "rust_analyzer", "marksman" }
for _, server in ipairs(servers) do
if vim.lsp.config then
vim.lsp.config(server, {
capabilities = capabilities,
on_attach = on_attach,
})
vim.lsp.enable(server)
else
local lspconfig_ok, lspconfig = pcall(require, "lspconfig")
if lspconfig_ok then
lspconfig[server].setup({
capabilities = capabilities,
on_attach = on_attach,
})
end
end
end
并运行以下无头命令完成配置nvim:
nvim --headless "+Lazy! sync" +qa
在tmux中使用mise
我在 macOS工作室 中设置好 mise 之后,确实通过这个环境能够使用不同的开发工具,非常惬意。但是我发现一个奇怪的现象,就是当我使用 tmux多会话终端管理 时,突然间 Ruby 程序就回落到操作系统默认安装的 /usr/bin/ruby 而不是我通过 mise 安装的 ~/.local/share/mise/installs/ruby/3/bin/ruby
询问了gemini说明是: Tmux 默认的行为是启动一个“登录 Shell(Login Shell)”,而不是“交互式非登录 Shell(Interactive Non-login Shell)”
也就是说 tmux 新建一个会话时,会在后台fork 出一个新的 Zsh 进程。为了保证环境的“纯净”,tmux 默认会把它当作 Login Shell 来初始化。
解决的方法: 修改 ~/.tmux.conf ,迫使tmux表现得像一个规规矩矩的普通终端子窗口,不要每次都去拉取 Login Shell
~/.tmux.conf 配置启用普通交互式non-login shell# 强迫 tmux 在新建 window/pane 时,只启动普通的交互式 non-login shell
set -g default-command "${SHELL}"
需要注意的时,此时即使重启终端,重新进入 tmux 该设置也 不一定 很可能不生效,原因是 虽然重启了终端窗口,但 Tmux 的后台守护进程(Server)在 macOS 内存里其实根本没有退出 执行tmux 时,它只是拉起了一个新的客户端(Client),去连接那个依然携带着旧配置、旧环境中旧 $PATH 的后台老进程。
解决方法是彻底杀掉系统中 tmux 服务进程:
# 1. 杀掉当前内存中所有驻留的 tmux 守护进程(这一步会关掉所有会话,请确保重要工作已保存)
tmux kill-server 2>/dev/null || true
# 2. 如果杀得不彻底,用系统信号killall
killall tmux 2>/dev/null || true
# 3. 稳妥起见,在外面普通终端先刷新一次 Zsh 环境,确保外层的 $PATH 里 ruby 是对的
source ~/.zshrc
# 4. 重新进入 tmux
tmux
升级mise
在检查或安装最新版本前,先更新 mise 的本地插件和工具版本索引:
mise plugin update
查看当前已安装工具的最新可用版本:
mise outdated
以下是输出举例:
name requested current latest source
node 24 24.16.0 24.21.0 ~/.config/mise/config.toml
python latest 3.14.5 3.14.8 ~/.config/mise/config.toml
ruby 3 3.4.9 3.4.11 ~/.config/mise/config.toml
tmux latest 3.6b 3.7c ~/.config/mise/config.toml
升级工作其实很简单,就是再次运行前面的安装命令,会自动升级,举例安装指定版本,或最新版本
# 安装 Python 最新 3.12 版本(或最新版 python@latest)
mise use --global python@3.12
# 安装 Ruby 最新版本
mise use --global ruby@latest
清理旧版本:
查看所有已安装的版本
mise ls
清理不使用的旧版本:
mise prune
备注