十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Homebrew 多语言 Formula 编写实战:Python、Node.js、Java 与 Ruby 打包规范全解

Homebrew 多语言 Formula 编写实战:Python、Node.js、Java 与 Ruby 打包规范全解 Homebrew 多语言 Formula 编写实战Python、Node.js、Java 与 Ruby 打包规范全解【免费下载链接】brew The Package Manager for Everywhere项目地址: https://gitcode.com/GitHub_Trending/br/brew这篇指南面向需要为 Homebrew 编写 formula 的开发者系统讲解在 formula 中处理Python、Node.js、Java、Ruby四类语言生态时的官方规范与实用模式。作为 Homebrew 本体Library/Homebrew中language/目录下各类 helper 模块python.rb、node.rb、java.rb等的直接使用手册读完你既能掌握从源码安装、依赖收敛到入口脚本包装的完整写法也能理解这些 helper 背后的设计动机与真实参数写出可被homebrew/core收录的合规 formula。本文以仓库文档docs/Language-Specific-Formulae.md为主体将其中的公式写法与命令一一展开并结合仓库源码剖析底层实现。基础语法请先阅读 Formula Cookbook什么软件值得打包参考 Acceptable Formulae 与 Package Acceptance Policy。一、所有语言通用的五个共同要求无论打包哪种语言的应用Language-Specific-Formulae.md都先强调五条跨语言底线它们是所有后续写法的前提显式声明每一个运行期/构建期语言运行时不得依赖贡献者本机恰好装了什么解释器或 JDKdepends_on必须写全。使用不可变、带校验和的源码与可复现的依赖集每一份远程源码都要有固定版本与sha256依赖集合要可重复解析、重复构建。把应用依赖装进 formula 自己的 prefix依赖安装在 formula 的 Cellar 前缀内通常是libexec绝不污染用户全局的语言环境如用户自己的site-packages、gem 目录、node_modules。禁止应用运行时自行联网下载代码安装完成后必须自包含任何需要的模块都应在安装阶段就位。必须附带功能测试test do ... end要真正运行并验证安装后的行为比如执行命令并断言版本输出而不是只检查可执行文件是否存在。此外一个重要的判断标准是“普通库”通常不值得打包——如果一个库能被该语言的包管理器pip、npm、gem装进项目环境就足够了。真正适合写成 formula 的是命令行应用、有较重原生构建的库、被其他 formula 依赖的绑定bindings。判断是否收录最终仍要服从 Package Acceptance Policy。二、Python从 CLI 应用到原生绑定2.1 先分清“应用程序”“库”与“绑定”Python 应用程序提供面向用户的行为即便发布在 PyPI 上也是优秀的 formula 候选。用户无需知道应用由 Python 写成更不需要手动把它的模块加进sys.path。普通可 import 的库应默认放进项目环境由 pip 管理只有具备“substantial native build可观的原生构建量”“被其他 formula 需要”或“需要 Homebrew 特定集成”时才考虑打包。由非 Python 项目提供的语言绑定bindings在该绑定实用且可维护时可随宿主项目一并安装。2.2 版本化 Python 依赖与resource块homebrew/core中的 Python 应用一律依赖其当前采用的版本化 Python formula写作python3.y并随 core 切换到新的受支持 minor 版本而同步更新依赖。所有“未被其他 formula 提供的” Python 模块依赖及其递归依赖必须逐一声明为resource块。这样做的直接效果是每个依赖的源码版本与 SHA-256 校验和都固化在 formula 里而 Homebrew 的 pip helper 安装时默认关闭依赖自动解析--no-deps装什么、装哪个版本完全由 formula 说了算从而保证可复现。要手写几十个 resource 显然不现实。文档给出的自动化路径是使用开发者命令# 生成或更新 formula 中的 resource 块 brew update-python-resources formula # 只打印结果预览不改动 formula brew update-python-resources formula --print-only命令行实现见dev-cmd/update-python-resources.rb--print-only-p仅把更新后的 resource 块打印到标准输出适合 review--ignore-errors对第三方 tap把发现到的每个资源都记录下来无法成功解析的则以RESOURCE-ERROR注释留在 formula 中该选项对所有官方 Homebrew tap 自动禁用——官方仓库要求所有资源完整解析代码中通过formula.tap.official?强制将ignore_errors与ignore_main_package_cooldown置为false另有--ignore-non-pypi-packagesformula 并非 PyPI 包时不失败、--version指定解析用版本、--package-name覆盖推断的包名等参数。生成结果后务必人工核对 URL 与校验和。2.3 用pypi_packages记录解析配置当brew update-python-resources无法仅凭 formula 名称/URL 推断正确的 PyPI 包时需要在 formula 中通过pypi_packagesstanza记录解析器配置让它随 formula 一起留存。四个关键字分别解决四类问题DSL 定义实际存储类见pypi_packages.rb关键字作用package_name:formula 名称或 URL 无法指向正确的 PyPI 包时指定上游真实包名extra_packages:追加额外的依赖根解析的起点包exclude_packages:排除已被其他 formula 提供的包dependencies:解析资源期间必须先安装的 formula它们为解析器提供构建/运行环境官方文档给出的标准写法pypi_packages package_name: upstream-name, extra_packages: extra-package, exclude_packages: package-from-homebrew, dependencies: resolver-dependencyDSL 实现还支持两个特殊用法四个关键字全为空会抛出ArgumentError: must provide at least one argument而package_name: 空字符串配合extra_packages:可跳过“非 extra 主包”的资源更新。测试覆盖见test/utils/pypi_spec.rb与test/dev-cmd/update-python-resources_spec.rb。2.4 安装 Python 应用程序virtualenv_install_with_resources标准应用布局推荐在 formula 中include Language::Python::Virtualenv并调用virtualenv_install_with_resources。以下示例中3.y指homebrew/core当前使用的 Python minor 版本占位符class Foo Formula include Language::Python::Virtualenv desc Example Python command-line application homepage https://example.com/foo url https://files.pythonhosted.org/packages/.../foo-1.0.tar.gz sha256 abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1 depends_on python3.y resource dependency do url https://files.pythonhosted.org/packages/.../dependency-1.2.3.tar.gz sha256 abc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1 end def install virtualenv_install_with_resources end test do assert_match version.to_s, shell_output(#{bin}/foo --version) end end对照language/python.rb的实现这个 helper 实际完成四件事在libexec下调用virtualenv_create创建虚拟环境默认system_site_packages: true、without_pip: true等价python -m venv --system-site-packages --without-pip libexec通过venv.pip_install依次安装 formula 上声明的全部resource通过venv.pip_install_and_link安装应用自身buildpath并把虚拟环境bin下新增的可执行文件符号链接进 formula 的bin默认还会把share/man下的 man page 一并链接link_manpages: true对lib/python*/orig-prefix.txt、pyvenv.cfg及指向 Cellar 的符号链接做重写使其指向opt前缀保证 Python 补丁升级后虚拟环境仍然可用。当只是 resource 的顺序或取舍不同时不必重写流程直接使用start_with:、end_with:与without:关键字调整例如把某个需要在主包之前装入的依赖提前。当安装步骤确实更复杂、需要显式分步调用pip_install/pip_install_and_link时才退而直接使用virtualenv_create。实现中还会扫描递归依赖中的site-packages并写入homebrew_deps.pth让那些由 Homebrew 公式提供 Python 绑定的依赖能被虚拟环境导入。2.5 安装 Python 绑定bindingsstd_pip_args若安装的是标准的pyproject.toml或setup.py包则声明与当前其他homebrew/coreformula 相同的版本化 Python 依赖并通过已声明的解释器与 Homebrew 的 pip 参数安装system python3.y, -m, pip, install, *std_pip_args(build_isolation: true), ./source/pythonstd_pip_args定义于formula.rb展开后大致是[--verbose, --no-deps, --no-binary:all:, --ignore-installed, --no-compile, --uploaded-prior-toP冷却天数D, --prefixprefix]逐条理解它的用意--no-deps禁用自动依赖解析依赖收敛到 resource 或 formula 依赖--no-binary:all:禁用二进制 wheel强制从源码构建--uploaded-prior-toPDAYSD延迟安装 Homebrew 发布冷却期release-cooldown常量定义于release_cooldown.rb内新发布的包降低供应链投毒风险--prefixprefix只安装到指定 prefix 下默认self.prefix即 Cellar 目录不会触碰用户全局 Python 环境build_isolation: false默认值时会追加--no-build-isolation改用 Homebrew 提供的构建依赖。实践要点当 Python minor 版本变化时命令中的解释器可执行名必须与depends_on的声明保持一致应尽量用上游构建系统的选项把绑定导向 formula prefix而不是去 patch 全局 Python 路径。2.6 bindings 的构建系统集成CMake / Meson / Autotools当依赖图里同时存在多个 Python 时必须把已声明的解释器显式传给构建系统避免它随机选中系统的其他 PythonCMake使用上游 discovery 模块认可的变量常见的是Python3_EXECUTABLE、Python_EXECUTABLE旧项目可能是PYTHON_EXECUTABLE。Meson先确认上游如何调用find_installation()再用它支持的选项指定解释器如果 Meson 无法推断 Homebrew 的安装目录应把python.purelibdir或python.platlibdir设为 formula prefix 内的路径。Autotools优先使用上游的--with-python类选项若不可用则禁用构建系统的安装步骤改用“已声明解释器 std_pip_args”的方式把绑定装进 prefix。三、Node.js让 npm 在 formula 内“规矩”地安装3.1 源码与依赖只要 npm registry 上发布的 tarball 包含完整的可分发应用就优先使用该 tarball 作为 formula 源码——它通常不含仅开发期需要的文件并已包含上游发布时的构建产物。使用精确的 tarball URL 与 SHA-256 校验和通常形如https://registry.npmjs.org/name/-/name-version.tgz与当前 Node.js 版本兼容的应用直接声明depends_on node仅当上游文档明确要求某个特定 Node 版本、且该版本化 formula 仍在维护时才改用nodeNN版本化依赖。3.2 标准 npm 安装std_npm_args常规 npm 应用的写法是装进libexec再链接可执行文件class Foo Formula def install system npm, install, *std_npm_args bin.install_symlink libexec.glob(bin/*) end endstd_npm_argsformula.rb默认展开为language/node.rb中的std_npm_install_args(libexec, ignore_scripts: true)核心行为包括使用 Homebrew 的 npm 缓存cachePackageManagerCache 目录见package_manager_cache.rb不污染用户配置应用发布冷却期--min-release-ageRELEASE_COOLDOWN_DAYS从源码构建原生依赖--build-from-source按 npmglobal 布局装进libexec--global --prefixlibexec默认忽略生命周期脚本--ignore-scripts减少安装期间被执行的包代码量实现会先执行npm pack --ignore-scripts生成 tarball并临时删除package.json中的prepare/prepack/postpack脚本再安装——因为 npm 5 对“目录”安装只建符号链接而 Homebrew 假定 buildpath 是一次性的喂给 tarball 才是“真正的安装”安装环境会把Formula[node].opt_libexec/bin前置到PATH确保用的是 Homebrew 自己的 npm 与 node-gyp。3.3 需要生命周期脚本时的两个变体场景 A包必须执行postinstall等安装期脚本。默认的--ignore-scripts会跳过它们只有确认安全时才放开system npm, install, *std_npm_args(ignore_scripts: false)但放开意味着安装期间会执行该包及其依赖的脚本代码因此在提交 PR 时必须解释为何这些脚本不可或缺。场景 Bnpm 只是更大构建流程的一环。此时改用本地布局装完继续执行上游构建最后把产物显式安装进 prefixsystem npm, install, *std_npm_args(prefix: false)注意prefix: false会让std_npm_args切换为local_npm_install_args去掉--global与--prefix随后仍须手动bin.install_symlink之类步骤完成收尾。3.4 原生插件native addons与 ABI 问题依赖树若包含原生插件构建时需要node-gyp依赖的工具链。当构建过程确实会调用 Python 时把它声明为构建期依赖depends_on python :build--build-from-source会触发对 node-gyp 的需求而 node-gyp 需要 Python 环境。另一个关键点原生插件绑定特定 Node.js ABI。formula 的test应能暴露“与不兼容的 Node 主版本一同安装”的症状这样当上游 Node 主版本升级导致 ABI 断裂时维护者能及时发现并给 formula 做 revision bump提升revision触发重新构建。四、JavaJDK 声明与JAVA_HOME包装4.1 声明构建/运行所需的 JDKJava 软件的 formula 必须声明所使用的 JDK支持当前 JDK 的软件用openjdk上游要求特定版本时用仍在维护的版本化 formuladepends_on openjdk21仅当“安装后的软件运行期完全不需要 Java”时才允许把 JDK 降级为纯构建依赖depends_on openjdk :build。4.2 用 env 包装脚本固定JAVA_HOME需要强制使用所声明 JDK时用Language::Java.java_home_env一次性包装libexec/bin下的所有命令bin.env_script_all_files libexec/bin, Language::Java.java_home_env(21)希望默认使用声明 JDK、但允许用户自行选择JAVA_HOME时用overridable_java_home_env(bin/foo).write_env_script libexec/bin/foo, Language::Java.overridable_java_home_env(21)源码实现见language/java.rbjava_home(version)通过find_openjdk_formula在openjdk及其版本化 formula 中找到“已安装且主版本匹配”的那个返回其opt_libexecversion既可以精确指定21也可以写成下界范围21即 21 及更新的主版本。java_home_env(21)返回{ JAVA_HOME: opt_libexec 路径 }形式的环境哈希。overridable_java_home_env返回的是{ JAVA_HOME: ${JAVA_HOME:-默认路径} }——脚本内若已存在JAVA_HOME就沿用否则回退到 Homebrew 的 OpenJDK实现“默认值可被用户覆盖”。两条硬性规则helper 传入的版本号必须与 formula 的依赖一致不得把 Cellar 路径或仅 macOS 生效的 JDK 位置如/Library/Java/...硬编码进安装后的脚本——统一通过opt前缀解析保证重定位与跨平台有效。4.3 字节码与构建工具上游发布的、符合收录政策的 Java 字节码比如预编译 jar可以直接安装若用 Maven、Gradle 等构建则要确保其依赖输入是版本化且可复现的锁文件、固定插件版本等避免构建漂移。五、Ruby用 Bundler 把 gem 装进libexec当上游提供记录完整依赖集的Gemfile.lock时使用 Bundler 安装并把 bundle 装入libexec而非用户 gem 环境ENV[GEM_HOME] libexec ENV[BUNDLE_WITHOUT] development system bundle, install随后从该环境中安装命令入口并在其包装脚本里保留GEM_HOME否则运行时 Ruby 找不到刚才装好的 gembin.install libexec/bin/foo bin.env_script_all_files libexec/bin, GEM_HOME: ENV.fetch(GEM_HOME)bin.install负责把应用的可执行文件放进 formula 的binenv_script_all_files则为libexec/bin下每个命令生成包装脚本并注入GEM_HOME。若上游没有可用的 lock 文件则退化为通用的可复现策略声明不可变、带校验和的resource资源或使用该生态认可的其他可复现安装方式。六、总结一份可自查的语言相关 Checklist把文档要点收敛为提交 formula 前的自查清单语言运行时python3.y/node/openjdk21/perl等在 formula 中显式声明且与 helper 参数、脚本内的解释器名一致所有非 formula 提供的模块依赖都以resource块固化版本与校验和Python 用brew update-python-resources生成后用--print-only审查应用与依赖全部安装进 formula 自己的libexec/prefix链接/包装脚本指向bin不污染用户全局语言环境运行期不联网下载代码无依赖自动解析残留pip 带--no-deps、npm 默认--ignore-scripts包装脚本使用opt前缀而非 Cellar 硬路径跨平台路径不写死test do真正执行安装后的命令并断言行为对原生插件还应能暴露 Node ABI 不兼容问题。这套“声明式依赖 前缀内安装 功能性测试”的纪律正是docs/Language-Specific-Formulae.md全篇反复强调的核心。遵循它你的 Python/Node/Java/Ruby formula 既能在 CI 上稳定复现构建也能像homebrew/core里的公式一样经受住维护者的评审与长期版本演进的考验。【免费下载链接】brew The Package Manager for Everywhere项目地址: https://gitcode.com/GitHub_Trending/br/brew创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表