1. 为什么要自己编译一个 huge 版 vim
系统自带的 vim 有个很典型的现象:你敲vim --version,看到一串-号,-python3、-lua、-clipboard、-terminal,甚至-syntax。这些减号代表当前这个二进制在编译阶段就没有把对应特性链接进去。平时改改配置文件感觉不出来,一旦你想用插件、想剪贴板互通、想用:terminal开个窗口跑命令,就会立刻撞墙。所以这篇内容讲的是在 RHEL 系系统上,从源码编译一个huge特性的 vim9,把那些减号全部变成加号,整个流程熟练之后三分钟能走完一遍。
我把话说在前面:这篇不介绍任何花哨的东西,全部围绕一条主线——拿到源码、配好依赖、开对编译选项、装到独立前缀不污染系统、最后验证特性列表。适合两类人看:一类是刚接触 RHEL 系、发现系统 vim 残缺但不知道差在哪的;另一类是已经会编译、但每次都在--with-features和依赖包上反复试错、想沉淀一套稳定命令的。
先解释一下huge到底是什么。vim 的特性等级从tiny、small、normal、big到huge,这是一组预设宏集合,本质是feature.h里按等级批量开关的#define。huge是最大的那一档,打开绝大多数可用特性:多级撤销、语法高亮、折叠、diff、命令行补全、鼠标支持、多字节字符、+eval表达式引擎等等。但注意一个关键点:huge不等于“所有特性都有”,它只覆盖 vim 自身代码里的特性宏,像+python3、+lua、+clipboard这类依赖外部库的,必须在configure阶段显式指定路径,否则即便--with-features=huge也照样是减号。这是最多人误解的地方,也是后面要重点处理的部分。
至于为什么选 vim9 而不是老版本,简单说就是脚本引擎换了代。vim9 script 把执行方式从逐行解释改成编译成指令再跑,函数调用、循环、类定义这些场景下性能差距很明显,尤其是那些用 vimscript 写了不少自动化逻辑的人,升级收益直接能感知到。加上 9.x 系列对 LSP 客户端、弹出窗口、文本属性的支持更完整,很多现代插件的硬性门槛就在这。所以对 RHEL 用户来说,系统源里的版本普遍偏旧,自己编一个是性价比很高的选择。
另外要强调的是不要把编译结果覆盖系统 vim。RHEL 上很多东西(包括一些系统脚本、vi的软链接关系)依赖系统自带的那份,你make install到/usr/local或者独立目录,用PATH或者alternatives去切换,是最稳的做法。我踩过的坑是早期图省事直接make install覆盖/usr/bin/vim,结果某次升级后包管理器又把它盖回去,两份二进制混着用,排查了半天。这个教训后面还会展开。
2. 编译前的准备工作:依赖清单与常见缺包
2.1 先把开发工具链补齐
RHEL 系的包管理用dnf(老版本是yum),编译源码第一件事是装开发工具组。Development Tools这个组包含 gcc、make、autoconf、automake 等一整套,一条命令搞定:
sudo dnf groupinstall -y "Development Tools"如果你在最小化安装的系统上操作,可能连tar、wget都没有,顺手补上:
sudo dnf install -y tar wget curl这里有个经验:不要在没装工具链的情况下直接跑./configure,它会在检测编译器阶段报一堆C compiler cannot create executables,看起来像源码有问题,实际就是没装 gcc。我在帮别人看问题时遇到过好几次,对方以为是下载的源码包损坏,反复重新下载,其实是环境问题。
2.2 决定你最终特性列表的那几个库
这一节是重点。很多教程只写dnf install ncurses-devel,然后编译完发现+python3、+clipboard全是减号,又回头找原因。我建议你先把想要的特性想清楚,再一次性把对应的-devel包装全。下表是我常用的对照关系:
| 想要开启的特性 | 需要安装的包 | configure 选项 |
|---|---|---|
| 终端界面基础(必需) | ncurses-devel | 无需显式指定 |
| Python3 插件支持 | python3-devel | --enable-python3interp=yes |
| Lua 插件支持 | lua-devel | --enable-luainterp=yes |
| 系统剪贴板互通 | libXt-devel、libX11-devel或终端剪贴板方案 | --with-x或依赖终端 |
终端窗口(:terminal) | 无额外依赖 | --enable-terminal |
| 字符编码转换 | libicu-devel | --enable-xim等 |
| 多语言消息 | gettext | 默认开启 |
一条命令把常用的装上:
sudo dnf install -y ncurses-devel python3-devel lua-devel libXt-devel libX11-devel git注意python3-devel装完后,configure才能找到python3-config这个脚本。如果你系统上有多个 Python 版本共存,要确保python3-config指向的是你想链接的那个版本,可以用which python3-config确认一下路径。这里有个细节:vim 编译时链接的是 Python 的动态库,运行时如果 Python 版本被替换或者路径变了,vim启动会直接报undefined symbol,所以尽量用系统自带的python3-devel,别去手动编译一个私有的 Python 再拿来链接,后期维护成本高。
关于剪贴板,这里要单独说明一下,因为这是 RHEL 用户最容易困惑的点。+clipboard特性在传统 X11 环境下依赖libXt,但如果你日常是在终端里工作(比如通过 SSH 连服务器),根本没有 X 显示,那么+clipboard即使编进去了也用不了,因为 vim 找不到可用的剪贴板提供者。这时候正确的思路是让终端模拟器去接管复制粘贴,vim 侧只需要+clipboard存在、能识别"+y寄存器就行——很多时候不装libXt-devel也会显示+clipboard,靠的是终端自身的 OSC 52 等机制配合。但为了保险,还是建议装上libXt-devel,让编译期完整。
2.3 确认源码来源和版本
源码从官方仓库拉最稳妥,用 git 可以随时切 tag:
git clone --depth 1 https://github.com/vim/vim.git ~/src/vim--depth 1只拉最新一次提交,省时间。如果你要某个具体版本,先git ls-remote --tags看有哪些 tag,再git fetch --depth 1 origin tag v9.x.xxxx切过去。这里我给个建议:生产环境上手之前,先在测试机上把某个固定 tag 编一遍并记录特性列表,之后再升级时对比列表差异,能快速发现某个依赖是不是没装上。这种“快照式”的验证方法在编译第三方软件时特别有用,比事后猜要高效得多。
如果你的机器不能直连外网,那就下载 release 的 tar 包,解压后进目录效果一样。注意解压后目录结构是vim-9.x/里面套src/,configure脚本在src/目录下,别在顶层目录找。
3. configure 选项逐个拆解:每个开关背后的取舍
3.1 安装前缀的选择:为什么不装到 /usr
--prefix决定最终make install装到哪里。默认是/usr/local,这个位置本身合理,因为它在系统包管理器的管辖范围之外,不会被dnf覆盖。但我更推荐装到一个带版本号的独立目录,比如:
--prefix=/opt/vim9理由是升级和回滚都方便。你想换版本,直接编译新的装到/opt/vim9-new,验证没问题再改软链接;想回滚,切回旧目录即可,不用重新编译。而/usr/local虽然也能用,多个版本混在同一目录里,make install会直接覆盖同名文件,出问题不好回溯。
还有一点值得说:如果你装到独立目录,还要处理运行时文件路径。vim 找语法文件、插件目录靠的是编译期写死的$VIMRUNTIME,具体来说configure会把 prefix 拼进去。所以编译前的 prefix 决定了安装后的路径,中途改 prefix 必须重新跑configure,不能只改 Makefile。
3.2 特性等级与解释器:huge 加满的完整命令行
下面这段是我现在固定使用的一套参数,你可以直接抄:
cd ~/src/vim/src ./configure \ --prefix=/opt/vim9 \ --with-features=huge \ --enable-multibyte \ --enable-python3interp=yes \ --enable-luainterp=yes \ --enable-terminal \ --enable-cscope \ --enable-gui=no \ --enable-fontset \ --with-tlib=ncursesw \ --disable-netbeans \ --with-compiledby="local-build"逐条说理由。--with-features=huge是主线,前面讲过了。--enable-multibyte是让 vim 内部用多字节方式处理文本,中文环境必须开,虽然huge默认会带,但显式写出来更保险。--enable-python3interp=yes和--enable-luainterp=yes是对应解释器支持,注意=yes和=dynamic的区别:
=yes:静态链接,vim 二进制直接依赖对应动态库,找不到库就启动失败。=dynamic:运行时用dlopen动态加载,找不到库时 vim 照常启动,只是该特性不可用。
我个人的选择是 Python3 用dynamic、Lua 用dynamic,理由是:如果哪天环境里的 Python 升级了,静态链接的 vim 可能直接起不来,而动态加载模式下 vim 至少还能用,只是:python3报错,你能进去改配置排查。所以更稳的写法是:
--enable-python3interp=dynamic \ --enable-luainterp=dynamic \这里补充一个容易忽略的点:用dynamic时,configure仍然需要python3-devel来获取头文件路径,只是链接方式变了。所以依赖包还是得装,别以为 dynamic 就什么都不用装。
--enable-terminal是开启:terminal内置终端,需要 vim 版本足够新,9.x 全都支持。这个特性是我最常用的之一,可以在编辑文件的同时开一个 shell 跑测试,不用切窗口。
--enable-cscope是给 C 代码跳转用的,如果你写 C/C++ 可以留着,不写也没坏处。
--enable-gui=no明确不要图形界面版本,只编终端版。编 GUI 版需要 GTK 或者 Motif 的整套开发库,体积大、编译慢,而且我日常就在终端里编辑,GUI 版用不上。如果你确实需要 gvim,把这里换成--enable-gui=gtk3并补装gtk3-devel。
--with-tlib=ncursesw指定链接带宽字符支持的 ncurses,这是中文、日文等宽字符正常显示的前提。RHEL 上默认可能探测成ncurses(非宽字符版),导致中文在终端里占位错乱。
--disable-netbeans是关掉 NetBeans 集成,这个功能现在基本没人用,关掉省依赖。
--with-compiledby是打在:version输出里的一行标识,写上自己方便和其他机器区分,纯属个人习惯。
3.3 configure 之后的检查:看它到底认了什么
configure跑完会打印一长串摘要,很容易被刷过去。我的做法是紧跟着做两件事。
第一件,确认退出码是 0:
echo $?如果非 0,往上翻输出找checking ... no里关键的那几项,通常是缺包或者路径不对。
第二件,直接看生成的配置文件里关键特性是否开启:
grep -E "FEAT_(HUGE|PYTHON3|LUA|TERMINAL|MULTIBYTE)" src/auto/config.h能看到#define FEAT_HUGE 1这类行就说明配进去了。这一步很重要,我见过有人 configure 时写错选项名(比如把--enable-luainterp写成--enable-lua),configure 不报错只是忽略,最后编译出来是减号,还以为源码有问题。选项拼写错误是 configure 阶段最隐蔽的坑,因为它不会让你失败,只会默默不生效。
提示:
configure日志建议重定向保存一份,比如./configure ... 2>&1 | tee ~/vim-configure.log。出问题时这份日志比事后回忆有用得多,尤其是依赖检测那一大段。
4. 编译与安装:时间、并发与路径细节
4.1 make 参数与耗时预期
源码目录src/下面直接跑:
make -j$(nproc)-j$(nproc)用满所有 CPU 核心,编译快很多。在普通 4 核测试机上,全量编译大约一到三分钟,配置高的机器更快,所以标题里说三分钟不算夸张,但前提是依赖提前装好、configure 一次过。
这里有个经验值得讲:如果编译中途报错中途停掉,第二次跑make之前先make clean。因为第一次编译可能留下半成品.o文件,直接重跑有时候会用到旧对象,出现莫名其妙的链接错误。虽然 vim 的 Makefile 依赖关系写得不错,但清理一下再编是最省心的做法,尤其在改动过 configure 选项之后——如果你改了--enable-python3interp这类影响 CFLAGS 和链接的选项,之前的.o全是按旧配置编的,不 clean 就会得到行为诡异的二进制。
4.2 安装与 PATH 配置
sudo make install装到/opt/vim9之后,/opt/vim9/bin/vim就是你的新二进制。让它生效有几种做法,我按推荐程度排:
第一种,改用户自己的PATH,在~/.bashrc里加:
export PATH=/opt/vim9/bin:$PATH放在前面让它优先命中。这种方式只影响当前用户,不影响其他人,也不动系统文件,是最轻量的。
第二种,用alternatives系统统一管理,适合多用户机器:
sudo alternatives --install /usr/bin/vim vim /opt/vim9/bin/vim 100数字是优先级,越大越优先。之后可以用alternatives --config vim交互切换。
第三种是软链接,sudo ln -sf /opt/vim9/bin/vim /usr/local/bin/vim。简单直接,但注意/usr/local/bin得在PATH里且在/usr/bin前面,否则系统 vim 还是抢先。
这里提醒一点:PATH 顺序决定了你到底在用哪个 vim。排查“我明明装了新版,怎么特性还是老的”这类问题时,第一个命令永远是which -a vim,把所有命中的路径列出来,再看vim --version输出第一行的路径,两者一对就清楚了。另外,别忘了很多系统里vi是个软链接,vim -v和vi可能指向不同二进制,测试时要用完整命令确认。
4.3 安装后验证:把特性列表逐项对一遍
vim --version | head -20重点看第一行的版本号和下面那一大段+/-。我会特别检查这几项:
| 特性 | 期望 | 不满足时的典型原因 |
|---|---|---|
+python3 | 有 | 没装python3-devel或选项拼错 |
+lua | 有 | 没装lua-devel或选项拼错 |
+terminal | 有 | 忘了--enable-terminal |
+clipboard | 有 | 缺libXt-devel或编译时报过警告 |
+syntax | 有 | 一般 huge 都带,若缺说明 prefix 有问题 |
+multi_byte | 有 | 没开 multibyte |
再用:echo has('python3')在 vim 里确认一次,返回1才算真通。有些情况下--version显示+python3但运行时报E370: Could not load library,那就是动态链接在运行时找不到.so,用ldd $(which vim) | grep python看链接情况,或者检查 Python 库路径。
最后确认$VIMRUNTIME指向新装的运行时目录:
:echo $VIMRUNTIME应该是/opt/vim9/share/vim/vim90这类路径。如果它指到系统旧目录,说明你跑的还是旧二进制,回到which -a vim那一步重查。
5. 踩坑实录:几个让人绕远路的细节
5.1 中文乱码与 tlib 探测失败
我在一台最小化安装的机器上编译完,打开带中文的文件,发现中文显示成方框或者每行错位。查下来是configure探测时把终端库定成了ncurses而不是ncursesw,导致宽字符处理没启用。表现是+multi_byte明明是加号,但实际渲染还是按字节宽度算。
解决办法就是前面提到的--with-tlib=ncursesw,前提是ncurses-devel装的是宽字符版本。RHEL 上ncurses-devel通常同时提供两者,但configure的自动探测顺序不一定命中宽字符那个。如果你已经编完才想起来,必须重新configure再make clean && make,只重新make是没用的,因为 tlib 是写进 config.h 的。
还有一个相关的小细节:终端本身的 locale 也要设置正确,echo $LANG要是en_US.UTF-8或zh_CN.UTF-8这类,纯POSIX环境下中文一样显示异常,这跟 vim 编译无关,别把两件事混在一起排查。
5.2 剪贴板寄存器显示为加号但粘贴没反应
这个坑我遇到不止一次。vim --version显示+clipboard,但在里面用"+y复制,去别的窗口Ctrl+V粘贴,什么都没有。原因分两种情况。
第一种是在纯终端环境(无 X11)里工作。这时候 vim 的+clipboard依赖外部提供者,如果检测不到可用的剪贴板管理程序,寄存器操作会静默失败。可以在 vim 里执行:echo has('clipboard')看是否返回 1,再看:echo $DISPLAY是否为空。空的情况下就要靠终端模拟器自身的复制粘贴能力,vim 这边不需要纠结"+y。
第二种是装了 X11 库但configure时libXt-devel缺失,导致+clipboard是加号但实现不完整。这种就得补装libXt-devel后重新编译。
这里给一个实操判断技巧:在 vim 里执行:reg +,看寄存器内容是否为空,再试:let @+ = "test"然后用系统工具去粘贴。如果:reg +显示有内容但系统剪贴板没有,那就是跨进程通道断了,属于环境问题而非编译问题。把这条链路分开验证,比一上来就怀疑编译错误高效。
5.3 重装后插件目录和配置的错位
从系统 vim 切到/opt/vim9之后,一个很典型的问题是插件不生效。原因是老版本 vim 的插件加载路径和新版的默认路径不完全一致,尤其是如果你之前手动往/usr/share/vim/vimXX/plugin里放过东西,新装的 vim 根本不会去读那个目录。
我的做法是把个人配置和插件全部放在~/.vim和~/.vimrc下,让新 vim 通过$HOME去读取,这样二进制换不换都不影响。如果你的 vim9 使用了新的默认配置目录(比如~/.vim与新式的~/.config/vim并存),先执行:echo $MYVIMRC确认它到底加载了哪个文件,再看:scriptnames列出所有被加载的脚本,插件没生效时这两个命令能直接定位断点。
一个额外的注意点:不要在新 vim 里沿用旧的~/.vimrc却不检查其中的版本判断。有些老配置里写了if v:version < 800之类的分支,逻辑在 vim9 下可能走偏,尤其是对set nocompatible、filetype plugin indent on这些顺序敏感的配置,建议先清空配置验证基本功能,再一段段把个人配置合并回去。
5.4 编译过程中最容易误判的报错
有几个报错信息看着吓人,实际不用管或者处理方式很简单,列出来省得你慌。
第一,checking for ... no出现一大堆是正常的,configure会探测几百个可选特性,大部分在huge下也用不到。你要关注的是摘要里明确列出、而你又想要的特性,别被满屏的no吓到。
第二,Warning: Compiling with ...这类警告一般不影响结果,但如果最后+python3是减号,就去翻src/auto/config.log里跟 python 相关的段落,那里会写清楚是缺头文件还是版本不匹配。
第三,make阶段的undefined reference to ...基本都是链接库没找到。最常见的是编 GUI 版缺 GTK 库,或者 Python 版本与头文件不一致。这时候看报错里涉及的库名,用dnf provides */libXXX.so定位包名装上去,再make clean重来。
第四,configure: error: cannot run C compiled programs一般出现在/tmp被挂成noexec的机器上,或者 SELinux 策略限制,换个可执行目录重试即可(TMPDIR=~/tmp ./configure ...)。
6. 装好之后的几件收尾事
编译装完之后,我一般还会做几件小事,让这套环境真正可用。第一是给新 vim 配一个独立的runtimepath规划,把插件目录、语法文件、字典放在同一棵树下,避免和系统 vim 交叉污染。我的习惯是set runtimepath^=~/.vim再加set packpath^=~/.vim,用 vim 自带的packages机制管理插件,这样不依赖外部插件管理器也能用。
第二是检查:help能不能正常打开。如果报E149: Sorry, no help for ...,说明$VIMRUNTIME/doc路径不对,回到前面确认 prefix 和 runtime 路径。这个功能平时不常用,但排查问题时查帮助很关键,装完就验证一下省得日后抓瞎。
第三是给这个二进制加个别名,我用的是alias v9='/opt/vim9/bin/vim',这样系统 vim 和新版同时留着,各自有明确入口,不会因为改 PATH 影响其他工具链。什么时候觉得新版完全稳了,再把 PATH 里的优先级调上去。
第四,把编译时用的完整 configure 命令行记到某个文本文件里(比如~/.vim-build-notes),连同当时的依赖包列表一起。下次换机器、或者一年后重编,直接照着跑就行,不用再从头推理一遍。这个习惯我坚持了很久,回报率极高,尤其是编译那些选项多、容易忘的软件时。
注意:每次升级 vim 后,如果之前用
dynamic方式链接了 Python3,务必先在一个子 shell 里跑一遍vim --version,确认不报库加载错误,再切换全局 PATH。否则可能出现在终端里完全打不开 vim、只能靠其他编辑器改 PATH 的尴尬局面。
我在实际使用中的体会是,编译 vim 这件事真正花时间的从来不是make那几分钟,而是前面确认特性需求和后面验证特性列表这两头。把依赖清单和 configure 选项固化下来之后,一台新机器从零到可用基本就是复制粘贴几条命令的事。而最值得记住的一条经验是:每次改动 configure 参数之后,一定make clean再编——这个动作能挡掉我遇到过的绝大多数“为什么编出来的特性跟预期不一样”的疑问。