修复 conda 不能升级的问题
我笔记本上装的是 Gentoo Linux。为了做各种生物信息学数据分析,我在用户目录里又装了一套 miniconda:用它建虚拟环境很方便,可以给不同的分析任务挑不同版本的 Python,再把各款生物信息学软件要的依赖装齐。
conda 的 channel 挂了 conda-forge、bioconda 和 defaults,平时升级 conda 就是一句 conda update conda,一直好使。最近它不灵了:版本停在 26.3.2,每次运行都会收到一条警告,说最新版是 26.9.4、请升级;照着警告里给的命令再跑一遍,结果还是那句 All requested packages already installed,版本号一动不动。
这篇记录一下排查过程。先说结论:问题的关键是 conda 和它的求解器插件 conda-libmamba-solver 必须一起换版本,而 conda update conda 只点名了其中一个。
一、现象
最小复现是这样的:
1$ conda update conda
2Channels:
3 - conda-forge
4 - bioconda
5 - defaults
6Platform: linux-64
7Collecting package metadata (repodata.json): done
8Solving environment: done
9
10==> WARNING: A newer version of conda exists. <==
11 current version: 26.3.2
12 latest version: 26.9.4
13
14Please update conda by running
15
16 $ conda update -n base -c conda-forge conda
17
18# All requested packages already installed.
19
20$ conda --version
21conda 26.3.2还有一点容易误判。那次 update 并不是完全没有动作:事后翻 conda-meta/history,它把 openssl、ca-certificates、certifi 升级了——这三个包属于 aggressive_update_packages,每次求解都会被要求升到最新。也就是说,命令确实完成了求解和安装事务,只是 conda 自己没挪窝。警告和结果并排出现,看起来才像互相矛盾。
二、排除几个猜测
遇到“提示有新版本但升不上去”,能想到的原因大概就那几个,我逐个排掉:
- 镜像数据过期。用的是清华 TUNA 镜像,本地
conda-forge/linux-64的 repodata 是当天刷新过的,里面已经有 conda 26.9.x,说明不是镜像的问题。 - 被 pin 住了。
conda-meta/pinned和~/.conda/pinned都不存在,pinned_packages也是空的。 - 新版本装不上。
conda install --dry-run conda=26.9.4能干净地解出方案,说明包在、依赖也不冲突。 - channel 优先级。换成
--override-channels -c conda-forge单独用 conda-forge,结果一样。
四样都排掉之后,剩下的可能只有一种:命令发出去的求解请求里,压根没有要求 conda 升级。
三、几种写法的差别
同样是升级 conda,换几种写法,结果差别很大:
| 命令 | 结果 |
|---|---|
conda update conda |
无变化,打印 already installed |
conda update conda=26.9.4 |
直接报错:conda update only supports name-only spec |
conda install conda=26.9.4 |
可行,dry-run 能给出完整方案 |
conda update --update-deps conda |
可行,但会连带升级约 90 个包 |
第一行和最后一行针对的是同一个包,一个什么都不做,一个能做。说明问题不在包本身,而在“命令如何被翻译成求解请求”。
中间那行也是一条有用的信息:conda update 只接受包名,不接受版本号。想要指定版本,只能走 conda install。
四、一对互相约束的包
看一眼新版本 conda 声明了什么依赖:
1$ conda search --info conda=26.9.4
2dependencies:
3 ...
4 conda-libmamba-solver >=26.7.0
5 conda-lockfiles >=0.2.2
6 conda-pypi >=0.13.0
7 conda-rattler-solver >=0.2.0
8 conda-self >=0.3.0
9 ...conda-libmamba-solver(下文简称 cls)是 conda 现在默认使用的 libmamba 求解器的插件包。这台机器上装的是 26.3.0,而新版 conda 要求 >=26.7.0。反过来看 cls 的新版本,它又要求更新的 conda:
1$ conda search --info conda-libmamba-solver=26.10.1
2dependencies:
3 ...
4 conda >=26.7.1两个包互相约束,版本必须成对地往上走。升级 conda 这件事,天然带着升级 cls 这件事。
五、update 只点名了一个
conda update conda 的意思是“把 conda 升到最新的兼容版本”。它会点名 conda,但不会顺带把 conda 的依赖也放进升级名单。
我打印过求解器实际收到的任务清单,结构大致是这样:
| 包 | 任务 |
|---|---|
| conda | Update(被点名升级) |
| conda-libmamba-solver | Keep,精确到 build:conda-libmamba-solver==26.3.0=pyhd8ed1ab_0 |
| openssl、certifi、ca-certificates | Update(来自 aggressive_update_packages) |
| base 环境其余包 | 按已装版本保持 |
于是同时存在两条要求:cls 停在 26.3.0,而新的 conda 要求 cls 至少 26.7.0。它们不能同时成立。由于只有被点名的包才承担“升级”的义务,其余包都按现状处理,求解器能交出的自洽结果就是维持原状——conda update 不把这种回落当成失败,于是打印 already installed,而不是报一个依赖冲突。
把 cls 也写进命令就不一样了:它从“保持 26.3.0”变成“被点名要升级”,版本约束解除,两个包可以一起往上走。dry-run 的方案立刻变了:
1conda 26.3.2 -> 26.9.4
2conda-libmamba-solver 26.3.0 -> 26.10.1
3packaging 26.0 -> 26.3
4新增 18 个包(conda-self、conda-pypi、conda-rattler-solver……)
5移除 jsonpatch、jsonpointer--update-deps 之所以也能成功,原因相同:它的实现就是先把被点名包的依赖链整体当成“用户点名的包”,再做一次求解。这相当于自动完成了点名 cls 这一步,代价是把整条依赖链都放开了。
六、解法
最终用的命令是把这一对一起点名:
1conda update -n base -c conda-forge conda conda-libmamba-solver实际耗时 20 秒左右,结果符合预期:
1$ time conda update -n base -c conda-forge conda conda-libmamba-solver
2...
3real 0m20.248s
4
5$ conda --version
6conda 26.9.4两个备选方案,看场景选:
conda install -n base -c conda-forge conda=26.9.4:计划与上面一致,适合明确想锁定某个版本的时候。注意conda update不接受版本号。conda update -n base -c conda-forge --update-deps conda:不用点名 cls,但会连带动约 90 个包,改动面大得多。
升级完成后,那句“有新版本”的警告自然就消失了。
七、经验
- 静默成功比报错更麻烦。
already installed只说明没有找到能做的改动,不说明没有可做的事。这次就栽在这儿:命令跑完了,事务也提交了,只有 conda 自己没动。 - 升级核心组件之前,先看依赖的方向。 conda 要 cls 的新版本,cls 又反过来要 conda 的新版本,这种互相咬住的场景,点一个不行,点两个才行。
--dry-run是这类问题最便宜的探针。 单独点名看不出变化时,把紧耦合的伙伴一起写进命令再跑一次,两份计划的差异往往直接指出卡点。update和install的语义不同。install可以接版本号,是硬要求;update只接包名,是软目标。要“必须到这个版本”,就得用install。
如果你也卡在类似的“升不上去又不报错”上,可以做一次对照实验:先跑 conda update --dry-run A,再跑 conda update --dry-run A B,其中 B 是 A 的紧耦合依赖。我这台机器上,两次的结果分别是“什么都不做”和“升两个包”,差异就是答案。
参考链接
注:本文的文字与解决方案均由AI生成,作者本人实测确认可行。