跳转到内容

brew

Homebrew 配方和 cask —— 无需安装 Homebrew

toml
[bootstrap.packages]
"brew:postgresql@17" = "latest"
"brew:ffmpeg" = "latest"
"brew:imagemagick" = "latest"
"brew-cask:firefox" = "latest"

mise 直接将 homebrew/core 的配方安装到 标准的 Homebrew 前缀中——arm64 macOS 上是 /opt/homebrew, Linux 上是 /home/linuxbrew/.linuxbrew。它会从 formulae.brew.sh API 获取元数据,解析运行时依赖闭包,从 ghcr.io 下载 预编译的 bottles(会验证 sha256 校验和),并执行与 brew 倒入 bottle 时相同的重定位、代码签名和链接工作。没有可用 bottle 的配方也会从源代码构建, 同样不需要 Homebrew(见 源代码配方)。mise 对 homebrew/core 配方从不调用 brew

当第三方 tap 发布了 Homebrew API 元数据(api/formula/<name>.jsonapi/cask/<token>.json)时,也可以直接支持。请使用与传给 Homebrew 相同的完整限定名称:

toml
[bootstrap.packages]
"brew:railwaycat/emacsmacport/emacs-mac" = "latest"
"brew-cask:owner/tap/app" = "latest"

对于无法从 GitHub URL 推断的 tap,请添加一个 tap 源。这与 [plugins] 类似:键是 tap 名称,值是 GitHub git URL。

toml
[bootstrap.brew.taps]
"acme/tools" = "https://github.com/acme/homebrew-tools.git"

[bootstrap.packages]
"brew:acme/tools/widget" = "latest"
"brew-cask:acme/tools/widget-app" = "latest"

mise bootstrap packages brew tapmise bootstrap packages brew untap 会管理 mise.toml 中的 [bootstrap.brew.taps];它们不会修改 Homebrew 安装。由于 mise 需要直接访问生成的 API 元数据的原始内容,目前不支持 非 GitHub 的 tap。

sh
mise bootstrap packages brew tap railwaycat/emacsmacport
mise bootstrap packages brew tap acme/tools https://github.com/acme/homebrew-tools.git
mise bootstrap packages brew untap acme/tools

Cask

Cask 使用 brew-cask: 管理器。mise 直接从 Homebrew cask API(或 tap API 元数据)获取 cask 元数据,下载制品,在 cask 提供 sha256 时验证其 sha256,提取归档,并将应用程序包安装到 /Applications,同时将版本记录在 <prefix>/Caskroom 下。与 Homebrew 一样,mise 会将每个应用程序包移动到 /Applications,并在其版本化的 Caskroom 路径留下一个符号链接,而不是在该处保留应用程序的第二份副本。

toml
[bootstrap.packages]
"brew-cask:firefox" = "latest"
"brew-cask:homebrew/cask/visual-studio-code" = "latest"

覆盖应用程序目录

默认情况下,app 制品会安装到 /Applications,与 Homebrew 的行为一致。设置 MISE_BREW_CASK_OPT_APPDIR 环境变量可将其安装到其他位置——例如无需提升权限、用户可写的 ~/Applications

sh
MISE_BREW_CASK_OPT_APPDIR="$HOME/Applications" mise bootstrap packages apply brew-cask:firefox

该值必须是绝对路径,不能包含 ..,并且不能解析为文件系统根目录。在使用前,它会被解析为真实路径(会跟随符号链接),因此会作为 mise 创建的应用程序链接的固定包含边界。空值会被忽略,并回退到 /Applications。设置覆盖项后,目标为默认 /Applications 的 cask(大多数 cask 都是如此)会被重新定位到覆盖目录,同时保留 cask 请求的任何子目录;cask 固定在 $HOMEBREW_PREFIX/Applications 下的目标会留在 Homebrew 前缀中,绝不会被重新定位。这与 Homebrew 自身的 --appdir 安装选项一致。 要接管已安装在 cask 目标位置的应用程序,请使用带有 adopt = true 的表格形式:

toml
[bootstrap.packages]
"brew-cask:textmate" = { version = "latest", adopt = true }

要为所有已配置的 cask 启用接管,请设置 Homebrew bootstrap 默认值。单个 cask 可以使用 adopt = false 选择退出:

toml
[bootstrap.brew]
adopt = true

[bootstrap.packages]
"brew-cask:textmate" = "latest"
"brew-cask:replace-me" = { adopt = false }

与 Homebrew 的 brew install --cask --adopt 一样,mise 会下载并验证当前 cask 制品,只有在其内容完全一致时才会接管现有应用程序。已有的不同应用程序会保持不变,并且安装会失败,但声明了 auto_updates: true 的 cask 除外:与 Homebrew 一致,这些 cask 会直接接管已有应用程序,因为它可能已经自行更新。被接管的应用程序会由 mise 收据进行跟踪,但不会在 Caskroom 中保留重复的应用程序包。

在 Homebrew 元数据中声明 auto_updates: true 的 cask 会以当前版本安装,之后交由其自行更新。mise 不提供 auto_updates 覆盖项:cask 定义仍然具有决定权。这些自行更新的应用程序也会由收据跟踪,但不会保留重复的 Caskroom 应用程序包,普通的 mise 升级会跳过它们。

mise bootstrap status 会将这些条目标记为已安装(自动更新)。 对于由 mise 管理的 cask,Current 列是 mise 收据中记录的版本;实时应用程序可能已经自行更新到不同版本。JSON 状态会保留稳定的 "state": "installed" 值,并添加 "auto_updates": true

macOS 隐私与安全(TCC)

替换 /Applications(或你配置的应用程序目录)下的应用程序包,与 brew reinstall --cask 属于同一类操作:macOS 可能会撤销该应用程序的隐私与安全授权(辅助功能、屏幕录制、完全磁盘访问、自动化以及类似权限)。mise 不管理 TCC;替换后,你可能需要在系统设置中重新授予权限。

迁移没有 Homebrew .metadata 的非托管应用程序包时,优先使用接管,这样 mise 可以记录所有权,而无需替换正在运行的应用程序包:

toml
[bootstrap.brew]
adopt = true

或者选择性接管:

toml
[bootstrap.packages]
"brew-cask:firefox" = { version = "latest", adopt = true }

每当替换现有 .app 时,mise 都会打印警告。当上游发布新的 cask 版本时,版本升级仍会替换应用程序包——请预期需要在这些升级后重新确认 TCC 提示,这与 Homebrew 的行为相同。

在 Linux 上,初始的 cask 支持仅限于不带生命周期钩子、不带结构化 preflight_stepspostflight_steps 的纯字体 cask——这些概念来自 Homebrew 的 cask DSL,详见 Homebrew Cask Cookbook。字体会安装到 $XDG_DATA_HOME/fonts,默认值为 ~/.local/share/fonts

toml
[bootstrap.packages]
"brew-cask:font-heavy-data-nerd-font" = "latest"

其他 Linux cask 会被报告为不可用,并在其来自 [bootstrap.packages] 时跳过,从而允许 macOS 和 Linux 共享软件包列表。像 mise bootstrap packages apply brew-cask:firefox 这样的显式请求仍会失败,并显示明确的平台不支持错误。你也可以使用 { os = "macos" } 显式标记 macOS cask。随着 mise 为更多 cask 制品类型获得可移植实现,这一边界将逐步扩展。

brew-cask 目前支持应用程序包 cask(app 制品)、二进制和生成的命令包装器 cask(binarycommand_wrapper 制品)、通用前缀制品(artifact)、简单的 macOS 安装程序包(pkg 制品)、基于脚本的 cask 安装程序,以及来自 dmg 和常见归档格式的 shell 补全(bash_completionfish_completionzsh_completiongenerate_completions_from_executable)。二进制制品和生成的包装器会暂存到 Caskroom 中,并链接到 Homebrew 前缀,通常位于 <prefix>/bin 下。安装程序会通过 mise 的常规系统软件包 sudo 路径运行,因此非交互式运行不会因等待密码而挂起。Pkg cask 必须在其 uninstall 元数据中包含 pkgutil 收据 ID,这样 mise 才能在安装程序将文件写入 Caskroom 之外后验证安装状态。zappkgutil ID 会被视为清理元数据,而不是安装收据。对于带有生命周期钩子的 cask,mise 会获取由 API 元数据固定且经过 sha256 验证的 cask Ruby 源代码,并通过自有的 Cask DSL shim 运行受支持的 preflight/postflight 钩子,而不会委托给 Homebrew。mise 还支持针对 staged_path 执行 move/remove 操作的结构化 preflight_stepspostflight_steps,支持使用 Homebrew 序列化命令基础、参数、环境、守卫和 sudo 设置的 run 操作,以及具有 Homebrew 兼容的名称/完整匹配、重试、通知和失败策略的 terminate_process 操作。结构化的 copysymlink 步骤支持 Homebrew 路径基础、模板、守卫、源 glob、替换和 sudo 行为。生命周期步骤创建的外部路径会记录在 mise 收据中,如果安装事务失败,会恢复这些路径。Cask formula 和 cask 依赖会优先安装,声明的 cask 冲突会在修改前导致失败。 需要自定义安装程序选项、服务、不受支持的钩子 DSL、不受支持的结构化生命周期步骤或其他 cask 制品类型的 cask,会显示明确的不支持制品错误并失败,而不是委托给 Homebrew。

直接 cask 倒入仍归 mise 所有。其完成状态会记录在 .mise-cask.toml 中;mise 不会生成 Homebrew 的私有 .metadata 收据。带有 .metadata 且恰好有一个 Caskroom 版本的 Homebrew 所有 cask,可以满足匹配的 brew-cask: 条目,而无需转移所有权。状态会将其报告为已安装,并使用该 Caskroom 目录名称作为 Current 版本;apply 会保持其不变,upgrade 会跳过其生命周期。mise 不会创建 .mise-cask.toml、接管 cask 或更改其元数据、应用程序目标、前缀二进制文件或补全链接;请使用 Homebrew 进行升级、重新安装或移除。 没有版本或包含多个版本的 Homebrew 元数据会失败,并显示 Homebrew 修复指导,而不是猜测哪个安装有效。

对于由 mise 管理的 cask,只要其收据和记录的目标仍然存在,状态就会将 cask 视为已安装。应用程序和字体内容指纹会保留用于 prune 和接管安全检查,但现有应用程序或字体内部的内容漂移不会将 cask 标记为缺失,也不会在 apply 时触发重新安装——替换 /Applications/*.app 会重置 macOS 隐私与安全(TCC)授权。二进制文件和补全符号链接仍要求记录的链接目标存在(通过廉价的 readlink 检查),并且目标可解析,因此悬空或被重新指向的链接仍可修复。缺失或未知的收据以及待处理事务仍会被报告为不健康,以便下一次 apply 进行协调。版本升级以及显式 remove + apply 仍会在你希望进行全新倒入时替换应用程序。

之所以存在这一点,是因为共享库包——postgres、ffmpeg、imagemagick、php——从根本上说无法由 mise 的按项目后端(如 aqua:github:)提供:它们的瓶装包是针对固定安装路径和共享依赖树构建的。将它们安装到 Homebrew 的标准前缀,才是让它们正常工作的关键。

支持的平台

平台前缀
macOS arm64(Apple Silicon)/opt/homebrew
Linux x86_64/home/linuxbrew/.linuxbrew
Linux arm64/home/linuxbrew/.linuxbrew

不支持 Intel Mac——brew 管理器会报告在该平台不可用。在 Linux 上,如果某个 formula 没有适用于你的架构的 bottle(大多数 homebrew/core 都有 arm64 Linux bottle,但并非全部),则会改为从源代码构建。

前缀

如果前缀不存在,mise 会使用标准布局创建它——这是 brew 管理器唯一使用 sudo 的时候,模仿 Homebrew 自己的安装程序所做的事情(mkdir + chown 到你的用户)。之后,安装都只是以你的用户身份进行普通文件操作;不会有任何操作以 root 身份运行。

与真实 Homebrew 共存

mise 会像 brew 一样将瓶装包倒入 Cellar,并在每个 keg 中写入与 brew 兼容的 INSTALL_RECEIPT.json 文件。对于真正的 Homebrew 安装来说,mise 倒入的 keg 看起来就像它自己安装的一样:brew listbrew upgradebrew uninstall 都可以对它们正常工作。反过来,mise 的状态检查会直接读取 Cellar,因此由 brew 安装的 formulae 也会被视为已安装。

对于非 keg-only formula,mise 会在 opt 记录旁维护 Homebrew 的 <prefix>/var/homebrew/linked/<name> 记录。对于已配置的 formula,如果任一记录缺失,mise bootstrap packages apply 会在不重新倒入 keg 或替换其公共链接的情况下恢复该记录。只有当旧版 mise 安装现有的公共链接与 keg 的布局匹配时,才会将其识别为已链接。不会执行依赖闭包迁移。

无论 formula 是由 mise 还是由真正的 Homebrew 倒入的,mise 都会直接读取 Homebrew 前缀。它绝不会覆盖前缀中并非由它创建的文件——链接冲突会列出冲突文件并失败,而不会强行覆盖它们。

导入和清理

mise bootstrap packages import --manager brew 会将已安装的 Homebrew formulae 快照到 [bootstrap.packages] 中,思路类似于 brew bundle dump。它会读取 Homebrew 前缀中的活动 opt 链接,并写入如下条目:

toml
[bootstrap.packages]
"brew:ffmpeg" = "latest"
"brew:postgresql@17" = "latest"

默认情况下,导入只记录那些其活动 keg 收据表明是按请求安装的 formulae。传入 --all 也会包含依赖 formulae。 带有 tap 的 formulae 会使用完整限定名写入,并且当 mise 能推导出常规的 GitHub tap URL 时,会自动添加推断出的 [bootstrap.brew.taps] 条目:

toml
[bootstrap.brew.taps]
"acme/tools" = "https://github.com/acme/homebrew-tools.git"

[bootstrap.packages]
"brew:acme/tools/widget" = "latest"

mise bootstrap packages prune --manager brew 会将当前配置以及可信、可加载、已跟踪的配置作为事实来源。它会移除那些不在已解析依赖闭包中的已链接 Homebrew formulae,这些闭包对应于已配置的 brew: 条目,包括由真实 Homebrew 安装的 formulae。

Prune 会移除活动 keg、其 opt 和已链接 keg 记录,以及指向该 keg 的前缀符号链接。使用 --dry-run 可预览操作,使用 --yes 可跳过确认提示。

这个命令是 mise 针对 bootstrap packages 的声明式清理,类似于 brew bundle cleanup。它不是上游的 brew prune,后者已被 Homebrew 移除,转而采用 cleanup 命令。

mise bootstrap packages prune --manager brew-cask 会将相同的合并配置模型应用于直接 cask 制品,但其所有权边界有意更加狭窄。只有当 cask 的安装时 .mise-cask.toml 收据明确标记其可安全清理,并且每个记录的目标仍具有 mise 在安装后记录的完全一致内容指纹时,才会移除该 cask。该命令会移除这些目标及 cask 的 Caskroom 条目;--dry-run 可预览计划,--yes 可跳过确认。被接管的应用程序和自行更新的应用程序会在没有重复 Caskroom 包的情况下进行跟踪,因此 mise 无法证明目标位置后来存在的应用程序包仍然是其所拥有的那个。这些仅存在元数据的应用程序因此永远不会被 prune 移除。

在其收据包含清理元数据之前安装的 cask 会被跳过,直到后续升级或重新安装刷新该收据。带有 pkg 或命令包装器构件、安装或卸载生命周期操作、待处理事务、Homebrew .metadata、已更改目标,或与其他 mise cask 共享目标的 cask,也会被跳过并说明原因。Prune 从不运行 zap 元数据,也不会根据当前的 Homebrew API 重建历史卸载行为。

倒酒的工作原理

对于依赖闭包中的每个公式(先处理依赖项):

  1. 获取适用于你平台的瓶子(来自 ghcr.io),并根据 API 元数据验证其 sha256 值。
  2. 提取到 Cellar 内的临时目录中(未完成的倒酒过程永远不会显示为已安装的软件包)。
  3. 重定位:瓶子中嵌入了类似 @@HOMEBREW_PREFIX@@ 的占位路径。mise 会将其重写为实际路径——在文本文件和二进制文件支持的可执行文件(例如 zipapp)的 shebang 前导部分中进行纯文本替换,同时保持其负载内容不变;并在 Mach-O 二进制文件中原地重写和重写加载命令(必要时将加载命令扩展到头部填充区域中),其行为与 brew 的 ruby-macho 完全一致。在 Linux 上,ELF 解释器和 rpath 会按照 brew 的 PatchELF gem 的方式进行修补:如果字符串不再适合原位置,就会将其移动到附加在二进制文件末尾的新段中,并将解释器指向 <prefix>/lib/ld.so(mise 会维护一个符号链接,将其指向系统的动态加载器;如果安装了通过 brew 构建的 glibc,则指向该 glibc)。
  4. 重新签名(macOS):任何被修改的二进制文件都会使用 codesign 进行临时签名——在 arm64 上这是必需的,因为内核会终止签名不匹配的二进制文件。
  5. 写入收据:写入兼容 brew 的 INSTALL_RECEIPT.json
  6. 链接:创建 <prefix>/opt/<name>,并将 keg 的 binlibincludeshare 等目录符号链接到 prefix 中。对于非 keg-only 公式,还会创建 Homebrew 的 linked-keg 记录。仅 keg 公式 会获得 opt 链接,但不会链接到 prefix 中,与 brew 的行为相同。

源码公式

有些公式根本没有 bottle(仅源码公式),还有一些虽然在其他平台有 bottle,但在你的平台上没有。mise 会直接从源码构建这些公式——仍然不依赖 Homebrew:

  1. Ruby — 由于公式本身就是 Ruby 代码,mise 会通过其常规工具机制提供一个由 mise 管理的 ruby(预编译、速度快;如果你已配置了 ruby,则会遵循你的配置)。
  2. Formula — 该公式的 .rb 会从 homebrew/core 下载,并固定到生成 API 元数据时对应的精确 commit,同时使用 API 提供的 sha256 进行校验。
  3. Source — 会下载稳定版源码归档,并使用 API 提供的 sha256 进行校验。
  4. Build deps — 该公式的构建依赖(cmake、pkgconf、……)会被加入安装闭包,并优先作为常规 bottle 安装。
  5. Build — mise 使用自己的 Formula-DSL shim 对公式进行求值,并在规范前缀下运行 def install,同时将 PATHPKG_CONFIG_PATH 和编译器标志指向依赖的 keg。该 keg 会获得与已倒入 bottle 相同、兼容 brew 的收据,并带有 poured_from_bottle: false——这与 brew 标记其自身源码构建的方式完全一致。

该 shim 实现了 Formula DSL 中常用的子集 (configure/cmake/meson 风格构建、resources、patches、标准路径和环境辅助函数)。对于使用了 shim 未覆盖的 DSL 部分的公式——例如语言特定的辅助函数如 virtualenv_install_with_resources、VCS 下载,以及类似功能——会明确报出 formula uses ... 错误,而不是悄悄编译错误。

源码构建需要可用的工具链(macOS 上需要 Xcode Command Line Tools,Linux 上需要 gcc/make),这与在纯 Homebrew 下的要求完全一致。

升级

mise bootstrap packages upgrade 会重新根据 formulae.brew.sh API 解析已配置的配方,并倾倒任何当前版本与已链接 keg 不同的配方——新的 keg 会替换旧的,链接也会重新指向,就像 brew upgrade 所做的那样。由于瓶装包只存在于配方的当前版本中,因此“升级”和“安装当前瓶装包”是同一个操作。

限制

  • Cask 工件覆盖范围有意保持狭窄。 在 macOS 上,brew-cask 支持应用程序包、二进制工件、字体工件,以及来自 dmg 和常见归档格式的简单 pkg 安装程序。在 Linux 上,它支持不带生命周期钩子的纯字体 cask,也不支持结构化的 preflight_stepspostflight_steps。其他工件类型、不带 pkgutil ID 的 pkg 安装程序,以及带有自定义选项的 pkg 安装程序都会明确失败。
  • 尚未实现 brew services
  • 尚未实现 Cask 导入。 Cask prune 仅限于由 mise 所有、且其安装时收据证明可以安全移除的直接 工件。在支持这些工件的卸载语义之前,pkg 工件和包含生命周期操作的 cask 会被跳过。
  • 源代码构建涵盖常见的 formula 形态。 mise 的 formula shim 实现了 DSL 中广泛使用的子集(请参阅 源代码 formula);超出该范围的 formula 会失败,并清晰指出不受支持的功能。
  • 使用规范的 formula 名称。 postgresql@17 是 formula 名称,而不是 mise 的版本固定值——API 的当前稳定版本决定实际安装的版本。别名(postgres)可以正确安装,但 mise bootstrap packages status 无法跟踪它们;mise 会发出警告并告知你规范名称。
  • PATH 由你自行设置:必须将 <prefix>/bin 添加到 PATH,才能使用链接的 二进制文件,这与 Homebrew 本身的使用方式相同。