起因只是一个小小的版本号

那天我想把 Python 从 3.13 升到 3.14。

听起来是个十分钟的事:装个新解释器,重建几个虚拟环境,跑一遍测试,完事。

但打开 D:\settings\Language 之后,我停住了。

C         0.72 GB     ← MinGW-Builds GCC 15.2.0
Go        0.22 GB
Java      0.63 GB     ← Oracle JDK 17 + 25
LLVM      2.83 GB
R         5.10 GB     ← 含 RStudio
Rust      3.16 GB     ← rustup 手工装,自定义 HOME
Python    ...
texlive   8.69 GB
MATLAB   22.11 GB

43.46 GB。这些目录有一个共同点:没有一个是我 "安装" 过的——它们全是解压出来的,或者手工装完就再没人管过的。

每一份里面都埋着隐患:

  • 系统里还躺着一份 Python 3.13.13 的卸载残骸,PATH 里有两条指向它的记录;
  • 有个装了但不在 PATH 里的 R,Rscript 敲下去直接找不到;
  • 13 个 Python 虚拟环境里,5 个的 pyvenv.cfg 指向已经被删掉的解释器;
  • 开始菜单里还挂着 Python 3.8 的图标,而那个版本两年前就没了。

这不是 "我想升级 Python",这是一台机器的语言工具链从来没有被统一管理过。

工程师最容易犯的错,是把「能跑」当成「没问题」。

所以我决定:不只是升级 Python,而是把能迁的语言全部迁到一个地方管。

先立规矩:哪些迁,哪些不迁

动手之前,我先让 AI 做了一轮评估。评估的维度不是 "有没有 Scoop 包",而是三件事:

  1. 这个包到底有没有人能维护(官方源还是第三方桶);
  2. 它会不会和别的语言打架(ABI、线程模型、注册表);
  3. 迁移的收益,值不值得折腾。

最后给出的结论,比我想象的保守:

判定 语言 理由
✅ 值得迁 Go 官方 zip,零风险
✅ 值得迁 LLVM 顺便升级
✅ 值得迁 Java 换成 Temurin,解决许可问题
✅ 值得迁 R 修好"装了不能用"的毛病
⚠️ 可选 Rust rustup 自己够用,迁过去收益低
❌ 不迁 MinGW mcf ↔ posix 线程模型不兼容
❌ 不迁 TeX Live 8.69 GB,tlmgr 自管更合适
❌ 不迁 MATLAB、MySQL 商业软件 / 系统服务

我当时在文档里给 MinGW 判了死刑,理由是 "Scoop 的 mingw 包都是 posix 线程模型,和我的 mcf 不兼容"。

这个判断后来被我自己推翻了。 但不是马上,是绕了一大圈之后。

第一个坑:Scoop 的"失败"是不出声的

先装最没风险的 Go。

scoop install go
→ Download: 75 MB, 8 大线程
→ Extracting go1.27.3.windows-amd64.zip...

然后……卡住了。

几分钟后,scoop list 显示:

go    1.27.3    Install failed

我以为是下载坏了,重装。结果报了更奇怪的:

WARN  Purging previous failed installation of go.
ERROR 'go' isn't installed correctly.
'go' wasn't installed.

继续等,7z 进程还在跑,robocopy 也在跑。1 万个文件、270 MB,Defender 逐个扫描——它其实没失败,只是慢,慢到 Scoop 自己判了个 "失败"。

最后的结果是:安装成功了,但状态栏写着 failed。

Scoop 的 Install failed 不是错误,是一个心态。它表示「我不确定」。真正有没有装上,得自己看目录、看 shim、敲命令。

这个认知在后面每一个包上都应验了一次。

第二个坑:CMake 在带空格的路径上生成了坏规则

接下来是 LLVM。我以为它和 Go 一样是解压包——直到你看到这条:

然后我收到一句:「LLVM 你搞错了,是用安装程序装的。我看到你搞错了,就自己手动卸载掉了。」

对,我连「安装方式」都判错了。LLVM 是 MSI 安装的程序,不是解压目录。

对,我连 "安装方式" 都判错了。LLVM 是 MSI 安装的程序,不是解压目录。

装上 Scoop 的 23.1.5 之后,新的问题来了:

No rule to make target 'D:/Code/.../归档/L\ Language/src/ast/ast.c'

L\ Language —— 注意那个反斜杠。CMake 4.4.5 的 MinGW Makefiles 生成器把空格转义成 \ ,而 mingw32-make 把 \ 当成路径分隔符。

这是一个纯粹的生成器 bug,但踩它的人得自己绕过去。

绕法:改用 Ninja。

cmake .. -G Ninja
cmake --build . -j4

一条命令解决,但前提是你得知道问题出在生成器,而不是自己的代码。

而 L Language 更麻烦的地方在于:Scoop 的 LLVM MSI 不含开发头文件。打开 include/ 一看:

clang-c
flang
flang-rt
llvm-c     ← 里面只有 Remarks.h 和 lto.h

Core.h、TargetMachine.h、Analysis.h 全都没有,include/llvm/ 整个目录不存在,连 LLVMConfig.cmake 都没有。

最后是从 Gitee 镜像拉回 12 个 llvm-c/*.h,又手写了 5 个 llvm/Config/*(llvm-config.h、Targets.def、AsmPrinters.def ……),才算把编译器自己的编译链接起来。

209 个单元测试跑过的那一刻,我意识到一件事:

发行包和 SDK 是两回事。能跑 LLVM 工具,不等于能用 LLVM 开发。

第三个坑:Oracle 的许可,和卸载器的懒惰

Java 的问题有两层。

第一层是许可:我装的是 Oracle JDK。个人用没人管,但它的许可条款一直是个悬着的问题。Scoop 的 java 桶里有完整的 Temurin/OpenJDK/Corretto/GraalVM,换成 Temurin 是最省事的合规方案。

第二层更有意思。我让系统只装 Temurin 21——因为我的项目实际要的版本在这里:

RuoYi-Vue-Plus 6.0.0  → java.version = 21
so-novel              → java.version = 21
LocalChatApp          → source = 17

我的项目要 21 和 17,机器上装的却是 17 和 25。缺的那个,恰好是 21。

Temurin 21 装好之后,旧的 Oracle JDK 我手动卸载了。然后发现:

D:\settings\Language\Java\jdk-17.0.1    287.7 MB / 503 文件
D:\settings\Language\Java\jdk-25.0.1    362.1 MB / 518 文件

注册表卸载项已经清空了,但目录原封不动。卸载器只负责删它自己的记录,不负责删你的文件。

650 MB,就这么躺在那里,谁也看不见。

第四个坑:R 装了,但从来不能用

R 的问题最荒谬。

D:\settings\Language\R\R-4.4.1\bin\R.exe 明明存在,双击能跑,Rscript 也有。但你敲:

> R
无法将"R"项识别为 cmdlet…

它的 bin 从来没进过 PATH。

这意味着什么?意味着过去用 R 的时候,我一直是在 RStudio 里点,或者在脚本里写绝对路径。Rscript 从来不是个能直接用的命令。

Scoop 的 r 包装完,这个问题自然消失——shim 直接进 PATH,R 和 Rscript 双双可用。

但 R 有个比 PATH 更麻烦的地方:包库不能复用。

旧环境 4.4.1 里装了 159 个包,新的 4.6.2 没法直接用。我导了清单,批量重装,先成功 154 个,剩 5 个:

MRMix、MRPRESSO、MVMR、RadialMR、TwoSampleMR

原因是——CRAN 对 R 4.6 根本不提供这些包的索引,连源码都搜不到。它们已经从主流程里下架了。

最后的解法是绕道 GitHub:下载 master 分支的 tarball,本地 R CMD INSTALL。而 gh-proxy.com 又在下 22 MB 的 MRInstruments 时超时——它的默认超时是 60 秒。

改 Invoke-WebRequest -TimeoutSec 180,一次过。

包管理器的「没找到」,有时候不是网络问题,是包真的不在了。

最后一个细节:R 的 rtools 第一次装也报了 Install failed。真正的原因不是下载,是它要在 C:\ 根目录建一个 rtools45 的 junction——权限不够,静默失败。

scoop reset rtools 修好了。

第五个坑:Rust,最痛的一步

Rust 是我最不想动,也是唯一被标成 "可选" 的一步。

理由很简单:它已经在跑,rustup 自己能升级,迁过去只是换个位置。但最后我还是做了——因为 "统一管理" 这件事,一旦开始,半途而废是最难看的。

结果撞了两堵墙。

第一堵:config.toml 不在 registry 里

我把 CARGO_HOME 整个目录看了一遍:

.rustup    1.70 GB
.cargo     1.46 GB

registry 有 1.22 GB,我复制了它,觉得搬完了。

然后三个项目全部挂掉:

undefined reference to `__MCF_cxa_atexit'
undefined reference to `__MCF__Exit'

__MCF_* —— 又是 mcf 线程模型。

排查了二十分钟我才想起来,CARGO_HOME 里还有一个小文件:config.toml。

它里面是:

[target.x86_64-pc-windows-gnu]
linker = "rust-lld"

这一行才是过去能在 mcf 版 MinGW 上编译 Rust 的真正原因。 rust-lld 使用 Rust 自带的 self-contained 运行时,绕开了系统 MinGW 的 mcf 问题。

我把 1.22 GB 的缓存搬了,漏了 4 KB 的配置。

迁移一个开发环境,最难的不是搬大文件,是找到 "没被命名成文件" 的那些规则。

第二堵:卸载器把新家删了

这件事是我自己造成的,但过程值得记下来。

旧的 Rust 我决定卸载。用 rustup 官方卸载器跑完,它说 "完成"。我去看 D:\settings\Language\Rust ——目录还在。

于是手动删。

删完,我让 AI 检查环境。结果:

.rustup: 1.70 GB → 不存在
.cargo:  1.38 GB → 不存在

新装的、在 Scoop\persist\rustup\ 下的那份,也被删了。

原因:Windows 的环境变量分用户级和系统级,同名的用户级优先。Scoop 装 rustup 时,把用户级的 RUSTUP_HOME/CARGO_HOME 指向了 Scoop\persist\rustup\。而卸载器读的是 "当前生效的值"——于是它删的是 Scoop 的目录,不是我以为的那个旧目录。

我看到的 "旧目录还在",是因为它早就不是 "当前安装位置" 了。

卸载器没有删错。它删的正是它认为的安装位置。错的是我以为那里还是。

代价:1.7 GB 工具链 + 1.22 GB 缓存 + 配置,全部重建。好在 rustup-init.exe 还在,重装用了 109 秒。

第六个坑:mcf,那个反复出现的少数派

到这里,mcf 已经出现了三次:

  1. Rust 链不上(__MCF_cxa_atexit);
  2. 我判断 MinGW 不能迁;
  3. L Language 也带了一堆 mcf 的运行时库。

所以当最后重新评估 MinGW 时,我发现了一件尴尬的事:

Scoop 有 mcf 版。 在 versions bucket 里:

versions/mingw-winlibs-ucrt-mcf    16.2.0-14.0.0-r3
  → winlibs-x86_64-mcf-seh-gcc-16.2.0-mingw-w64ucrt-14.0.0-r3.7z

我当初的判断,只查了 main bucket。

这是我这次犯的第二个错误:把 "我没找到" 当成 "不存在"。

不过犹豫仍然是合理的。换 MinGW 意味着:

  • GCC 从 15.2.0 跳到 16.2.0;
  • 构建者从 MinGW-Builds(niXman)换成 WinLibs(Brecht Sanders);
  • 我的项目里有 IUP 这种预编译 GUI 库。

于是我下载了 105 MB 的包,解压到临时目录,做了一轮完整验证——不动 PATH,只用绝对路径调用。

结果比我预期好得多:

Thread model: mcf          ← 一致
CRT:          UCRT          ← 一致
异常:         SEH           ← 一致

然后是最有说服力的一步:把 HuffmanCompress(IUP GUI 项目)复制到临时目录,用新 MinGW 编译。

gcc -Wall -Wextra -Iinclude -Ilibs/iup-3.31_Win64_dllw6_lib/include ...
→ 退出码 0
→ huffman.exe 84 KB + huffman_gui.exe 97 KB
→ 运行正常

依赖检查:

DLL Name: KERNEL32.dll
DLL Name: api-ms-win-crt-*.dll     ← 全是 UCRT

没有 libstdc++,没有 libwinpthread,没有 libmcfgthread。

原因很简单:IUP 是纯 C 库,iup.dll 自己只依赖 msvcrt.dll;而我的项目全是静态链接。GCC 15 到 16 的 C++ ABI 变化,对它们几乎没有影响。

于是迁了。搬完重编译 5 个项目:

项目 结果
Sort ✅ sort.exe 86 KB
KMP ✅ main.exe 75 KB
Stu_scores_system ✅ main.exe 75 KB
HuffmanCompress(IUP) ✅ huffman.exe + huffman_gui.exe
L Language(LLVM C API) ✅ 28 个目标,209/209 测试通过

有一个小插曲:WinLibs 不自带 cc.exe,而旧 MinGW-Builds 带。Sort 的 Makefile 写的是 CC ?= gcc,但 GNU Make 预定义 CC=cc,?= 不生效——于是它去找 cc,找不到,编译失败。

复制 gcc.exe 为 cc.exe,解决。

收尾:43 GB 变成了三块

最后盘点 D:\settings\Language:

MATLAB     22.1 GB    ← 商业软件,不可迁
texlive     8.7 GB    ← tlmgr 自管,不该迁
Python      6.5 GB    ← venvs + wheels,正在用
r-local-pkgs 27 MB    ← R 的 GitHub 包 tarball,离线备份
+ 4 个 R 重建脚本 + 评估文档

原来的七个手工语言目录,现在只剩下三块——而且这三块的保留理由都很清楚。

迁移后的形态:

Scoop 管一切:
  go 1.27.3            llvm 23.1.5         temurin21-jdk 21.0.13
  r 4.6.2              rtools 4.5           rstudio 2026.10.1
  rustup 1.29.3        mingw-winlibs-ucrt-mcf 16.2.0
  maven 3.10.0         nodejs24 24.22.2     pnpm 12.18.0
  uv 0.13.7  →  Python 3.14.10

一共释放了约 15 GB(迁移掉的旧目录 12.66 GB + Rust/Java 缓存与卸载残留约 2 GB)。

最后:一个意外的好消息

审查环境时发现一个 C++ 动态链接崩溃的问题:

g++ x.cpp -o x.exe
x.exe → -1073741819 (0xC0000005 访问冲突)

根因是 PATH 里有三个不同版本的 libstdc++-6.dll:

来源 日期
HealthcareAI\.venv\gtk3\runtime\bin 2022-01-04
Scoop\apps\perl\...\bin 2024-05-04
Scoop\apps\mingw-winlibs-ucrt-mcf\...\bin 2026-11-06

系统 PATH 优先,所以加载的是 2022 年那个——和 GCC 16.2.0 编译的代码 ABI 不兼容。

我本来打算不管它(毕竟所有项目都是静态链接)。结果后来清理环境时,顺手删了 gtk3(一个从未被使用的 GTK 运行时)和 perl(一个没有依赖的孤立安装)。

删完再测:

g++ x.cpp -o x.exe
x.exe → cxx-ok

PATH 里只剩 mingw 的那一份,问题自动消失。

有些问题不是 "解决" 掉的,是被更彻底的清理顺手带走的。

几条想记住的

1. Install failed 不等于失败。 Scoop 的四个包(go / rtools / rustup / mingw)都报过这个状态,其中三个其实是成功的。判断标准永远是:目录在不在、shim 有没有、命令能不能跑。

2. 「我没找到」不等于「不存在」。 我说 "Scoop 没有 mcf 版 MinGW",是因为只查了 main。实际在 versions。查东西要查全。

3. 迁移要搬的是 "规则",不是 "文件"。 registry 1.22 GB,config.toml 4 KB。漏了后者,编译全挂。

4. 卸载器删的是 "当前生效的安装位置"。 用户级变量优先于系统级。卸载前先 rustup show 看一眼 rustup home 到底在哪。

5. 中文路径是 Windows 工具链的老大难。 windres、ld、objdump,全都在中文路径上出问题。Gobang 放在 作业\ 下就编不了,挪到 D:\tmp-gobang 立刻正常。

6. 评估可以错,但要能改。 我最初把 LLVM 判成 "解压类",把 MinGW 判成 "不可迁"。两次都是靠实测推翻的。文档里保留这些错误判断,比只写结论更有价值。

附:最终环境

Go      1.27.3      (scoop)
LLVM    23.1.5      (scoop)
Java    21.0.13     (scoop, temurin21, held)
R       4.6.2       (scoop, + rtools 4.5 + rstudio 2026.10.1)
Rust    1.99.1      (scoop rustup, GNU toolchain)
C/C++   16.2.0      (scoop mingw-winlibs-ucrt-mcf, mcf/UCRT/SEH, held)
Python  3.14.10     (uv + scoop)
Node    24.22.2     (scoop)

一切路径都能在当前会话里用 where 直接查出来,不再有 "装在某个我想不起来的地方" 这种东西。