☰
Buck 仓库中的 bazel-skylib:Skylark 构建规则标准库与 skylark_library 规则解析
2026/9/25 3:07:32 网站建设 项目流程
  • 开发工具
  • 构建工具

【免费下载链接】buck

A fast build system that encourages the creation of small, reusable modules over a variety of platforms and languages.

项目地址:https://gitcode.com/gh_mirrors/bu/buck
点击查看免费下载

Skylib 是一套面向 Bazel/Skylark 构建规则开发的“标准库”,提供集合、字典、文件路径、select 选择、shell 转义和单元测试等工具函数。本文基于 Buck 仓库中 vendored 的 bazel-skylib 快照 及其模块源码,讲解其模块组织方式、各模块的实际 API、聚合规则skylark_library的实现原理,以及它在 Buck 自身 Bazel 迁移规则中的真实用途,读完后可独立为.bzl规则文件选择和使用这些标准函数。

Skylib 的定位:为什么 Buck 仓库里有一份 Bazel 库

README.md 的开篇定义:Skylib 是“a standard library that provides functions useful for manipulating collections, file paths, and other features that are useful when writing custom build rules in Bazel”——即一套专门服务于自定义构建规则编写的函数库,而非常规构建产物库。README 同时明确提示:该库处于早期开发阶段,API 在此期间可能发生变化。

结合仓库内的其他文件可以确认它的来源与用途:

  • README.facebook 说明该目录是从bazelbuild/bazel-skylib上游仓库整体拷贝并“as-is”展开的,属于第三方 vendored 代码;
  • WORKSPACE 中声明workspace(name = "bazel_skylib"),即它作为独立 Bazel 工作区时暴露的仓库名;
  • 从 Buck 自身的构建文件来看,它在被加载时使用了@buck_bazel_skylib这一仓库名,例如 tools/build_rules/module_rules.bzl 与 tools/build_rules/java_rules.bzl 均有load("@buck_bazel_skylib//lib:collections.bzl", "collections"),programs/BUCK 中则有load("@buck_bazel_skylib//lib:dicts.bzl", "dicts")。

也就是说,Buck 仓库把 Skylib 作为外部依赖(external repository)引入,供其 Bazel 迁移用的tools/build_rules/*.bzl规则集调用,这正是 Skylib 设计初衷“支撑自定义规则开发”的落地场景。

模块体系:.bzl文件即“模块”,lib.bzl作为索引

README 定义了 Skylib 的核心组织约定:

  • lib/目录下的每个.bzl文件定义一个“模块”(module)——一个struct,内含一组相关函数和其他符号,可作为单个单元被加载;
  • 顶层文件lib.bzl充当索引(index),供其他模块从中统一导入;
  • 使用方式是load("@bazel_skylib//:lib.bzl", ...)后通过点号访问 struct 字段。README 给出的示例:
load("@bazel_skylib//:lib.bzl", "paths", "shell") p = paths.basename("foo.bar") s = shell.quote(p)

对照实际源码,每个模块都严格遵循“私有实现 + 公开 struct 导出”的模式。以 lib/collections.bzl 为例:

collections = struct( after_each = _after_each, before_each = _before_each, uniq = _uniq, )

私有函数以下划线命名(_after_each等),仅在文件内部可见,调用方通过collections.uniq(...)这样的命名空间访问公开 API。README 中“编写新模块”一节正是这一约定的规范化描述(详见后文)。

需要说明的一个事实细节:当前快照中的 lib.bzl 仅包含版权头和一行 docstring("""Index from which multiple modules can be loaded."""),并未内联任何load语句。实际的聚合工作由根目录 BUILD 中的skylark_library(name = "lib", ...)目标承担,其deps列出了全部模块://lib:collections、//lib:dicts、//lib:paths、//lib:selects、//lib:sets、//lib:shell、//lib:structs、//lib:unittest。

README 的“List of modules”列出的是 collections、dicts、paths、selects、sets、shell、unittest 七个模块;而lib/目录中实际还存在 structs.bzl,并被根 BUILD 的聚合目标依赖,但未被 README 列入清单——这印证了 README 中“API 可能变动”的版本提示,引用时以实际文件为准。

核心模块 API 与实现解析

以下逐个模块给出本快照中实际可用的函数及实现要点,路径均相对于仓库根目录。

collections:序列操作

lib/collections.bzl 导出三个函数:

  • collections.after_each(separator, iterable):在每个元素之后插入分隔符,返回新列表;
  • collections.before_each(separator, iterable):在每个元素之前插入分隔符;
  • collections.uniq(iterable):利用字典推导式{element: None for element in iterable}去重,要求元素可哈希。

uniq的实现(第 49-60 行)展示了典型的 Bazel/Skylark 技巧——Skylark 语言没有内置集合类型,用 dict 的键唯一性模拟 set 去重,且保持首次出现顺序。

dicts:字典合并

lib/dicts.bzl 只导出一个函数dicts.add(*dictionaries):返回包含所有入参字典条目的新 dict,同键时参数列表中靠后的字典覆盖靠前的。其 docstring 特别说明设计动机是“支持零个或多个参数”——零参数返回空字典,单参数等价于拷贝,从而满足算术恒等律,让调用方无需为输入数量写特判。这正是 Buck 的 programs/BUCK 加载它来合并字典配置的用途所在。

paths:文件路径操作(功能最重的模块)

lib/paths.bzl 是本快照中最大的模块(约 236 行),提供 8 个函数,全部针对Unix 风格分隔符——模块头部注释明确声明不支持 Windows 反斜杠路径或盘符:

函数行为要点
basename(p)p.rpartition("/")[-1],取文件部分;p以斜杠结尾时返回空串(与 Pythonos.path.basename一致,而与 Unixbasename命令不同)
dirname(p)取不含 basename 的前缀,并像os.path.dirname一样去掉末尾连续斜杠;无前缀时返回分隔符本身
is_absolute(path)path.startswith("/")
join(path, *others)模拟 POSIX 下os.path.join:任一后续组件为绝对路径时丢弃之前所有组件
normalize(path)模拟os.path.normpath:空路径返回"."、删除.段、折叠多余斜杠、处理..段(相对路径保留前导..)
relativize(path, start)返回path相对于祖先路径start的部分;因无法访问文件系统,若path不在start之下会直接fail报错,而非向上回溯
replace_extension(p, new_extension)替换扩展名,无扩展名时追加新扩展名
split_extension(p)拆分为(root, ext)元组,恒有root + ext == p;basename 前导点不计为扩展点,故split_extension(".bashrc")返回(".bashrc", "")

其中normalize(第 96-149 行)用栈式遍历逐段处理..,并保留了“单双前导斜杠原样、三个及以上折叠为一个”的 POSIX 语义;relativize(第 151-186 行)则通过逐段 zip 比较前缀校验“path 必须位于 start 之下”。这些行为与tests/paths_tests.bzl(280 行,全库最大的测试文件)中的用例一一对应。

selects:支持 OR 键的 select()

lib/selects.bzl 提供selects.with_or(input_dict)与selects.with_or_dict(input_dict):

  • with_or是原生select()的替代品,键除了普通形式"//foo:config1",还允许元组形式("//foo:config1", "//foo:config2")表示“任一命中即可”,最终被展开为多个同值键;
  • with_or_dict返回展开后的 dict 本体,便于 Skylark 宏检查select()内容(原生select()的结果无法直接检查);
  • 实现上会校验同一 config setting 标签不得在输入中出现多次,重复时直接fail("key %s appears multiple times" % ...)。

sets 与 shell:集合与 shell 转义

  • lib/sets.bzl(137 行)提供以 dict 模拟集合的运算函数,对应测试为 tests/sets_tests.bzl;
  • lib/shell.bzl 导出两个函数:
    • shell.quote(s):单引号包裹并将内部单引号替换为'\\'',即标准 POSIX shell 转义法(实现仅一行:"'" + s.replace("'", "'\\''") + "'");
    • shell.array_literal(iterable):生成可直接嵌入 shell 脚本的数组字面量,例如shell.array_literal(["a", "b", "c"])得到("a" "b" "c");所有元素一律加引号以保证安全。

这两个函数在编写genrule的cmd或sh_binary包装脚本时,是拼接安全命令行的常用工具。

structs 与 unittest:结构转换与规则级单测

  • lib/structs.bzl 的structs.to_dict(s)用dir(s)枚举字段并移除to_json/to_proto两个内置方法后构造 dict,用于把 struct 转为可迭代的数据结构;
  • lib/unittest.bzl(262 行)导出两个模块:unittest(声明与定义单元测试)与asserts(断言函数)。其核心unittest.make(impl)(第 24-76 行)把实现函数包装成_skylark_testable = True且test = True的 rule,并从str(impl)中解析函数名用于测试输出;用法模式为:
def _your_test(ctx): env = unittest.begin(ctx) # 断言语句 unittest.end(env) your_test = unittest.make(_your_test)

docstring 同时提醒:测试规则名必须以_test结尾。该模块还load(":sets.bzl", "sets"),是库内模块互相依赖的实例。

skylark_library:聚合.bzl的规则与 Provider

README 最后一节介绍skylark_library.bzl规则,其完整实现见 skylark_library.bzl。该规则用于“聚合一组 Skylark 文件及其依赖,供测试目标和文档生成使用”。

规则定义(第 46-59 行):

skylark_library = rule( implementation = _skylark_library_impl, attrs = { "srcs": attr.label_list(allow_files = [".bzl"]), "deps": attr.label_list( allow_files = [".bzl"], providers = [[SkylarkLibraryInfo]], ), }, )
  • srcs:本目标直接包含的.bzl文件;
  • deps:依赖的其他skylark_library目标(要求提供SkylarkLibraryInfo)。

实现逻辑(第 26-44 行):以depset(ctx.files.srcs, order = "postorder")起步,累加各依赖的文件集,然后同时返回DefaultInfo(files与runfiles都填充全部文件)和自定义 providerSkylarkLibraryInfo(srcs与transitive_srcs两个字段)。源码注释解释了双重填充的意图:全部依赖文件同时出现在files与runfiles中,保证skylark_library既可以被其他程序作为data引用,也可以出现在genrule()的tools里。

docstring 中的标准用法示例(第 60-109 行)展示了在一个含checkstyle/、lua/子包的工作区里,如何为各子包声明skylark_library目标:

load("@buck_bazel_skylib//:skylark_library.bzl", "skylark_library") skylark_library( name = "lua-rules", srcs = ["lua.bzl", "luarocks.bzl"], )

在 Skylib 自身的 BUILD 中,该规则被用于两个目标:lib(聚合索引入口,deps 为全部 8 个模块)和skylark_library本身;每个模块在 lib/BUILD 中也有各自的skylark_library目标(如unittest依赖:sets,与源码中的load关系一致)。此外两个 BUILD 都声明了testonly = True的test_depsfilegroup,供规则测试收集文件。

测试组织与“编写新模块”流程

tests/目录为每个模块配备同名测试文件:collections_tests.bzl、paths_tests.bzl、shell_tests.bzl 等,全部基于unittest模块编写,与模块一一对应——这是 Skylib 对“每个模块必须带单测”约定的直接体现。

README 的“Writing a new module”一节给出完整的模块新增流程,与库内现有代码完全吻合:

  1. 在lib/下新建.bzl文件;

  2. 在文件内以下划线前缀私有方式定义函数或常量等符号;

  3. 创建导出的模块 struct,把公开名映射到私有实现。README 的示例:

    def _manipulate(): ... things = struct( manipulate=_manipulate, )
  4. 在lib.bzl中加一行load("@bazel_skylib//lib:things.bzl", "things")使其可从索引访问(注意:按前文所述,本快照中该索引职责已由根 BUILD 的skylark_library(name = "lib")目标承担,实际维护时应同步更新该deps列表);

  5. 调用方即可load("@bazel_skylib//:lib.bzl", "things")后things.manipulate()使用;

  6. 在tests/目录为模块补充单元测试。

适用前提与使用边界

  • 版本边界:本库是上游早期快照(版权头 2017 年),README 明确 API 可能变化;例如 README 模块清单未包含structs,而lib.bzl索引文件仅有 docstring 无内联 load。引用时以仓库内实际文件为准;
  • 平台边界:paths模块仅支持 Unix 风格路径分隔符(见 lib/paths.bzl 模块头注释);
  • 语言边界:全部函数运行于 Bazel 的 Skylark 环境,不能直接用于普通 Python 或 Buck 的 Python DSL 中;
  • 在 Buck 中的角色:它是 Buck 仓库 Bazel 迁移规则集(tools/build_rules/)的外部依赖,通过@buck_bazel_skylib仓库名加载lib/collections.bzl、lib/dicts.bzl等模块,而非 Buck 构建流程本身的组成部分。

综上,bazel-skylib 以“模块 struct + 索引聚合 + skylark_library 依赖管理 + 逐模块单测”的完整范式,为在 Buck 这类大型构建系统中编写和维护可复用的 Skylark 规则提供了标准函数基座与工程化模板。

  • 开发工具
  • 构建工具

【免费下载链接】buck

A fast build system that encourages the creation of small, reusable modules over a variety of platforms and languages.

项目地址:https://gitcode.com/gh_mirrors/bu/buck
点击查看免费下载

相关推荐

上一篇:UKB_RAP 项目模块化使用指南
下一篇:wx-livespy:微信视频号直播数据实时捕获与分析引擎

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询