homebrew 2
文章目录
- Cask 使用手册
- Cask 语言是声明式的
- 标题行详情
- 段落顺序
- 节段
- 必需配置项
- 至少需要一个工件配置项
- 可选配置项
- 段落描述
- Stanza: `app`
- 重命名目标应用
- *target* 可能包含绝对路径
- *target* 适用于大多数工件类型
- *target* 应仅在特定情况下使用
- Stanza: `binary`
- 注意事项:`caveats`
- 字符串形式的 `caveats`
- 作为代码块的 `caveats`
- `caveats` 迷你 DSL
- Stanza: `conflicts_with`
- `conflicts_with` *cask*
- `conflicts_with` *formula*
- Stanza: `depends_on`
- `depends_on` *cask*
- `depends_on` *formula*
- `depends_on` *macos*
- 要求特定的 macOS 版本
- 设置最低 macOS 版本要求
- `depends_on` *arch*
- 段落:`deprecate!` / `disable!`
- `date:` 参数
- `because:` 参数
- `replacement_formula:` / `replacement_cask:` 参数
- Stanza: `desc`
- 注意事项
- Stanza: `*flight`
- 块执行总是延迟的
- `*flight` 迷你 DSL
- Stanza: `installer`
- `installer` *手动安装指南*
- `installer` *脚本*
- Stanza: `language`
- 安装
- Stanza: `livecheck`
- Stanza: `no_autobump!`
- Stanza: `name`
- Stanza: `pkg`
- `pkg` *allow_untrusted*
- `pkg` *选项*
- Stanza: `sha256`
- 计算 SHA-256 哈希值
- 特殊值 `:no_check`
- Stanza: `suite`
- Stanza: `uninstall`
- 使用 `pkg` 或 `installer` 安装的 cask 必须包含 `uninstall` 指令
- 存在多种卸载技术
- `uninstall pkgutil:` 是最简单且实用的卸载方式
- 关键点摘要
- `uninstall` *pkgutil*
- 列出与包ID关联的文件
- 卸载 *launchctl*
- `uninstall` *退出*
- `uninstall` *signal* 信号
- `uninstall` *login_item*
- 卸载内核扩展 `uninstall` *kext*
- `uninstall` *脚本*
- `uninstall` *delete*
- `uninstall` *垃圾清理*
- 手动操作 `.pkg` 文件
- Stanza: `url`
- 优先使用 HTTPS URL
- 额外的 `url` 参数
- 当URL与主页域名不一致时,需添加`verified:`参数
- 难以找到下载链接的原因
- Subversion URL
- Git 地址
- SourceForge/OSDN 网址
- 部分服务商屏蔽命令行下载
- 使用代码块延迟执行
- 问题描述
- 编写块
- 混合使用额外URL参数与块语法
- Stanza: `version`
- `version` 方法
- 特殊值 `:latest`
- Stanza: `zap`
- `zap` 的作用
- `zap` 语法
- `zap` 创建
- 条件语句
- 处理不同系统配置
- 切换语言或区域
- 任意 Ruby 方法
- 令牌参考
- 目的
- 查找供应商发行版的简化名称
- 应用程序简化命名规则
- 转换为ASCII字符
- 基于 `pkg` 安装包的简化命名规则
- 非应用软件的简化命名规则
- 将简化名称转换为令牌
- 固定到特定版本的 Casks
- 固定在开发渠道的 Casks
- Cask 文件名
- Cask 头部信息
- Cask 令牌示例
- 特殊词缀
- 令牌冲突
- 可能产生误导的名称
- 可接受的公式
- `homebrew/core` 的要求
- 支持平台
- 系统软件包的重复项
- 版本化公式
- 通常不采用分叉版本
- 我们不欢迎能自我升级的工具
- 我们不欢迎下载未版本化内容的安装脚本
- 我们不接受二进制公式
- 稳定版本
- 小众(或自主提交)软件要求
- 构建`.app`的注意事项
- 默认构建图形界面的情况(但并非必须)
- 无法使用最新稳定版Xcode Clang编译的项目
- 需要大量手动安装前后干预的事项
- 共享库与静态库的选择
- 需要自带 Homebrew 公式版本的情况
- 有时会有例外情况
- 可接受的 Casks
- 为您的Cask找到合适的归属
- 稳定版本
- Beta版、不稳定版、开发版、每日构建版或遗留版
- 区域化与本地化
- 试用版与免费增值版
- 同名分支与应用程序的处理
- 非官方、无供应商及封闭构建版本
- 捆绑恶意软件的应用程序
- 知名度门槛的例外情况
- 通常不接受分叉版本
- Homebrew Cask 并非应用发现服务
- 被拒绝的 Cask
- 无法保证每个 Cask 都会被接受
- 许可证指南
- 指定许可证
- 复杂SPDX许可证表达式
- 指定禁止的许可证
- 公式版本管理
- 可接受的版本化公式
- 公式的弃用、禁用与移除
- 概述
- 弃用说明
- 禁用机制
- 移除标准
- 弃用与禁用原因
- 针对公式作者的 Node 指南
- 运行 `npm install`
- 下载地址
- 依赖项
- 原生模块的特殊要求
- 安装指南
- 使用 `std_npm_args` 将全局样式模块安装到 `libexec`
- 使用 `std_npm_args` 本地安装模块依赖
- 示例
- 工具支持
- Python 公式编写指南
- 应用程序
- 应用程序的Python声明
- 安装应用程序
- 示例公式
- 绑定配置
- 绑定依赖项
- 安装绑定
- Autotools
- CMake
- Meson
- 库文件
- homebrew-core 中允许的库示例
- Python 库的声明规范
- 安装库文件
- 库依赖项
- 深入探索
- Setuptools 与 Distutils 及 pip 的对比
- 运行 `setup.py`
- 什么是 `--single-version-externally-managed`?
- `--prefix` 与 `--root` 的区别
- `pip` 与 `setup.py` 对比
- `brew livecheck`
- 行为模式
- 创建检查项
- 通用准则
- URL 指南
- 正则表达式指南
- 示例 `livecheck` 配置块
- 文件名
- 版本目录
- Git 标签
- 引用配方/桶装软件
- `POST` 请求
- `strategy` 块
- `PageMatch` `strategy` 块
- `HeaderMatch` `strategy` 策略块
- `Git` `strategy` 块
- `GithubLatest` `strategy` 代码块
- `GithubReleases` 的 `strategy` 代码块
- `Crate` `strategy` 代码块
- `ElectronBuilder` `strategy` 模块
- `Json` `strategy` 块
- `Sparkle` `strategy` 代码块
- `Xml` `strategy` 块
- `Yaml` `strategy` 代码块
- `ExtractPlist` `strategy` 代码块
- `throttle`
- `skip`
- 自动版本更新
- 从自动更新中排除软件包
- 自动升级排除原因
- 将 Formula 迁移到 Tap
- 重命名 Formula
- 构建非Homebrew依赖项
- 历史沿革
- 今日指南
- 如何创建和维护一个 Tap
- 创建 Tap
- 为公式命名以避免冲突
- 安装指南
- 维护一个 tap
- 更新
- Casks
- 命名规范
- 外部命令
- 上游仓库(Upstream taps)
- BrewTestBot
- 拉取请求
- 文档风格指南
- 目标与受众
- 指南
- 风格与用法
- 人称代词使用准则
- 结构与标记规范
- 排版规范
- 术语、词汇及书写规范
- 如何使用这些指南
- 使用 Sorbet 进行类型检查
- Homebrew 代码库中的 Sorbet
- 行内类型注解
- Ruby 接口文件 (`.rbi`)
- `Library/Homebrew/sorbet` 目录
- 使用 `brew typecheck`
- 解决类型错误
- 可复现构建
- 构建时间
- 可复现的 gzip 压缩
- 可重定位性
- 新维护者检查清单
- 维护者
- PLC
- TSC(技术指导委员会)
- 所有者
- 成员
- 维护者:避免过度疲劳
- 1. 使用 Homebrew
- 2. 无负担退出
- 3. 维护者优先于用户
- 4. 学会拒绝
- 5. 放慢节奏
- 维护者指南
- 概述
- 代码审查流程
- 使命
- 常见“陷阱”
- 添加注释
- 避免臃肿的差异记录
- 关闭问题/PR的规范
- 撤销PR
- 为其他维护者预留审核时间
- 沟通方式
- Homebrew/brew 维护者指南
- 合并 PR
- 自动批准机制
- 持续集成 (CI)
- `brew tests` 与 Codecov
- 手册页与 Shell 自动补全
- Homebrew/homebrew-core 维护者指南
- 快速合并检查清单
- 合并、变基与拣选提交
- 如何避免影响 bottles 进行合并
- 命名规范
- 测试
- 重复项处理
- 移除公式的准则
- 详细合并检查清单
- 常见构建失败问题及处理方法
- 测试错误
- “未定义的引用……”
- 预发布分支
- 摘要
- 如何使用暂存分支
- Homebrew/homebrew-cask 维护者指南
- 常见情况
- 合并操作
- 其他小贴士
- BrewTestBot 维护者指南
- 发布 Bottles
- 重新灌装
- 维护者常见问题
- 概述
- 问题
- 瓶子发布失败但git历史中的提交记录正确
- `ld: internal error: atom not found in symbolIndex(__ZN10SQInstance3GetERK11SQObjectPtrRS0_) for architecture x86_64`
- 版本发布
- 主版本/次版本额外步骤(如 X.0.0 或 X.Y.0)
- 重要注意事项
- Homebrew 治理架构
- 1. 定义
- 2. 成员管理
- 3. 会员大会
- 3.1. 章程修订条款
- 4. 项目领导委员会
- 5. 项目领导委员会会议
- 6. 项目负责人
- 7. 技术指导委员会
- 8. 维护者管理
- Homebrew 领导职责
- 项目领导委员会
- PLC 专属职责
- PLC 共同职责
- PLC年度例行任务
- 项目负责人
- PL 专属职责
- PL 共享职责
- 技术指导委员会
- TSC 的专属职责
- TSC 共同职责
- Homebrew 治理档案
Cask 使用手册
https://docs.brew.sh/Cask-Cookbook
每个 Cask 都是一个 Ruby 代码块,以特殊的头部行开始。Cask 定义本身始终包裹在 do … end
代码块中。例如:
cask "anybar" doversion "0.2.3"sha256 "c87dbc6aff5411676a471e84905d69c671b62b93b1210bd95c9d776d087de95c"url "https://github.com/tonsky/AnyBar/releases/download/#{version}/AnyBar-#{version}.zip"name "AnyBar"desc "Menu bar status indicator"homepage "https://github.com/tonsky/AnyBar"app "AnyBar.app"
end
Cask 语言是声明式的
每个 cask 包含一系列声明字段(或称"节"),用于声明软件的获取和安装方式。在声明式语言中,作者无需关心顺序问题。只要所有必需字段齐全,Homebrew Cask 就会在安装时自动处理所需操作。
为便于维护,最常更新的字段通常置于顶部。但这只是惯例,并非强制规则。
例外情况:如 postflight
这类 do
代码块可能包含纯 Ruby 代码。这些代码块内的行遵循过程式(顺序依赖)范式。
标题行详情
标题行 cask <cask-token> do
中的 cask 名称 (<cask-token>
) 应与 cask 文件名匹配(不包含 .rb
扩展名),并用双引号包裹。
目前对 cask 令牌仍存在一些临时限制,这些限制正在逐步移除。在过渡期间,GitHub Actions 会捕获所有错误。
段落顺序
为段落设定统一顺序能使cask更易于更新和解析。以下是完整的段落序列(并非所有cask都会包含全部段落)。此处展示的空行同样重要,它们有助于在视觉上划分信息区块。
arch
osversion
sha256languageurl
name
desc
homepagelivecheckno_autobump!deprecate!
disable!auto_updates
conflicts_with
depends_on
containersuite
app
pkg
installer
binary
manpage
bash_completion
fish_completion
zsh_completion
colorpicker
dictionary
font
input_method
internet_plugin
keyboard_layout
prefpane
qlplugin
mdimporter
screen_saver
service
audio_unit_plugin
vst_plugin
vst3_plugin
artifact, target: # target: shown here as is required with `artifact`
stage_onlypreflightpostflightuninstall_preflightuninstall_postflightuninstallzapcaveats
请注意,每个包含额外参数的节(在逗号后有:symbols
的)都应将这些参数单独分行列出,每行一个,并按字母顺序排列。但target:
通常由简短行组成,是个例外。
节段
必需配置项
每个 cask 都必须包含以下配置项。
名称 | 是否允许多次出现? | 值说明 |
---|---|---|
version | 否 | 应用程序版本号,或特殊值 :latest 。 |
sha256 | 否 | 从 url 下载文件的 SHA-256 校验值(通过命令 shasum -a 256 <file> 计算得出),或特殊值 :no_check 。 |
url | 否 | 包含应用程序的 .dmg /.zip /.tgz /.tbz2 文件 URL。若 url 与 homepage 配置项的域名不同,需添加注释。 |
name | 是 | 由供应商定义的完整且规范的名称字符串。 |
desc | 否 | Cask 的单行描述。执行 brew info 时显示。 |
homepage | 否 | 应用程序主页;用于 brew home 命令。 |
livecheck | 否 | 描述如何检查此 cask 更新的 Ruby 代码块。替代 appcast 。 |
depends_on | 是 | 此 cask 的依赖项和需求列表。 |
zap | 是 | 用于更彻底卸载的附加操作,包括用户文件和共享资源。 |
至少需要一个工件配置项
每个 Cask 必须声明一个或多个 artifacts(即需要安装的内容)。
名称 | 是否允许多次出现? | 说明 |
---|---|---|
app | 是 | 指向应安装到 /Applications 文件夹的 .app 的相对路径。 |
suite | 是 | 指向应安装到 /Applications 文件夹的包含目录的相对路径。 |
pkg | 是 | 指向包含分发的 .pkg 文件的相对路径。 |
installer | 是 | 描述必须运行以完成安装的可执行文件。 |
binary | 是 | 指向应链接到 $(brew --prefix)/bin 文件夹的二进制文件的相对路径。 |
manpage | 是 | 指向应链接到相应 man 页文件夹的 Man 页的相对路径,例如 my_app.3 应链接到 /opt/homebrew/share/man/man3 。 |
bash_completion | 是 | 指向应链接到 $(brew --prefix)/etc/bash_completion.d 文件夹的 Bash 补全文件的相对路径。 |
fish_completion | 是 | 指向应链接到 $(brew --prefix)/share/fish/vendor_completions.d 文件夹的 fish 补全文件的相对路径。 |
zsh_completion | 是 | 指向应链接到 $(brew --prefix)/share/zsh/site-functions 文件夹的 Zsh 补全文件的相对路径。 |
colorpicker | 是 | 指向应安装到 ~/Library/ColorPickers 文件夹的 ColorPicker 插件的相对路径。 |
dictionary | 是 | 指向应安装到 ~/Library/Dictionaries 文件夹的字典的相对路径。 |
font | 是 | 指向应安装到 ~/Library/Fonts 文件夹的字体的相对路径。 |
input_method | 是 | 指向应安装到 ~/Library/Input Methods 文件夹的输入法的相对路径。 |
internet_plugin | 是 | 指向应安装到 ~/Library/Internet Plug-Ins 文件夹的互联网插件的相对路径。 |
keyboard_layout | 是 | 指向应安装到 /Library/Keyboard Layouts 文件夹的键盘布局的相对路径。 |
prefpane | 是 | 指向应安装到 ~/Library/PreferencePanes 文件夹的偏好设置面板的相对路径。 |
qlplugin | 是 | 指向应安装到 ~/Library/QuickLook 文件夹的 Quick Look 插件的相对路径。 |
mdimporter | 是 | 指向应安装到 ~/Library/Spotlight 文件夹的 Spotlight 元数据导入器的相对路径。 |
screen_saver | 是 | 指向应安装到 ~/Library/Screen Savers 文件夹的屏幕保护程序的相对路径。 |
service | 是 | 指向应安装到 ~/Library/Services 文件夹的服务的相对路径。 |
audio_unit_plugin | 是 | 指向应安装到 ~/Library/Audio/Components 文件夹的音频单元插件的相对路径。 |
vst_plugin | 是 | 指向应安装到 ~/Library/Audio/VST 文件夹的 VST 插件的相对路径。 |
vst3_plugin | 是 | 指向应安装到 ~/Library/Audio/VST3 文件夹的 VST3 插件的相对路径。 |
artifact | 是 | 指向应安装的任意路径的相对路径。必须提供绝对路径作为 target 。(示例:free-gpgmail.rb)此配置项仅用于特殊情况;移动 .app 包时强烈建议使用 app 配置项。 |
stage_only | 否 | true 。声明该 Cask 不包含可激活的工件。 |
可选配置项
名称 | 是否允许多次出现? | 值说明 |
---|---|---|
uninstall | 是 | 卸载cask的操作流程。除非使用了pkg 或installer 构件配置项,否则为可选。 |
conflicts_with | 是 | 列出与该cask存在冲突的项。 |
caveats | 是 | 字符串或Ruby代码块,在安装时向用户提供cask特定信息。 |
deprecate! | 否 | 格式为YYYY-MM-DD 的日期字符串,以及说明原因的字符串或符号。 |
disable! | 否 | 格式为YYYY-MM-DD 的日期字符串,以及说明原因的字符串或符号。 |
preflight | 是 | 包含安装前操作的Ruby代码块(仅在极少数情况下需要)。 |
postflight | 是 | 包含安装后操作的Ruby代码块。 |
uninstall_preflight | 是 | 包含卸载前操作的Ruby代码块(仅在极少数情况下需要)。 |
uninstall_postflight | 是 | 包含卸载后操作的Ruby代码块。 |
language | 必需 | 接收语言代码参数的Ruby代码块,可包含其他配置项和/或返回值。 |
container nested: | 否 | 内部容器的相对路径,必须在继续安装前解压。支持.tar 中的.dmg 、.dmg 中的.zip 等嵌套格式(示例:blocs.rb)。 |
container type: | 否 | 用于覆盖自动检测容器类型的符号。可选值::air , :bz2 , :cab , :dmg , :generic_unar , :gzip , :otf , :pkg , :rar , :seven_zip , :sit , :tar , :ttf , :xar , :zip , :naked (示例:parse.rb)。 |
auto_updates | 否 | 设为true 时表示该cask构件支持自动更新。仅当应用菜单中存在检查更新... 等类似功能时使用(若仅打开网页而不自动下载安装则不符合条件)。 |
no_autobump! | 否 | 允许使用符号或字符串。设置后将排除该cask的自动版本升级。 |
段落描述
Stanza: app
在app
接收字符串参数的简单情况下,源文件会被移动到目标/Applications
目录。例如:
app "Alfred 2.app"
默认情况下会将源文件移动到:
/Applications/Alfred 2.app
重命名目标应用
你可以通过向 app
添加 target:
键来修改显示在 /Applications
目录中的目标应用名称。示例(来自 scala-ide.rb):
app "eclipse.app", target: "Scala IDE.app"
target 可能包含绝对路径
如果 target:
以斜杠开头,则会被解释为绝对路径。若该绝对路径所在的目录不存在,系统会自动创建该目录。示例(来自 sapmachine-jdk.rb):
artifact "sapmachine-jdk-#{version}.jdk", target: "/Library/Java/JavaVirtualMachines/sapmachine-jdk-#{version}.jdk"
target 适用于大多数工件类型
target:
键在大多数 cask 工件中的工作方式类似,例如 app
、binary
、bash_completion
、fish_completion
、zsh_completion
、colorpicker
、dictionary
、font
、input_method
、internet_plugin
、keyboard_layout
、prefpane
、qlplugin
、mdimporter
、screen_saver
、service
、suite
、audio_unit_plugin
、vst_plugin
、vst3_plugin
以及 artifact
。
target 应仅在特定情况下使用
不要出于美观原因使用 target:
,例如移除版本号(app "Slack #{version}.app", target: "Slack.app"
)。应在功能上有意义时才使用,并在 Cask 中明确记录原因,可选用以下模板之一:为了清晰性;为了一致性;避免冲突;基于开发者建议。
Stanza: binary
在binary
指令接收字符串参数的简单情况下,安装时源文件会被链接到$(brew --prefix)/bin
目录。例如(摘自operadriver.rb):
binary "operadriver_mac64/operadriver"
创建一个符号链接指向:
$(brew --prefix)/bin/operadriver
来自一个源文件,例如:
$(brew --caskroom)/operadriver/106.0.5249.119/operadriver_mac64/operadriver
二进制文件(或多个文件)也可以包含在应用程序包中:
app "Atom.app"
binary "#{appdir}/Atom.app/Contents/Resources/app/apm/bin/apm"
您可以通过在 binary
中添加 target:
键来重命名出现在二进制目录中的目标文件。
binary "#{appdir}/Atom.app/Contents/Resources/app/atom.sh", target: "atom"
target:
的行为和使用方式与 app
相同。但对于 binary
而言,选择条件不必如此严格。可以更自由地调整 target:
以保持与其他命令行工具的一致性,例如修改大小写、移除扩展名或清理名称。
注意事项:caveats
有时某些软件的安装存在特殊细节,无法或不应通过 Homebrew Cask 以编程方式处理。此时,caveats
就是用来向用户传递这些信息的方式。当通过 install
或 info
命令调用 cask 时,caveats
中的内容会显示给用户。
为了避免用过多消息淹没用户(从而降低他们对重要提示的敏感度),应谨慎使用 caveats
,并严格限定于与安装相关的事项。如果不确定某个你认为相关的 caveat
是否属于安装问题,请向维护者咨询。通常来说,如果你的情况未包含在我们全面的 caveats 迷你 DSL
中,那么它很可能不会被采纳。
字符串形式的 caveats
当 caveats
以字符串形式存在时,它会在编译时被求值。如果将 caveats
放在其惯用的 cask 末尾位置,以下方法可用于插值:
方法 | 描述 |
---|---|
token | cask 的 token |
version | cask 的版本号 |
homepage | cask 的主页 |
caskroom_path | 该 cask 的存放目录:$(brew --caskroom)/<token> (仅在块形式中可用) |
staged_path | 该 cask 的暂存路径,包含版本号:$(brew --caskroom)/<token>/<version> (仅在块形式中可用) |
示例:
caveats "Using #{token} may be hazardous to your health."
作为代码块的 caveats
当 caveats
是一个 Ruby 代码块时,其执行会延迟到安装阶段。在代码块中,你可以引用 @cask
实例变量,并调用 @cask
上可用的任何方法。
caveats
迷你 DSL
在 caveats
代码块中可以使用一个迷你 DSL(领域特定语言)。
以下方法可用于生成标准警告信息:
方法 | 描述 |
---|---|
path_environment_variable "path" | 用户应确保 path 已添加到 PATH 环境变量中。 |
zsh_path_helper "path" | zsh 用户需采取额外步骤确保 path 已添加到 PATH 环境变量中。 |
depends_on_java "version" | 用户应确保已安装指定版本的 Java。version 可以是精确版本(如 6 )、最低版本(如 7+ )或省略(表示任意版本均可)。 |
requires_rosetta | 该 cask 需要 Rosetta 2 才能在 Apple Silicon 上运行。 |
logout | 用户需注销并重新登录以完成安装。 |
reboot | 用户需重启系统以完成安装。 |
files_in_usr_local | 该 cask 会将文件安装到 /usr/local ,这可能会干扰 Homebrew。 |
kext | 用户可能需要在 系统设置 → 隐私与安全性(或早期 macOS 版本中的 系统偏好设置 → 安全性与隐私 → 通用)中启用其内核扩展。 |
unsigned_accessibility | 由于该应用未签名,用户每次更新后都需在 系统设置 → 隐私与安全性(或早期 macOS 版本中的 系统偏好设置 → 安全性与隐私 → 隐私)中重新启用该应用。 |
license "web_page" | 用户可在 web_page 查看该软件的使用许可。 |
free_license "web_page" | 用户可在 web_page 获取该软件的官方使用许可。 |
示例:
caveats dopath_environment_variable "/usr/texbin"
end
Stanza: conflicts_with
conflicts_with
用于声明会导致当前 cask 无法正常安装或运行的冲突项。
conflicts_with
cask
该参数值应为另一个 cask 标识符。
示例:macFUSE,它与 macfuse-dev
存在冲突。
conflicts_with cask: "macfuse-dev"
conflicts_with
formula
注意: conflicts_with formula:
目前仅为存根,尚未实现功能。
该值应为另一个 formula 名称。
示例:MacVim,它与 macvim
formula 存在冲突。
conflicts_with formula: "macvim"
Stanza: depends_on
depends_on
用于声明 cask 的依赖项和需求条件。只有在尝试执行 install
时才会检查 depends_on
的配置。
键值 | 描述 |
---|---|
cask: | 必需的 Homebrew cask 标识符(字符串或数组形式) |
formula: | 必需的 Homebrew 公式名称(字符串或数组形式) |
macos: | macOS 版本要求(符号、数组或字符串比较表达式形式) |
arch: | 硬件要求(符号或数组形式) |
java: | 存根 - 功能尚未实现 |
depends_on
cask
该参数值应为当前cask所需的一个或多个cask标识符,可以是字符串或字符串数组。
示例:NTFSTool,该cask依赖于macFUSE。
depends_on cask: "macfuse"
depends_on
formula
该值应为当前 cask 所需的一个或多个 formula 名称,可以是字符串或字符串数组。
例如:某些发行版以 7z
等归档格式打包,而苹果原生工具不支持这些格式。针对这种情况,可以通过声明依赖 unar
formula 在安装时引入功能更强大的归档阅读器:
depends_on formula: "unar"
depends_on
macos
要求特定的 macOS 版本
depends_on macos:
的值可以是一个符号或符号数组,用于列出确切的兼容 macOS 版本。支持的 macOS 版本值可在 MacOSVersion
类 文档中找到。
仅涵盖主要版本(10.x 含单个点号的版本号,或自 macOS 11 起的整数版本号)。符号形式用于提高可读性。以下是列举 cask 所需确切 macOS 版本的有效方式:
depends_on macos: :big_sur
depends_on macos: [:catalina,:big_sur,
]
设置最低 macOS 版本要求
depends_on macos:
也可以接受以比较运算符(如 >=
)开头的字符串,后接上述格式的 macOS 版本号。以下是表示"至少需要 macOS Big Sur (11.0)"的有效表达式:
depends_on macos: ">= :big_sur"
比较表达式不能与任何其他形式的 depends_on macos:
结合使用。
depends_on
arch
depends_on arch:
的值可以是一个符号或符号数组,用于列出 cask 的硬件兼容性要求。只要提供的任一 arch:
值与用户硬件匹配,安装时即视为满足要求。
可用的硬件符号如下:
符号 | 含义 |
---|---|
:arm64 | Apple 芯片 |
:x86_64 | 64 位 Intel |
:intel | 64 位 Intel |
以下是所有有效的表达式:
depends_on arch: :arm64
depends_on arch: :intel
depends_on arch: :x86_64 # same meaning as above
depends_on arch: [:x86_64] # same meaning as above
段落:deprecate!
/ disable!
deprecate!
和 disable!
用于声明某个 Cask 不再可用或不再受支持。
包含 deprecate!
段落的 Cask 仍可安装,但在安装或升级时会显示警告信息。
包含 disable!
段落的 Cask 则无法安装或升级,并会显示错误信息。
这两个段落的语法格式相同:
deprecate! date: "YYYY-MM-DD", because: "is ..."
disable! date: "YYYY-MM-DD", because: "is ..."# Or with a preset reason and suggested replacement (see the parameter sections below)
deprecate! date: "YYYY-MM-DD", because: :discontinued, replacement_formula: "another"
disable! date: "YYYY-MM-DD", because: :unmaintained, replacement_cask: "alternative"
date:
参数
date:
参数控制弃用或禁用功能何时生效。
对于包含未来日期 deprecate!
声明的 Cask,在到达该日期前不会被视作已弃用。
对于包含未来日期 disable!
声明的 Cask,在到达该日期前会自动标记为弃用状态,到期后将转为禁用状态。
because:
参数
because:
参数用于指定某个 Cask 被弃用或禁用的原因。提示信息将显示为 <cask> is deprecated because it <reason>!
,因此请将原因格式化为适合该句子的形式。例如,because: "is broken"
将生成 <cask> is deprecated because it is broken!
。
because:
参数还可以接受一个符号,该符号对应预设的原因,例如:
deprecate! date: "YYYY-MM-DD", because: :discontinued
允许使用的符号完整列表可在 DeprecateDisable
模块 文档中查看。
replacement_formula:
/ replacement_cask:
参数
replacement_formula:
和 replacement_cask:
参数接受字符串值,用于向用户推荐替代的 formula 或 cask。
有关 cask 弃用流程的更多信息,请参阅 弃用、禁用和移除 Casks。
Stanza: desc
desc
接受一个单行 UTF-8 字符串,用于提供软件的简短描述。由于它有助于提高搜索性和消除歧义,因此必须简洁地说明软件的功能(或您能用它完成什么)。
desc
不是用来写应用口号的!供应商的描述往往充斥着“现代”、“轻量级”这类泛泛的形容词。这些都是毫无意义的营销废话(您见过有应用自豪地自称“过时”和“笨重”吗?),必须删除。以软件官网的信息作为起点是可以的,但在几乎所有情况下都需要进一步编辑。
注意事项
- 务必以大写字母开头。
- desc "sound and music editor"
+ desc "Sound and music editor"
- 务必保持简洁,即每行不超过80个字符。
- desc "Sound and music editor which comes with effects, instruments, sounds and all kinds of creative features"
+ desc "Sound and music editor"
- 要 描述软件的功能或用途。
- desc "Development of musical ideas made easy"
+ desc "Sound and music editor"
- 不要包含平台信息。Casks 始终兼容 macOS,因此这是冗余信息。
- desc "Sound and music editor for macOS"
+ desc "Sound and music editor"
- 不要包含该桶装软件的名称。
- desc "Ableton Live is a sound and music editor"
+ desc "Sound and music editor"
- 不要包含供应商名称。这应添加到 cask 的 name 字段中。
- desc "Sound and music editor made by Ableton"
+ desc "Sound and music editor"
- 不要添加用户代词。
- desc "Edit your music files"
+ desc "Sound and music editor"
- 不要使用空洞的市场营销术语。
- desc "Beautiful and powerful modern sound and music editor"
+ desc "Sound and music editor"
Stanza: *flight
stanza preflight
、postflight
、uninstall_preflight
和 uninstall_postflight
用于定义在安装或卸载前后需要执行的操作。
块执行总是延迟的
这些节定义的 Ruby 块直到安装或卸载时才会被求值。在块内部,你可以引用 @cask
实例变量,并调用 @cask
上可用的任何方法。
*flight
迷你 DSL
在这些代码块中可以使用一个迷你 DSL。
以下方法可用于执行标准任务:
方法 | 可用范围 | 描述 |
---|---|---|
set_ownership(paths) | preflight , postflight , uninstall_preflight | 设置 paths 的用户和组所有权。(示例:docker-toolbox.rb) |
set_permissions(paths, permissions_str) | preflight , postflight , uninstall_preflight | 将 paths 的权限设置为 permissions_str 。(示例:ngrok.rb) |
set_ownership(paths)
默认将用户和组所有权设置为当前用户和 staff
。可以通过传递额外选项来修改这些值:set_ownership(paths, user: "user", group: "group")
。(示例:hummingbird.rb)
Stanza: installer
该节必须始终与 uninstall
配合使用。
installer
节接受一系列键值对,其中第一个键必须是 manual:
或 script:
。
installer
手动安装指南
installer manual:
接收一个字符串值作为交互式安装程序的路径,该安装程序需要用户稍后手动运行。路径可以是绝对路径,也可以是相对于当前 cask 的相对路径。示例(来自 rubymotion.rb):
installer manual: "RubyMotion Installer.app"
installer
脚本
installer script:
接收一系列键值对,用于描述一个自动化完成安装的命令。切勿将其用于交互式安装。 其形式与 uninstall script:
类似:
键 | 值 |
---|---|
executable: | 要运行的安装脚本路径 |
args: | 传递给安装脚本的参数数组 |
input: | 发送到脚本 stdin 的输入行数组 |
must_succeed: | 设为 false 则允许脚本失败 |
print_stderr: | 设为 false 可抑制 stderr 输出 |
print_stdout: | 设为 false 可抑制 stdout 输出 |
sudo: | 若脚本需要 sudo 权限则设为 true |
路径可以是绝对路径,或相对于 cask 的相对路径。示例(摘自 miniforge.rb):
installer script: {executable: "Miniforge3-#{version}-MacOSX-x86_64.sh",args: ["-b", "-p", "#{caskroom_path}/base"],
}
如果installer script:
不需要任何键值对,可以直接提供安装脚本的路径:
installer script: "#{staged_path}/install.sh"
Stanza: language
language
stanza 可以匹配 ISO 639-1 语言代码、文字代码 (ISO 15924) 和地区标识符 (ISO 3166-1 Alpha 2),或其组合。
默认语言应始终使用美式英语。
language "zh", "CN" do"zh_CN"
endlanguage "de" do"de_DE"
endlanguage "en-GB" do"en_GB"
endlanguage "en", default: true do"en_US"
end
请注意以下内容并不相同:
language "en", "GB" do# matches all locales containing "en" or "GB"
endlanguage "en-GB" do# matches only locales containing "en" and "GB"
end
可以通过直接调用 language
来访问匹配的 language
块的返回值。
homepage "https://example.org/#{language}"
示例:firefox.rb、battle-net.rb
安装
要通过指定语言安装 cask,可以向 brew install
传递 --language=
选项:
brew install firefox --language=it
Stanza: livecheck
livecheck
stanza 用于从变更日志、发布说明、应用更新通知等来源自动获取 cask 的最新版本。
每个 livecheck
代码块必须包含一个 url
参数,该参数可以是字符串,也可以是指向 cask 中其他 URL 的符号(如 :url
或 :homepage
)。
有关如何编写 livecheck
代码块的详细信息,请参阅 brew livecheck
文档。
Stanza: no_autobump!
no_autobump!
这个 stanza 会将 cask 排除在自动更新列表之外。这意味着所有更新都需要通过向 Homebrew/homebrew-cask
仓库提交 pull request 来手动处理。
使用 no_autobump!
时必须通过 because:
参数提供理由。该参数接受字符串或预设理由对应的符号,例如:
no_autobump! because: :incompatible_version_format
允许使用的符号完整列表可在 NO_AUTOBUMP_REASONS_LIST
中找到。
在 livecheck
块中使用 strategy :extract_plist
或设置了 version :latest
的 Cask 会自动排除在自动升级列表之外,无需声明 no_autobump!
。
有关 Homebrew 中自动升级流程的更多信息,请参阅 Autobump 页面。
Stanza: name
name
接受一个 UTF-8 字符串,用于定义软件的名称,包括大小写和标点符号。这有助于提高可搜索性和消除歧义。
与简化为有限字符集的 token 不同,name
stanza 可以包含正确的大小写、空格和标点符号,以匹配软件的官方名称。为了消除歧义,建议拼写出应用程序的全名,必要时包括供应商名称。一个很好的例子是 pycharm-ce
cask,其名称拼写为 Jetbrains PyCharm Community Edition
,尽管可能从未在任何地方这样引用过。
有关软件的更多详细信息可以在 desc
stanza 中提供。
如果有其他有用的替代名称,name
stanza 可以重复多次。第一个实例应使用拉丁字母。例如,参见 cave-story
cask,其原始名称不使用拉丁字母。
Stanza: pkg
该配置项必须始终与 uninstall
配合使用。
pkg
配置项的第一个参数应是要安装的 .pkg
文件的相对路径。例如:
pkg "Unity.pkg"
pkg
的后续参数是键值对,用于修改安装过程。当前支持的键包括 allow_untrusted:
和 choices:
。
pkg
allow_untrusted
pkg allow_untrusted: true
可通过向 /usr/sbin/installer
传递 -allowUntrusted
参数,来安装包含不受信任证书的 .pkg
文件。
此选项在官方 Homebrew Cask 仓库中不允许使用,仅提供给第三方仓库或本地 Cask 使用。
历史案例(来自 alinof-timer.rb):
pkg "AlinofTimer.pkg", allow_untrusted: true
pkg
选项
pkg choices:
可用于通过 -applyChoiceChangesXML
覆盖 .pkg
的默认安装选项。它使用了 choiceChanges
属性列表的反序列化版本(参考 installer
手册页的 CHOICE CHANGES FILE
部分,可通过运行 man -P 'less --pattern "^CHOICE CHANGES FILE"' installer
查看)。
运行以下 macOS installer
命令:
installer -showChoicesXML -pkg '/path/to/my.pkg'
将输出可用于提取 choices:
值及其对应 GUI 选项的 XML。
具体操作示例可参考:
- wireshark-chmodbpf 的拉取请求
- wine-staging 的拉取请求
示例代码(摘自 lando.rb):
pkg "LandoInstaller.pkg",choices: [{"choiceIdentifier" => "choiceDocker","choiceAttribute" => "selected","attributeSetting" => 0,},{"choiceIdentifier" => "choiceLando","choiceAttribute" => "selected","attributeSetting" => 1,},]
示例(来自 microsoft-office.rb):
pkg "Microsoft_365_and_Office_#{version}_Installer.pkg",choices: [{"choiceIdentifier" => "com.microsoft.autoupdate", # Office16_all_autoupdate.pkg"choiceAttribute" => "selected","attributeSetting" => 0,},]
Stanza: sha256
计算 SHA-256 哈希值
通常使用 shasum
命令来计算 sha256
值:
shasum --algorithm 256 <file>
特殊值 :no_check
特殊值 sha256 :no_check
用于在因上游配置导致校验不切实际时关闭 SHA 检查,例如当 url
在不同版本间保持不变时。
version :latest
必须搭配 sha256 :no_check
使用,这种组合很常见。但 sha256 :no_check
并不强制要求配合 version :latest
。
我们会尽可能使用校验和。
Stanza: suite
某些发行版会提供一组多个应用程序,或一个带有必需数据的应用程序,需要一起安装在 /Applications
的子目录中。
对于这类 Cask,使用 suite
stanza 来定义包含应用程序套件的目录。示例(来自 racket.rb):
suite "Racket v#{version}"
suite
的值永远不会是 .app
包,而是一个普通目录。
Stanza: uninstall
如果你无法设计出可用的
uninstall
stanza,请直接提交你的 cask。维护者会协助你编写uninstall
stanza,只需提出请求即可!
使用 pkg
或 installer
安装的 cask 必须包含 uninstall
指令
对于大多数 cask 而言,卸载操作会被自动处理,因此不需要显式声明 uninstall
指令。但如果 cask 使用了 pkg
或 installer
指令,则必须通过 uninstall
指令明确卸载逻辑,否则无法正确执行卸载。
虽然 cask DSL 没有强制要求,但为每个 pkg
和 installer
配备对应的 uninstall
指令能显著提升用户体验。
uninstall
指令也适用于其他构件类型,并能处理某些特殊情况。但下文文档主要针对最常见的使用场景——为 pkg
定义卸载流程。
存在多种卸载技术
由于 pkg
安装程序可以执行任意操作,因此在不同情况下需要采用不同的卸载技术。您可能需要为 uninstall
命令指定以下一个或多个键值对作为参数。
uninstall pkgutil:
是最简单且实用的卸载方式
最简单且实用的卸载指令是 pkgutil:
,它能满足大多数使用场景。
关键点摘要
early_script:
(字符串或哈希) - 类似于script:
,但会提前执行(适用于特殊情况,最好避免使用)launchctl:
(字符串或数组) - 需要移除的launchd
任务 IDquit:
(字符串或数组) - 需要退出的运行中应用的 bundle ID(当卸载由brew upgrade
或brew reinstall
触发时不执行)signal:
(数组的数组) - 当quit:
无效时,向运行中应用发送 Unix 信号的信号编号和 bundle ID(当卸载由brew upgrade
或brew reinstall
触发时不执行)login_item:
(字符串或数组) - 需要移除的登录项名称kext:
(字符串或数组) - 需要从系统中卸载的 kext 的 bundle IDscript:
(字符串或哈希) - 需要通过 sudo 运行的卸载脚本的相对路径;如果需要参数,则使用哈希形式pkgutil:
(字符串、正则表达式或字符串和正则表达式的数组) - 用于匹配使用pkgutil
卸载的软件包 bundle ID 的字符串或正则表达式delete:
(字符串或数组) - 需要移除的文件或目录树的绝对路径,用双引号包裹。应仅作为最后手段使用;强烈推荐优先使用pkgutil:
rmdir:
(字符串或数组) - 需要移除的空目录的绝对路径,用双引号包裹;递归操作trash:
(字符串或数组) - 需要移动到废纸篓的文件或目录树的绝对路径,用双引号包裹
每个 uninstall
方法按上述顺序应用。uninstall
键在 cask 文件中的出现顺序会被忽略。
如需帮助填写 uninstall
键的正确值,Homebrew Cask 仓库的 developer/bin
目录下提供了多个辅助脚本。每个脚本都支持 -help
选项以获取更多文档说明。
在已安装并运行该软件包的系统中,最容易确定 uninstall
节的内容。如需操作未安装的 .pkg
文件,请参阅下文的手动操作 .pkg
文件。
uninstall
pkgutil
这是最有用的卸载指令键。pkgutil:
通常足以完全卸载一个 pkg
,强烈建议优先使用它而非 delete:
。
要列出最近安装的软件包ID,可以使用 list_recent_pkg_ids
:
"$(brew --repository homebrew/cask)/developer/bin/list_recent_pkg_ids"
pkgutil:
也支持使用正则表达式来匹配多个软件包 ID。这些正则表达式有些非标准。要测试 pkgutil:
正则表达式在当前已安装软件包中的匹配情况,可以使用 list_pkg_ids_by_regexp
。
"$(brew --repository homebrew/cask)/developer/bin/list_pkg_ids_by_regexp" <regular-expression>
列出与包ID关联的文件
在获取已安装包的ID后(参见上文),你可以使用macOS的pkgutil
命令列出系统中与该包ID关联的所有文件:
pkgutil --files <package.id.goes.here>
列出关联文件可以帮助你评估该软件包是否包含任何 launchd
任务或内核扩展(kexts)。
卸载 launchctl
要列出当前已加载的 launchd
任务 ID,可以使用 list_loaded_launchjob_ids
:
"$(brew --repository homebrew/cask)/developer/bin/list_loaded_launchjob_ids"
可以通过 list_installed_launchjob_ids
列出所有已安装 launchd
作业的 ID:
"$(brew --repository homebrew/cask)/developer/bin/list_installed_launchjob_ids"
uninstall
退出
可以使用 list_running_app_ids
列出当前正在运行的应用程序的 Bundle ID。
"$(brew --repository homebrew/cask)/developer/bin/list_running_app_ids"
可以使用 list_ids_in_app
列出磁盘上应用程序包内的 Bundle ID。
"$(brew --repository homebrew/cask)/developer/bin/list_ids_in_app" '/path/to/application.app'
uninstall
signal 信号
signal:
仅应在进程不响应 quit:
的罕见情况下使用。
获取 signal:
目标的 Bundle ID 方式与 quit:
相同。signal:
的值是一个由数组组成的数组,每个单元格包含两个元素:所需的 Unix 信号及对应的 Bundle ID。
Unix 信号可以用数字或字符串形式指定(详情请参阅 kill
(1) 手册页)。
signal:
数组中的元素会按顺序应用,仅当存在与 Bundle ID 关联的进程时才会执行,并在该进程终止时停止。可以重复使用同一个 Bundle ID 向同一进程发送多个信号。
建议使用能停止进程的最低严重级别信号。特别是 KILL
信号可能会产生意外副作用。
以下是一个示例,按严重程度升序列出常用信号:
uninstall signal: [["TERM", "fr.madrau.switchresx.daemon"],["QUIT", "fr.madrau.switchresx.daemon"],["INT", "fr.madrau.switchresx.daemon"],["HUP", "fr.madrau.switchresx.daemon"],["KILL", "fr.madrau.switchresx.daemon"],
]
请注意,当多个正在运行的进程匹配给定的 bundle ID 时,所有匹配的进程都将收到信号。
与 quit:
指令不同,Unix 信号源自当前用户而非超级用户。这被视为一项安全特性,因为超级用户能够通过信号使系统宕机。然而,这种不一致性也可能被视为一个缺陷,未来版本可能会以某种方式解决该问题。
uninstall
login_item
可以通过 list_login_items_for_app
列出与磁盘上应用程序包关联的登录项:
"$(brew --repository homebrew/cask)/developer/bin/list_login_items_for_app" '/path/to/application.app'
请注意,您可能需要至少打开过该应用一次,登录项才会显示。
卸载内核扩展 uninstall
kext
可以使用 list_loaded_kext_ids
列出当前已加载内核扩展的 ID:
"$(brew --repository homebrew/cask)/developer/bin/list_loaded_kext_ids"
可以使用 list_id_in_kext
列出磁盘上 kext 包内的 ID:
"$(brew --repository homebrew/cask)/developer/bin/list_id_in_kext" '/path/to/name.kext'
uninstall
脚本
uninstall script:
引入了一系列键值对,用于描述一个能自动完成卸载操作的命令。其形式与 installer script:
类似:
键名 | 值 |
---|---|
executable: | 要运行的卸载脚本路径 |
args: | 传递给卸载脚本的参数数组 |
input: | 要发送到脚本 stdin 的输入行数组 |
must_succeed: | 设为 false 则允许脚本执行失败 |
print_stderr: | 设为 false 可隐藏 stderr 输出 |
print_stdout: | 设为 false 可隐藏 stdout 输出 |
sudo: | 设为 true 表示脚本需要 sudo 权限 |
路径可以是绝对路径,也可以是相对于 cask 的相对路径。示例(来自 virtualbox.rb):
uninstall script: {executable: "VirtualBox_Uninstall.tool",args: ["--unattended"],sudo: true,},pkgutil: "org.virtualbox.pkg.*",delete: "/usr/local/bin/vboximg-mount"
需要注意的是,尽管上例中的 script:
确实尝试完全卸载 pkg
,但它不应替代 pkgutil:
,而应尽可能作为补充使用。
uninstall
delete
delete:
仅在其他 uninstall
方法无效时作为最后手段使用。
调用 uninstall delete:
时应遵循以下基本规则:
- 路径会执行基本的波浪号扩展,即开头的
~
会被展开为主目录。 - 路径必须是绝对路径。
- 使用标准通配符集进行通配符扩展。
如需移除用户专属文件,请使用 zap
stanza。
uninstall
垃圾清理
trash:
参数遵循与上述 delete:
相同的规则。
手动操作 .pkg
文件
高级用户可能希望在不安装软件包的情况下手动处理 .pkg
文件。
可以通过 list_payload_in_pkg
工具提取 .pkg
文件中可能安装的文件列表:
"$(brew --repository homebrew/cask)/developer/bin/list_payload_in_pkg" '/path/to/my.pkg'
有助于确定cask名称的候选应用程序名称可以从.pkg
文件中提取,方法是使用list_apps_in_pkg
。
"$(brew --repository homebrew/cask)/developer/bin/list_apps_in_pkg" '/path/to/my.pkg'
可以通过 list_ids_in_pkg
工具从 .pkg
文件中提取可能在 pkgutil:
键中有用的候选包 ID。
"$(brew --repository homebrew/cask)/developer/bin/list_ids_in_pkg" '/path/to/my.pkg'
以下是手动查找包文件中 Bundle ID 的完整方法:
1、使用命令 pkgutil --expand /path/to/my.pkg /tmp/expanded.unpkg
解压 /path/to/my.pkg
(请替换为你的包名)。
2、解压后的包是一个文件夹。Bundle ID 包含在名为 PackageInfo
的文件中,可通过命令 find /tmp/expanded.unpkg -name PackageInfo
查找这些文件。
3、PackageInfo
文件是 XML 格式,Bundle ID 位于 <pkg-info>
标签的 identifier
属性中,例如 <pkg-info ... identifier="com.oracle.jdk7u51" ... >
(多余属性已用省略号替代)。
4、包中的内核扩展(Kexts)也会在 PackageInfo
文件中描述。如果存在任何内核扩展,运行命令 find /tmp/expanded.unpkg -name PackageInfo -print0 | xargs -0 grep -i kext
会返回带有 .kext
扩展名的 path
属性的 <bundle id>
标签,例如 <bundle id="com.wavtap.driver.WavTap" ... path="./WavTap.kext" ... />
。
5、识别出 Bundle ID 后,可删除解压的包目录。
Stanza: url
优先使用 HTTPS URL
在可用的情况下,应优先选择 HTTPS URL。只有在没有安全替代方案时,才使用普通的 HTTP URL。
额外的 url
参数
当纯 URL 字符串不足以获取文件时,可以通过向 url
附加键值对的形式,为基于 curl
的下载器提供额外信息:
键 | 值 |
---|---|
verified: | 重复 url 开头的字符串,用于验证目的 |
using: | 仅允许使用符号 :post 和 :homebrew_curl |
cookies: | 为下载请求设置的 cookie 哈希(示例:oracle-jdk-javadoc.rb) |
referer: | 设置下载请求的 Referer 头字符串(示例:firealpaca.rb) |
header: | 设置下载请求的头部字符串或字符串数组(示例:pull-6545, issue-15590) |
user_agent: | 设置下载请求的用户代理字符串。也可设为符号 :fake ,表示使用通用的浏览器式用户代理字符串。当服务器不要求特定用户代理时,我们优先使用 :fake 。 |
data: | 设置 POST 请求参数的哈希(示例:segger-jlink.rb) |
当URL与主页域名不一致时,需添加verified:
参数
当url
和homepage
的域名不一致时,应通过verified:
参数记录差异,只需重复URL中能唯一标识应用或供应商的最小部分(不包含协议部分)。例如:1password-cli.rb
添加此参数是为了让审核Cask的用户知道,尽管URL看起来可能非官方,但Homebrew Cask团队已验证该URL是由供应商提供的。作为Homebrew Cask维护者,我们有责任在首次添加(或后续修改,版本更新除外)时验证url
和homepage
信息。
该参数并不意味着你应该盲目信任来源,但我们只批准那些用户可以通过基本手段轻松验证其真实性的Cask,例如检查官方主页或公共代码库。偶尔可能会使用稍复杂的技术,例如检查我们确定为官方的livecheck
URL。对于无法快速验证的情况(例如下载URL需要注册),我们会采取更严格的处理方式。
难以找到下载链接的原因
浏览器可能出于多种原因隐藏下载文件的直接url
。Homebrew Cask提供了一个list_url_attributes_on_file
脚本,该脚本能通过读取文件的扩展属性来提取macOS系统中浏览器下载文件的实际源URL。该脚本通常会输出多个候选URL,你可能需要逐一测试它们:
$(brew --repository homebrew/cask)/developer/bin/list_url_attributes_on_file <file>
Subversion URL
在极少数情况下,可能无法通过普通HTTP(S)获取某个发行版。此时也支持使用Subversion URL,可通过在url
后追加以下键值对来指定:
键名 | 值说明 |
---|---|
using: | 唯一合法值为符号:svn |
revision: | 标识待下载Subversion版本的字符串 |
trust_cert: | 设为true 可自动信任服务器提供的证书(避免交互式提示) |
Git 地址
工件也可以通过 Git 仓库进行分发。以 .git
结尾的 URL 会被自动识别为 Git 仓库,并且可以在 url
后追加以下键值对:
键名 | 值 |
---|---|
using: | 唯一合法值为符号 :git |
tag: | 标识要下载的 Git 标签的字符串 |
revision: | 标识要下载的 Git 修订版本的字符串 |
branch: | 标识要下载的 Git 分支的字符串 |
only_path: | 用于限制检出范围的仓库内路径。如果只需要大型仓库中的单个目录,使用此选项可以显著加快下载速度。如果提供此选项,工件路径将相对于该路径。(示例:font-geo.rb) |
SourceForge/OSDN 网址
SourceForge 和 OSDN(原名为 SourceForge.JP
)项目是分发二进制文件的常见方式,但它们提供了多种不同风格的 URL 来获取资源。
我们更推荐使用以下格式的 URL:
https://downloads.sourceforge.net/<project_name>/<filename>.<ext>
或者,如果来自OSDN,其中<subdomain>
通常采用dl
或<user>.dl
的形式:
http://<subdomain>.osdn.jp/<project_name>/<release_id>/<filename>.<ext>
如果这些格式不可用,且应用程序为 macOS 独占(否则命令行下载会默认选择 Windows 版本),我们建议优先使用以下格式:
https://sourceforge.net/projects/<project_name>/files/latest/download
部分服务商屏蔽命令行下载
某些托管服务商会主动屏蔽命令行 HTTP 客户端。此类 URL 不能用于 cask 配方。
其他服务商可能使用周期性变更的 URL,甚至每次访问都会变化(例如 FossHub)。虽然某些情况可通过代码块延迟执行来规避,但这些现象往往源于供应商刻意阻止自动化下载,因此我们倾向于不将此类 cask 配方添加至主仓库。
使用代码块延迟执行
某些应用程序(尤其是夜间构建版)的下载URL会频繁更新版本号,导致通过常规流程保持URL最新变得不切实际。对于这类情况,我们需要动态确定url
。
注意: 由于会干扰API生成流程,Homebrew/homebrew-cask仓库不允许提交包含动态确定url
的Cask配方。
问题描述
理论上,用户可以直接在 cask 定义中编写任意 Ruby 代码来获取并构造一次性 URL。
然而,这种做法通常需要向目标网站发起 HTTP 请求,可能导致较长的等待时间。由于 Homebrew Cask 加载和解析 cask 的方式限制,在 cask 定义主体中直接执行这类高开销操作是不可取的。
编写块
与 preflight
、postflight
、uninstall_preflight
和 uninstall_postflight
块类似,url
配置节也支持可选的块语法:
url "https://handbrake.fr/nightly.php" do |page|file_path = page[/href=["']?([^"' >]*Handbrake[._-][^"' >]+.dmg)["' >]/i, 1]file_path ? URI.join(page.url, file_path) : nil
end
你也可以在 url do
块内部嵌套 url do
块,以实现 URL 链式追踪。
该代码块仅在需要时才会被求值,例如在下载时或审核 cask 时。在块内部,你可以安全地执行诸如 HTTP(S) 请求等可能耗时较长的操作。你还可以引用 @cask
实例变量,并调用 @cask
上可用的任何方法。
该代码块会在下载前立即被调用;其返回值应为一个 String
(或包含参数的 String
和 Hash
组成的键值对),随后将作为下载 URL 使用。
你可以选择直接传入参数或使用代码块来定义 url
stanza,但不能同时使用两种方式。
使用块语法的历史示例:vlc@nightly.rb
混合使用额外URL参数与块语法
在极少数情况下,您可能需要在采用块语法的同时设置cookies
或referer
等URL参数。
这可以通过返回一个包含两个元素的数组作为块结果来实现。数组的第一个元素必须是下载URL;第二个元素必须是包含参数的Hash
对象。
Stanza: version
version
虽然与应用程序自身的版本号相关,但不必完全一致。通常会稍作修改,以便能在其他 stanza 中进行字符串插值,常见于 url
中,从而创建一个只需在更新时修改 version
和 sha256
的 cask。如有需要,还可以进一步使用 Ruby 的 String
方法来实现更复杂的处理。
例如,以下写法:
version "1.2.3"
url "https://example.com/file-version-123.dmg"
我们可以使用:
version "1.2.3"
url "https://example.com/file-version-#{version.delete(".")}.dmg"
我们还可以利用正则表达式的强大功能。因此,无需像这样:
version "1.2.3build4"
url "https://example.com/1.2.3/file-version-1.2.3build4.dmg"
我们可以使用:
version "1.2.3build4"
url "https://example.com/#{version.sub(/build\d+/, "")}/file-version-#{version}.dmg"
version
方法
上述示例可能会变得难以阅读。由于许多这类变更很常见,我们提供了一系列辅助方法,以便清晰解析原本晦涩的情况:
方法 | 输入 | 输出 |
---|---|---|
major | 1.2.3-a45,ccdd88 | 1 |
minor | 1.2.3-a45,ccdd88 | 2 |
patch | 1.2.3-a45,ccdd88 | 3-a45 |
major_minor | 1.2.3-a45,ccdd88 | 1.2 |
major_minor_patch | 1.2.3-a45,ccdd88 | 1.2.3-a45 |
minor_patch | 1.2.3-a45,ccdd88 | 2.3-a45 |
csv.first | 1.2.3-a45,ccdd88 | 1.2.3-a45 |
csv.second | 1.2.3-a45,ccdd88 | ccdd88 |
dots_to_hyphens | 1.2.3-a45,ccdd88 | 1-2-3-a45,ccdd88 |
no_dots | 1.2.3-a45,ccdd88 | 123-a45,ccdd88 |
与 dots_to_hyphens
类似,我们提供了所有逻辑组合的方法,形式为 {dots,hyphens,underscores}_to_{dots,hyphens,underscores}
。同样适用于 no_dots
的形式 no_{dots,hyphens,underscores}
,并额外提供 no_dividers
方法一次性应用所有这些转换。
最后是 csv
方法,它返回一个逗号分隔值的数组,取代了已弃用的 before_comma
和 after_comma
方法。逗号分隔版本应仅用于其他复杂情况;理想情况下,每个 version
中不应超过两个 ,
实例,尽管提供了最多 csv.fifth
的方法。
特殊值 :latest
在以下情况下会使用特殊值 version :latest
:
- 当
url
不包含任何版本信息且无法通过livecheck
获取版本时,或 - 即使借助自动化系统,也难以或无法为
version
提供准确值。例如 chromium.rb 这类每天发布多个版本的软件。
这两种情况都需同时使用特殊值 sha256 :no_check
。使用 version :latest
的 Cask 会被排除在 autobumping 流程之外。
Stanza: zap
zap
的作用
zap
配置段用于描述与 cask 相关的更彻底的文件卸载。默认情况下不会执行 zap
流程,仅当用户在 uninstall
时使用 --zap
参数才会触发。
brew uninstall --zap firefox
zap
操作可能会删除以下内容:
-
存储在用户
~/Library
目录中的偏好设置文件和缓存。 -
共享资源(例如应用程序更新程序)。由于共享资源可能被删除,其他应用程序可能会受到
brew uninstall --zap
的影响。理解这一点是最终用户的责任。
zap
操作不应删除以下内容:
- 用户直接创建的文件。
在命令后追加 --force
参数,即使该 cask 已不再安装,也允许执行这些操作。
brew uninstall --zap --force firefox
zap
语法
zap
节的结构与 uninstall
节相同,所有相同的指令都适用。推荐使用 trash:
键而非 delete:
。
示例:dropbox.rb
zap
创建
最简单的方法是使用 @nrlquaker 的 CreateZap,它可以自动生成配置片段。少数情况下可能无法识别任何内容,此时可能需要手动创建。
手动创建可通过以下方式实现:
- 使用
developer/bin
目录下的部分辅助脚本 - 执行
sudo find / -iname "*<搜索项>*"
- 使用卸载工具如 AppCleaner
- 检查常规路径,包括
/Library/{'Application Support',LaunchAgents,LaunchDaemons,Frameworks,Logs,Preferences,PrivilegedHelperTools}
和~/Library/{'Application Support',Caches,Containers,LaunchAgents,Logs,Preferences,'Saved Application State'}
如果未发现额外文件,则无需添加 zap 配置片段,改为包含以下注释:
# No zap stanza required
条件语句
处理不同系统配置
Cask 可以根据当前 macOS 版本或 CPU 架构,通过定制 url
/ sha256
/ version
字段、使用 on_<system>
语法(替代基于 MacOS.version
或 Hardware::CPU
的条件判断),或同时采用这两种方式,来交付特定版本的构件。
如果您的 Cask 构件针对 Apple Silicon 和 Intel 架构提供了不同的下载包,这些包的下载 URL 通常仅存在细微差异。要根据当前 CPU 架构调整 URL,您可以为 sha256
的 arm:
和 intel:
参数分别提供哈希值,并使用特殊的 arch
字段定义各自 URL 中的独特部分,以便在 url
中进行替换。通过直接调用 on_arch_conditional
还可以定义额外的替换规则。示例(摘自 libreoffice.rb):
cask "libreoffice" doarch arm: "aarch64", intel: "x86-64"folder = on_arch_conditional arm: "aarch64", intel: "x86_64"version "7.6.0"sha256 arm: "81eab945a33622fc156951e804024d23aa9a745c06743b4947215ed9303ad1c4",intel: "ede541af151487f60eb518e310d20dad1a973f3dbe9ff78d782dd29b14ba2946"url "https://download.documentfoundation.org/libreoffice/stable/#{version}/mac/#{folder}/LibreOffice_#{version}_MacOS_#{arch}.dmg",verified: "download.documentfoundation.org/libreoffice/stable/"
end
如果每个架构的版本号不同,请在 on_arm
和 on_intel
代码块中定位唯一的 version
及(若勾选)sha256
字段。示例(摘自 inkscape.rb):
cask "inkscape" doarch arm: "arm64", intel: "x86_64"on_arm doversion "1.3.0,42339"sha256 "e37b5f8b8995a0ecc41ca7fcae90d79bcd652b7a25d2f6e52c4e2e79aef7fec1"endon_intel doversion "1.3.0,42338"sha256 "e97de6804d8811dd2f1bc45d709d87fb6fe45963aae710c24a4ed655ecd8eb8a"endurl "https://inkscape.org/gallery/item/#{version.csv.second}/Inkscape-#{version.csv.first}_#{arch}.dmg"
end
要根据当前 macOS 版本调整安装版本,可使用一系列覆盖支持版本范围的 on_<system>
代码块。每个代码块可包含多个配置节,用于设置下载版本,并针对一个或多个系统版本自定义安装/卸载行为及实时检查逻辑。示例(摘自 calibre.rb):
cask "calibre" doon_high_sierra :or_older doversion "3.48.0"sha256 "68829cd902b8e0b2b7d5cf7be132df37bcc274a1e5720b4605d2dd95f3a29168"livecheck doskip "Legacy version"endendon_mojave do# ...endon_catalina do# ...endon_big_sur :or_newer doversion "6.25.0"sha256 "a7ed19ae0526630ccb138b9afee6dc5169904180b02f7a3089e78d3e0022753b"livecheck dourl "https://github.com/kovidgoyal/calibre"strategy :github_latestendend
end
这类 on_<system>
代码块可以嵌套使用,并包含此处未列出的其他配置节。但其中不应包含 depends_on macos:
配置节——该配置节应统一出现在 on_<system>
代码块下方,并涵盖该 Cask 中列出的所有发行版本。具体示例可参考:calhash.rb、openzfs.rb、r.rb、wireshark.rb
切换语言或区域
如果一个软件包(cask)支持多语言版本,你可以使用 language
stanza 来根据系统区域设置切换不同语言或地区版本。
任意 Ruby 方法
在极少数情况下,当 cask DSL 功能不足时,可以通过创建 Utils
命名空间在 cask 中定义任意的 Ruby 变量和方法。例如:
cask "myapp" domodule Utilsdef self.arbitrary_method# ...endendversion "1.0"sha256 "a32565cdb1673f4071593d4cc9e1c26bc884218b62fef8abc450daa47ba8fa92"url "https://#{Utils.arbitrary_method}"name "MyApp"homepage "https://www.example.com/"# ...
end
应谨慎使用此方法:任何被两个或更多 Cask 需要的功能都应整合到 Homebrew/brew 中。同时必须确保这些方法具有极高的效率。
变量和方法不应定义在 Utils
命名空间之外,以免与 Homebrew Cask 内部实现发生冲突。
令牌参考
本节描述了generate_cask_token
脚本实现的算法,并涵盖了大多数情况下不需要的详细规则和例外情况。
- 用途
- 查找供应商分发的简化名称
- 将简化名称转换为令牌
- Cask文件名
- Cask头部
- Cask令牌示例
- 特殊词缀
目的
软件供应商在命名方面往往缺乏一致性。通过实施严格的命名规范,我们旨在实现以下目标:
- 防止重复提交
- 尽量减少重命名事件
- 将软件名称明确简化为唯一标识符
- 避免与 Homebrew/homebrew-core 配方发生冲突
在转换为最小化标记时,软件名称和品牌的细节不可避免地会丢失。若要记录供应商的完整发行版名称,请在 cask 中使用 name
字段,该字段接受无限制的 UTF-8 字符串。
查找供应商发行版的简化名称
应用程序简化命名规则
-
以磁盘上显示的应用程序包确切名称开头,例如
Google Chrome.app
。 -
若名称包含 A-Z 范围外的字母,按转换为ASCII章节所述进行转换。
-
移除末尾的
.app
后缀。 -
从末尾移除:若厂商将名称格式化为 “Software App.app” 样式,则删除 “app” 字符串。
- 例外情况:当 “app” 是名称不可分割的部分(移除后会导致名称失去意义时),例如 whatsapp.rb。
-
从末尾移除:版本号或增量发布标识,如 “alpha”、“beta” 或 “release candidate”。当前允许保留区分功能或代码库的字符串(如 “Community Edition”)。
- 例外情况:当数字不是增量发布计数器,而是用于区分不同厂商的产品时,例如 kdiff3.rb。
-
若版本号出现在应用名称中间,也应移除。
-
从末尾移除:“Launcher”、“Quick Launcher”。
-
从末尾移除:诸如 “Desktop”、“for Desktop” 的字符串。
-
从末尾移除:诸如 “Mac”、“for Mac”、“for OS X”、“macOS”、“for macOS” 的字符串。这些术语通常出现在移植软件中,例如 “MAME OS X.app”。
- 例外情况:当软件非移植版本且 “Mac” 是名称不可分割的部分时(如 PlayOnMac.app)。
-
从末尾移除:硬件标识如 “for x86”、“32-bit”、“ARM”。
-
从末尾移除:软件框架名称如 “Cocoa”、“Qt”、“Gtk”、“Wx”、“Java”、“Oracle JVM” 等。
- 例外情况:当框架本身就是待打包产品时。
-
从末尾移除:本地化字符串如 “en-US”。
-
若处理结果属于通用术语(如 “Macintosh Installer”),可尝试在名称前添加厂商或开发者名称加连字符。若仍不理想,则参考厂商网页创建最佳名称。
-
若结果与现有 Cask 或 Homebrew/homebrew-core 配方名称冲突,可通过添加厂商/开发者名称加连字符确保唯一性。示例:unison.rb 与 panic-unison.rb。
-
若仍与 Homebrew/homebrew-core 配方名称冲突,可通过追加
-app
等后缀调整名称以体现差异。示例:appium
配方与appium-desktop
Cask,angband
配方与angband-app
Cask。 -
不可避免地存在少量规则未涵盖的例外情况。如有疑问,请随时使用论坛。
转换为ASCII字符
-
如果供应商提供了英文本地化字符串,则优先使用。以下是可能找到这些字符串的位置(按优先级排序):
- 应用包主
Info.plist
文件中的CFBundleDisplayName
- 应用包主
Info.plist
文件中的CFBundleName
en.lproj
本地化目录下InfoPlist.strings
中的CFBundleDisplayName
en.lproj
本地化目录下InfoPlist.strings
中的CFBundleName
English.lproj
本地化目录下InfoPlist.strings
中的CFBundleDisplayName
English.lproj
本地化目录下InfoPlist.strings
中的CFBundleName
- 应用包主
-
当没有供应商提供的本地化字符串时,通过音译或分解来罗马化名称。
-
最后的手段是将应用包的名称翻译成英文。
基于 pkg
安装包的简化命名规则
- 确定
pkg
安装包的简化名称可能比应用程序更复杂。如果pkg
安装的是一个应用程序,则按照上述规则使用该应用程序名称。如果不是,则根据供应商网页信息尽可能创建最合适的名称。
非应用软件的简化命名规则
- 目前,对于通过Homebrew Cask安装的偏好设置面板(Preference Panes)、快速查看插件(Quick Look plugins)等类型软件,其令牌生成规则尚未明确定义。请根据磁盘上的文件名或供应商网页信息,尽可能创建最佳名称。需注意避免重复命名。
未来非应用软件的令牌命名将逐步实现标准化。
将简化名称转换为令牌
由于令牌是cask的主要标识符,它是用户操作cask时引用的唯一字符串。
要将应用程序的简化名称(如上所述)转换为令牌,请遵循以下步骤:
- 将所有字母转换为小写。
- 将
+
符号扩展为独立的英文单词:-plus-
。 - 将
@
符号扩展为独立的英文单词:-at-
。 - 空格转换为连字符。
- 下划线转换为连字符。
- 中间点/间隔符转换为连字符。
- 连字符保持为连字符。
- 数字保持为数字。
- 删除任何非字母数字或连字符的字符。
- 将连续的多个连字符合并为一个连字符。
- 删除任何开头或结尾的连字符。
固定到特定版本的 Casks
对于固定到应用程序特定版本的 Casks(例如 carbon-copy-cloner@5
),应当使用与标准 Cask 相同的 token,并在其后添加 @<版本号>
后缀。以 Carbon Copy Cloner(carbon-copy-cloner
)为例,若固定到版本 5,其 token 应为 carbon-copy-cloner@5
。
固定在开发渠道的 Casks
那些使用开发"渠道"(如测试版)的 Casks,应当采用与标准 Cask 相同的命名标识,并添加 @<渠道>
后缀。以 Google Chrome (google-chrome
) 为例,若使用"beta"渠道,其命名标识应为 google-chrome@beta
。
Cask 文件名
Cask 定义在以 token 命名的 Ruby 文件中,文件扩展名为 .rb
。
Cask 头部信息
每个 cask 的头部行中也会提供该令牌。
Cask 令牌示例
以下示例展示了生成令牌的大部分规则:
磁盘上的应用名称 | 简化应用名称 | Cask 令牌 | 文件名 |
---|---|---|---|
Audio Hijack Pro.app | Audio Hijack Pro | audio-hijack-pro | audio-hijack-pro.rb |
VLC.app | VLC | vlc | vlc.rb |
BetterTouchTool.app | BetterTouchTool | bettertouchtool | bettertouchtool.rb |
LPK25 Editor.app | LPK25 Editor | lpk25-editor | lpk25-editor.rb |
Sublime Text 2.app | Sublime Text | sublime-text | sublime-text.rb |
针对版本化/开发渠道的 Cask:
标准 Cask 令牌 | 衍生形式 | Cask 令牌 | 文件名 |
---|---|---|---|
google-chrome | Beta 渠道 | google-chrome@beta | google-chrome@beta.rb |
vlc | Nightly 渠道 | vlc@nightly | vlc@nightly.rb |
carbon-copy-cloner | 固定版本 5 | carbon-copy-cloner@5 | carbon-copy-cloner@5.rb |
特殊词缀
在某些情况下,需要为令牌添加前缀或后缀。
令牌冲突
当新配方的令牌与已存在配方的令牌发生冲突时,冲突的性质将决定令牌的命名方式,可能同时影响两个配方。有关处理方式,请参阅分支与同名应用获取详细信息。
可能产生误导的名称
如果某个非官方软件的令牌在与流行服务交互时会使其看起来像官方软件,而供应商又未被授权使用该名称,则必须添加前缀以消除歧义。
当前缀本身存在歧义且会使应用看起来像官方版本时,可以使用-unofficial
后缀。
可接受的公式
https://docs.brew.sh/Acceptable-Formulae
某些公式不应放入 homebrew/core。但还有其他的 有趣的 Taps 和 Forks,任何人都可以 创建自己的 Tap!
homebrew/core
的要求
支持平台
该公式需要在最新的3个受支持的macOS版本(Apple Silicon和x86_64)以及x86_64架构的Linux上构建并通过测试。请查看homebrew/core
中拉取请求的持续集成任务,以获取完整的操作系统列表。如果上游不支持其中某个平台,可以例外处理并针对该平台禁用该公式。
系统软件包的重复项
我们现在允许包含在 macOS 中的软件包,只要它们使用 keg_only :provided_by_macos
默认设置为仅限 keg 安装即可。
版本化公式
我们现在接受版本化公式,只要它们符合要求。
通常不采用分叉版本
我们不会使用分叉版本添加新配方,除非满足以下至少一个条件:
- 该分叉已被原始代码库(例如在README中)或以原作者公开可验证的方式(例如在issue或pull request评论中)指定为官方继任者
- 该分叉已被至少两个其他主要发行版(如Debian、Fedora、Arch、Gentoo等广泛使用的发行版,而非小众Linux发行版)用作替代方案
分叉版本仍需满足所有其他配方要求(包括流行度和自主提交等条件)。
替代原配方分叉的另一种方案是创建新配方。例如,如果MikeMcQuaid
分叉了curl
且非常流行:那么创建curl-mikemcquaid
配方可能是合理的。
我们不欢迎能自我升级的工具
那些具备自我升级能力的软件往往与 Homebrew 自带的升级功能难以协调。在编写 formula 时,应当禁用这类自我更新机制(同时尽量减少对 formula 的复杂度影响)。不过对于 Casks 而言,这一功能是被允许且良好支持的。
我们不欢迎下载未版本化内容的安装脚本
我们不推荐那些从Git仓库默认分支拉取内容,或者下载未版本化、无校验和压缩包的安装脚本。理想情况下,这些操作应该改用带有特定版本号或经过校验的压缩包的resource
代码块来实现。需要注意的是,我们现在允许安装过程中使用cargo
、gem
和pip
等工具下载带版本号的库文件。既然可以直接调用这些语言包管理器,就无需再通过resource
代码块来复现它们的功能。
我们不接受二进制公式
我们的政策是:核心仓库(homebrew/core)中的公式必须采用Debian自由软件指南许可的开源软件,并且需要从源代码构建或生成跨平台二进制文件(例如Java、Mono)。仅提供二进制文件的公式应提交到homebrew/cask。
此外,核心公式还不得依赖cask或其他任何专有软件。这包括在运行时自动安装cask的行为。
稳定版本
核心仓库中的软件包必须包含上游项目标记的稳定版本。相比 Git 检出,我们更倾向于使用 tarball 压缩包,且 tarball 文件名应尽可能包含版本号。
我们不接受未标记版本的软件,因为它们会因上游变更而频繁损坏,且我们无法为这类软件提供 预编译包。
小众(或自主提交)软件要求
相关软件必须满足以下条件:
- 持续维护(即最近有发布更新,无需补丁即可在所有 Homebrew 支持的 OS 版本上运行,且不存在未修复的安全漏洞)
- 稳定可靠(例如上游未标记为“不稳定”或“测试版”)
- 具备知名度(例如 GitHub 仓库应满足:≥30 个分支、≥30 个关注者或≥75 颗星)
- 实际被使用
- 拥有主页
对于过于冷门的公式(formulae),我们将予以拒绝。部分原因是这类软件难以持续维护,另一部分原因是我们需要划定明确的收录边界。
我们不鼓励作者提交自己的作品,除非该作品已广受欢迎。
别忘了 Homebrew 底层完全基于 Git!如有需要,请自行维护 Tap!
主仓库可能存在例外情况:我们可能收录不符合上述标准的软件,也可能拒绝符合标准的提交。请相信我们会基于管理包管理器的经验审慎决策。
构建.app
的注意事项
请勿让你的 formula 构建 .app
(原生 macOS 应用程序);我们不希望这些内容出现在 Homebrew 中。建议上游项目自行构建并支持 .app
,以便通过 homebrew/cask 分发(同时也能独立使用)。
默认构建图形界面的情况(但并非必须)
建议默认构建命令行工具或库。如果图形界面确实有用且会被广泛使用,则可以同时构建图形界面。但不要构建基于X11/XQuartz的图形界面,因为它们在macOS上用户体验较差。
无法使用最新稳定版Xcode Clang编译的项目
Clang是macOS上默认的C/C++编译器(长期以来一直如此)。如果软件无法用它编译,说明尚未充分适配macOS平台。
需要大量手动安装前后干预的事项
我们是一个包管理器,因此希望能为用户处理依赖解析和应用程序设置等工作。如果某些事项需要过多手动干预,那么它们在包管理器中的实用性就会大打折扣。
共享库与静态库的选择
通常情况下,如果软件包需要提供共享库或静态库:应当优先提供共享库版本。
如果确实存在对静态库的需求,可以同时提供两种类型。
应尽量避免仅提供静态库的情况,特别是当该软件包被其他软件包依赖时——因为依赖方在不重新编译的情况下将无法更新。
需要自带 Homebrew 公式版本的情况
Homebrew 公式应避免将多个独立的上游项目捆绑在单一软件包中,以防止分发已存在公式的过时或不安全软件版本。Veracode 的《软件安全状况报告》指出:
事实上,79% 的情况下,开发者在将第三方库纳入代码库后从未更新过它们。
更多信息可参考 Debian 和 Fedora 对此问题的立场。
然而,现实情况日益复杂:有时(过于)难以避免。Homebrew 的首要使命是保持实用性,而非追求意识形态的纯粹性。如果某些软件必须依赖自带的上游版本才能打包,那就接受这一现实;毕竟,能在 Homebrew 中提供软件总比完全不提供要好。
有时会有例外情况
即使满足所有标准,我们也可能不接受某个配方。即使某些标准未达标,我们也可能接受该配方。新配方需要比现有配方满足更高的标准。文档更新往往会滞后于实际决策。虽然有些拒绝决定可能看起来武断或奇怪,但这些决策是基于多年经验积累,以确保 Homebrew 能够为用户提供可接受的体验。
可接受的 Casks
https://docs.brew.sh/Acceptable-Casks
并非所有 casks 都适合放入 homebrew/cask。不过还有更多 有趣的 Taps 和 Forks,任何人都可以 创建自己的 Tap!
为您的Cask找到合适的归属
我们为不同类型的二进制文件维护了独立的分支仓库。命名规则如下:
- 稳定版(Stable):开发者提供的最新版本,且被明确定义为稳定版本。
- 测试版/开发版/不稳定版(Beta/Development/Unstable):稳定版之后的版本,尚处于开发阶段且功能不完整,最终目标是成为新的稳定版。也包括专门面向开发者的特殊版本。
- 每日构建版(Nightly):持续更新的当前开发状态版本。
- 旧版(Legacy):所有非最新发布的稳定版本。
- 区域化版本(Regional/Localized):当存在美式英语版本时,其他语言或地区的版本。
- 试用版(Trial):有时限的版本,到期后将完全停止工作,需付费解除限制。
- 免费增值版(Freemium):可无限期使用的免费版本,但存在需付费才能解除的功能限制。
- 分支版(Fork):基于现有项目但经过修改的源代码和二进制文件的替代版本。
- 非官方版(Unofficial):由第三方编译的声称未修改的二进制文件,且源代码所有者未提供官方构建版本。
- 无供应商版(Vendorless):通过非官方网站渠道(如论坛帖子)分发的二进制文件。
- 围墙版(Walled):下载URL既需要登录/注册,且主机域名与官网首页不同的情况。
- 字体文件(Font):包含一组字形、字符或符号的数据文件,会改变显示文本的样式。
稳定版本
稳定版本存放在主仓库 Homebrew/homebrew-cask 中。这些版本应能在最新主要版本的 macOS 上运行。
Beta版、不稳定版、开发版、每日构建版或遗留版
这些版本同样存放在主仓库 Homebrew/homebrew-cask 中。文件名和标识符应包含 @beta
、@nightly
等后缀,以便与稳定版本区分开来。
区域化与本地化
当应用程序支持多种语言或拥有不同区域版本时,应使用 language
配置节来切换语言或区域。
试用版与免费增值版
在提交试用版前,请确保其能升级为完整功能版本而无需重新下载。若某应用提供试用版,但仅能通过Mac应用商店购买完整版,则该应用不应出现在任何官方仓库中。免费增值版本不受此限制。
同名分支与应用程序的处理
分支版本必须在cask文件名和token前添加供应商名称作为前缀。即使原软件已停止开发,分支版本仍需遵循此规则,以避免给用户带来困惑。但在以下两种例外情况下,允许分支版本替代主cask:
- 原停更软件官方推荐该分支版本;
- 分支版本具有绝对优势的流行度,已超越原版成为用户提及该名称时的实际代表项目。
对于共享名称的无关联应用程序,最流行的版本(通常是已存在的版本)可保留无前缀命名。由于判断可能存在主观性,若您对某项决定有异议,请提交issue并向维护者陈述您的理由。
非官方、无供应商及封闭构建版本
我们不接受这些cask,因为它们涉及高于常规的安全风险。
捆绑恶意软件的应用程序
遗憾的是,在软件世界中存在一些不良行为者,他们会将恶意软件与应用程序捆绑发布。即便如此,Homebrew Cask 早已决定不会主动充当审查者(macOS 自身已具备防护机制),且用户需自行了解所安装的软件。这意味着我们不会始终移除链接到这类应用的 cask,部分原因在于实用程序、潜在有害程序与各类恶意软件之间并无明确界限——对某些用户有用的程序,可能被其他用户视为恶意软件。
但我们仍希望用户在享受一定保护的同时,尽量减少误将合法开发者标记为恶意软件传播者的情况。为此,我们会逐案评估 cask,任何用户均可向我们举报潜在的恶意软件案例。但请务必牢记:最后一道防线始终是用户自身。
若某款捆绑恶意软件的应用程序未使用 Apple 开发者 ID 签名,而您又主动禁用或绕过了 Gatekeeper,我们将不会采取任何措施。当您禁用安全功能时,需自行承担风险。反之,若该应用已签名,Apple 可撤销其权限,使其无法在开启安全功能的用户设备上运行——无论是否为 Homebrew Cask 用户,这都将使所有人受益。要举报已签名的恶意软件捆绑应用,请使用 Apple 反馈助手。
对于有充分证据表明存在恶意的应用,我们也会考虑移除其 cask。建议删除某个 cask 时,请提交包含删除理由的 pull request。通常这意味着需提供该应用的 VirusTotal 扫描报告以证明其恶意性,最好还能附加其他表明非误报的佐证材料。
同理,若某软件同时提供"纯净版"和携带恶意软件的版本,即使我们能获取其正常版本——只要开发者仍诱导用户安装问题版本,我们可能会将其移出仓库。这是因为此类情况下,两个版本同时(或即将)以某种方式被攻陷的风险远高于常规水平。
若您依赖的 cask 因此规则被移除,无需担忧。从官方仓库移除 cask 仅代表我们不再提供支持,您仍可通过托管自己的 tap 继续使用。
知名度门槛的例外情况
未达到最低知名度门槛的 Cask(参见被拒绝的 Cask)不会被主仓库接受,因为增加的维护负担与它们可能获得的低使用量不成正比。这一知名度检查由我们提供的审计命令自动执行,但其判定并非不可更改。如果满足以下条件,未通过知名度检查的 Cask 仍可能被添加:
- 热门应用但开发者使用 GitHub 托管二进制文件:该应用本身可能很受欢迎,但托管其二进制文件的 GitHub 仓库可能知名度不足。
- 由维护者或活跃贡献者提交:知名度规则的主要考量是冷门软件容易因缺乏关注而导致 Cask 被废弃、过时或损坏。而对 Homebrew Cask 有长期投入的贡献者不太可能放任其依赖的软件出现此类问题。
- 近期发布且引发广泛讨论的软件:例如在 Twitter 和 Hacker News 上引发热议,甚至已收到多个过早提交的情况。这类应用显然会迅速达到门槛,因此我们不会立即关闭此类 PR(但可能会暂缓合并)。
需注意,这些例外情况并不保证一定会被收录,仅代表我们可能重新评估的典型场景。
通常不接受分叉版本
我们不会为分叉版本添加新的 cask,除非满足以下至少一个条件:
- 该分叉已被原始代码仓库明确指定为官方继承者(例如在 README 中说明),或由原作者通过公开可验证的方式确认(例如在 issue 或 pull request 评论中声明)
- 该分叉已被至少两个其他主流发行版采用作为替代品(例如 Debian、Fedora、Arch、Gentoo 等广泛使用的 Linux 发行版,不包括小众发行版)
即使满足上述条件,分叉版本仍需符合所有其他 cask 的准入要求(包括流行度和自主提交等标准)。
作为替代方案,可以考虑新建一个独立的 cask。例如:若 MikeMcQuaid
分叉了 google-chrome
且广受欢迎,那么创建 mikemcquaid-google-chrome
cask 可能是更合适的方案。
Homebrew Cask 并非应用发现服务
自 Homebrew Cask 诞生以来,许多请求都可以归入这一回应范畴。尽管这类请求相当常见,但经过多次慎重考虑后,我们始终得出相同结论:我们并非应用发现服务,用户在使用我们安装应用前,理应对目标应用具备基本认知。例如,按类别分组 cask 就不属于本项目范畴。
这类请求的后勤维护成本对 Homebrew Cask 而言是不可持续的。在提交此类请求前,请务必通读相关历史议题及其链接的所有讨论,以充分理解:为何现状如此,为何"但某项目做了某功能"的论点不适用,以及为何不同包管理器各有差异。
您还应能针对这些顾虑提出明确可行的改进方案。仅提出需求而无解决方案的议题将被关闭。
需注意"应用发现"(寻找未知的新应用)与"应用检索"(定位已知应用的安装方式)存在本质区别。前者永远不会成为我们的目标,但后者确实是我们持续优化的重要方向。
被拒绝的 Cask
在向我们的任何代码库提交 cask 之前,您必须阅读我们关于可接受 cask 的文档,并至少快速搜索一下,看看之前是否有引入它的尝试。
完全拒绝 cask 的常见原因:
- 应用在 Homebrew 支持的 macOS 版本和平台上启用 GateKeeper 时失败(例如,未签名的应用无法在 Apple Silicon Mac 上启动)。
- 应用过于冷门。例如:
- 来自代码仓库但知名度不足的应用(少于 30 个 fork、30 个 watcher 或 75 个 star)。
- 电子身份认证(eID)软件。
- 应用需要禁用 SIP 才能安装或使用。
- 应用安装程序是
pkg
,且需要allow_untrusted: true
。 - 应用是试用版,且获取完整版的唯一途径是通过 Mac App Store。
- 类似情况(较难发现):应用已迁移至 Mac App Store,但仍提供旧版本直接下载。我们会在所有官方代码库中拒绝这些应用,以免用户误以为自己在使用最新版本(可能存在安全风险)。
- 应用无人维护,即过去一年内没有发布新版本,或明确已停止开发。
- 应用主页无任何信息(例如:没有 README 的 GitHub 仓库)。
- Cask 没有公开渠道,因此
brew install
是安装该软件的唯一方式,用户无法轻松验证其真实性。- 或者,如果 Cask 的下载 URL 既需要登录/注册,又与主页的托管方不同。
- Cask 维护难度过高。例如 Audacity 和旧版 Java 开发 cask。
- Cask 曾因无法解决的问题被拒绝,而新提交未修复该问题。例如
soapui
的首次提交,其安装问题在后续的两次提交 中 仍未解决。 - Cask 重复提交。这种情况通常发生在未遵循 token 参考 时。
- 作者明确要求不包含该应用。
- 应用是开源且仅限 CLI(即仅使用
binary
构件)。根据去重原则,应首先将其作为源码构建的 formula 提交至 homebrew/core。如果被拒绝,可再尝试作为 cask 提交(在 pull request 中附上相关 issue 链接以便查看讨论和拒绝原因)。 - 应用是开源且有 GUI,但未提供编译版本(或仅提供旧版本)。最好将其放入 homebrew/core,以免用户长期使用过时版本。例如
gedit
。 - 我们有充分理由认为包含该 cask 会使整个项目面临风险。目前仅发生过一次,即 Popcorn Time。
从主仓库 homebrew/cask
拒绝 cask 的常见原因:
- Cask 提交到了错误的代码库。起草 cask 时,请参考为您的 Cask 寻找归属 以确定其应属位置。
无法保证每个 Cask 都会被接受
遵循上述指南能大大提高你的提交被接受的概率。但请记住,文档往往滞后于实际决策过程,我们无法预见所有情况。当维护者根据经验判断某些例外情况能让 Homebrew 整体变得更好时,可能会推翻这些规则。
许可证指南
https://docs.brew.sh/License-Guidelines
我们仅接受符合以下条件的配方(formulae)加入 homebrew/core
:
- 使用Debian自由软件指南许可证
- 或根据Debian关于公有领域软件的指南声明为公有领域
指定许可证
所有许可证均通过其在SPDX许可证列表中的许可证标识符进行识别。
通过将许可证传递给license
方法来指定:
license "MIT"
公共领域可以使用以下符号表示:
license :public_domain
如果某个公式的许可证无法使用SPDX表达式表示:
license :cannot_represent
复杂SPDX许可证表达式
某些公式包含多个许可证,需要以不同方式组合使用。这种情况下,可以采用更复杂的许可证表达式。这些表达式基于SPDX许可证表达式指南。
添加+
符号表示用户可以选择同一许可证的后续版本:
license "EPL-1.0+"
GNU许可证(GPL
、LGPL
、AGPL
和GFDL
)要求使用-only
或-or-later
后缀来表明是否允许使用该许可证的后续版本:
license "LGPL-2.1-only"
license "GPL-1.0-or-later"
使用 :any_of
表示用户可以选择适用的许可证:
license any_of: ["MIT", "0BSD"]
使用 :all_of
表示用户必须遵守多个许可证:
license all_of: ["MIT", "0BSD"]
使用 :with
来指定许可证例外情况:
license "MIT" => { with: "LLVM-exception" }
这些表达式可以根据需要进行嵌套:
license any_of: ["MIT",:public_domain,{ all_of: ["0BSD", "Zlib", "Artistic-1.0+"],"Apache-2.0" => { with: "LLVM-exception" } },
]
指定禁止的许可证
可以通过设置 HOMEBREW_FORBIDDEN_LICENSES
环境变量来禁止安装那些需要特定许可证或其依赖需要特定许可证的 formulae(配方)。
HOMEBREW_FORBIDDEN_LICENSES
应设置为一个以空格分隔的许可证列表。使用 public_domain
可以禁止安装带有 :public_domain
许可证的 formulae。
例如,以下设置将禁止安装 MIT
、Artistic-1.0
和 :public_domain
许可证:
export HOMEBREW_FORBIDDEN_LICENSES="MIT Artistic-1.0 public_domain"
在这个示例中,Homebrew 会拒绝安装任何声明使用 MIT
许可证的配方。即使原始配方拥有允许的许可证,Homebrew 也会禁止安装任何依赖声明为 MIT
许可证配方的公式。
Homebrew 能够解析复杂的许可证表达式,并判断这些许可证是否允许安装。延续上述示例,Homebrew 不会允许安装具有以下许可证声明的配方:
license any_of: ["MIT", "Artistic-1.0"]
license all_of: ["MIT", "0BSD"]
Homebrew 将允许安装包含以下声明的配方:
license any_of: ["MIT", "0BSD"]
HOMEBREW_FORBIDDEN_LICENSES
也可用于禁止特定许可证的未来版本。例如,要禁止 Artistic-1.0
、Artistic-2.0
及所有未来的 Artistic 许可证,可使用:
export HOMEBREW_FORBIDDEN_LICENSES="Artistic-1.0+"
对于 GNU 许可证(如 GPL
、LGPL
、AGPL
和 GFDL
),请使用 -only
或 -or-later
后缀。例如,以下配置将禁止安装 GPL-2.0
、LGPL-2.1
和 LGPL-3.0
版本的公式,但允许安装 GPL-3.0
。
export HOMEBREW_FORBIDDEN_LICENSES="GPL-2.0-only LGPL-2.1-or-later"
公式版本管理
https://docs.brew.sh/Versions
homebrew/core 通过特殊的命名格式支持多版本公式。例如,GCC 9 的公式文件被命名为 gcc@9.rb
,并以 class GccAT9 < Formula
开头。
可接受的版本化公式
我们纳入homebrew/core的版本化公式必须符合以下标准:
- 版本化软件应能在Homebrew支持的所有macOS版本上构建。
- 版本化公式应与当前稳定版在主版本/次版本(而非修订版本)上存在差异。因为修订版本通常表示错误修复或安全更新,我们希望确保用户应用这些安全更新。
- 不稳定版本(alpha、beta、开发版本)不可作为版本化(或非版本化)公式。
- 上游应为每个公式版本维护发布分支,并明确制定必要时为每个版本发布安全更新的策略。例如,2020年1月时PHP 7.0已不受支持但PHP 7.2仍受支持。相比之下,多数软件项目仅为其最新版本提供安全更新,因此它们的早期版本不符合版本化条件。
- 版本化公式应与主公式共享代码库。若项目被拆分至不同仓库,建议创建新公式(使用
formula2
而非formula@2
或formula@1
)。 - 依赖版本化公式的公式不得在其递归依赖中两次依赖同一公式的不同版本。例如,若同时依赖
openssl@1.0
和foo
,而foo
依赖openssl
,则必须改用openssl
。 - 仅当上游项目支持时(例如通过使用带后缀的二进制文件),版本化公式才能与其非版本化对应项同时链接。若不可行,需使用
keg_only :versioned_formula
以允许用户同时安装多个版本。 keg_only :versioned_formula
不应在HOMEBREW_PREFIX
中执行任何与主对应项(或其他版本化公式)冲突或重复的post_install
操作。例如,node@6
公式不应像node
公式那样将其npm
安装到HOMEBREW_PREFIX
。- 提交的版本化公式预期应有大量用户使用。若不再符合此条件,将被移除。我们将尽量保留top 3,000
install_on_request
公式中的版本化公式。 - 版本化公式不应包含需要安全更新的
resource
。例如,node@6
公式不应包含npm
资源,而应依赖上游压缩包提供的npm
。 - 版本化公式应尽可能与主公式保持合理相似。创建或更新版本化公式时,应借此机会审视主公式,反之亦然。例如,是否可以移除某些无用选项或将其设为默认?
- 任何时刻支持的公式版本(含主版本)不得超过五个,除非它们非常流行(例如拥有超过1000次90天安装量统计)。移除违反此规定的公式时,我们将基于使用量和支持状态而非存在时间来决定。
- 版本化公式必须在版本分支的生命周期内保持ABI稳定性。对版本化公式的更新不得引入ABI不兼容或要求依赖项进行修订版本提升。实践中,这意味着它们的依赖项永远不需要通过
revision
提升来针对新版本重建。违反此规定的版本更新应被拒绝,并从该时点起弃用该公式。
Homebrew的版本不应被用于根据个人需求"固定"公式。若您或您的组织希望控制版本,或公式不符合上述标准,应创建自己的tap。即使软件会定期发布破坏API或ABI的更新,仍需满足所有上述要求;brew upgrade
导致问题不能成为我们为您添加和维护公式的理由。
若某公式当前或曾经存在于Homebrew/homebrew-core仓库(即被迁移或删除),您可通过brew extract
命令恢复供自己使用。该命令会将指定版本的公式复制到自定义tap中。例如,若您的项目依赖automake
1.12而非最新版本,可运行以下命令获取1.12版本的automake
公式:
brew extract automake --version=1.12 <YOUR_GITHUB_USER>/<YOUR_TAP_REPOSITORY_NAME>
通过这种方式获得的公式可能包含已弃用、禁用或移除的 Homebrew 语法(例如校验和可能使用 sha1
而非 sha256
);brew extract
命令不会编辑或更新公式以满足当前标准和样式要求。
我们可能会在 homebrew/core 中临时添加不符合这些标准的版本化公式以满足自身需求。该仓库中存在版本化公式并不代表我们会无限期维护它,也不意味着我们愿意接受更多不符合上述要求的版本。
公式的弃用、禁用与移除
https://docs.brew.sh/Deprecating-Disabling-and-Removing-Formulae
公式可能因多种原因被弃用、禁用或移除。本文档将解释这三种处理方式的区别,并说明在何种情况下应选择哪种方法。
概述
可以遵循以下通用经验法则:
deprecate!
应用于那些应当不再使用的配方(formulae)。disable!
应用于那些无法使用的配方。- 对于已不再被
homebrew/core
接受或已禁用超过一年的配方,应当予以移除。
弃用说明
当用户尝试安装已弃用的配方时,系统会显示警告信息,但安装过程仍会继续。
配方被标记为弃用状态,是为了向用户表明该配方不应再使用,并将在未来被禁用。已弃用的配方仍需由 Homebrew 维护者持续维护,以确保它们仍能从源代码构建,且其预编译包(bottles)能继续正常工作(即使上游已停止维护)。若无法满足此条件,则应直接禁用该配方。
最常见的弃用原因包括:上游项目已弃用、无人维护或已归档。
只有当满足以下至少一项条件时,才应将配方标记为弃用:
- 该配方无法在任何受支持的操作系统版本上构建
- 配方安装的软件存在未修复的严重漏洞(CVE)
- 配方安装的软件已被上游停止开发或放弃维护
- 该配方在过去90天内安装量为零
除非所有依赖项也已被弃用,否则不应弃用存在依赖项的配方。
如需弃用某个配方,请添加 deprecate!
调用。该调用需包含 ISO 8601 格式的弃用日期及弃用原因说明。
deprecate! date: "YYYY-MM-DD", because: :reason
date
参数应设置为弃用期开始的日期,通常为当天日期。如果将 date
参数设为未来某个日期,则公式直到该日期才会被弃用。当上游开发者已明确项目或版本停止支持的日期时,此功能非常有用。请勿将 date
参数设为过去日期,否则会给用户和维护者造成混淆。
because
参数可以是预设原因(使用符号)或自定义原因。有关 because
参数的更多详细信息,请参阅下方的弃用和禁用原因部分。
还可以指定可选的 replacement_formula
或 replacement_cask
参数,向用户推荐替代的 formula 或 cask。该参数值为字符串形式。
deprecate! date: "YYYY-MM-DD", because: :reason, replacement_formula: "foo"
禁用机制
当用户尝试安装被禁用的 formula 时,系统会显示错误信息并终止安装流程。
禁用某个 formula 的目的是向用户表明该 formula 已不可用且将在未来被移除。被禁用的 formula 可能无法再从源代码构建,或不再提供可用的预编译包。
禁用 formula 最常见的原因包括:
- 无法在任何受支持的操作系统版本上从源代码构建(意味着无法生成新的预编译包)
- 已被弃用较长时间
- 项目未提供许可证
对于流行 formula(例如过去 90 天内安装量超过 1000 次的),即使存在无法从源代码构建或缺少许可证等情况,也应至少保留六个月的弃用期后才可禁用。
非流行 formula(例如过去 90 天内安装量不足 1000 次的)若出现上述任一情况(如在所有受支持的 macOS 或 Linux 版本上均无法从源代码构建),可立即禁用。这些 formula 在禁用三个月后可被手动移除。
要禁用某个 formula,需添加 disable!
调用。该调用应包含 ISO 8601 格式的弃用日期及具体原因说明:
disable! date: "YYYY-MM-DD", because: :reason
date
参数应设置为禁用原因生效的日期。如果没有明确日期但需要禁用该公式,则使用当天日期。如果将 date
参数设置为未来某个日期,该公式将被弃用直至该日期(届时公式将转为禁用状态)。
because
参数可以是预设原因(使用符号)或自定义原因。有关 because
参数的更多详细信息,请参阅下方的弃用与禁用原因部分。
与弃用公式类似,对于禁用的公式也可以指定可选的 replacement_formula
或 replacement_cask
参数,向用户推荐替代公式或 cask。该参数值为字符串形式。
disable! date: "YYYY-MM-DD", because: :reason, replacement_cask: "foo"
移除标准
当某个配方(formula)出现以下情况时,应当予以移除:
- 不符合我们制定的可接受配方标准
- 不符合版本化配方标准
- 采用非开源许可证
- 已被禁用超过一年
重要说明:在homebrew/core
中被禁用的配方,将在禁用日期满一年后自动移除。
弃用与禁用原因
当某个配方(formula)被弃用或禁用时,必须提供解释该操作的原因。
有两种方式可以标明原因。首选方式是使用预定义的符号来表示原因。可用的符号如下所示,这些符号可在 DeprecateDisable
模块 中找到:
:does_not_build
:该配方无法在任何受支持的 macOS 版本或 Linux 上从源代码构建。:no_license
:我们无法为该配方确定许可证。:repo_archived
:上游仓库已被归档,且未指向任何可用的替代方案。:repo_removed
:上游仓库已被移除,且首页未提及任何可用的替代方案。:unmaintained
:该项目似乎已被放弃,即至少一年内没有提交,且存在已报告但长期未解决的关键错误或 CVE。注意: 某些软件是“已完成”的;缺乏活动并不意味着需要移除。:unsupported
:Homebrew 对该软件的编译不受上游开发者支持(例如上游仅支持早于 10.15 的 macOS 版本)。:deprecated_upstream
:该项目在上游已被弃用,且未指向任何可用的替代方案。:versioned_formula
:该配方是一个版本化配方,且不再满足要求。:checksum_mismatch
:该配方当前版本的源代码校验和在构建 bottles 后发生了变化,且我们无法找到可信的来源或声明证明这一变化是合理的。
这些原因可以通过其符号指定(注释部分显示将向用户展示的消息):
# Warning: <formula> has been deprecated because it is deprecated upstream!
deprecate! date: "2020-01-01", because: :deprecated_upstream
# Error: <formula> has been disabled because it does not build!
disable! date: "2020-01-01", because: :does_not_build
如果这些预设原因不适用,可以指定自定义原因。此类原因应写成符合句式<formula>已被弃用/禁用,因为它<原因>!
的形式。
一个措辞良好的自定义原因示例如下:
# Warning: <formula> has been deprecated because it fetches unversioned dependencies at runtime!
deprecate! date: "2020-01-01", because: "fetches unversioned dependencies at runtime"
一个表述不当的自定义原因示例是:
# Error: <formula> has been disabled because it invalid license!
disable! date: "2020-01-01", because: "invalid license"
针对公式作者的 Node 指南
https://docs.brew.sh/Node-for-Formula-Authors
本文档介绍如何在使用 Node 模块的 Homebrew 公式中成功运用 Node 和 npm。
运行 npm install
Homebrew 提供了一个辅助方法 std_npm_args
,用于为 npm 设置正确的环境并返回 npm install
所需的参数。在调用 npm install
时,您的 formula 应使用此方法。标准 Node 模块安装的语法如下:
system "npm", "install", *std_npm_args
下载地址
如果 Node 模块同时在 npm 注册表中提供,我们优先选择 npm 托管的发布压缩包,而非 GitHub(或其他地方)托管的源码压缩包。这类压缩包的优势在于:
- 不包含
.npmignore
中列出的文件(例如测试代码),从而减小下载体积 - 所有可能的转译步骤(如 CoffeeScript 文件编译)已完成,无需额外构建步骤
npm 注册表 URL 通常遵循以下格式:
https://registry.npmjs.org/<name>/-/<name>-<version>.tgz
或者,你也可以使用 curl
命令获取 https://registry.npmjs.org/<name>
的 JSON 数据,然后查找 versions[<version>].dist.tarball
的值来获取正确的压缩包 URL。
依赖项
与最新 Node 版本兼容的 Node 模块应声明对 node
公式的依赖关系。
depends_on "node"
如果你的公式需要在较旧的 Node 版本中运行,应该使用其版本化公式(例如 node@20
)。
原生模块的特殊要求
如果你的 Node 模块是原生插件,或者其依赖树中某处包含原生插件,你必须声明额外的依赖项。由于编译原生插件会调用 node-gyp
,我们需要额外添加一个构建时依赖项 "python"
(因为 GYP 依赖于 Python)。
depends_on "python" => :build
还需注意,此类公式仅兼容最初编译时所用的相同 Node 主版本。这意味着每当 node
公式的主版本升级时,我们需要对所有包含 Node 原生插件的公式进行修订。为确保在 Node 主版本升级时不会遗漏您的公式,请编写一个有意义的测试用例,该测试在遇到 ABI 不兼容的 Node 版本时会失败(即使用不兼容的 Node 版本调用时)。
安装指南
Node 模块应安装到 libexec
目录。这样可以避免 Node 模块污染全局的 node_modules
目录,确保 npm 不会尝试管理通过 Homebrew 安装的 Node 模块,这一点非常重要。
以下我们将通过 Homebrew 安装的 Node 模块分为两种类型:
- 符合 npm 全局模块格式的标准 Node 模块,应使用
std_npm_args
(例如angular-cli
或webpack
) - 需要额外安装步骤(例如编译非 JavaScript 源代码)的模块,必须使用
std_npm_args
(例如emscripten
或grunt-cli
)
这两种方法的共同点是:它们都为在 Homebrew 中使用 npm 设置了正确的环境,并返回针对特定用例调用 npm install
所需的参数。这包括通过使用 HOMEBREW_CACHE
中的自定义 npm_cache
来解决 npm 缓存的重要边界情况(由 Homebrew 在构建和测试过程中重定向 HOME
引起),否则会导致构建时间过长和磁盘空间占用过高的问题。
使用 std_npm_args
将全局样式模块安装到 libexec
在您的 formula 的 install
方法中,如有需要,只需 cd
到 Node 模块的顶层目录,然后使用 system
调用 npm install
并带上 std_npm_args
参数,例如:
system "npm", "install", *std_npm_args
这将以 npm 的全局模块风格安装你的 Node 模块,并使用自定义前缀 libexec
。所有模块的可执行文件将由 npm 自动解析到 libexec/bin
目录下,而不会符号链接到 Homebrew 的前缀路径中。为了确保这些可执行文件被正确安装,我们需要将所有可执行文件符号链接到 bin
目录,具体操作如下:
bin.install_symlink Dir["#{libexec}/bin/*"]
使用 std_npm_args
本地安装模块依赖
在 formula 的 install
方法中,先执行所有需要在 npm install
步骤之前完成的安装操作,然后通过 cd
命令切换到包含的 Node 模块的顶层目录。接着,使用 system
调用 npm install
并传入 std_npm_args(prefix: false)
参数,示例如下:
system "npm", "install", *std_npm_args(prefix: false)
这将把所有 Node 模块的依赖项安装到本地构建路径中。现在,您可以继续执行构建步骤,并按照 Homebrew 公式通用指南 自行处理安装到 Homebrew 前缀的操作。
示例
安装基于标准 Node 模块的公式操作如下:
class Foo < Formuladesc "An example formula"homepage "https://example.com"url "https://registry.npmjs.org/foo/-/foo-1.4.2.tgz"sha256 "abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1"depends_on "node"# uncomment if there is a native addon inside the dependency tree# depends_on "python" => :builddef installsystem "npm", "install", *std_npm_argsbin.install_symlink Dir["#{libexec}/bin/*"]endtest do# add a meaningful test here, version isn't usually meaningfulassert_match version.to_s, shell_output("#{bin}/foo --version")end
end
工具支持
你可以使用 homebrew-npm-noob 来自动为 npm 包生成类似上文的示例公式。
Python 公式编写指南
https://docs.brew.sh/Python-for-Formula-Authors
本文档介绍如何在 Homebrew 公式中成功使用 Python。
Homebrew 对 Python 应用程序和 Python 库进行了明确区分。主要区别在于:用户通常不关心应用程序是否用 Python 编写,用户很少会期望在安装应用程序后能执行import foo
操作。典型的应用程序示例包括 ansible
和 jrnl
。
Python 库的存在是为了被其他 Python 模块导入,它们通常是 Python 应用程序的依赖项。这些库在终端环境中通常没有直接用途,典型示例有 certifi
和 numpy
。
绑定库是特殊类型的库,它允许 Python 代码与用其他语言实现的库或应用程序交互。例如 libxml2
安装的 Python 绑定就属于这类。
Homebrew 乐于接受用 Python 构建的应用程序,无论这些应用是否可通过 PyPI 获取。但通常不会接受那些能用pip install foo
正确安装的库。对于提供绑定的软件包,特别是当 pip 无法提供同等功能时,可以安装其绑定库。同样,包含大量原生代码且因此需要长时间编译的库也是不错的选择。如有疑问,请勿打包库文件。
应用程序应当无条件捆绑其所有 Python 语言依赖项和库,并应自动安装任何未满足的依赖关系。下文将深入探讨这些策略的具体实现方法。
应用程序
应用程序的Python声明
对于需要Python 3的应用程序,其配方必须声明对"python@3.y"
的无条件依赖。这些应用程序必须能与当前Homebrew的Python 3.y配方兼容使用。
安装应用程序
从 Python@3.12 开始,Homebrew 遵循 PEP 668 规范。应用程序必须安装到以 libexec
为根目录的 Python 虚拟环境 中。这样可以防止应用程序的 Python 模块污染系统的 site-packages
,反之亦然。
应用程序的所有 Python 模块依赖(及其递归依赖)应在配方中声明为 resource
,并同样安装到虚拟环境中。每个依赖必须明确指定;请勿依赖 setup.py
或 pip
进行自动依赖解析,原因详见此处说明。
你可以使用 brew update-python-resources
来辅助编写资源声明。只需运行 brew update-python-resources <formula>
即可。有时该命令可能无法自动更新资源,此时可尝试运行 brew update-python-resources --print-only <formula>
直接打印资源声明而非修改文件,然后根据需要手动复制粘贴。
如果 brew update-python-resources
不可行,可以使用 homebrew-pypi-poet 工具。使用方法:先创建虚拟环境并安装目标包及其所有依赖,然后在同一虚拟环境中执行 pip install homebrew-pypi-poet
。运行 poet some_package
即可生成所需的资源声明。具体操作如下:
# Use a temporary directory for the virtual environment
cd "$(mktemp -d)"# Create and source a new virtual environment in the venv/ directory
python3 -m venv venv
source venv/bin/activate# Install the package of interest as well as homebrew-pypi-poet
pip install some_package homebrew-pypi-poet
poet some_package# Destroy the virtual environment
deactivate
rm -rf venv
Homebrew 提供了用于实例化和填充虚拟环境的辅助方法。您只需在 Formula
类定义的顶部添加 include Language::Python::Virtualenv
即可使用这些方法。
对于大多数应用场景,您只需编写以下代码:
class Foo < Formulainclude Language::Python::Virtualenv# ...url "https://example.com/foo-1.0.tar.gz"sha256 "abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1"depends_on "python@3.y"def installvirtualenv_install_with_resourcesend
end
这与以下写法完全相同:
class Foo < Formulainclude Language::Python::Virtualenv# ...url "https://example.com/foo-1.0.tar.gz"sha256 "abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1"depends_on "python@3.y"def install# Create a virtualenv in `libexec`.venv = virtualenv_create(libexec, "python3.y")# Install all of the resources declared on the formula into the virtualenv.venv.pip_install resources# `pip_install_and_link` takes a look at the virtualenv's bin directory# before and after installing its argument. New scripts will be symlinked# into `bin`. `pip_install_and_link buildpath` will install the package# that the formula points to, because buildpath is the location where the# formula's tarball was unpacked.venv.pip_install_and_link buildpathend
end
示例公式
安装一个带有依赖项的公式会如下所示:
class Foo < Formulainclude Language::Python::Virtualenvdesc "Description"homepage "https://example.com"url "..."resource "six" dourl "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz"sha256 "1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"endresource "parsedatetime" dourl "https://files.pythonhosted.org/packages/a8/20/cb587f6672dbe585d101f590c3871d16e7aec5a576a1694997a3777312ac/parsedatetime-2.6.tar.gz"sha256 "4cb368fbb18a0b7231f4d76119165451c8d2e35951455dfee97c62a87b04d455"enddef installvirtualenv_install_with_resourcesend
end
你也可以使用更详细的形式,请求安装特定的资源:
class Foo < Formulainclude Language::Python::Virtualenvdesc "Description"homepage "https://example.com"url "..."def installvenv = virtualenv_create(libexec)%w[six parsedatetime].each do |r|venv.pip_install resource(r)endvenv.pip_install_and_link buildpathend
end
当您需要对不同资源执行不同操作时。
绑定配置
要为 Python 3 添加绑定,请添加 depends_on "python@3.y"
以兼容当前 Homebrew 的 Python 3.y 公式。
绑定依赖项
绑定应遵循与库相同的Python模块依赖项建议;更多详情请参阅下文。
安装绑定
如果通过调用 setup.py
来安装绑定,可以按照以下方式操作:
system "python3.y", "-m", "pip", "install", *std_pip_args(build_isolation: true), "./source/python"
Autotools
如果配置脚本支持 --with-python
参数,通常无需额外指定 Python 路径。但当依赖树中存在多个 Python 公式时,可能需要手动指定正确的版本。
若 configure
和 make
脚本拒绝安装到 Cellar 目录,可尝试以下方案:
- 执行
./configure --without-python
(或类似名称的选项) - 对包含 Python 绑定的目录运行
pip
命令(如前文所述)
某些情况下,我们需要即时修改 Makefile
,通过 Homebrew 的 inreplace
辅助方法将 Python 绑定路径替换为我们的前缀路径。
CMake
如果 cmake
找到了与直接依赖不同的 Python 版本,有时可以通过以下方式帮助它找到正确的 Python:使用 -D
选项设置以下变量之一:
Python3_EXECUTABLE
- 用于FindPython3
模块Python_EXECUTABLE
- 用于FindPython
模块PYTHON_EXECUTABLE
- 用于FindPythonInterp
模块
Meson
由于 Homebrew 的符号链接安装方式及对 Python sysconfig 的补丁修改,meson
可能无法自动检测到用于安装 Python 绑定的 Cellar 目录。如果配方(formula)的 meson
构建定义使用了 install_sources()
或类似方法,可以通过设置 python.purelibdir
和/或 python.platlibdir
来覆盖默认路径。
若 meson
检测到的 Python 版本与直接依赖项不同,且配方的 meson
选项定义文件未提供用户可设置的选项,则需要检查 Python 可执行文件的检测逻辑。常见做法是通过 find_installation()
方法实现,该方法会根据 name_or_path
参数的设定值表现出不同行为。
库文件
请注意:库文件的适用场景非常有限(例如需要编译大量原生代码时)。如有疑问,请不要将其打包。
这类配方(formulae)我们不使用 python-
前缀!
homebrew-core 中允许的库示例
numpy
、scipy
:构建时间长,构建过程复杂cryptography
:使用rust
构建certifi
:修改后的配方允许任何基于 Python 的配方利用自制的 CA 证书(参见 https://github.com/orgs/Homebrew/discussions/4691)
Python 库的声明规范
为 Python 3 构建的库必须包含 depends_on "python@3.y"
声明,这将使其基于 Homebrew 的 Python 3.y 版本进行封装。
安装库文件
可以通过在prefix
的site-packages目录下写入.pth
文件(例如命名为"homebrew-foo.pth"),将库安装到libexec
并添加到sys.path
中。这种做法能有效简化因误用pip升级Homebrew安装包导致的后续问题,同时避免在Homebrew的site-packages目录中积累过时的.pyc
文件。
目前大多数配方(formulae)仅安装到prefix
目录。所有过时的.pyc
文件将由brew cleanup
命令自动处理。
库依赖项
必须安装库依赖项以确保它们可被导入。为尽量减少链接冲突的可能性,应将依赖项安装到 libexec/<vendor>
目录,并通过在 prefix
的 site-packages 目录中写入第二个 .pth
文件(命名为类似 “homebrew-foo-dependencies.pth” 的形式)将其添加到 sys.path
。
对于具有通用 Python 库依赖项(如 setuptools
、six
)的配方(formulae),不应采用此方法,因为这会将 libexec/<vendor>
中安装的所有库污染到系统的 site-packages
目录。
深入探索
进一步解释 Homebrew 为何采取某些特定做法的附加说明。
Setuptools 与 Distutils 及 pip 的对比
Distutils 曾是 Python 标准库中的一个模块,为开发者提供基础的包管理 API,直至 Python 3.12 版本中被移除。Setuptools 是标准库外分发的模块,它扩展并替代了 Distutils。按照惯例,Python 包会提供一个调用 Distutils 或 Setuptools 中 setup()
函数的 setup.py
文件。
Setuptools 曾提供 easy_install
命令,这是一个面向终端用户的包管理工具,用于从 Python 包索引 PyPI 获取并安装包。easy_install
控制台脚本在 Setuptools v52.0.0 中被移除,自 v58.3.0 起直接使用已被弃用。pip
是另一个较新的终端用户包管理工具,同样在标准库外提供。虽然 pip
取代了 easy_install
,但它并未替代 Setuptools 模块的其他功能。
Distutils 和 pip 采用“扁平化”安装层级结构,将模块作为单独文件安装到 site-packages
目录下,而 easy_install
则会将压缩的 egg 文件安装到 site-packages
。
Distribute(注意不要与 Distutils 混淆)是 Setuptools 的一个已废弃分支。Distlib 是标准库外维护的包,pip 用它执行某些底层打包操作,对大多数 setup.py
用户而言无需关注。
运行 setup.py
当某个 formula 需要与 setup.py
交互而非调用 pip
时,Homebrew 提供了辅助方法 Language::Python.setup_install_args
,该方法会返回用于调用 setup.py
的有效参数。你的 formula 应当使用此方法,而非直接显式调用 setup.py
。语法如下:
system Formula["python@3.y"].opt_bin/"python3.y", *Language::Python.setup_install_args(prefix)
其中 prefix
是目标前缀(通常为 libexec
或 prefix
)。
什么是 --single-version-externally-managed
?
--single-version-externally-managed
(简称“SVEM”)是专用于 Setuptools 的 setup.py install
参数。SVEM 的主要作用是让 Distutils 替代 Setuptools 的 easy_install
来执行安装。
我们需要避免 easy_install
的以下行为:
- 自动获取并安装依赖项
- 就地升级
sys.path
中的依赖项 - 生成无用的
.pth
和site.py
文件,这些文件会导致链接冲突
Setuptools 要求 SVEM 必须与 --record
参数配合使用,后者会生成文件列表用于后续卸载包。由于 Homebrew 可以管理卸载,我们并不需要此功能,但为了满足 Setuptools 的要求仍会遵守约定。Homebrew 的惯例是将记录文件命名为 “installed.txt”。
虽然很难检测 setup.py
使用的是 Setuptools 还是 Distutils 的 setup()
函数,但我们始终需要向基于 Setuptools 的脚本传递该标志。pip
也面临同样的问题,它通过在 setup.py
执行前加载 Setuptools 的垫片代码,强制 setup()
使用 Setuptools 版本。由于 Setuptools 会猴子补丁修改 Distutils 并替换其 setup
函数,这提供了统一接口。我们已借鉴这段代码并在 Language::Python.setup_install_args
中使用。
--prefix
与 --root
的区别
setup.py
接受一系列令人困惑的安装选项。对于 Homebrew 来说,正确的参数是 --prefix
,它会自动设置 --install-foo
系列选项,并采用合理的 POSIX 标准值。
--root
用于安装到某个前缀路径,但该路径不会成为文件最终安装位置的一部分,例如在构建 RPM 或二进制分发时。当使用基于 setup.py
的 Setuptools 时,--root
会附带激活 --single-version-externally-managed
的副作用。如果 --prefix
为空,则不能安全使用 --root
,因为在字节编译模块时,root
路径会被从最终路径中移除。
虽然可以同时使用 --prefix
和 --root=/
(这种方式对基于 Setuptools 或 Distutils 的 setup.py
都有效),但这种写法略显粗糙。
pip
与 setup.py
对比
PEP 453 向下游分发者(即我们)建议:应该使用 pip
而非直接调用 setup.py
来安装 sdist 压缩包。由于历史原因,我们此前未遵循 PEP 453 的建议,因此部分配方仍在使用 setup.py
安装方式。如今,随着我们逐步迁移至这种更推荐的安装方法,大多数核心配方已改用 pip
。
brew livecheck
https://docs.brew.sh/Brew-Livecheck
brew livecheck
命令通过检查上游源来查找 formula 或 cask 软件的最新版本。Livecheck 提供多种策略来识别不同来源的版本号,例如 Git 代码库、网站等。
行为模式
当 livecheck 未指定如何检查上游版本时,默认会执行以下操作:
- 收集待检查的 URL 列表
- 对于 formulae:依次使用其
stable
、head
和homepage
的 URL - 对于 formula resources:使用其
url
- 对于 casks:依次使用其
url
和homepage
的 URL
- 对于 formulae:依次使用其
- 判断是否有策略适用于第一个 URL。若不适用,则尝试下一个 URL
- 若有适用策略,则使用该策略检查新版本
- 返回最新版本(若所有可用 URL 均未找到版本则返回错误)
有时需要覆盖此默认行为以创建有效的检查。如果某个来源未提供最新版本,我们需要检查其他来源。如果 livecheck 未能正确匹配版本文本,我们需要提供适当的正则表达式或 strategy
代码块。
这可以通过向 formula/cask/resource 添加 livecheck
代码块来实现。有关可用方法的更多信息,请参阅 Livecheck
类文档。
创建检查项
1、利用调试输出来了解情况。brew livecheck --debug <formula>|<cask>
会显示 livecheck 尝试访问的 URL、适用的策略、匹配到的版本等信息。
2、研究可用资源以选择 URL。尝试从 stable
/url
中移除文件名,查看是否能获取目录列表页面。若无效,尝试寻找包含该文件链接的页面(例如下载页)。如果无法在网站上找到最新版本,可检查 formula/cask 中的其他资源。必要时,可在 formula/cask 之外搜索其他来源。
3、必要时创建正则表达式。如果检查无需正则表达式即可工作,且添加后无明显益处,通常可省略。更多关于编写正则表达式的信息,请参阅正则表达式指南部分。
通用准则
- 仅在必要时使用
strategy
。例如,如果 livecheck 已经对某个 URL 使用了Git
策略,则无需再指定strategy :git
。但如果Git
适用于某个 URL 而我们仍需使用PageMatch
,则必须明确指定strategy :page_match
。 - 仅在必要且正确的情况下使用
GithubLatest
和GithubReleases
策略。GitHub 会对 API 请求进行速率限制,因此我们仅在Git
策略不适用或不足时才使用这些策略。GithubLatest
应仅在上游仓库有适合版本的 “latest” 发布、且配方/Cask 使用发布资源或Git
策略无法正确识别最新发布版本时使用。GithubReleases
应仅在上游仓库使用发布机制、且Git
和GithubLatest
策略均不适用时使用。
URL 指南
livecheck
块中必须包含url
。可以是 URL 字符串(例如"https://www.example.com/downloads/"
)或公式/cask 的 URL 符号(即:stable
、:url
、:head
、:homepage
)。此规则的例外是仅使用skip
的livecheck
块。- 尽可能在稳定版存档的相同位置检查版本。
- 尽量避免检查分页的发布页面。例如,我们通常避免检查 GitHub 项目的
release
页面,因为最新的稳定版本可能会被预发布版本挤到第一页之外。在这种情况下,使用Git
策略更可靠,该策略会获取仓库中的所有标签。
正则表达式指南
livecheck
块中的正则表达式会将匹配限制在获取内容的子集内,并在版本文本周围使用捕获组。
- 尽可能使正则表达式不区分大小写,通过在末尾添加
i
(例如/.../i
或%r{...}i
)。这提高了可靠性,因为正则表达式可以处理字母大小写的变化而无需修改。 - 正则表达式应仅在版本文本周围使用捕获组。例如,在
/href=.*?example-v?(\d+(?:.\d+)+)(?:-src)?.t/i
中,我们仅在版本文本周围使用捕获组(匹配如1.2
、1.2.3
等版本),而在其他地方使用非捕获组(例如(?:-src)?
)。 - 锚定正则表达式的开头/结尾以限制其范围。例如,在 HTML 页面上,我们经常匹配
href
属性 URL 中的文件名或版本目录(例如/href=.*?example[._-]v?(\d+(?:.\d+)+).zip/i
)。总体思路是,限制范围有助于排除不需要的匹配项。 - 避免使用通用通配符如
.*
或.+
,转而使用非贪婪和/或上下文适当的内容。例如,要匹配 HTML 属性边界内的字符,请使用[^"' >]+?
。 - 在文件名中的软件名称和版本之间使用
[._-]
代替句点/下划线/连字符。对于名为example-1.2.3.tar.gz
的文件,example[._-]v?(\d+(?:.\d+)+).t
将继续匹配,即使上游文件名格式更改为example_1.2.3.tar.gz
或example.1.2.3.tar.gz
。 - 使用
.t
代替.tgz
、.tar.gz
等。压缩包有多种不同的文件扩展名(例如.tar.bz2
、tbz2
、.tar.gz
、.tgz
、.tar.xz
、.txz
等),上游源可能会随着时间的推移从一种压缩格式切换到另一种。.t
通过匹配以t
开头的当前和未来格式来避免此问题。对于非压缩包,我们在正则表达式中使用完整的文件扩展名,如.zip
、.jar
等。
示例 livecheck
配置块
以下示例涵盖了您可能遇到的多种模式。这些示例具有代表性,可以轻松调整使用。
当不确定时,建议从这些示例开始,而不是随意从某个公式或 cask 中复制粘贴 livecheck
块。
文件名
在从HTML页面文件名中匹配版本号时,我们通常将匹配范围限制在href
属性内。href=.*?
会匹配起始分隔符("
、'
)以及文件名前的URL部分。
livecheck dourl "https://www.example.com/downloads/"regex(/href=.*?example[._-]v?(\d+(?:.\d+)+).t/i)
end
有时我们会更明确地排除不需要的匹配项。带有前置路径的URL可以使用href=.*?/
,而其他情况可以使用href=["']?
。例如,当页面还包含带有更长前缀的不需要文件时(如another-example-1.2.tar.gz
),这样做是必要的。
版本目录
在检查目录列表页面时,有时文件会被分类到版本目录中(例如 1.2.3/
)。这种情况下,我们必须从目录名称中识别出版本信息。
livecheck dourl "https://www.example.com/releases/example/"regex(%r{href=["']?v?(\d+(?:.\d+)+)/?["' >]}i)
end
Git 标签
当 stable
URL 使用 Git
策略时,以下示例将仅匹配类似 1.2
/v1.2
等形式的标签。
livecheck dourl :stableregex(/^v?(\d+(?:.\d+)+)$/i)
end
如果标签包含软件名称作为前缀(例如 example-1.2.3
),可以轻松修改正则表达式为:/^example[._-]v?(\d+(?:.\d+)+)$/i
引用配方/桶装软件
配方或桶装软件可以通过使用 formula
或 cask
来复用其他配方的检查逻辑。
livecheck doformula "another-formula"
end
引用的 formula/cask 必须位于同一个 tap 中,因为引用来自其他 tap 的 formula/cask 会在用户尚未添加该 tap 时导致错误。
若某个 formula 资源需要与其父级 formula 保持版本同步,可以使用 formula :parent
实现相同的检查机制。
livecheck doformula :parent
end
POST
请求
某些检查需要发起 POST
请求,这可以通过在 livecheck
代码块的 url
中添加 post_form
或 post_json
选项来实现。
livecheck dourl "https://example.com/download.php", post_form: {Name: "","E-mail": "",}regex(/href=.*?example[._-]v?(\d+(?:.\d+)+).t/i)
end
post_form
用于表单数据,而 post_json
则用于 JSON 数据。Livecheck 会在发起请求前将提供的哈希值编码为相应格式。
POST
支持仅适用于直接或间接使用 Strategy::page_headers
或 ::page_content
的策略,因此不适用于 ExtractPlist
、Git
、GithubLatest
、GithubReleases
等策略。
strategy
块
如果需要对上游版本格式进行调整以匹配 formula/cask 的格式要求,可以使用 strategy
块来替代 regex
。
PageMatch
strategy
块
以下是一个基础示例,从页面中提取一个简化版本:
livecheck dourl "https://example.org/my-app/download"regex(%r{href=.*?/MyApp-(\d+(?:.\d+)*).zip}i)strategy :page_match
end
更复杂的版本可以通过指定一个代码块来处理。
livecheck dourl "https://example.org/my-app/download"regex(%r{href=.*?/(\d+)/MyApp-(\d+(?:.\d+)*).zip}i)strategy :page_match do |page, regex|match = page.match(regex)next if match.blank?"#{match[2]},#{match[1]}"end
end
在以下示例中,我们扫描主页内容以查找类似2020-01-01
的日期格式,并将其转换为20200101
。
livecheck dourl :homepagestrategy :page_match do |page|page.scan(/href=.*?example[._-]v?(\d{4}-\d{2}-\d{2}).t/i).map { |match| match&.first&.gsub(/\D/, "") }end
end
此处展示的 PageMatch
strategy
块样式同样适用于任何内部使用 PageMatch
的站点特定策略。
HeaderMatch
strategy
策略块
HeaderMatch
的 strategy
策略块会尝试从文件名(位于 Content-Disposition
头中)和最终 URL(位于 Location
头中)解析出版本号。如果此方法无效,还可以指定一个 regex
正则表达式。
livecheck dourl "https://example.org/my-app/download/latest"regex(/MyApp-(\d+(?:.\d+)*).zip/i)strategy :header_match
end
如果版本依赖于多个标头字段,则可以指定一个块。
livecheck dourl "https://example.org/my-app/download/latest"strategy :header_match do |headers|v = headers["content-disposition"][/MyApp-(\d+(?:.\d+)*).zip/i, 1]id = headers["location"][%r{/(\d+)/download$}i, 1]next if v.blank? || id.blank?"#{v},#{id}"end
end
Git
strategy
块
Git
的 strategy
块略有不同,因为该块接收的是一个标签字符串数组,而非页面内容字符串。与 PageMatch
示例类似,这里会将 2020-01-01
这类日期格式的标签转换为 20200101
。
livecheck dourl :stablestrategy :git do |tags|tags.filter_map { |tag| tag[/^(\d{4}-\d{2}-\d{2})$/i, 1]&.gsub(/\D/, "") }end
end
GithubLatest
strategy
代码块
GithubLatest
的 strategy
代码块会接收来自 GitHub API 的仓库“最新”发行版解析后的 JSON 数据,以及一个正则表达式。当 livecheck
代码块中未提供正则表达式时,策略的默认正则表达式会传入 strategy
代码块作为替代。
默认情况下,该策略会匹配发行版标签或标题中的版本文本,但 strategy
代码块可用于检查发行版 JSON 中的任何字段。以下 strategy
代码块中的逻辑与默认行为类似,但出于演示目的,仅检查发行版标签:
livecheck dourl :stableregex(/^example[._-]v?(\d+(?:.\d+)+)$/i)strategy :github_latest do |json, regex|match = json["tag_name"]&.match(regex)next if match.blank?match[1]end
end
您可以在相关 GitHub REST API 文档 中找到有关此 API 端点响应 JSON 的更多信息。
GithubReleases
的 strategy
代码块
GithubReleases
的 strategy
代码块接收来自 GitHub API 的仓库最新发布信息的解析后 JSON 数据,以及一个正则表达式。当 livecheck
代码块中未提供正则表达式时,策略的默认正则表达式会被传入 strategy
代码块。
默认情况下,该策略会匹配每个发布版本的标签或标题中的版本文本,但 strategy
代码块可用于检查发布 JSON 中的任何字段。以下 strategy
代码块中的逻辑与默认行为类似,但出于演示目的,仅检查发布标签:
livecheck dourl :stableregex(/^example[._-]v?(\d+(?:.\d+)+)$/i)strategy :github_releases do |json, regex|json.map do |release|next if release["draft"] || release["prerelease"]match = release["tag_name"]&.match(regex)next if match.blank?match[1]endend
end
该策略的默认逻辑会跳过标记为草案或预发布的版本,但可以通过使用 strategy
代码块来修改这一行为。在前面的示例中移除 release["prerelease"]
条件将允许我们处理预发布版本,不过正则表达式可能也需要调整以适应不稳定的版本格式。
有关此API端点返回的JSON响应更多信息,请参阅相关的GitHub REST API文档。
Crate
strategy
代码块
Crate
的 strategy
代码块会从注册表 API 的 versions
端点接收解析后的 JSON 数据,以及提供的或默认的策略正则表达式。默认情况下,该策略采用以下逻辑,因此这个 strategy
代码块可以作为修改方案的理想起点:
livecheck dourl :stablestrategy :crate do |json, regex|json["versions"]&.map do |version|next if version["yanked"]next unless (match = version["num"]&.match(regex))match[1]endend
end
ElectronBuilder
strategy
模块
ElectronBuilder
的 strategy
模块会从指定 URL 获取内容,并将其解析为 YAML 格式的 electron-builder 应用更新信息。该模块专用于通过 Electron 框架构建的 macOS 应用 Cask。
livecheck dourl "https://example.org/my-app/latest-mac.yml"strategy :electron_builder
end
如果需要修改版本,可以像这样访问 strategy
块中的 YAML 哈希:
livecheck dourl "https://example.org/my-app/latest-mac.yml"strategy :electron_builder do |yaml|yaml["version"]&.gsub(/\D/, "")end
end
同样,你可以像这样操作 files
数组:
livecheck dourl "https://example.org/my-app/latest-mac.yml"regex(/MyApp[._-]v?(\d+(?:.\d+)+)-(\h+).dmg/i)strategy :electron_builder do |yaml, regex|yaml["files"]&.map do |file|match = file["url"]&.match(regex)next if match.blank?"#{match[1]},#{match[2]}"endend
end
Json
strategy
块
Json
的 strategy
块接收解析后的 JSON 数据,并可选择性地接收一个正则表达式。例如,假设我们有一个包含对象数组的对象,其中每个对象都有 version
字符串,我们可以筛选出符合正则表达式的成员,并提取相关版本文本,如下所示:
livecheck dourl "https://www.example.com/example.json"regex(/^v?(\d+(?:.\d+)+)$/i)strategy :json do |json, regex|json["versions"].select { |item| item["version"]&.match?(regex) }.map { |item| item["version"][regex, 1] }end
end
Sparkle
strategy
代码块
Sparkle
的 strategy
代码块会接收一个 item
对象,该对象包含以下方法:version
、short_version
、nice_version
、url
、channel
和 title
。它需要一个 XML 订阅源的 URL,该订阅源为使用 Sparkle 框架实现自更新的 macOS 应用程序提供版本信息。此 URL 可以在应用包内的 Contents/Info.plist
文件中通过 SUFeedURL
属性找到,或者使用 find-appcast
命令获取。运行方式如下:
brew find-appcast '/path/to/application.app'
Sparkle
策略的默认模式是:如果同时设置了sparkle:shortVersionString
和sparkle:version
,则从中生成"#{item.short_version},#{item.version}"
。在下面的示例中,返回值还包含从url
获取的必要下载ID:
livecheck dourl "https://www.example.com/example.xml"strategy :sparkle do |item|"#{item.short_version},#{item.version}:#{item.url[%r{/(\d+)/[^/]+.zip}i, 1]}"end
end
要仅使用其中一个版本,请指定 &:version
、&:short_version
或 &:nice_version
:
livecheck dourl "https://www.example.com/example.xml"strategy :sparkle, &:short_version
end
如果item
返回的值不是最新的或不符合预期,可以改用items
来遍历订阅源中的所有条目:
livecheck dourl "https://www.example.com/example.xml"strategy :sparkle do |items|items.find { |item| item.channel.nil? }&.short_versionend
end
Xml
strategy
块
Xml
的 strategy
块接收一个 REXML::Document
对象,并可选择性地接收一个正则表达式。例如,如果 XML 中包含带有嵌套 version
元素的 versions
元素,且其内部文本包含版本字符串,我们可以通过如下正则表达式来提取它:
livecheck dourl "https://www.example.com/example.xml"regex(/v?(\d+(?:.\d+)+)/i)strategy :xml do |xml, regex|xml.get_elements("versions//version").map { |item| item.text[regex, 1] }end
end
如需了解如何操作 REXML::Document
对象,请参阅 REXML::Document
和 REXML::Element
文档。
Yaml
strategy
代码块
Yaml
的 strategy
代码块接收解析后的 YAML 数据,并可选择性地接收一个正则表达式。沿用 Json
的示例,如果我们有一个包含对象数组的对象,其中每个对象都有 version
字符串字段,我们可以通过以下方式筛选出符合正则表达式的成员,并提取出相关的版本文本:
livecheck dourl "https://www.example.com/example.yaml"regex(/^v?(\d+(?:.\d+)+)$/i)strategy :yaml do |yaml, regex|yaml["versions"].select { |item| item["version"]&.match?(regex) }.map { |item| item["version"][regex, 1] }end
end
ExtractPlist
strategy
代码块
如果无法通过在线方式检查 macOS 软件包的当前版本,作为最后手段,:extract_plist
策略会让 brew livecheck
下载该软件包,并从包含的 .plist
文件中提取版本字符串。
livecheck dourl :urlstrategy :extract_plist
end
ExtractPlist
的 strategy
块接收一个哈希,其中包含每个已发现 bundle identifier 的键,以及带有各 version
和 short_version
方法的 item
。
livecheck dourl :urlstrategy :extract_plist do |items|items["com.example.MyApp"].short_versionend
end
请注意,如果某个软件包使用了此实时检查策略,它将被排除在自动更新之外,因为该策略会对CI时间产生负面影响。
throttle
对于发布极其频繁但并非所有版本都需要作为 formula/cask 更新发布的软件,可以通过设置 throttle
来限制 livecheck 返回的版本数量。在此示例中,只有最后一位数字能被 10 整除的版本才会被返回。
livecheck dourl :stableregex(/^v?(\d+(?:.\d+)+)$/i)throttle 10
end
skip
Livecheck 会自动跳过某些 formula/cask,原因多种多样(如已弃用、禁用等)。但在极少数情况下,我们需要使用 livecheck
块来手动跳过。skip
方法接受一个字符串参数,用于简要说明跳过原因。
livecheck doskip "No version information available"
end
自动版本更新
https://docs.brew.sh/Autobump
在官方仓库中,BrewTestBot会自动检查Homebrew"自动更新列表"中软件包的可用更新。这些软件包不需要贡献者手动进行版本号升级(即bump操作)。相反,如果需要更新,GitHub Action会每3小时自动开启一个新的pull request来将它们升级到最新版本。
从自动更新中排除软件包
默认情况下,来自 Homebrew/core 和 Homebrew/cask 仓库的所有新 formula 和 cask 都会自动更新。若要将某个软件包排除在自动更新列表之外,必须满足以下任一条件:
- 存在活跃的
deprecate!
或disable!
调用 - 包含
skip
调用的livecheck do
代码块 - 存在
no_autobump!
调用
其他导致软件包不被自动更新的 formula 和 cask 特定原因,请分别参阅 Formula 手册 和 Cask 手册。
自动升级排除原因
使用 no_autobump!
时,必须提供排除原因。
有两种方式可以指定原因。推荐的方法是使用预定义的符号,这些符号可以在 NO_AUTOBUMP_REASONS_LIST
中找到,例如:
no_autobump! because: :bumped_by_upstream
如果这些预设原因不适用,可以指定自定义原因:
no_autobump! because: "some unique reason"
如果有多个软件包具有类似的定制原因,可以将其作为新符号添加到 NO_AUTOBUMP_REASONS_LIST
中。
将 Formula 迁移到 Tap
https://docs.brew.sh/Migrating-A-Formula-To-A-Tap
有时我们可能需要将某个 formula 从一个 tap 迁移到另一个 tap。具体操作步骤如下:
- 在新 tap 上创建拉取请求,直接添加原 tap 中的 formula 文件。修复可能因新 formula 比现有 formula 要求更严格而导致的测试失败(例如该 formula 必须通过
brew audit --strict
检查)。 - 在原 tap 上创建拉取请求,删除 formula 文件并将其添加到
tap_migrations.json
中,提交信息类似gv: migrate to homebrew/core
。 - 在每个拉取请求中附上另一个拉取请求的链接,以便维护者可以同时合并这两个请求。
恭喜,你已成功将 formula 迁移到另一个 tap!
对于 Homebrew 维护者来说,formula 只应迁移到 Homebrew 组织内部(例如从 homebrew/core
迁移到 homebrew/cask
,或从第三方 tap 迁移到 homebrew/core
),而不能迁移出该组织。
重命名 Formula
https://docs.brew.sh/Rename-A-Formula
有时软件和 Formula 需要重命名。要重命名 Formula,你需要:
- 将 Formula 文件及其类重命名为新的 Formula 名称。新名称必须符合所有常规的 Formula 命名规则。修复由于新 Formula 比现有 Formula 要求更严格而可能出现的测试失败(例如,该 Formula 必须通过
brew audit --strict
检查)。 - 在对应的 tap 上创建一个 pull request,删除旧的 Formula 文件,添加新的 Formula 文件,并将其添加到
formula_renames.json
中,提交信息类似newack: renamed from ack
。使用规范名称(例如ack
而不是user/repo/ack
)。
以下是 Formula 重命名的 formula_renames.json
示例:
{"ack": "newack"
}
构建非Homebrew依赖项
https://docs.brew.sh/Building-Against-Non-Homebrew-Dependencies
(注:根据翻译原则,标题中的"Homebrew"作为专有名词保留不译,并保持Markdown标题格式)
历史沿革
最初 Homebrew 是一个基于源码构建的包管理器,所有用户环境变量和非 Homebrew 安装的软件都能被构建过程访问。随后 Homebrew 逐步引入了以下机制:
- 通过
Requirement
来声明对非 Homebrew 软件(如brew cask
提供的 X11/XQuartz)的依赖 - 采用
superenv
构建系统来剔除未声明的依赖项 - 实施环境过滤机制防止用户环境变量渗入构建过程
- 通过
default_formula
指定某个Requirement
可由特定 formula 满足
随着 Homebrew 发展为以二进制包管理为主,大多数用户开始通过 default_formula
而非任意替代方案来满足依赖要求。为提升质量并减少差异,当前 Homebrew 仅支持将默认 formula 作为常规依赖项使用,不再支持任意替代方案。
今日指南
若您希望基于 Homebrew 提供的自定义非 Homebrew 依赖项(例如非 Homebrew、非 macOS 系统的 ruby
)进行构建,则必须创建并维护自己的 tap,因为这些公式将不会被 Homebrew/homebrew-core 仓库接受。
完成此操作后,您可以在公式中指定 env :std
,这将允许诸如 which ruby
等命令访问您现有的 PATH
变量,并使编译过程能够链接到该 Ruby 版本。
此外,您还可以在公式中引入自定义 Requirement,以更精确地描述您所依赖的非 Homebrew 软件。
如何创建和维护一个 Tap
https://docs.brew.sh/How-to-Create-and-Maintain-a-Tap
Taps 是 Homebrew 公式、casks 和/或外部命令的外部来源。任何人都可以创建 Tap,向任何 Homebrew 用户提供自己的公式、casks 和/或外部命令。
创建 Tap
Tap 通常是一个在线的 Git 仓库,但你可以使用任何 Git 能识别的协议,甚至只是一个包含文件的目录。如果托管在 GitHub 上,我们建议仓库名称以 homebrew-
开头,这样就可以使用简短的 brew tap
命令。更多关于仓库命名的信息,请参阅 brew
手册页。
brew tap-new
命令可用于创建一个新的 Tap 并附带一些模板文件。
Tap 中的 formula 遵循与核心 formula 相同的格式,可以添加到 Formula
子目录、HomebrewFormula
子目录或仓库的根目录下。系统会使用第一个可用的目录,其他位置将被忽略。我们建议使用子目录,因为这使得仓库结构更易于理解,并且顶层文件不会与 formula 混在一起。
有关带有 Formula
子目录的 Tap 示例,请参阅 homebrew/core。
为公式命名以避免冲突
如果你的 tap 中的某个公式与 Homebrew/homebrew-core 中的公式同名,它们将无法同时安装。如果你想创建一个与 Homebrew/homebrew-core 中公式不同版本的公式(例如通过 option
s 实现),建议为其赋予不同的名称;例如,为功能更全面的 nginx
公式命名为 nginx-full
。这样就能同时安装 nginx
和 nginx-full
(前提是其中一个被标记为 keg_only
或链接文件不会冲突)。
安装指南
如果您的配方(formula)托管在 GitHub 上,用户可以通过 brew install user/repo/formula
命令安装任意配方。
Homebrew 会在安装前自动添加您的 github.com/user/homebrew-repository
仓库作为 tap 源。
这里的 user/repo/formula
指向的是 github.com/user/homebrew-repo/**/formula.rb
文件。
若用户希望仅添加您的 tap 源而不立即安装任何配方,可以使用 brew tap
命令。
对于 GitHub 托管的仓库,只需执行 brew tap user/repository
,其中 user
是您的 GitHub 用户名,homebrew-repository
是您的仓库名。
若 tap 源托管在 GitHub 之外,则需使用 brew tap user/repo <URL>
命令,其中 user
和 repository
将作为 tap 源的引用名称,<URL>
是 Git 克隆地址。
添加 tap 源后,用户可通过以下方式安装配方:
- 当核心配方库不存在同名配方时,使用
brew install foo
- 为避免命名冲突,使用
brew install user/repo/foo
指定来源安装
维护一个 tap
tap 本质上就是一个 Git 仓库,因此在修改时无需特殊操作,只需提交并推送变更即可。
更新
安装好你的 tap 后,每当用户运行 brew update
时,Homebrew 都会自动更新它。当用户运行 brew upgrade
时,过时的 formula 会像核心 formula 一样被升级。
Casks
Casks 也可以通过 tap 进行安装。Casks 可以与 formulae 一起存放在 tap 中,也可以单独存放在仅包含 casks 的 tap 中。若要将任意 cask 文件提供给用户使用,请将其放置在 tap 根目录下的 Casks
目录中。
参考 homebrew/cask 示例,这是一个包含 Casks
子目录的 tap。
命名规范
与公式不同,Cask 必须具有全局唯一的名称以避免冲突。可以通过在 Cask 名称前添加您的 GitHub 用户名来实现这一点,例如:username-formula-name
。
外部命令
您可以通过在 cmd
子目录中添加自定义命令,为您的 tap 用户提供特定的 brew
命令。了解更多关于外部命令的信息。
参考 Homebrew/test-bot 查看一个包含外部命令的 tap 示例。
上游仓库(Upstream taps)
部分上游软件供应商倾向于将他们的软件打包在自己的 Homebrew 仓库中。当这些软件符合 Homebrew/homebrew-core 的收录标准 时,我们更倾向于将其维护在核心仓库中,以便于更新、提升可发现性,并利用 formulae.brew.sh 等工具。
我们不会为了迁就上游仓库而移除 Homebrew/homebrew-core 中已打包的软件,也不会引导用户转而使用上游仓库。如果上游项目对 Homebrew 的打包方式存在异议:请提交 issue(或更理想的方式是直接提交 pull request)来解决这些问题。
商业开源领域日益流行一种"保持控制权"的理念,例如精确控制分发给用户的二进制文件。
不支持用户(甚至软件发行版)从源码构建的做法,与开源价值观背道而驰。
如果你认为 Homebrew 在这方面的立场令人不快:不妨看看 Debian 是如何应对要求分发预编译二进制文件的请求的。
BrewTestBot
https://docs.brew.sh/BrewTestBot
brew test-bot
是运行自动化审查和测试系统的工具,该系统由我们2013年在Kickstarter上的众筹项目资助。
该系统包含三台托管虚拟机的Mac Pro主机、多台Intel和M1芯片的Mac mini以及若干云端Ubuntu实例,它们通过运行外部命令test-bot.rb
来执行软件包构建,并对Homebrew及其代码库的任何变更进行全生命周期的自动化测试。
拉取请求
该机器人会自动构建拉取请求,并根据任务结果更新其状态。
例如,一个已排队但尚未完成的任务会在拉取请求中显示如下部分:
构建失败时显示如下:
构建成功并通过合并审批时显示如下:
对于失败或成功的构建,您可以点击每个检查项的"Details"链接,在 GitHub Actions 中查看其输出详情。
文档风格指南
https://docs.brew.sh/Prose-Style-Guidelines
本文档为Homebrew面向用户、贡献者和维护者的文字内容(非可执行代码)提供了一套风格和使用规范。适用于Homebrew/brew
代码库中docs
目录下的文档、公告邮件,以及与其他Homebrew社区成员的沟通内容。
本规范不适用于任何Ruby或其他编程语言的代码。虽然您可以参考这些规范来处理从代码中提取的技术文档(如嵌入式手册页),但这仅作为建议使用。
(注:根据核心翻译原则,保留所有代码标记docs
、Homebrew/brew
和Ruby等专有名词,删除多余的HTML标签,并保持Markdown格式的简洁性)
目标与受众
Homebrew 文档的首要目标是与其用户及贡献者社区进行有效沟通。此处的“用户”包含“贡献者”——凡提及“用户”时均可理解为“用户与贡献者”。
可理解性高于任何具体的格式规范。
除非文档明确面向维护者,否则用户需求优先于维护者需求。
Homebrew 的受众涵盖不同教育背景和经验水平的用户,也包括非英语母语者。我们致力于为尽可能多的用户提供支持。
我们追求“准确”而非“华丽”的表达方式,应以新闻报道而非学术论文为标准。
本指南是一套需要人工判断的柔性规范,而非铁律。它更接近《加纳现代美语用法指南》的风格,而非《Ruby 风格指南》。所有条款均可讨论和灵活解释,不要求100%机械遵循。
本文档旨在帮助作者把握行文的清晰度、风格和一致性,而非用于争论谁的英语更好。请勿借此文档刁难他人。
指南
我们倾向于:
风格与用法
- 总体上优先使用英式/英联邦英语而非美式英语
- "e.g.“和"i.e.”:可以直接使用"e.g."或"i.e."而不用完整拼写。不必在它们后面加逗号。
- "e.g.“表示"例如”;"i.e.“表示"即”
- 用逗号分隔非简单从属子句
人称代词使用准则
- 我们尊重所有人对个人代词的自主选择
- 当性别未知时,使用单数形式的"they"
- 在非必要情况下避免使用性别特定用语
(注:根据核心翻译原则,保留原文的markdown标题层级符号###和列表格式*,对英文内容进行本地化转换,同时保持技术文档的简洁严谨性。被动语态"is unknown"主动化为"未知",并采用符合中文阅读习惯的短句结构)
结构与标记规范
h1
标题采用首字母大写格式;其余标题使用句子格式(仅首字母大写)- 当列表中多数条目为完整句子时,需在条目末尾添加句号
- 更通用的原则是保持列表条目结构平行
- 即使条目不是完整句子,也可统一采用首字母大写格式,但需确保单列表内及整页风格一致
- 若某条目包含多句段落,应使用次级列表项,而非将其混入句子片段构成的列表中
- 优先使用Markdown而非其他标记格式,除非需要特定功能
- GitHub风味Markdown是唯一标准,GitHub的实现即为规范
- 链接到其他文档时,使用相对于Markdown文件名的相对路径而非完整URL
- 例如:
FAQ.md
而非https://docs.brew.sh/FAQ
- 例如:
(注:保留所有代码块及格式标记符,严格遵循核心翻译原则)
排版规范
- 命令和代码中的字面文本使用
等宽字体
样式 - 代码片段中的占位符用
<...>
括号标记- 例如:
git remote add <my-user-name> https://github.com/<my-user-name>/homebrew-core.git
- 例如:
- 命令名称如
git
和brew
使用等宽字体
样式 - 在代码片段外提及环境变量时不带"$"符号
- 例如:“设置
BLAH
为5”,而非"设置$BLAH
为5"
- 例如:“设置
- 句号后使用一个空格,而非两个
- 专有名词首字母大写
- 我们不会过度使用非标准的大写、排版或其他品牌名称样式,仅遵循专有名词的正常大写规则和简单的内部大写
- 不使用"TM"、™、SM、©、®或其他显式的权利所有权或商标标识;我们认为在提及品牌名称时这些已隐含理解
- 仓库名称如
homebrew/core
使用等宽字体
样式。仓库名称可以视情况采用以下任一形式:- 等宽字体样式如"
Homebrew/homebrew-core
" - 链接形式如"Homebrew/homebrew-core"
- 普通文本如"Homebrew/homebrew-core"
- 但在单个文档中需保持一致性
- 仓库名称大写需与GitHub上的用户和仓库名称匹配。仓库名称保持小写。
- 等宽字体样式如"
- 逗号使用规范
- 不使用牛津逗号
- 采用"宽松"的逗号风格:“如有疑问,直接省略”,除非需要用于明确表达
术语、词汇及书写规范
- 使用“pull request”,而非“Pull Request”
- “check out”作动词使用;“checkout”作名词使用
- 特定技术词汇需完整拼写
- 使用“repository”,而非“repo”
- 若需缩写,应在文档首次出现该术语时注明缩写形式
- 但部分缩写(用户群体普遍理解的)可直接使用
- 可使用“Mac”,无需写成“Macintosh”
- 通用版本使用“macOS”,描述特定旧版本时使用“OS X”或“Mac OS X”
- 使用“RuboCop”,而非“Rubocop”
- 提交pull request时用“on”修饰代码库;代码库地址用“at”修饰URL
如何使用这些指南
在撰写 Homebrew 相关文档和沟通内容时,请参考本指南来做出格式与用词风格的决定。
我们欢迎并鼓励提交涉及整篇或多篇文档风格修正的 PR(拉取请求)。但仅针对一两处风格调整的 PR 则显得过于琐碎。
在涉及文档的 PR 或提交中提供格式与用词反馈是合适且受鼓励的。但请注意这些仅是指导原则——作者可能出于提升可读性或美观性的考虑,特意选择突破某些规则。
使用 Sorbet 进行类型检查
https://docs.brew.sh/Typechecking
Homebrew 中的大部分代码采用动态语言 Ruby 编写。为了获得静态类型检查的优势,我们在代码库中配置了 Sorbet,它能给 Ruby 这类动态语言带来静态类型检查的能力。
若想深入了解 Sorbet 及其功能,Sorbet 官方文档是很好的入门资料。
Homebrew 代码库中的 Sorbet
行内类型注解
sig
方法用于标注方法签名。这里有一个简单的示例:
class MyClasssig { params(name: String).returns(String) }def my_method(name)"Hello, #{name}!"end
end
通过params
我们声明了一个必须为String
类型的参数name
,而returns
则表明该方法始终返回一个String
类型值。
如需了解如何表达更复杂的类型,请参阅官方文档:
-
方法签名
-
类类型
-
可空类型
-
联合类型
Ruby 接口文件 (.rbi
)
RBI 文件 能帮助 Sorbet 识别那些它无法原生理解的常量、继承关系和方法定义。我们也可以创建 RBI 文件来协助 Sorbet 理解动态定义。这些文件部分由系统自动生成(参见下一节),部分为手动编写,例如 on_system.rbi
。
此外,Homebrew 会在加载 sorbet-runtime
之前预先加载极少数文件(如 utils/gems.rb
)。由于这些文件无法直接在代码旁添加类型签名,因此改用 RBI 文件来维持静态类型检查功能。
Library/Homebrew/sorbet
目录
rbi
目录包含所有通过运行 brew typecheck --update
自动生成的 Ruby 接口文件(.rbi
):
gems
:使用 Tapioca 为所有 gem 生成的 RBI 文件。dsl
:由我们的 Tapioca 编译器 自动生成的 RBI 文件。upstream.rbi
:该文件为手动编写,包含针对上游 Sorbet 问题的临时解决方案。通常为空文件。
tapioca
目录包含 Tapioca 的配置文件和编译器,使 Sorbet 能够对代码库中动态生成的组件进行类型检查。
config
文件是一个以换行符分隔的参数列表,用于传递给 srb tc
,效果与通过命令行传递相同。配置文件中的参数总是优先传递,其后才是命令行提供的参数。我们通过该文件忽略不希望进行类型检查的目录(例如 gem 目录)。
使用 brew typecheck
代码库中的每个 Ruby 文件顶部都有一个特殊的 # typed: <level>
注释,其中 <level>
是 Sorbet 的严格级别之一,通常为 false
、true
或 strict
。标记为 false
的文件仅报告与语法、常量解析和方法签名正确性相关的错误,但不报告类型错误。我们的长期目标是将所有 false
文件升级为 true
,并开始在这些文件中报告类型错误。因此,在添加新文件时,理想情况下应标记为 # typed: true
并解决由此产生的任何类型错误。
当不带任何参数运行时,brew typecheck
会根据 Homebrew 核心代码库中各个 Ruby 文件设置的严格级别进行检查。然而,当针对特定文件或目录运行时,可能会显示更多错误,因为 Sorbet 无法解析指定文件范围外定义的常量。这些问题可以通过 RBI 文件解决。目前 brew typecheck
提供 --quiet
、--file
、--dir
和 --ignore
选项,但您可以通过 srb tc --help
探索更多选项,并通过 srb tc
传递这些选项。
解决类型错误
Sorbet 会报告类型错误并附带一个错误参考代码,可用于在 Sorbet 文档 中查找更多关于如何调试错误或错误原因的信息。以下是调试一些常见类型错误的方法:
-
使用
T.reveal_type
:在true
或更高严格级别的文件中,通过将变量或方法调用包裹在T.reveal_type
中,Sorbet 会在srb tc
的输出中显示它认为该变量的类型。这在编写 方法签名 和调试时特别有用。请确保在提交代码更改之前删除此行,因为这只是一个调试工具。 -
我们遇到的最常见错误之一是
7003: Method does not exist.
。由于 Ruby 是一种非常动态的语言,方法可能以 Sorbet 无法静态看到的方式定义。在这种情况下,检查方法在运行时是否存在;如果不存在,那么 Sorbet 已经捕获了一个未来的错误!但是,也有可能即使方法在运行时存在,Sorbet 也无法看到它。在这种情况下,我们使用.rbi
文件。 -
由于 Sorbet 不会自动假设
Kernel
被包含在模块中,我们在尝试使用puts
、ohai
、odebug
等方法时可能会遇到许多错误。通常有两种方法可以解决这个问题:- 如果你使用
module_function
但从未在任何地方运行include ModuleName
,请删除module_definition
并将所有方法转换为类方法(在名称前加上self.
)。 - 如果你在其他地方包含了该模块,请向模块添加一个
requires_ancestor
,定义该模块可以包含在哪些类型的类中。这可能像requires_ancestor { Kernel }
一样简单,大多数类都继承自Kernel
。
- 如果你使用
-
以上提示非常通用,适用于许多情况。有关使用 Sorbet 时的一些常见陷阱,请参阅 Sorbet 错误参考 和 常见问题解答。
可复现构建
https://docs.brew.sh/Reproducible-Builds
Homebrew 的构建环境在设计时尽可能以可复现构建为目标。同时为配方作者提供了一些便利工具,帮助实现确定性构建。
构建时间
某些构建工具会嵌入或记录构建发生的时间。这可能导致相同源代码的重复构建产物出现差异。为避免此问题,Homebrew 构建环境为使用该变量的工具设置了 SOURCE_DATE_EPOCH
环境变量,其值为源代码的修改时间。
若需手动设置构建时间,可使用 Ruby 的 DateTime
对象 time
,该对象包含与 SOURCE_DATE_EPOCH
环境变量相同的时间戳。随后可利用 Ruby 为 DateTime
对象提供的方法,将此时间格式化为所需形式。
def installsystem "make", "install","VERSION=#{version}","DATE=#{time.iso8601}","PREFIX=#{prefix}"
end
请参考 kustomize
公式示例了解如何使用 time.iso8601
,或查看 git-town
公式示例了解如何搭配自定义格式说明符使用 time.strftime
。
可复现的 gzip 压缩
某些公式在构建过程中可能会生成 gzip 压缩文件(例如压缩 man 手册页或其他数据文件)。不同的构建机器可能提供不同实现的 gzip
工具,且默认情况下 gzip
会记录被压缩文件的修改时间(该时间通常随构建时间而变化)。因此,依赖构建机器的 gzip
工具通常会导致构建产物中包含不可复现的输出。
为避免此问题,Homebrew 提供了 Utils::Gzip.compress
辅助函数,专门用于需要可复现 gzip 压缩的场景。该函数接受一个或多个待压缩路径,并将压缩后的文件(添加 .gz
后缀)放置在原始文件旁,其行为与 gzip
工具一致。同时,该函数会返回一个由 Pathname
对象组成的数组,可供其他方法调用。
def installsystem "make", "install"man1.install Utils::Gzip.compress("mycommand.1")
end
def installsystem "make", "install"(pkgshare/"data").install Utils::Gzip.compress(*Dir["#{buildpath}/path/to/some/folder/contents/*"])
end
参考 par
公式 查看单文件示例,或 pari-elldata
公式 查看多文件示例。
可重定位性
某些公式或构建工具会在配置文件或二进制文件中记录特定于构建环境的路径。在构建可重新分发的 bottles 时,Homebrew 会扫描已构建的文件,并将常见的 Homebrew 路径(如 Homebrew 前缀和 Cellar)替换为占位符,例如 @@HOMEBREW_PREFIX@@
或 @@HOMEBREW_CELLAR@@
。当安装这些 bottles 时,Homebrew 会将这些占位符展开为最终用户机器上的实际路径。
这一机制使得部分 bottles 能够被那些将 Homebrew 安装在非默认前缀路径下的用户使用。同时,它还确保了在不同平台之间,当 Homebrew 安装位置是唯一差异时,生成的 bottles 能够保持逐比特级别的完全一致。
此搜索替换过程会自动执行,公式作者无需采取任何额外操作即可使用该功能。
新维护者检查清单
https://docs.brew.sh/New-Maintainer-Checklist
现有维护者和项目领导团队使用本指南来邀请和引导新的维护者及项目负责人。
普通 Homebrew 用户可能会觉得有趣,但这里的内容并非用户必须了解的。
- Homebrew 维护者
- 项目领导委员会
- 技术指导委员会
- 所有者
- 普通成员
维护者
如果有人持续为 Homebrew 做出高质量的贡献,并且展现出能完成比单纯更新 formula 更高级的贡献能力?那就邀请他们成为维护者吧!
首先,向他们发送邀请邮件:
The Homebrew team and I really appreciate your help on issues, pull requests and
your contributions to Homebrew.We would like to invite you to have commit access and be a Homebrew maintainer.
If you agree to be a maintainer, you should spend the majority of the time you
are working on Homebrew (in descending order of priority):
- reviewing pull requests (from users and other maintainers)
- triaging, debugging and fixing user-reported issues and applying
- opening PRs for widely used changes (e.g. version updates)You should also be making contributions to Homebrew at least once per quarter.You should watch or regularly check Homebrew/brew and/or Homebrew/homebrew-core
and/or Homebrew/homebrew-cask. Let us know which so we can grant you commit
access appropriately.If you're no longer able to perform all of these tasks, please continue to
contribute to Homebrew, but we will ask you to step down as a maintainer.A few requests:
- Please make pull requests for any changes in the Homebrew repositories (insteadof committing directly) and don't merge them unless you get at least one approvaland passing tests.
- Please review the Maintainer Guidelines at https://docs.brew.sh/Maintainer-Guidelines
- Please review the team-specific guides for whichever teams you will be a part of.Here are links to these guides:- Homebrew/brew: https://docs.brew.sh/Homebrew-brew-Maintainer-Guide- Homebrew/homebrew-core: https://docs.brew.sh/Homebrew-homebrew-core-Maintainer-Guide- Homebrew/homebrew-cask: https://docs.brew.sh/Homebrew-homebrew-cask-Maintainer-Guide
- Create branches in the main repository rather than on your fork to ease collaborationwith other maintainers and allow security assumptions to be made based on GitHub access.
- If still in doubt please ask for help and we'll help you out.
- Please read:- https://docs.brew.sh/Maintainer-Guidelines- the team-specific guides linked above and in the maintainer guidelines- anything else you haven't read on https://docs.brew.shHow does that sound?Thanks for all your work so far!
如果他们接受邀请,请按以下步骤完成设置:
-
邀请他们加入 @Homebrew/maintainers 团队(或相关子团队),授予其对相关仓库的写入权限(但不要设为所有者)。他们需要启用 GitHub 双重认证。
-
邀请他们作为正式成员加入
machomebrew
私有 Slack(并确保其已阅读沟通准则),要求他们在 Slack 中使用真实姓名(而非 GitHub 等平台可能使用的昵称)。 -
要求他们禁用 GitHub 账户中的短信双重认证或备用验证方式,改用其他认证方法。
-
要求他们(定期)检查并移除不必要的 GitHub 个人访问令牌。
-
启动流程将其添加为 Homebrew 成员,以获得正式投票权及担任 Homebrew 职务的资格。
若遇到问题,可请其退出维护者身份。
当维护者因任何原因离职时,需撤销其上述所有访问权限。
为松散验证维护者身份并增进团队情谊,若您与其他 Homebrew 维护者同处一地(如居住、旅行或参加会议),应主动安排会面。会面产生的餐费可申请报销(需符合Homebrew 报销政策)。此政策借鉴了 Debian 等项目更严格的线下身份核验机制,但执行更为宽松。
现在您可以放松休息,让新维护者处理更多贡献了。
PLC
如果某位维护者或成员当选为Homebrew的项目领导委员会成员:
- 邀请其加入@Homebrew/plc团队
- 在Homebrew GitHub组织中将其设为账单管理员和版主
- 邀请其访问
homebrew
私有1Password并加入"plc"群组
当其不再担任PLC成员时,撤销或降级其对上述所有资源的访问权限。
TSC(技术指导委员会)
如果某位维护者当选为 Homebrew 的技术指导委员会成员:
- 将其加入 @Homebrew/tsc 团队
- 在 Homebrew 的 GitHub 组织中授予其账单管理员和版主权限
当其不再担任 TSC 成员时,需撤销或降级其所有上述权限。
所有者
项目负责人、另一名PLC成员(最好是维护者)以及另一名TSC成员应被设为GitHub和Slack的所有者:
- 在Homebrew GitHub组织中将其设为所有者
- 在
machomebrew
私有Slack中将其设为所有者 - 在
homebrew
私有1Password中将其设为所有者
当其不再担任所有者时,需撤销或降级他们在上述所有平台的访问权限。
成员
那些不符合或不愿意成为 Homebrew 维护者,但持续参与 Homebrew 社区活动的人,经项目领导委员会多数投票通过后,可被接纳为成员。
成为成员后需进行以下操作:
- 邀请他们作为单频道访客加入
machomebrew
私有 Slack 的 #members 频道(并确保其已阅读沟通准则),要求他们在该频道使用真实姓名(而非在 GitHub 等平台可能使用的化名)。 - 将其加入治理档案中当前年度的成员名单。
若成员有意参与运维/基础设施/系统管理工作:
- 邀请其加入
homebrew
私有 1Password 并添加至“ops”组。
若成员有意参与安全工作:
- 邀请其加入
homebrew
私有 1Password 并添加至“security”组。
关于成员资格的有效期,请参阅 Homebrew 治理章程。
维护者:避免过度疲劳
https://docs.brew.sh/Maintainers-Avoiding-Burnout
本指南面向项目维护者。这些特殊成员拥有对 Homebrew 代码库的写入权限,并协助合并他人的贡献。您可能会觉得本文内容很有趣,但它显然并不适合所有人。
1. 使用 Homebrew
Homebrew 的维护者应当定期使用它。这不仅是由于只有设身处地为用户着想才能成为优秀的维护者,还因为如果你决定不再使用 Homebrew,那么此时你也应当决定不再担任维护者,转而投入其他工作。
2. 无负担退出
所有维护者都可以随时停止为Homebrew工作,无需感到愧疚或作出解释(就像离职一样)。即使在你离开后,我们可能仍会就某些问题寻求你的帮助,但你完全没有义务必须回应。如同职场规则,如果你留下一堆烂摊子然后离开,虽然你依然没有义务处理,但我们可能会对你的评价降低(或者更现实地说,可能直接回退有问题的提交)。就像工作一样,你或许应该每年至少休息几次,暂时离开Homebrew。
这也意味着贡献者首先应该是使用者。如果某位所有者发现自己并未在实际中使用该项目,就应该重新考虑是否继续参与项目。
3. 维护者优先于用户
以用户为中心固然重要,但归根结底,只要你遵循上述第一条原则,Homebrew 的最低用户数量就是维护者的人数。然而,如果 Homebrew 没有维护者,它很快就会对所有用户变得毫无用处,项目也将消亡。因此,任何用户的抱怨、行为或需求都不能凌驾于维护者的倦怠之上。如果用户不喜欢项目的方向,影响项目的最简单方法就是做出重大、高质量的代码贡献并成为维护者。
4. 学会拒绝
Homebrew 会收到大量功能请求、无法复现的错误报告、使用问题以及我们不会接受的 PR。一旦意识到这些问题无法解决或合并,就应该立即关闭。这比经过长时间审核后再做决定更为友善。我们的问题跟踪器应该只反映待完成的工作。
5. 放慢节奏
我们是一个由志愿者运营的开源项目,拥有大量用户。这可能会让人感觉必须尽快完成修复、打包或项目,压力很大。请试着不要被这种压力影响;放慢脚步,专注于让 Homebrew 保持乐趣。如果有疑问:充分沟通你的决策过程。现在可以随时回退,明天再妥善修复。
感谢 https://gist.github.com/ryanflorence/124070e7c4b3839d4573 对本文档的启发。
维护者指南
https://docs.brew.sh/Maintainer-Guidelines
本指南面向维护者。这些特殊成员拥有对 Homebrew 代码库的写入权限,并协助合并他人的贡献。你可能会觉得这里写的内容很有趣,但这绝对不是新手入门指南。
也许你想找的是配方手册或Cask 手册?
概述
我们鼓励所有 Homebrew 维护者为项目的各个部分做出贡献,但维护者通常主要归属于以下四个团队:
brew
维护者团队:该团队负责维护Homebrew/brew
代码库。有关成为brew
维护者的更多详情,请参阅 Homebrew/brew 维护者指南。- 核心维护者团队:该团队负责维护
Homebrew/homebrew-core
代码库。有关成为核心维护者的更多详情,请参阅 Homebrew/homebrew-core 维护者指南。 - Linux 维护者团队:该团队负责在 Linux 系统上维护
Homebrew/homebrew-core
代码库。 - Cask 维护者团队:该团队负责维护
Homebrew/homebrew-cask
代码库。有关成为 Cask 维护者的更多详情,请参阅 Homebrew/homebrew-cask 维护者指南。
这些文档旨在作为指导原则。作为维护者,您可以根据贡献者的熟悉程度和过往贡献,决定是请求其修改还是直接提供帮助。请记住,作为团队,我们坚持维护者优先于用户的原则以避免过度消耗。若您希望修改或讨论任何指南条款,可通过提交 PR 来建议变更。
代码审查流程
在审查PR时,请根据以下准则选择"批准"、“带评论批准”、“评论"或"请求更改”:
- ✅ 批准:如果你认为PR当前状态良好,可以直接✅批准。
- ✅ 带评论批准:如果有几个需要解答的问题或建议,且这些问题解决后PR即可合并,可以选择✅带评论批准。
- 请相信其他维护者会在问题解决前不会合并该PR,不必让作者等待24小时获取二次审查。
- 如果启用了自动合并:无需担心,PR需要手动解决所有评论后才会自动合并。
即使其他人仅发表了评论,在作者解决评论前不会合并,你仍可放心使用✅批准或✅带评论批准。
- 🗣️ 评论:如果你需要先提问才能决定是否批准,但允许他人在你之前批准,请使用评论审查并提出问题。
- 🚫 请求更改:最后手段。
- 审查非维护者的PR时:表示"必须完成这些修改才能合并"。
其他维护者可在修改完成后撤销该审查。 - 审查其他维护者的PR时:应尽量避免使用。
- 仅用于"如果在我个人批准前合并该PR,极可能导致用户可见问题"的情况。
- 项目负责人可用于表示"此功能在Homebrew中不可接受"。
此时:应暂停代码修改,直到达成功能可接受的共识。
- 审查非维护者的PR时:表示"必须完成这些修改才能合并"。
相关说明:
- 默认选项应为✅批准(无论是否带评论)。
- 尽可能使用GitHub的"建议"功能直接编辑你期望的代码样式。
如果你没时间或不愿操作:可能你也没有足够时间进行完整审查。 - 我们是全球化分布式团队,这有助于加快开发速度。
- PR审查主要是安全措施,而非在合并前追求完美代码风格的共识过程。
- 批准后仍可修改PR,通过后续PR解决评论,或在发布标签前回滚PR。
- 未经批准几乎不可能合并PR。
- 使用
gh pr checkout <URL>
命令(GitHub CLI)能极简地检出PR分支。
使命
Homebrew 致力于成为 macOS(和 Linux)上缺失的软件包管理器。其主要目标是尽可能服务更多用户,同时由一小群志愿者以专业的高标准进行可持续维护。在可行且合理的情况下,应优先利用 macOS 的特性来融入 macOS 和 Apple 生态系统。在 Linux 和 Windows 上,则应尽可能实现自包含。
常见“陷阱”
1、确保正确设置用户名和邮箱地址
2、如果修改了cherry-pick提交,请添加签名(使用 git -s
)
3、如果提交修复了某个bug,请使用issue关联语法(例如“Fixes #104”)来关闭bug报告并关联回该提交
添加注释
仅引用问题工单可能不够,需确保变更内容和上下文足够清晰,让首次阅读的人也能理解。你不希望自己编写的代码因为新人不懂其用途而被删除。代码回退是糟糕的情况。
这一点同样适用于问题和PR的正文描述。要尽可能详细明确。如果某个拉取请求属于更大计划的一部分:请链接到相关的跟踪问题。如果还没有跟踪问题:创建一个以改善沟通并达成共识。
避免臃肿的差异记录
在 cherry-pick 时进行修正,移除仅包含空白字符变更的提交。这类提交不可接受,因为我们的提交历史很重要,且 git blame
功能需要保持实用。
允许进行空白字符修正(例如遵循 Ruby 标准等),但前提是该行本身包含除空白变化之外的其他实质性修改(实际上这是进行此类修正的好时机)。但修改内联补丁时需谨慎——确保它们仍能正常应用。
关闭问题/PR的规范
维护者(包括项目负责人)不应关闭其他维护者提交的问题或拉取请求(注意:合并操作在此情况下不被视为关闭)。除非这些议题已过期,此时任何维护者都可以关闭它们。当任何维护者希望对已关闭的问题进行额外工作时,我们鼓励其重新开启该议题。
任何维护者在仔细审核并通过CI测试后,都可以合并由其他维护者提交的PR。如果您不希望其他维护者合并您的PR:请使用草稿拉取请求状态进行标注,直到您准备好自行合并。
撤销PR
任何维护者都可以在用户提交问题或导致CI失败后,撤销由其他维护者创建的PR。应给予原始PR的创建者至少一小时的时间自行修复问题,或者如果他们愿意,可以自行决定撤销该PR。
为其他维护者预留审核时间
对于现有功能的"增强型"PR(即非针对用户公开问题/讨论的修复、非版本升级、非安全修复、非CI故障修复,而是可用性改进、新功能、重构等),应在周一至周五期间等待24小时后再合并。例如:
- 周四下午5点提交的新功能PR应等到周五下午5点才能合并
- 周五下午5点提交的可用性修复PR应等到下周一下午5点才能合并
- 用户报告的问题修复PR在CI通过后即可立即合并
如果在此期间有维护者处于休假/病假状态,并在返回后留下评论:请将合并后的PR评论和反馈视为在时限内提出的意见,并通过另一个PR来解决他们的要求(如达成共识)。
绝大多数Homebrew/homebrew-core
的PR都是错误修复或版本升级,这些PR在CI完成后可以自行合并。
沟通方式
维护者之间可通过多种渠道进行交流:
- GitHub 上的 Homebrew 公开代码库
- 维护者私人群组沟通渠道(如 GitHub/Slack 等)进行三人以上的讨论
- 维护者私人点对点沟通渠道(如 iMessage/Slack/飞鸽传书等)进行一对一交流
所有沟通原则上都应在 GitHub 公开进行。若遇特殊情况(如安全漏洞披露、维护者间人际矛盾、需紧急修复的重大故障),可转为私人群组沟通,必要时可采用一对一形式。但技术决策不应在私聊中形成——若已发生(或历史遗留),必须将结论以可链接形式回归 GitHub 公开记录。例如:若某项技术决策一年前在 Slack 达成,当其他维护者/贡献者/用户在 GitHub 询问时,这正是公开解释并创建永久可引用记录的好时机。
这种机制能让其他维护者、贡献者和用户更轻松地追踪项目动态(更重要的是理解决策缘由),同时确保每个决定都有可追溯的公开链接。
所有维护者(及项目负责人)的沟通行为都受 Homebrew 行为准则 约束。对他人实施辱骂、骚扰等行为将先予以警告,若持续不改则撤销维护者资格。
我们鼓励维护者之间进行友好专业的技术争论:在 issue 跟踪器公开表达不同意见能促进项目发展。人际矛盾则应通过 Slack 私下处理(建议有调解人参与)。当工作记录或决策说明不充分时,任何维护者或贡献者都有权要求澄清。禁止用「我说了算」或「这事是我做的」等无实质理由的说辞,也严禁在 issue 跟踪器进行离题讨论、自行车棚效应式争论或人身攻击。
Homebrew/brew 维护者指南
https://docs.brew.sh/Homebrew-brew-Maintainer-Guide
本文档介绍了 Homebrew/brew
代码库中一些维护者需要了解的组件,这些内容通常不需要出现在面向普通用户和贡献者的文档中。
合并 PR
在 Homebrew/brew
代码库中,合并操作需使用标准的 “Merge” 按钮来完成,以保留历史记录和 GPG 提交签名功能。“Squash and Merge” 和 “Rebase and Merge” 按钮已被禁用。
PR 必须满足以下条件才能被合并:
- 获得至少一位维护者的批准
- 通过 CI(持续集成)测试。这是强制性步骤。绝对不要合并未通过 CI 测试的 PR。有关
Homebrew/brew
CI 的更多信息,请参阅下方的 CI 章节
如果可能的话,PR 还应包含签名提交。
自动批准机制
为确保非紧急PR有机会被其他有意查看的维护者审阅,所有PR在合并前都需要获得至少一个批准。
持续集成 (CI)
在 Homebrew/brew
中的每个拉取请求 (PR) 都会运行一系列 CI 测试,以防止引入错误。PR 必须通过所有 CI 测试才能被合并。
每个 PR 都会运行多项检查。以下是各项检查及其含义的简要说明:
Vendor Gems / vendor-gems
:除 dependabot 的 PR 外,此项通常跳过。它会更新 RBI 文件以匹配任何新增或变更的依赖项。有关 RBI 文件和类型检查的更多信息,请参阅 使用 Sorbet 进行类型检查。Codecov / codecov/patch
和codecov/project
:这些显示 PR 的 Codecov 报告。有关 Codecov 的更多信息,请参阅下面的brew tests
与 Codecov 部分。CI / vendored gems
:检查 Linux 环境下是否有对 vendored gems 的变更需要提交到 PR 分支。CI / test default formula (Linux)
:在 Linux 上运行brew test-bot
,确保其仍按预期工作。CI / syntax
:首先运行此项,检查 PR 是否通过brew style
和brew typecheck
。如果此任务失败,后续任务将不会运行。CI / tap syntax
:在所有官方 tap 上运行brew style
和brew audit
(注意:尽管在 Linux 上运行,但会检查所有 cask 仓库)。CI / docker
:构建并部署新的 Homebrew Docker 镜像到 GitHub Packages 和 Docker Hub。CI / test everything (macOS)
:在 macOS 上运行多项检查,包括brew tests
、brew update-tests
、brew test-bot --only-formulae --test-default-formula
、brew readall
和brew doctor
。CI / tests (generic OS)
和CI / tests (Linux)
:在 Linux 上以不同选项运行brew tests
。Documentation CI / linting
和rubydoc
:检查文档内容的语法和格式,并验证 rubydoc API 文档 能否无问题地构建。
请注意,此列表并不完整,可能会随时间变化。
brew tests
与 Codecov
每次提交 Pull Request (PR) 时,Codecov 都会生成覆盖率报告,其结果会显示在 CI 任务中。这些报告可在 Homebrew/brew 的 Codecov 页面公开查看。此外,在 PR 的 “Files changed” 标签页中会显示代码行未被 brew tests
覆盖的注解。如果 Codecov 任务失败,则表明可能需要为 PR 中添加的功能补充更多测试。
Codecov 应作为指导工具,用于提示何时可能需要更多测试,但要求每一行代码都有对应的测试是不现实的,尤其是当测试需要运行缓慢的集成测试时。因此,必要时可以合并未通过 Codecov 检查的 PR,但应尽可能避免这种情况。
CodeCov 还会监控每次推送到 Homebrew/brew
的 CI 任务,以检测随时间推移出现的偶发性测试失败(flaky tests)。相关报告可在 CodeCov 查看。
通过 CodeCov 可以识别哪些偶发性测试对 CI 套件造成的干扰最大。为了最大程度提升构建可靠性,我们应优先处理最具破坏性的偶发性测试(即导致最多间歇性失败的测试)。
为帮助定位特定偶发性测试的根本原因,CodeCov 会提供最近一次测试失败后又无需修改代码即通过的 CI 任务及提交的链接。您可以检出该提交的代码尝试在本地复现失败。此外,通过 CodeCov 上的近期失败列表还能判断测试是否总是以相同方式失败。
手册页与 Shell 自动补全
Homebrew 的手册页和 Shell 自动补全功能由 brew generate-man-completions
命令自动生成。欢迎贡献者运行此命令并在 PR 中提交变更,但这不是强制要求。如果未执行此操作,当原始 PR 合并后,@BrewTestBot 会自动开启一个后续 PR 来实施必要的更改。若变更内容无误,这些后续 PR 可立即合并。
如需手动触发更新,请前往“Actions”标签页下的 更新赞助商、维护者、手册页及自动补全 区域启动工作流。点击“Run workflow”下拉菜单,再点击“Run workflow”按钮。若有任何变更,系统将很快开启一个 PR。
Homebrew/homebrew-core 维护者指南
https://docs.brew.sh/Homebrew-homebrew-core-Maintainer-Guide
快速合并检查清单
详细检查清单可参考下方。以下是核心要点:
- 确保命名合理
- 添加别名
- 若软件已随macOS系统提供,需声明
keg_only :provided_by_macos
- 确保不是可通过gem、cpan或pip安装的库
- 确保依赖项准确且最小化。我们不需要支持软件的所有可选功能
- 当不需要或不受bottle影响时:
- 单公式PR使用GitHub的"squash & merge"工作流
- 多公式PR使用"rebase & merge"工作流
- 详见下方无bottle时如何合并章节
- 其他情况使用
brew pr-publish
或brew pr-pull
命令(这些命令会自动添加关闭PR的注释并拉取BrewTestBot构建的bottle) - 向贡献者致谢
依赖检查至关重要,因为它们可能会永久存在。实际上很少有人会复查这些依赖是否必要。
应尽可能减少依赖。例如能禁用X11功能就禁用。我们虽然构建Wireshark,但不包含沉重的GUI组件。
Homebrew专注于Unix软件。构建结果为.app
的软件应归属Homebrew Cask管理。
合并、变基与拣选提交
对于大多数涉及公式修改的PR,您只需批准PR,BrewTestBot便会自动执行合并操作(包含二进制包)。更多信息请参阅BrewTestBot维护指南。
部分PR即使获得批准后,BrewTestBot也不会自动合并,包括带有new formula
和automerge-skip
标签的PR。如需触发此类PR的合并,请运行brew pr-publish
命令。
若PR修改的公式无需二进制包,或变更内容不涉及新二进制包的拉取,则应使用GitHub的压缩合并(squash & merge)或变基合并(rebase & merge)工作流。
其他情况下,请使用brew pr-pull
(或通过rebase
/cherry-pick
处理贡献内容)。
在最终push
前不要执行rebase
操作。一旦main
分支被推送,就不能再变基:您现在可是维护者了!
拣选提交(cherry-pick)会改变提交日期,这可能会带来不便。
不要合并存在混乱历史的分支。如果贡献者仍在学习git
且分支包含无意义的合并记录,请执行rebase
并压缩提交。我们的主分支历史应对他人具有参考价值,而非造成困惑。
对于通过CI测试的新增或更新公式,只需一名维护者批准即可合并。但若该公式引发争议,后续的问题解答和修复工作将由添加该公式的维护者负责。
如何避免影响 bottles 进行合并
以下是关于何时使用 squash & merge 与 rebase & merge 的指南。这些选项仅适用于不影响 bottles 的 PR。
PR 修改单个 formula | PR 修改多个 formulae | |
---|---|---|
提交记录良好 | rebase & merge 或 squash & merge | rebase & merge |
提交记录需整理 | squash & merge | 需通过命令行手动合并 |
命名规范
名称是最严格的要求项,因为我们都希望避免后续的改名操作。
应选择项目最通用的名称。例如,我们最初选用 objective-caml
,但实际上应该选用 ocaml
。选择人们在日常交流中提到该项目时使用的名称。
若公式也被其他包管理器(如 Debian、Ubuntu)打包,名称应保持一致(允许因 Homebrew 公式命名规范产生的细微差异)。
通过在仓库根目录的 Aliases
文件夹内创建符号链接来添加其他别名。请确保主页引用的名称包含在这些别名中,因为主页名称可能不同,可能包含下划线或连字符等符号。
我们现在接受带版本号的公式,只要它们符合要求。
测试
我们至少需要确保它能成功构建。为此可以使用 BrewTestBot。
尽可能验证公式的有效性。如果无法判断(例如对于库文件),可以信任原始贡献者——既然对他们有效,大概率是没问题的。如果你不是该工具的专家,实际上很难判断公式是否正确安装了程序。总会有专家出现,大声指出问题并修复它。这正是开源的工作方式。理想情况下,可以要求添加 test do
代码块来持续测试功能的可用性。
如果公式使用代码仓库,那么 url
参数应包含标签或修订版本号。url
应当带有版本号且保持稳定(该特性尚未实现!)。
不要合并任何导致 brew test
失败的公式更新。如果 test do
代码块测试失败,必须修复它。这可能涉及用更可靠的测试替代复杂测试——这是可以接受的:误报优于漏报,因为我们不希望维护者养成合并红色PR的习惯。如果测试失败被确认为 Homebrew/brew
或 CI 系统的缺陷,必须在合并 PR 前修复该缺陷,或在公式中通过变通方案使测试通过。
重复项处理
我们现在允许包含在 macOS 系统中的软件包,只要它们使用 keg_only :provided_by_macos
声明默认以 keg-only 形式存在。
移除公式的准则
符合以下条件的公式不应从 Homebrew 中移除:
- 在默认的 Homebrew 安装路径下,至少能在我们支持的 2/3 macOS 版本上正常工作
- 不需要依赖上游已拒绝的补丁即可运行
- 我们所打包的版本不存在已知安全漏洞或 CVE 记录
- 根据用户数据分析显示仍有安装量,且构建错误率 (
BuildError
) 低于 25%
例外情况是版本化公式,这类公式需要满足更高的使用标准,并且对单个公式的版本数量有严格限制。
如需了解关于弃用、禁用和移除公式的更多信息,请参阅弃用、禁用和移除公式页面。
详细合并检查清单
以下清单旨在帮助维护者决定是否合并、请求更改或关闭一个 PR。除了可接受公式的要求外,它还为贡献者提供了更多的透明度。
- 先前已打开且活跃的 PR,因为我们希望对先来的贡献者公平
- 已应用于上游并可移除的补丁/
inreplace
- 公式中关于
url
的注释,因为我们确实会跳过某些版本(例如vim
或v8
) - 需要更新的打包资源(例如
emscripten
) - 打包的依赖项(例如
certbot
) - 稳定/已宣布的版本:
- 一些团队使用奇数小版本号进行测试,偶数小版本号用于稳定版本
- 其他团队发布小版本号为 0 的新版本,但仅在几个小版本后才将其提升为稳定版本
- 如果软件仅使用托管版本控制(如 GitHub、GitLab 或 Bitbucket),则版本应被标记,如果上游标记了最新/预发布版本,PR 必须使用最新版本
- 变更日志是否提到了依赖项的添加/移除,PR 中是否解决了这个问题?
- 公式是否依赖于可升级的版本化公式(例如
python@3.7
、go@1.10
、erlang@17
)?
- 公式是否依赖于可升级的版本化公式(例如
- 提交:
- 每个提交包含一个公式更改
- 要求作者压缩提交
- 在合并时进行变基
- 版本更新遵循简单版本更新的首选消息格式:
foobar 7.3
- 其他修复的格式为
foobar: fix flibble matrix
- 每个提交包含一个公式更改
- 未移除
bottle
块
建议回复:
Please keep bottle block in place; [@BrewTestBot](https://github.com/BrewTestBot) takes care of it.
- 是否有除检查版本或打印帮助之外的测试模块?建议考虑添加一个
- 如果CI失败:
- 由于测试模块导致 - 粘贴相关行并添加
test failure
标签 - 由于构建错误导致 - 粘贴相关行并添加
build failure
标签 - 由于其他配方需要修订版本号 - 建议使用以下命令:
- 由于测试模块导致 - 粘贴相关行并添加
# in this example: PR is for `libuv` formula and `urbit` needs revision bump
brew bump-revision --message 'for libuv' urbit
确保每次版本升级对应一个独立的提交
-
如果CI测试通过且…
- 需要拉取bottle文件,且…
- 提交内容正确无需修改,且BrewTestBot可以自动合并(没有
automerge-skip
标签):批准PR以触发自动合并(如需手动触发新公式合并,可使用brew pr-publish $PR_ID
命令) - 提交内容正确无需修改,但BrewTestBot无法自动合并(存在
automerge-skip
标签):使用brew pr-publish $PR_ID
命令 - 提交需要修改:使用
brew pr-pull $PR_ID
命令拉取PR,修改后执行git push
- 提交内容正确无需修改,且BrewTestBot可以自动合并(没有
- 需要拉取bottle文件,且…
-
别忘了感谢贡献者
- 特别祝贺首次贡献者
- 若非上述情况,建议下次使用
brew bump-formula-pr
命令
常见构建失败问题及处理方法
测试错误
“未定义的引用……”
当传递给 gcc
的参数顺序错误时,可能会出现此错误。
以 libmagic
公式为例:
==> brew test libmagic --verbose
Testing libmagic
==> /usr/bin/gcc -I/home/linuxbrew/.linuxbrew/Cellar/libmagic/5.38/include -L/home/linuxbrew/.linuxbrew/Cellar/libmagic/5.38/lib -lmagic test.c -o test
/tmp/ccNeDVRt.o: In function `main':
test.c:(.text+0x15): undefined reference to `magic_open'
test.c:(.text+0x4a): undefined reference to `magic_load'
test.c:(.text+0x81): undefined reference to `magic_file'
collect2: error: ld returned 1 exit status
Solution:
if OS.mac?system ENV.cc, "-I#{include}", "-L#{lib}", "-lmagic", "test.c", "-o", "test"
elsesystem ENV.cc, "test.c", "-I#{include}", "-L#{lib}", "-lmagic", "-o", "test"
end
要了解这种情况发生的原因,请参阅 Ubuntu 11.04 工具链文档。
预发布分支
摘要
某些公式(如 Python、OpenSSL、ICU、Boost)拥有大量依赖项。这使得更新这些公式(或其依赖项)变得困难,因为标准流程需要在单个拉取请求中更新大量公式。使用暂存分支(staging branch)可以显著简化这一流程。
暂存分支的核心思路是:将更新内容合并到非默认分支,并为受影响的公式发布二进制包(bottles)。这种方式允许通过多个小型 PR 逐步完成工作,而非在一个涉及大量公式修改的大型 PR 中操作。当暂存分支准备就绪后,可将其合并至 main
/默认分支。
使用暂存分支前需注意一个重要缺点:一旦将二进制包更新合并到暂存分支后,极难撤回操作。这通常需要删除已上传的二进制包,有时甚至需要 Homebrew GitHub 组织的所有者逐个删除已上传的包。
如何使用暂存分支
以下是使用暂存分支的大致流程:
1、在 Homebrew/homebrew-core 中创建暂存分支。分支名称必须以根配方名开头,后接 -
,并以 -staging
结尾。对于带版本的配方,可以省略 @
及其后内容(例如 icu4c-staging
、openssl-migration-staging
、python@3.12-staging
)。可参考解析分支名称的代码来检查 PR 是否针对暂存分支。
2、在 homebrew-core 中创建议题邀请贡献者协助。需包含操作指南和待更新配方清单,示例见 Homebrew/homebrew-core#134251。
3、创建将暂存分支合并至 main
分支的草稿PR,用于跟踪当前进度。建议为此 PR 添加 no long build conflict
标签,避免冲突变更被合并到 main
分支。
4、针对暂存分支提交更新受影响配方的 PR。每个 PR 应尽可能少涉及配方,典型情况下每次仅更新一个配方。暂存分支 PR 的合并流程与针对 main
分支的 PR 相同。理想情况下,这些 PR 应按依赖图的拓扑顺序提交(当前缺乏相关工具支持,欢迎贡献)。
5、为暂存分支的 PR 添加 staging-branch-pr
标签以便跟踪和审查(待实现:在 homebrew-core 中添加自动化处理)。
6、监控步骤3中草稿 PR 的合并冲突。若出现冲突,需通过将 main
分支合并到暂存分支的 PR 来解决。
7、当暂存分支准备就绪时,将草稿 PR 标记为可审查状态并合并至 main
分支。注意 PR 可能在合并队列中等待较长时间以完成 bottle fetch 测试。
实际应用案例可参考标有 openssl-3-migration-staging
的 PR,或 Homebrew/homebrew-core#134260、Homebrew/homebrew-core#133611。
最后说明:虽然暂存分支在过往两次实践中效果显著(见前文链接的 PR),但上述流程仍有改进空间,欢迎提出优化建议。
Homebrew/homebrew-cask 维护者指南
https://docs.brew.sh/Homebrew-homebrew-cask-Maintainer-Guide
本指南旨在帮助维护者有效管理 cask 代码库。建议与更通用的维护者指南结合使用。
常见情况
以下是 Cask PR 中最常遇到的情况及处理方法:
version
和sha256
同时变更(格式保持不变):直接合并- 仅
sha256
变更:通常直接合并,除非版本号也需要更新。上游供应商原地更新版本的情况并不罕见,但需警惕可能的上游入侵事件 livecheck
更新:根据最佳判断处理,确保变更符合livecheck
指南- 仅
version
变更或版本格式变更:相对罕见,若变更合理则合并(需自行判断) - 其他变更(包括新增 Cask):参照 Cask 手册 判断正确性
如有疑问,可通过 GitHub 或 Slack 咨询其他 Cask 维护者。
请注意:与 Formula 不同,Cask 不将 sha256
校验值视为有效的安全措施,因为维护者实际上无法验证其真实性。Cask 始终从上游下载资源,若 URL 被恶意攻击者劫持,可能会伪造版本更新。
合并操作
通常情况下,使用 GitHub 的 “Merge” 按钮是合并 PR 的最佳方式。当 PR 仅修改一个 cask 时(无论提交次数多少或提交信息格式是否正确),都可以采用此方法。使用这种方式合并时,可以按需修改提交信息。通常版本更新类提交信息遵循 Update CASK from OLD_VERSION to NEW_VERSION
的格式模板。
如果 PR 涉及修改多个 cask,请使用 “Rebase and Merge” 按钮来合并 PR。这种方式会沿用 PR 中的原始提交信息,因此在合并前请确保这些信息格式规范。必要时可检出该 PR,对提交进行压缩/重写,然后强制推送回 PR 分支以确保提交格式正确。
最后,记得感谢贡献者提交 PR!
其他小贴士
维护者只需在 PR 中添加一条 /rebase
评论,即可轻松将 PR 变基到最新的默认分支上。BrewTestBot
会在变基操作开始和完成时自动执行变基,并在评论中添加反应标记。
BrewTestBot 维护者指南
https://docs.brew.sh/BrewTestBot-For-Maintainers
brew test-bot
是我们的 CI 运行的一个命令,用于测试和构建 formula 的二进制安装包。
发布 Bottles
如果 CI 在拉取请求上通过且无需任何修改(例如提交信息、版本号更新等):
- 审核并批准该拉取请求。记得感谢贡献者!
- 等待 BrewTestBot 自动合并该拉取请求。若以下条件均满足,此任务通常会在 1 分钟内启动:
- 拉取请求已获得对 homebrew-core 有写入权限的维护者批准。
- CI 测试已通过。
如果有任何任务未成功完成,拉取请求将不会自动合并。此外,若发布失败,BrewTestBot 会在拉取请求中发表评论。
如果拉取请求无法被 BrewTestBot 自动合并(带有 autosquash
、automerge-skip
或 new formula
标签,或存在某种可接受的 CI 失败):
- 确保 Bottles 已成功构建。
- 运行
brew pr-publish 12345
,其中12345
是拉取请求编号(或 URL)。 - 监视 actions 队列 以确保任务完成。BrewTestBot 也会通过 ping 通知你失败情况。
如果拉取请求需要以 autosquash 不支持的方式修改提交信息(带有 automerge-skip
标签):
- 确保 Bottles 已成功构建。
- 运行
brew pr-pull 12345
,其中12345
是拉取请求编号(或 URL)。 - 如有需要,修改相关提交,然后运行
git push
将提交推送到拉取请求。
重新灌装
如果 homebrew-core 中的某个 formula 因任何原因需要重新灌装:
- 访问 homebrew-core 的重新灌装工作流页面
- 点击页面右侧的 “Run workflow” 按钮
- 填写所有必填字段
维护者常见问题
https://docs.brew.sh/Common-Issues-for-Core-Contributors
概述
这是供维护人员诊断特定构建错误的页面。
问题
瓶子发布失败但git历史中的提交记录正确
按照以下步骤解决此问题:
- 下载并解压瓶子构件。
- 在瓶子目录中执行
brew pr-upload --no-commit
。
使用 pr-pull
的替代方案:
- 在
homebrew/core
中执行git reset --hard <SHA>
,重置到brew pr-pull
创建的所有提交之前的提交。 - 执行
brew pr-pull <options>
上传正确的瓶子。如果瓶子已部分上传且你确信瓶子校验和会与公式中bottle do
块已有的校验和匹配,可添加--warn-on-upload-failure
开关。 - 执行
git reset --hard origin/HEAD
返回到最新提交,并丢弃由brew pr-pull
创建的提交。
ld: internal error: atom not found in symbolIndex(__ZN10SQInstance3GetERK11SQObjectPtrRS0_) for architecture x86_64
具体涉及的原子符号可能有所不同。
此问题通常是由于向链接器传递了已废弃的 -s
参数所致,可通过使用 inreplace
方法修复。
版本发布
https://docs.brew.sh/Releases
自 Homebrew 1.0.0 起,大多数 Homebrew 用户(约 99.9% 的分析数据显示,未运行过 dev-cmd
或设置 HOMEBREW_DEVELOPER=1
的用户)需要依赖 Homebrew/brew 仓库 的标签才能获取新版本。以下是发布新版本的主要步骤:
-
检查待处理事项:确认以下渠道是否存在急需修复或合并的内容:
Homebrew/brew
拉取请求Homebrew/brew
问题Homebrew/homebrew-core
问题- Homebrew 讨论区
如有,需先完成修复和合并。
-
确保发布条件:
- 代码至少稳定数小时(理想情况为 4 小时)未变动
- 至少有一个 Homebrew/homebrew-core 的拉取请求 CI 任务成功完成
- Homebrew/brew
main
分支的 CI 状态清晰(即主任务通过或重试后通过) - 确认当前
main
分支不存在重大回归问题
-
创建发布草案:运行
brew release
生成新草案。主版本或次版本升级需分别添加--major
或--minor
参数。 -
发布版本:在 GitHub 发布页面 发布该草案。
主版本/次版本额外步骤(如 X.0.0 或 X.Y.0)
-
代码清理:
- 删除所有
odisabled
代码 - 将
odeprecated
代码标记为odisabled
- 取消所有
# odeprecated
代码的注释 - 添加所需的新
odeprecations
同时删除所有包含replacement: ...
的命令参数定义。
- 删除所有
-
撰写发布博客:在 https://brew.sh 发布说明(参考 brew.sh#319)。需基于
brew release [--major|--minor]
的输出调整措辞,使其更易理解并解释变更原因。 -
社交媒体宣传:版本发布且博客合并后,通过 @MacHomebrew Twitter 账号 推文(凭据存储在 1Password),或个人账号转发。
-
考虑其他渠道:如 Hacker News、Reddit
- 优点:扩大覆盖范围并获得用户反馈
- 缺点:常见负面评论,用户可能借机抱怨 Homebrew(无论是否实际使用)
重要注意事项
请勿基于 main
分支的旧提交手动创建发布。这很难判断这些提交是否经过充分测试,或是否与当前 Homebrew/homebrew-core 状态产生冲突。若急需新分支但 main
存在暂不可发布的内容(例如新弃用项需先发布补丁版本),应还原相关 PR → 按上述流程发布 → 重新还原 PR 以恢复更改。
Homebrew 治理架构
https://docs.brew.sh/Homebrew-Governance
1. 定义
- 本文件中使用的关键词“必须(MUST)”、“不得(MUST NOT)”、“必需(REQUIRED)”、“应(SHALL)”、“不应(SHALL NOT)”、“应当(SHOULD)”、“不应当(SHOULD NOT)”、“推荐(RECOMMENDED)”、“可以(MAY)”和“可选(OPTIONAL)”应按照RFC 2119中的描述进行解释。
- PLC:项目指导委员会
- TSC:技术指导委员会
- AGM:年度全体会议
- 普通决议需获得投票数的简单多数通过。
- 特别决议需获得投票数的三分之二绝对多数通过。
- 主要代码库:Homebrew项目中流量最高且涉及安全性的三个关键代码库:
- Homebrew/brew (贡献统计),
- Homebrew/homebrew-core (贡献统计),
- Homebrew/homebrew-cask (贡献统计)
2. 成员管理
1、新成员(除非被提名为维护者,详见下文)需经过PLC普通决议通过后,方可加入GitHub上的Homebrew组织。
2、成员应保持对Homebrew的持续参与。非活跃维护者或非活跃委员会成员的成员,必须每年通过参与年度事项投票(包括弃权投票)来确认其继续保留成员资格的意愿。未活跃参与、未完成年度确认或未参与投票的成员,将在年度会议结束后的14天内被移除,除非获得PLC特批。
3、PLC可通过普通决议移除成员。被移除的成员需按常规加入流程重新申请。
4、所有成员须遵守Homebrew行为准则。行为准则的修改必须经PLC批准。
5、当成员存在其他成员未涉及的利害冲突时,应主动回避投票。任何成员均不得被强制要求回避投票。
3. 会员大会
1、会员大会可通过PLC普通决议或全体会员多数表决召开。召开会员大会需至少提前三周通知全体会员。年度会员大会(AGM)应以线下形式举行,并为无法出席的会员提供在线视频会议选项。其他会员大会应通过在线视频会议形式召开。
2、会员大会表决决议和选举的法定人数为3名有表决权的会员,或占全体有表决权会员的10%(以较大者为准)。仅含投票议程的会员大会可采用异步形式召开,其他情况必须采用同步在线视频会议形式。投票将使用PLC选定的在线投票系统进行,投票期为一周或直至后续投票无法改变当前结果时终止。若举行同步会议,必须在计票前完成会议议程。
3、Homebrew会员将在年度会员大会(AGM)上集会,具体形式由PLC决定。
4、选举将在年度会员大会上举行。
5、PLC需在选举日期前三周公布候选名单及提案。
6、会员应在选举日期前三周内完成投票。
3.1. 章程修订条款
1、本章程的修订必须通过会员大会特别决议方可进行。
2、任何会员均可通过GitHub对本文件发起pull request提议修订。当PLC(项目领导委员会)超过半数成员批准该pull request后,所提议的修订可与其他修订合并审议。
3、会员必须对所有修订提案进行投票,所有票数将被统计。
若一个或多个修订提案被接受,投票将开放三周——除非年度大会(AGM)在一个月内召开,此时修订提案将与选举同时进行表决。
4、所有通过的修订条款将在投票结束三周后生效。
4. 项目领导委员会
1、Homebrew的财务管理、年度大会组织、行为准则执行以及成员除名等事务由PLC负责。PLC将代表Homebrew处理与Open Collective的所有往来事宜。
2、PLC由五名成员组成,其中一人担任项目负责人(Project Leader)。其他委员会成员通过Meek单记可让渡投票制选举产生,采用Droop配额法。每位PLC成员任期两年,或直至继任者当选为止。非项目负责人的PLC成员最多可连任两届(即使这意味着没有继任者)。任何PLC职位的突然空缺将在下次全体会议(通常是下届年度大会)上通过常规选举程序填补。
3、当PLC席位需要选举或出现空缺时,任何成员均可通过以下方式成为候选人:在年度大会前三周内,于Homebrew Slack的#members
频道提交简要声明,说明相关经验及当选后的规划意向。PLC将维护候选人名单直至年度大会前一周发送选票,期间成员应完成投票。候选人可在年度大会前或会议期间以书面或口头形式发表陈述,但已投选票不可更改。现任PLC需在候选人截止日期与年度大会之间的三周内,投票并发布声明推荐其倾向的候选人。
4、PLC必须在行动发生后一周内,于Homebrew/homebrew-governance-private GitHub仓库向所有成员汇报会议纪要、讨论参与者名单及投票明细。年度大会上,PLC须汇报自上次大会以来的活动与决策摘要。财务报告可通过Homebrew的OpenCollective公开查阅。
5、同一雇主的雇员在PLC中任职不得超过两人。
6、PLC成员的免职必须通过成员特别决议方可执行。
7、所有PLC成员将担任GitHub组织的"billing managers"和"moderators",并管理相关资源(如Slack、1Password等)。
8、除项目负责人外,PLC需指定一名成员在GitHub组织及相关资源中担任Owner
角色。优先考虑现任Homebrew维护者的PLC成员,若无则任何PLC成员均可担任该角色。
5. 项目领导委员会会议
1、PLC全体成员每年至少需通过同步视频会议或面对面方式召开一次会议。该会议应在年度大会(AGM)上以面对面形式举行,并提前至少两个月通知。
2、PLC决议表决的法定人数需超过半数成员。电子投票时,为期一周的投票期可替代法定人数要求。任何通过的决议将立即生效。
3、通过普通决议需获得PLC全体成员的过半数同意。
4、PLC将每年审核所有成员状态,移除未在年度大会投票且未重新承诺参与Homebrew的成员。在年度大会投票即确认成员希望保持项目活跃状态。年度大会后,PLC将询问未投票成员是否希望继续参与项目。对于三周内未回应二次询问的成员,PLC将予以移除。
5、PLC负责任命技术指导委员会(TSC)成员。
6、任何成员均可向PLC提交财务问题、年度大会问题或行为准则违规事项。所有技术问题应提交项目负责人处理,技术争议则提交TSC处理。成员在提交PLC、项目负责人或TSC前,应本着诚信原则努力通过协商解决争议。
6. 项目负责人
1、项目负责人将公开代表Homebrew,管理所有日常技术决策,并解决维护者、成员、其他贡献者和用户之间与Homebrew运营相关的争议。
2、项目负责人每两年由Homebrew成员通过舒尔茨·孔多塞投票法(又称"beatpath")选举产生。PLC将提名至少一名项目负责人候选人。任何成员均可提名候选人或自我提名。提名必须在年度大会前三周向全体成员公布。
3、项目负责人的任何空缺将由PLC任命填补。
4、项目负责人的技术决策可被TSC的普通决议推翻。
5、项目负责人的非技术决策可被PLC的普通决议推翻。
6、项目负责人必须通过成员特别决议才能被免职。
7、项目负责人必须参与PLC与Open Collective的所有往来通信,以及所有涉及共同责任的沟通。
8、项目负责人必须是维护者,而不仅仅是成员。
9、项目负责人将担任GitHub组织、Slack、1Password及相关资源的"所有者"身份。
7. 技术指导委员会
1、TSC有权裁决任何维护者与项目负责人之间的技术争议。不涉及项目负责人的争议必须通过项目负责人处理。
2、项目负责人是TSC成员之一。PLC将任命3至5名维护者作为TSC成员,PLC成员不得担任这些被任命职位。被任命的TSC成员任期一年,或直至继任者被任命。
3、任何成员均可将技术问题或争议提交TSC裁决。成员在提交TSC前应本着诚信原则努力通过协商解决争议。
4、同一雇主雇佣的成员在TSC中任职不得超过两人。
5、除项目负责人外,TSC成员必须经PLC普通决议方可除名。
6、所有TSC成员自动成为GitHub组织的"协管员"。
7、TSC中一名非项目负责人成员将担任GitHub组织、Slack、1Password及相关资源的"所有者"权限。
8. 维护者管理
1、所有维护者自动成为成员,但成员不一定是维护者(只有部分成员具有维护者身份)。
2、维护者是指对至少一个核心代码库拥有提交/写入权限的成员。
3、新维护者可由现有维护者提名。提名需满足以下条件方可生效:
- 获得项目负责人(PL)或技术指导委员会(TSC)任一成员的批准
- 且在24小时内(周五UTC 19:00至下周一UTC 19:00除外)无人反对
- 若出现反对意见,TSC需在Slack私有频道#tsc进行投票,投票期为一周或当后续投票无法改变结果时(例如TSC多数成员已投赞成或反对票)提前结束。提名以简单多数票通过。
4、根据Homebrew最小权限安全原则,项目负责人(PL)须在年度大会(AGM)前六周审核维护者的写入/提交权限,并对不符合以下持续贡献标准的维护者撤销权限:
- 在至少一个核心代码库中的贡献量超过大多数非维护者贡献者
- 参与核心代码库中其他维护者和贡献者的PR审核与合并
- PL将排除同一人提交合并的非必要PR
- 审核所属子团队(如Homebrew/linux)在Homebrew组织内任何代码库的GitHub评审请求
- 及时响应GitHub上的直接提及和PL/其他维护者在Slack的@消息
- 与PL及其他维护者保持积极协作关系
- 主动通过中立调解解决与PL或其他维护者的冲突
未达标者将被取消维护者身份,但可保留成员资格(若自愿)。
PL不会将以下活动纳入评估范围(因不涉及安全关键代码库的提交/写入权限):
- 对Homebrew外围组织、非核心代码库或生态系统的贡献
- 往年作为维护者/贡献者的历史贡献
- 对治理文档、PLC、GSoC、MLH、社交媒体、讨论区等的贡献
申诉流程:
- 被撤销权限的维护者可在72小时内申请TSC复审
- 申诉人需承诺解决导致撤销权限的未达标项
- TSC将在一周内完成复审
- 非PL的TSC成员需立即公布维持或撤销原决定的裁决
- 若TSC裁决恢复权限,PL应尽快操作权限恢复
- 若TSC/PL认为申诉人在权限恢复后30天内改进不足,可要求二次复审
- 对明显失联/不响应的情况可随时发起复审
- 每位维护者在季度内仅可申诉一次,直至下次AGM
- 2023年AGM后三个月内不受理任何维护者撤销复审
紧急情况处理:
对于恶意提交、可疑活动、资源滥用等危害Homebrew代码库/系统/组织安全的行为,PL或权限管理员应立即撤销相关维护者的所有访问权限(GitHub/Slack/1Password等),并同步通知PLC和TSC。PLC将评估事件,TSC需在两周内完成审查,仅当问题解决时才指示PL恢复权限(此流程视同上述申诉程序)。TSC需撰写事件报告通报成员,并建议更新安全设置、维护者政策或治理文档以防止事件重演。
Homebrew 领导职责
https://docs.brew.sh/Homebrew-Leadership-Responsibilities
项目领导委员会
PLC 专属职责
- 组织年度大会(AGM)
- 对维护者硬件资助进行投票表决(需在采购前完成)
- 对维护者参加黑客松/会议/年度大会的差旅费用进行投票表决(需在预订前完成)
- 响应并处理《行为准则》投诉
- 移除未参与年度大会投票的非活跃成员(非维护者身份)
PLC 共同职责
- 审批已由 PLC 预期或达成共识的 Open Collective 支出(例如个人信用卡上的 Homebrew 云服务使用)(仅需单次批准)
- 封禁滥用 GitHub 的用户
- 在 Homebrew GitHub 组织中执行 GitHub 管理员操作
- 在 Homebrew Slack 中执行 Slack 管理员操作
PLC年度例行任务
- 一月:检查会员资格,宣布年度大会投票事项
- 征集PLC和项目负责人的提名,询问有意加入TSC的人员
- 在https://www.opavote.com上创建选举投票
- 请项目负责人及PLC、TSC代表准备年度大会报告
- 征集有意在年度大会进行闪电演讲的会员
- 二月:组织年度大会(AGM)
- 创建专用Slack频道
- 预订团体晚餐(由Homebrew支付)并确认饮食需求
- 安排人员携带会议/桌面麦克风以便远程参与年度大会
- 根据通胀等因素重新评估当前OpenCollective的小时费率标准
- 年度大会结束后(二月):
- 将年度大会纪要归档至Homebrew/brew的治理文档库
- 在Homebrew/brew仓库创建议题调查未投票会员的留任意向
- 非维护者的会员至少需满足以下条件之一:
- 现任或曾任活跃维护者、PLC/TSC成员或项目负责人
- Homebrew社区的长期成员(例如持续提交优质错误报告超过两年)
- 调查结束后,在治理文档库新建文件记录当年会员名单
- 非维护者的会员至少需满足以下条件之一:
- 十月:筹备线下年度大会
- 为符合以下条件之一的Homebrew维护者提供差旅资助:
- 活跃的Homebrew维护者(非普通贡献者)
- 新任Homebrew维护者(首次参加年度大会)
- PLC/TSC/项目负责人现任成员或候选人
- 授权相关人员预订差旅行程
- 为符合以下条件之一的Homebrew维护者提供差旅资助:
项目负责人
PL 专属职责
- 负责所有日常技术决策
- 解决维护者、成员、其他贡献者及用户之间与 Homebrew 运营相关的争议
- 为各类 Homebrew 产品进行产品管理
- 在二月份年度大会前:核查非 PLC/TSC 维护者的活跃度,若过去 12 个月活跃度不足则要求其退出
PL 共享职责
- 批准新的 Homebrew 维护者(只需一人同意)
- 批准 Open Collective 上已由 PLC 预先同意或预期发生的支出(例如个人信用卡上的 Homebrew 云服务使用)(只需一人同意)
- 封禁滥用 GitHub 的用户
- 在 Homebrew GitHub 组织中执行管理员操作
- 在 Homebrew Slack 中执行管理员操作
技术指导委员会
TSC 的专属职责
- 裁决 Homebrew 维护者与项目负责人之间的技术争议
TSC 共同职责
- 批准新的 Homebrew 维护者(只需一人批准即可)
- 封禁滥用 GitHub 的用户
- 在 Homebrew GitHub 组织中执行 GitHub 管理操作
Homebrew 治理档案
https://docs.brew.sh/Homebrew-Governance-Archives
- 2019年成员名单
- Mozilla 开源支持提名(Track III)
- 2019年 Homebrew 项目领导委员会会议记录
- 2020年成员名单
- 2020年 Homebrew 项目领导委员会会议记录
- 2021年 Homebrew 年度大会
- 2021年特别大会
- 2021年成员名单
- 2021年 Homebrew 项目领导委员会会议记录
- 2022年 Homebrew 年度大会
- 2022年成员名单
- 2023年 Homebrew 年度大会
- 2024年 Homebrew 年度大会
2025-08-16(六)