news 2026/9/8 19:34:06

Homebrew 许可证指南:Formula 的 SPDX 声明语法与 HOMEBREW_FORBIDDEN_LICENSES 合规拦截全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Homebrew 许可证指南:Formula 的 SPDX 声明语法与 HOMEBREW_FORBIDDEN_LICENSES 合规拦截全解析

Homebrew 许可证指南:Formula 的 SPDX 声明语法与 HOMEBREW_FORBIDDEN_LICENSES 合规拦截全解析

【免费下载链接】brew🍺 The Package Manager for Everywhere项目地址: https://gitcode.com/GitHub_Trending/br/brew

Homebrew 为进入homebrew/core的每一个 Formula 都建立了一套基于 SPDX License List 的许可证(licence)声明规范,并把这套声明与安装流程深度绑定——不仅用于元数据展示与审计,还能通过HOMEBREW_FORBIDDEN_LICENSES环境变量在安装阶段直接拦截不符合许可策略的软件及其依赖。本文以仓库中的 docs/Licence-Guidelines.md 为主体,结合 Homebrew 的 Ruby 源码与测试用例,系统讲解许可证的声明语法、复杂 SPDX 表达式的语义,以及禁装策略的真实判定逻辑。读完你既能写出规范合法的license声明,也能在企业或合规场景下精确配置许可证黑名单。

总体要求:DFSG 兼容是进入homebrew/core的前提

根据官方指南,homebrew/core中的 Formula 所使用的许可证必须与 Debian Free Software Guidelines(DFSG)兼容,或者依据 DFSG 的公共领域(public domain)判定标准正式释入公共领域。也就是说,许可证审查是 Homebrew 上游合并公式时的硬性门槛,它约束的是"这个软件能不能进官方核心仓库",而不是"用户能不能在本机安装"。

在实现层面,Homebrew 并不自带全量许可证知识,而是把 SPDX License List 的官方 JSON 数据本地缓存为 Library/Homebrew/data/spdx/spdx_licenses.json 与 Library/Homebrew/data/spdx/spdx_exceptions.json。Library/Homebrew/utils/spdx.rb 中的SPDX模块统一负责读取、校验与逻辑判断(对应函数如license_datavalid_license?licenses_forbid_installation?等),这意味着你写下的每一个标识符最终都会与该离线数据做大小写不敏感比对。

为 Formula 声明许可证:三种基础写法

license是 Formula 的公开 API 方法,其完整定义位于 Library/Homebrew/formula.rb#L4104-L4110:无参调用时返回当前已设置的@licenses,传参时则完成设置,类型为SPDX::LicenseExpression

写法一:SPDX 标识符字符串

绝大多数情况直接传 SPDX License List 中的标识符字符串即可:

license "MIT"

写法二:公共领域使用符号

公共领域不能用普通字符串表达,需用:public_domain符号:

license :public_domain

写法三:无法用 SPDX 表达

如果该 Formula 的许可证实在无法用任何 SPDX 表达式表示(例如多份相互冲突的自定义许可),则显式声明:cannot_represent

license :cannot_represent

从 Library/Homebrew/utils/spdx.rb#L14-L18 的源码可以看到,Homebrew 共定义了三个被允许的符号常量:ALLOWED_LICENSE_SYMBOLS = [:public_domain, :cannot_represent, :truncated]。其中:truncated是内部用途——当许可证文本超过 OCI 注解的长度限制(默认 255 字符)时,SPDX.truncate_license会在保留一段合法的AND前缀后追加该标记,避免整体丢弃(见 Library/Homebrew/utils/spdx.rb#L210-L240)。

声明之后的校验链路

写错的标识符不会只是"摆着难看"。SPDX.valid_license?(Library/Homebrew/utils/spdx.rb#L87-L92)会去掉末尾的+后与本地 SPDX 数据做忽略大小写的匹配;SPDX.deprecated_license?还会标记已经过时的 SPDX 标识符。这些校验服务于brew audit等检查流程,用于在提交公式前发现笔误。

此外,符号类许可证在序列化时会统一映射为LicenseRef-Homebrew-*前缀的文本形式(license_expression_to_stringSymbol分支的实现),例如:public_domain会展开为LicenseRef-Homebrew-public-domain,确保与 SPDX 体系外的语义也能在标准文本通道中传递。

复杂 SPDX 许可证表达式:组合、例外与嵌套

很多软件由多份许可证共同约束,甚至允许使用者在若干许可证之间做选择。Homebrew 支持用 Ruby 原生数据结构还原 SPDX License Expression Guidelines 的全部组合能力。

加号+:允许更高版本

+表示使用者可以选择同一许可证的后续版本:

license "EPL-1.0+"

GNU 系必须显式后缀:-only-or-later

GPLLGPLAGPLGFDL这四类 GNU 许可证不允许使用+,必须用-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:许可证例外条款

表示某个许可证附带一个 SPDX 例外(exception),典型如 LLVM 编译器的Apache-2.0+LLVM-exception组合:

license "MIT" => { with: "LLVM-exception" }

例外标识符同样需要真实存在于本地 Library/Homebrew/data/spdx/spdx_exceptions.json 中,SPDX.valid_license_exception?会做逐条校验。

自由嵌套:一张"足够复杂"的表达式

以上结构可以无限制嵌套组合,官方指南给出的完整示例即是:

license any_of: [ "MIT", :public_domain, { all_of: ["0BSD", "Zlib", "Artistic-1.0+"], "Apache-2.0" => { with: "LLVM-exception" } }, ]

语义解读:使用方要么选MIT,要么选公共领域,要么同时满足0BSDZlibArtistic-1.0+并且在使用Apache-2.0时附带LLVM-exception例外。

Ruby 结构如何翻译成 SPDX 文本

如果你好奇这些 Hash 与数组最终在输出/报错时是什么样子,看 Library/Homebrew/utils/spdx.rb#L119-L156 的license_expression_to_string即可:any_of:被拼接为ORall_of:被拼接为AND{ "X" => { with: "Y" } }被拼接为X WITH Y,必要时用括号包裹嵌套子表达式(例如(0BSD AND Zlib AND Artistic-1.0+ AND Apache-2.0 WITH LLVM-exception))。这意味着错误提示、日志和文档输出中的表达式始终符合标准 SPDX 语法,可被其他工具直接读取。

HOMEBREW_FORBIDDEN_LICENSES拦截安装

如果说上面的声明语法是"上游如何标注",那么禁装变量就是"下游如何执行"。HOMEBREW_FORBIDDEN_LICENSES让使用者(组织策略执行者)声明哪些许可证的软件及其依赖一律不得安装。

设置方式与基本语义

在 shell 环境中把它设为一个以空格分隔的许可证列表即可。若要禁止MITArtistic-1.0以及所有:public_domain声明的公式:

export HOMEBREW_FORBIDDEN_LICENSES="MIT Artistic-1.0 public_domain"

设置后,Homebrew 会拒绝安装任何声明了MIT的公式;更重要的是,禁止会沿着依赖树传播——即使主公式本身许可证合规,只要它声明了对某个MIT公式的依赖,同样会被拒绝(源码路径见 Library/Homebrew/formula_installer.rb#L1823-L1833,错误信息中会明确报告"The installation ofXhas a dependency onYwhere all its licenses were forbidden")。

复杂表达式下的判定语义:any_of看全部、all_of看任一

禁装判断不是简单的字符串包含,而是对整套表达式做布尔推理,其核心算法在SPDX.licenses_forbid_installation?(Library/Homebrew/utils/spdx.rb#L266-L287):

  • any_of:列表,只有当每一个候选许可证都被禁时才禁止安装(源码取.all?)——因为只要还有一条合规出路,使用方就可选它,安装依然合法;
  • all_of:列表,只要任意一个被禁即禁止安装(源码取.any?)——因为全部条款都必须同时满足,缺一不可;
  • with:例外结构,直接判断基础许可证是否被禁。

沿用上例(禁MITArtistic-1.0public_domain)来验证:

拒绝安装的声明:

license any_of: ["MIT", "Artistic-1.0"] # 两条出路全部被禁 → 拒绝
license all_of: ["MIT", "0BSD"] # 存在被禁的 MIT → 拒绝

允许安装的声明:

license any_of: ["MIT", "0BSD"] # 仍有 0BSD 可选 → 放行

以上判定行为在 Library/Homebrew/test/utils/spdx_spec.rb 的.licenses_forbid_installation?测试组中有大量对应断言(例如any_of在单个禁项下返回false、在多项全禁时返回trueall_of含任一禁项即返回true),可作为行为契约参考。

版本语义:+与 GNU-only/-or-later的黑名单写法

黑名单同样支持版本策略。HOMEBREW_FORBIDDEN_LICENSES中带+的条目会禁掉该许可证及其所有未来更高版本。例如要禁止Artistic-1.0Artistic-2.0及今后任何 Artistic 版本:

export HOMEBREW_FORBIDDEN_LICENSES="Artistic-1.0+"

对 GNU 系许可证则用-only/-or-later表达版本范围。下面的配置会禁止GPL-2.0LGPL-2.1LGPL-3.0,但放行GPL-3.0

export HOMEBREW_FORBIDDEN_LICENSES="GPL-2.0-only LGPL-2.1-or-later"

这里的关键在于"or-later 会覆盖其后的所有版本":LGPL-2.1-or-later被禁等价于 2.1 及其后全部 LGPL 都被禁。实现位于SPDX.license_version_infoSPDX.forbidden_licenses_include?(Library/Homebrew/utils/spdx.rb#L252-L309):解析器用正则把GPL-2.0-or-later拆成["GPL", "2.0", true](名字、版本、是否允许更高版本)三元组,随后执行字符串版本比较——当黑名单条目允许更高版本且其版本号 ≤ 公式声明版本号时判定命中,否则仅在版本号完全相等时命中。注意该实现依据版本号字符串比较,判断前会先做名称对齐(GPLGPL-3.0)。

非法的黑名单条目会怎样

如果黑名单里混入了 SPDX 中不存在的标识符(比如拼写错误),Homebrew 并不会直接崩溃,而是用opoo橙色警告提示哪些条目无效、不会被生效,并指引你通过brew info <formula>查看公式的真实许可证,或对照官方 SPDX License List 修正(见 Library/Homebrew/formula_installer.rb#L1806-L1814)。

触发时机与可选关联变量

许可证校验发生在 Formula 安装过程中,对应方法为FormulaInstaller#forbidden_license_check(Library/Homebrew/formula_installer.rb#L1786-L1844)。它先解析环境变量、把public_domain等字面量规整为符号,再按"先依赖、后本体"的顺序执行判定,命中时抛出CannotInstallFormulaError终止安装。如果只想单独安装公式而不做依赖级联检查,可使用安装时与依赖相关的选项(如--ignore-dependencies);判定逻辑对"仅依赖"模式也有独立分支。

HOMEBREW_FORBIDDEN_LICENSES的官方定义与说明记录在 Library/Homebrew/env_config.rb#L376-L379。与之配套的还有两个归属类变量:HOMEBREW_FORBIDDEN_OWNER(设置禁项的责任人,默认you)与HOMEBREW_FORBIDDEN_OWNER_CONTACT(联系方式),它们会被拼进拒绝安装的错误信息里,方便在组织内多人共用同一台构建机时,让报错者知道"该找谁、怎么找"(见 Library/Homebrew/formula_installer.rb#L1818-L1821)。安装集成侧的整套行为(含错误消息断言)由 Library/Homebrew/test/formula_installer_spec.rb#L1281 的#forbidden_license_check用例覆盖。

实战排查速查

  • 查看某公式实际声明的许可证:运行brew info <formula>,输出中会列出其许可证标识与依赖的许可证信息,这是核对黑名单误判时最直接的命令。
  • 验证复杂表达式是否会被拦截:参照上文any_of(全禁才禁)、all_of(任一即禁)的判定规则逐层展开;若表达式被嵌套,规则递归应用于每一层。
  • 合规提交上游 Formula:确保许可证满足 DFSG 兼容要求,使用规范的 SPDX 标识符,GNU 系许可证务必写-only/-or-later而非裸写版本号。
  • 临时放开某项限制:在启动brew install的同一 shell 中unset HOMEBREW_FORBIDDEN_LICENSES(或修正列表),注意该变量按进程继承,需在真正执行 brew 的会话中生效。

小结

许可证声明与合规拦截在 Homebrew 中是一体两面:license方法(Library/Homebrew/formula.rb#L4104-L4110)负责以标准 SPDX 语法标注公式的授权方式,支撑homebrew/core的 DFSG 合规审查;而HOMEBREW_FORBIDDEN_LICENSES则把同一份声明在安装期变成可执行的安全边界——精确到复杂表达式的any_of/all_of语义、+-or-later的版本覆盖范围,并沿依赖树向下传播。理解 Library/Homebrew/utils/spdx.rb 中那套递归判定算法,你就能准确预判任何一条黑名单配置对任意 Formula 的实际影响,把"许可证合规"从人工审查真正落到自动化防线之上。

【免费下载链接】brew🍺 The Package Manager for Everywhere项目地址: https://gitcode.com/GitHub_Trending/br/brew

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 19:33:58

智能体边缘AI:边缘计算迈向自主决策的新范式

Agentic Edge AI&#xff0c;准确说&#xff0c;智能体边缘智能&#xff0c;这个词最近在技术圈的出现频率高得吓人。我最早注意到它&#xff0c;是在一次项目评审会上&#xff0c;团队正在为一个制造业客户做质检方案&#xff0c;甲方直接问“这套系统能不能自己判断什么时候该…

作者头像 李华
网站建设 2026/9/8 19:29:44

2026江苏军队文职培训哪家强?线上日常+赴京集训备考方案测评

先给结论&#xff1a; 北京是全国军队文职培训资源最密集的城市&#xff0c;师资厚度与教研深度领先&#xff1b;但对外地考生而言&#xff0c;全程赴京脱产并不现实。对于江苏等异地考生&#xff0c;"线上日常学习考前短期赴京集训"是兼顾质量与成本的最优组合模式&…

作者头像 李华
网站建设 2026/9/8 19:29:43

POE供电温湿度传感器如何接入SCADA系统:从Modbus配置到部署实战

拿到这个项目的时候&#xff0c;我第一反应是&#xff1a;一个温湿度传感器&#xff0c;为什么要折腾POE供电&#xff1f;后来到了现场才明白&#xff0c;温湿度探头往往装在天花板夹层、空调出风口、配电柜角落这种地方&#xff0c;旁边压根没有插座&#xff0c;可网线倒是方便…

作者头像 李华
网站建设 2026/9/8 19:28:11

C# 未排序数组中第 k 个最小/最大元素 | 最坏情况下的线性时间

目录 例如 方法 实现上述想法的步骤 示例代码 详细时间复杂度分析 递推关系式变为 代入递推式 结论 如果您喜欢此文章&#xff0c;请收藏、点赞、评论&#xff0c;谢谢&#xff0c;祝您快乐每一天。 未排序数组中第 k 个最小/最大元素 | 最坏情况下的线性时间(K’th S…

作者头像 李华
网站建设 2026/9/8 19:26:49

Luma企业治理实战:独立团队与项目额度上限的落地指南

Luma 最近更新了面向企业客户的功能模块&#xff0c;核心就两件事&#xff1a;支持独立团队、支持按项目设置额度上限。做企业级产品的朋友应该一眼就能看出来&#xff0c;这两个能力补的是"从工具到平台"之间最难啃的骨头——治理。今天我不聊官方文档里那些功能介绍…

作者头像 李华