Homebrew 停止 Intel 支持:迁移到 MacPorts 的完整笔记
2026 年 9 月起,Homebrew 停止提供 Intel (x86_64) 的预编译 bottle。对 Intel Mac 用户意味着:任何没有预编译包的 formula,每次升级都要在本机源码编译。实测 rust 类程序在 8GiB 内存的机器上编译常报 SIGSEGV 栈溢出,llvm 等大依赖一次要编译很久。
替代方案 MacPorts 2.12.6 仍持续发布 x86_64 预编译二进制包。将重编译型软件迁到 MacPorts,brew 只留小工具,是一条可行的路。本文整理多台 Intel Mac 迁移的完整经验。
迁移决策:只迁有预编译 archive 的包
核心原则:MacPorts 有预编译 archive 才迁移;需要源码编译的保留 brew 版。另有一类根本不进系统包管理器——语言生态自带的官方工具链(Python 的 uv、Rust 的 rustup 等),见下文。
archive 探测(200 即迁,404 则保留):
curl -sI -o /dev/null -w "%{http_code}\n" \
https://packages.macports.org/eza/eza-0.23.5_0.darwin_24.x86_64.tbz2
_0 是 port revision,可能为 _1/_2。404 不一定是没有:
- variant 后缀:有的 archive 文件名带 variant,如
ripgrep-15.2.0_0+pcre.darwin_24.x86_64.tbz2、wget-1.25.0_1+gnutls.darwin_24.x86_64.tbz2。裸文件名探 404,加上 variant 就是 200。port install会自动选择默认 variant,无需手动加。 - 判断是否真的没有:看目录列表
https://packages.macports.org/<port>/,能列出全部可用 archive 文件名。
端口命名与 brew 的差异
| brew | MacPorts 端口 |
|---|---|
| python@3.14 | python314 |
| go | go-1.27(无裸名端口) |
| httpd | apache2 |
| node@22 | nodejs22 |
| mysql-client@8.4 | mysql9(等官方二进制) |
| imagemagick | ImageMagick7 |
| ffmpeg | ffmpeg(+gpl2 variant 才有完整编解码) |
前置准备
免密执行 port
批量迁移要全自动,给 port 开 NOPASSWD(只限这一条命令):
echo "user ALL=(ALL) NOPASSWD: /opt/local/bin/port" | sudo tee /etc/sudoers.d/port-nopasswd
PATH 优先级
~/.zprofile 里把 /opt/local 前置,新终端自动优先用 MacPorts 版本:
export PATH="/opt/local/bin:/opt/local/sbin:$PATH"
关闭 rev-upgrade(关键,见坑 1)
迁移流程(每批)
- 建清单:
brew list --formula、brew leaves - 逐项探测 archive
sudo port install <port>→ 看到---> Fetching archive即走预编译- 验证:
port installed active+ 跑二进制--version冒烟 - 卸载 brew 副本:
brew uninstall <...>+brew autoremove - 复查 leaves 与依赖方
坑与经验
1. rev-upgrade 假阳性死循环(最值钱的教训)
症状:每次 port install/upgrade 后 ffmpeg 被反复重建,一次约 40 分钟,连续失败。
根因:MacPorts 默认每次装完自动跑 rev-upgrade,扫描已装二进制的动态库链接一致性。ffmpeg 链接 CoreImage.framework:SDK stub 声明 compat 1.0.1、运行时框架报 1.0.0 → 被永久误判 broken → 自动 rebuild。
修复:
# /opt/local/etc/macports/macports.conf
revupgrade_autorun no
revupgrade_mode none
revupgrade_autorun no:install/upgrade 后不再自动跑 rev-upgrade(断掉死循环)revupgrade_mode none:即使手动sudo port rev-upgrade,也只报告不 rebuild(双保险)
路径陷阱:MacPorts 只读 /opt/local/etc/macports/macports.conf;根目录 /opt/local/etc/macports.conf 是废文件,改了没用。无 sudo 时可用用户级 ~/.macports/macports.conf 覆盖——解析顺序为 系统 → 用户级,后读覆盖先读。
验证:用 perl/tclsh 按同样顺序解析两个文件,确认最终 revupgrade_autorun = no、revupgrade_mode = none。
2. brew autoremove 清不干净孤儿
症状:卸载主包后,它的旧依赖会逐个浮出为 brew leaves(如 libheif→aom/libde265,curl→pcre2,gpgme→gnupg/libgcrypt,pipx→python@3.14),而 autoremove 因旧回执缺 installed_as_dependency 标记经常漏掉它们。
处置:brew uses --installed <pkg> 确认无任何已装 formula 依赖 → brew uninstall。要清理好几波,每波卸载后可能又冒出新孤儿;gpgme/gnupg 那条加密链(gmp/gnutls/nettle/p11-kit 等)尤其长。
3. ssh 远程批量运维
- 复合命令(ssh + 多行管道)容易超时/被拒,拆成最小单命令逐个跑
- 引号:
ssh host 'perl ~/...'用单引号让~在远程展开,否则会在本地展开报错
4. ls 被 eza 替代
装了 eza 并 alias 成 ls 的机器,脚本里 ls 参数不兼容会静默误报"文件不存在"。探测一律用 /bin/ls 或 [ -f path ]。
系统包管理之外的:改用语言官方工具链
brew/port 不是万能的,也不该是所有软件的归宿。凡是语言生态自带包管理器/官方安装器的,优先用官方工具——它们自带隔离环境、独立升级、不污染系统路径,也不被 brew/port 的依赖链和 rev-upgrade 拖累。
| 生态 | 官方工具 | 安装方式 | 说明 |
|---|---|---|---|
| Python | uv | curl -LsSf https://astral.sh/uv/install.sh | sh |
独立管理 venv 与 Python 版本,替代 brew python + pipx |
| Rust | rustup | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh |
toolchain 隔离,rustc 慢编译不进系统 |
| Node | nvm / fnm | 官方安装脚本 | 按项目切版本,绕过 nodejs port |
| Go | go install | go install <pkg>@latest |
二进制直接进 $(go env GOBIN) |
| 前端 CLI | corepack / npm i -g | 随 Node | pnpm/yarn 等自带 |
两个注意点:
- PATH 顺序:官方安装器一般装到
~/.local/bin、~/.cargo/bin、~/.nvm/…,确认它们排在/opt/local之前(which uv、which node验一下),别让系统包管理版本抢跑。 - 与上文的分类:brew/port 管系统级工具,语言官方工具链管语言项目工具,两者职责分开。迁移时少了很多"要不要迁"的纠结——能走官方工具链的直接走官方工具链。
保留在 brew 的类别
| 类别 | 例子 |
|---|---|
| MP 无 x86_64 archive 的 rust 程序 | eza、glab(rustc 源码编译易栈溢出) |
| 等 MP 官方二进制 | MySQL(mysql9 archive 发布后迁) |
| MP 无对应端口 | sdl2-compat、tldr |
| brew 版本领先 MP | cmake(brew 4.4.3 vs MP 3.31.12) |
终态检查
brew leaves只剩故意保留的项port installed active数量显著上升- 服务方式变化:
brew services→sudo port load <port>(如 apache2)
复查清单
- mysql9:
curl -sI https://packages.macports.org/mysql9/mysql9-9.7.2_1.darwin_24.x86_64.tbz2200 即sudo port install mysql9→ 卸 brew mysql-client - 各台保留的 eza/glab/lynx/pkgconf:archive 发布后补迁
日常维护:
# 手动一键更新
sudo /opt/local/bin/port selfupdate && sudo /opt/local/bin/port upgrade outdated # MacPorts
/usr/local/bin/brew update && /usr/local/bin/brew upgrade && /usr/local/bin/brew cleanup # Homebrew
uv self update # uv(语言工具链,无需 sudo)
npm update -g # Node 全局包(若 node 走 brew)
要全自动,挂 crontab 定时(周一三五早 5 点错峰;sudo port 依赖前文 NOPASSWD 配置;crontab 环境 PATH 不含 /opt/local/bin 与 ~/.local/bin,必须写全路径):
9 5 * * 1,3,5 /usr/local/bin/brew upgrade >> /tmp/brew_upgrade.log 2>&1
11 5 * * 1,3,5 sudo /opt/local/bin/port selfupdate && sudo /opt/local/bin/port upgrade outdated >> /tmp/port_upgrade.log 2>&1
13 5 * * * $HOME/.local/bin/uv self update >> /tmp/uv_update.log 2>&1
只想"检查不升级":brew outdated、port outdated;uv 是单文件工具,没有独立 outdated 概念,uv self update 直接就是更新。