- 图形学
- 图像处理
【免费下载链接】skia
Skia is a complete 2D graphic library for drawing Text, Geometries, and Images.
本文聚焦 Skia 仓库中 expat(C 语言 XML 解析库)的自定义构建配置,核心讲解bazel/external/expat/config目录的组成、expat_config.h中每个宏的含义,以及这套配置如何通过 Bazel 的download_config_files规则与 GN 构建系统被消费。读完本文,你将掌握 Skia 为第三方库提供"定制配置头文件"的完整工程模式,并能在升级 expat 时正确更新与验证配置。
一、为什么 Skia 需要一份"自定义的 expat 配置"
expat 是 Skia 依赖的第三方库之一,用于解析 XML 数据(例如 SVG 与部分字体配置场景)。和大多数 C 库一样,expat 在编译时需要根据目标平台的能力生成一个名为expat_config.h的配置头文件——它记录了当前环境是否具备某些系统调用、头文件、字节序等信息,直接决定xmlparse.c、xmltok.c等源码编译出的行为。
关联文档 用两句话点明了这套配置的本质:
This file represents the custom configuration of how we build expat. The files here are symlinked from //third_party/expat/include/expat_config
也就是说,bazel/external/expat/config/目录承载的是"Skia 构建 expat 的定制配置",而配置文件的真正出处(源文件)位于third_party/expat/include/expat_config。这样拆分的原因在于:Bazel 的 external 仓库(repository)不能直接引用 Skia 主仓库内部的文件,因此 Skia 必须把配置以"独立仓库"的形式提供给 expat 的 Bazel 构建。
二、配置目录的四个文件:各自的职责
bazel/external/expat/config/目录下共四个文件:
| 文件 | 职责 |
|---|---|
README.md | 说明目录用途(即本篇讲解的关联文档) |
expat_config.h | 真正被编译消费的配置头文件,约 140 行宏定义 |
BUILD.bazel | 用exports_files把expat_config.h暴露给外部使用者 |
WORKSPACE.bazel | 将该目录声明为独立的 Bazel workspace |
其中 BUILD.bazel 的内容非常简短:
exports_files( [ "expat_config.h", ], visibility = ["//visibility:public"], )它只做一件事:把expat_config.h声明为可被其他仓库引用的导出文件,且对全局可见。这保证 expat 的外部构建(例如以@expat_config//:expat_config.h方式引用)能够拿到该文件。
而 WORKSPACE.bazel 的存在原因,注释解释得很清楚:
# This blank WORKSPACE.bazel simply indicates that this folder is a distinct Bazel workspace # from the main Skia one. This avoids a circular dependency by having Skia depend on expat # and expat trying to use configuration files from Skia.关键设计:这个几乎为空的WORKSPACE.bazel把bazel/external/expat/config标记为与 Skia 主仓库相互独立的 Bazel workspace,从而避免"Skia 依赖 expat、expat 又反过来从 Skia 取配置"的循环依赖。这是 Bazel 多仓库构建中一种典型的解耦手法。
三、expat_config.h 宏定义逐项解读
expat_config.h 是这份配置的核心,头部注释表明它"Generated from expat_config.h.in by configure"——即由 expat 自带的 autoconf 流程生成,再由 Skia 维护到仓库中。按其作用可分为四类:
1. 功能开关宏(决定编译出的能力)
| 宏 | 本仓库取值 | 含义 |
|---|---|---|
XML_NS | 1 | 启用 XML Namespaces 功能 |
XML_DTD | 1 | 启用参数实体(parameter entity)解析,即 DTD 支持 |
XML_CONTEXT_BYTES | 1024 | 当前解析点周围保留多少字节上下文(用于错误报告与实体解析) |
XML_DEV_URANDOM | 1 | 允许从/dev/urandom读取熵(用于生成随机 ID) |
XML_ATTR_INFO | 未定义 | 不启用"获取属性名/属性值字节偏移"的扩展 API |
其中XML_NS与XML_DTD对应 expat 顶层构建脚本中的--enable-xml-ns、--enable-xml-dtd选项;XML_CONTEXT_BYTES对应--with-xml-context-bytes。Skia 全部开启 Namespaces 与 DTD,说明其 XML 处理场景需要完整的实体与命名空间支持。
2. 环境探测宏(平台能力检测结果)
这部分记录 configure 在生成环境上的探测结果,包括:
- 字节序:
BYTEORDER 1234(小端,1234 = LILENDIAN); - 头文件存在性:
HAVE_DLFCN_H、HAVE_FCNTL_H、HAVE_INTTYPES_H、HAVE_STDINT_H、HAVE_STDIO_H、HAVE_STDLIB_H、HAVE_STRINGS_H、HAVE_STRING_H、HAVE_SYS_PARAM_H、HAVE_SYS_STAT_H、HAVE_SYS_TYPES_H、HAVE_UNISTD_H均定义为1; - 系统调用与函数:
HAVE_GETPAGESIZE 1、HAVE_MMAP 1; - 被显式关闭的项:
HAVE_ARC4RANDOM、HAVE_GETRANDOM、HAVE_SYSCALL_GETRANDOM、HAVE_ARC4RANDOM_BUF、HAVE_LIBBSD均被注释掉(见下文的 patch 机制); - 标准头文件:
STDC_HEADERS 1。
注意这里有一个重要细节:当前这份expat_config.h是面向 Linux 类环境的探测结果。例如它启用了HAVE_MMAP与/dev/urandom(XML_DEV_URANDOM),这些在 Windows 或 iOS 等平台上并不成立。因此这份配置并不具有跨平台普适性,而是"Skia 在默认构建环境下为 expat 准备的基线配置"。
3. 字节序条件编译(WORDS_BIGENDIAN)
文件末尾有一段针对大端处理器的条件编译逻辑:
#if defined AC_APPLE_UNIVERSAL_BUILD # if defined __BIG_ENDIAN__ # define WORDS_BIGENDIAN 1 # endif #else # ifndef WORDS_BIGENDIAN /* # undef WORDS_BIGENDIAN */ # endif #endif它配合BYTEORDER 1234使用:默认按小端(Intel 系)处理,同时为 Apple Universal Binary(同时包含大端与瘦二进制)保留运行时判定路径。这样同一份头文件在 x86 与 PowerPC/ARM 大端变体上都能得到正确的字节序语义。
4. 包信息宏(版本与元数据)
PACKAGE "expat"、PACKAGE_NAME "expat"、PACKAGE_TARNAME "expat";PACKAGE_STRING "expat 2.5.0"、PACKAGE_VERSION "2.5.0"、VERSION "2.5.0";PACKAGE_BUGREPORT "expat-bugs@libexpat.org";LT_OBJDIR ".libs/"(libtool 未安装库的存放子目录)。
这些宏表明本仓库锁定的是expat 2.5.0,版本一致性由 roll 脚本保证(见第六节)。
四、配置的"真身":third_party/expat/include/expat_config
README 明确说明配置目录的文件"symlinked from//third_party/expat/include/expat_config"。在仓库中对应的真实文件是 third_party/expat/include/expat_config/expat_config.h,其内容与bazel/external/expat/config/expat_config.h完全一致(140 行、所有宏定义逐字相同)。
之所以采用"源文件放在third_party/、Bazel 侧放一份(符号链接或复制)"的布局,是因为:
- GN 构建也需要它:Skia 的 GN 构建(
gn工具链)通过third_party/expat/BUILD.gn直接把include/expat_config加入头文件搜索路径; - Bazel 构建需要独立分发:Bazel 的 external 仓库无法读取主仓库内任意路径,所以
bazel/external/expat/config必须自成一个可被下载/引用的单元。
两份文件内容同步的维护工作由自动化脚本完成(见第六节),开发者无需手工同步。
五、GN 构建如何消费这份配置
在 third_party/expat/BUILD.gn 中,expat 的 GN 目标这样组织:
declare_args() { skia_use_system_expat = is_official_build } import("../third_party.gni") if (skia_use_system_expat) { system("expat") { libs = [ "expat" ] } } else { third_party("expat") { _src = "../externals/expat" public_defines = [ "XML_STATIC" ] public_include_dirs = [ "../externals/expat/expat/lib", "include/expat_config", ] public = [ "$_src/expat/lib/expat.h" ] sources = [ "$_src/expat/lib/ascii.h", ... "$_src/expat/lib/xmlparse.c", "$_src/expat/lib/xmlrole.c", "$_src/expat/lib/xmltok.c", ... ] unused_sources = [ "$_src/expat/lib/xmltok_impl.c", "$_src/expat/lib/xmltok_ns.c", ] } }关键点:
skia_use_system_expat = is_official_build:官方(official)构建默认使用系统安装的 expat;非官方构建才走third_party自带源码路径;public_include_dirs中加入include/expat_config,使xmlparse.c等源文件通过#include "expat_config.h"时能命中 third_party/expat/include/expat_config/expat_config.h;- 同时定义
XML_STATIC,用于在 Windows 上以静态库方式链接 expat; xmltok_impl.c与xmltok_ns.c被列入unused_sources,因为它们在 expat 源码里是条件包含进xmltok.c的,不能独立编译——GN 构建刻意避免重复编译。
六、配置如何分发到 Bazel 外部仓库:download_config_files 规则
Bazel 侧的分发机制位于 bazel/deps.bzl 的header_based_configs()函数:
def header_based_configs(): maybe( download_config_files, name = "expat_config", skia_revision = "7b730016006e6b66d24a6f94eefe8bec00ac1674", files = { "BUILD.bazel": "bazel/external/expat/config/BUILD.bazel", "expat_config.h": "third_party/expat/include/expat_config/expat_config.h", }, )它调用 download_config_files.bzl 中定义的download_config_filesrepository 规则。该规则的核心实现只有十几行:
def _download_config_files_impl(repo_ctx): for dst, src in repo_ctx.attr.files.items(): url = "https://raw.githubusercontent.com/google/skia/{}/{}".format(repo_ctx.attr.skia_revision, src) repo_ctx.download(url, output = dst)即:以指定的skia_revision为版本锚点,从 Skia 仓库按映射关系把src路径的文件下载到 Bazel external 仓库的dst路径。映射表清楚地显示了"源文件在third_party/,对外暴露在bazel/external/"的对应关系。
规则文档还说明了一个重要事实:Skia 自身并不直接使用这条规则,而是用local_repository让配置在本仓库内直接可用;这条规则的存在是为了让外部客户端(无法引用 Skia 源码树内部路径的仓库)也能独立获取这些配置。与之配套的还有 freetype、harfbuzz、icu 的同类配置,它们统一由header_based_configs()管理。
七、升级 expat 时如何更新配置:roll-expat.sh 与两个 patch
配置的更新不是手写的,而是由 third_party/expat/roll-expat.sh 驱动。脚本的主要步骤依次为:
previousrev:从DEPS中解析当前 expat 版本对应的 commit;nextrev:从 chromium 的 expat 镜像仓库 fetch 最新提交;rolldeps:更新DEPS中的版本并执行tools/git-sync-deps;update_expat_config_h:关键步骤——在 expat 源码目录中执行./buildconf.sh && ./configure重新生成expat_config.h,然后:
cp "${EXPAT_GIT_DIR}/expat/expat_config.h" "${EXPAT_BUILD_DIR}/include/expat_config/" && patch -d "${EXPAT_BUILD_DIR}" -p3 < "${EXPAT_BUILD_DIR}/0001-Do-not-claim-getrandom.patch" && patch -d "${EXPAT_BUILD_DIR}" -p3 < "${EXPAT_BUILD_DIR}/0002-Do-not-claim-arc4random_buf.patch"即拷贝新生成的配置头文件到third_party/expat/include/expat_config/,紧接着应用两个定制 patch:
- 0001-Do-not-claim-getrandom.patch:把
HAVE_GETRANDOM与HAVE_SYSCALL_GETRANDOM从定义改为注释——Skia 不希望 expat 依赖getrandom()系统调用; - 0002-Do-not-claim-arc4random_buf.patch:把
HAVE_ARC4RANDOM_BUF改为注释——不依赖 BSD 风格的arc4random_buf()。
这正是 expat_config.h 中这些宏被注释掉的原因:configure 在开发机上探测到了这些函数,但 Skia 出于可移植性和可控性主动放弃使用它们。patch 之后的文件既会git add回third_party/expat/include/expat_config/expat_config.h,也会在后续通过header_based_configs()同步到bazel/external/expat/config。
check_all_files_are_categorized:校验 expat 的expat/lib/*.{c,h}与BUILD.gn中的源码清单一一对应,防止升级后新增/删除了源文件而BUILD.gn未同步。
八、小结:这套配置模式的通用价值
从bazel/external/expat/config这一小目录出发,可以总结出 Skia 管理第三方库构建配置的完整闭环:
- 单一事实源:配置头文件由 expat 的 autoconf 生成,经
roll-expat.sh拷贝进third_party/expat/include/expat_config/,并通过 patch 定制; - 双构建系统共享:GN 通过
public_include_dirs直接使用;Bazel 通过download_config_files以skia_revision为锚点分发到bazel/external/expat/config/; - 循环依赖隔离:空
WORKSPACE.bazel使配置目录成为独立 workspace,切断了 Skia ↔ expat 的双向依赖; - 可验证性:
check_all_files_are_categorized保证升级时源码清单与构建文件同步。
对于任何需要在 Bazel 工程中为第三方 C/C++ 库提供定制配置头文件的开发者,这套"third_party源文件 +bazel/external分发 + repository 规则 + 独立 workspace"的组合拳,都是一份可以直接参考的工程范式。理解expat_config.h中每个宏(XML_NS、XML_DTD、XML_CONTEXT_BYTES、BYTEORDER、各HAVE_*探测项)的取值与含义,也是在 Skia 中排查 XML 解析相关构建问题时的重要切入点。
- 图形学
- 图像处理
【免费下载链接】skia
Skia is a complete 2D graphic library for drawing Text, Geometries, and Images.
相关推荐
在 Skia 仓库中自定义 Bazel 构建配置:bazel/user/buildrc 实战指南
在 Skia 仓库中自定义 Bazel 构建配置:bazel/user/buildrc 实战指南 导读 Skia 的 Bazel 构建系统为开发者预留了一个专属
图形学使用Scarab管理Hollow Knight Mods时的常见问题排查指南
使用Scarab管理Hollow Knight Mods时的常见问题排查指南 Scarab作为一款专为Hollow Knight设计的现代化Mod管理器,以其直
图形学图像处理深入 Skia 的 Bazel RBE 构建:自建容器镜像、Hermetic 工具链与平台定义全解
深入 Skia 的 Bazel RBE 构建:自建容器镜像、Hermetic 工具链与平台定义全解 导读 Skia 使用 Bazel 作为构建系统,并将大规模构
图形学
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考