起因只是一个小小的版本号
那天我想把 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 包",而是三件事:
- 这个包到底有没有人能维护(官方源还是第三方桶);
- 它会不会和别的语言打架(ABI、线程模型、注册表);
- 迁移的收益,值不值得折腾。
最后给出的结论,比我想象的保守:
| 判定 | 语言 | 理由 |
|---|---|---|
| ✅ 值得迁 | 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 已经出现了三次:
- Rust 链不上(
__MCF_cxa_atexit); - 我判断 MinGW 不能迁;
- 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 直接查出来,不再有 "装在某个我想不起来的地方" 这种东西。
评论交流
欢迎留下你的想法