- 开发工具
- 构建工具
【免费下载链接】hatch
Modern, extensible Python project management
导读
Hatch 的构建体系以“构建目标(build target)”为抽象单元,而每一个构建目标都对应一个构建器插件(builder plugin)。本文以官方参考文档 docs/plugins/builder/reference.md 为核心骨架,逐项剖析其核心类BuilderInterface的全部公开成员(PLUGIN_NAME、app、root、build_config、target_config、config、get_config_class、get_version_api、get_default_versions、clean、recurse_included_files、get_default_build_data),并结合 Hatchling 源码(interface.py、config.py)与测试(test_interface.py)还原其底层调用链。读完本文,你将掌握如何编写一个可注册、可配置、可与构建钩子协作的自定义构建器插件。
一、构建器插件是什么
在 Hatch 中,构建目标(build target)是pyproject.toml里tool.hatch.build.targets表下的一个具名小节,而构建器插件就是该小节的实现者。官方参考文档开篇即指向 构建配置文档,其中明确说明:
[tool.hatch.build.targets.<TARGET_NAME>]构建器插件与构建钩子(build hook)插件共同组成了 Hatchling 可扩展的构建管线:构建器负责产出产物,构建钩子负责在构建的不同阶段干预(如初始化、最终化、清理)。
内置构建器
从 hooks.py 的注册代码可以看到,Hatchling 内置了五个构建器:
@hookimpl def hatch_register_builder() -> list[type[BuilderInterface]]: return [AppBuilder, BinaryBuilder, CustomBuilder, SdistBuilder, WheelBuilder]它们分别对应:
- wheel 构建器 —— 二进制 wheel 分发包,构建目标名
wheel; - sdist 构建器 —— 源码分发包,构建目标名
sdist; - custom 构建器 —— 从项目内 Python 文件加载自定义构建器,构建目标名
custom; app、binary构建器——详见 binary 构建器文档。
已知第三方构建器
参考文档还列出了两个社区典型的第三方构建器,用于说明“构建器插件”这一抽象的实际用途:
- hatch-aws:用于配合 SAM 构建 AWS Lambda 函数,把普通 Python 项目打包成可部署的 Lambda 制品;
- hatch-zipped-directory:用于构建 ZIP 归档,以便安装到各类外部包安装系统中。
两者共同说明了一个事实:构建目标不一定是 pip 可安装的标准发行物,通过BuilderInterface你可以产出任意形态的构建产物,这正是“可扩展(extensible)”的项目管理的落点之一。
二、BuilderInterface:构建器插件的统一接口
构建器插件的核心是一个继承自hatchling.builders.plugin.interface.BuilderInterface的类。该抽象基类(ABC)定义在 interface.py,并在泛型层面约束了两类类型参数:
class BuilderInterface(ABC, Generic[BuilderConfigBound, PluginManagerBound]):BuilderConfigBound:构建器的配置类,必须是BuilderConfig的子类;PluginManagerBound:插件管理器类型,用于插件查找。
参考文档通过 mkdocstrings 自动生成了该类的成员清单。下面按“声明属性 / 配置属性 / 构建流程方法 / 文件收集方法”四组逐一展开,所有行为均以源码为据。
2.1 PLUGIN_NAME:插件的选择名
PLUGIN_NAME = "" """The name used for selection."""PLUGIN_NAME是插件在tool.hatch.build.targets.<TARGET_NAME>中被选用的名字。例如 wheel 构建器把该属性设为"wheel"、sdist 构建器设为"sdist"。当 Hatch 解析到[tool.hatch.build.targets.foo]时,就会通过插件管理器按名称foo查找并实例化对应的构建器类。
需要注意的是,custom构建器是个特例:参考 custom 构建器文档 的说明,custom会忽略自定义类中定义的PLUGIN_NAME并强制设为"custom"。这一行为在 custom.py 中有直接实现:
# Always keep the name to avoid confusion hook.PLUGIN_NAME = cls.PLUGIN_NAME2.2 构建器与应用程序、项目元数据的桥接
BuilderInterface的构造签名(见 interface.py)如下:
def __init__( self, root: str, plugin_manager: PluginManagerBound | None = None, config: dict[str, Any] | None = None, metadata: ProjectMetadata[PluginManagerBound] | None = None, app: Application | None = None, ) -> None:其中root是项目根目录的绝对路径。其余参数均为可选,会在首次访问对应属性时按需惰性创建(lazy initialization),这正是文档成员app、root、config、build_config、target_config的底层来源。
root:项目树根目录
@property def root(self) -> str: """The root of the project tree.""" return self.__root返回项目根目录,是所有相对路径(文件选择、配置定位)的基准。
app:Application 实例
@property def app(self) -> Application: """An instance of Application."""提供对 Hatchling 桥接层Application的访问,用于显示调试信息(display_debug)等终端交互。参考文档中指向 utilities 文档 的链接说明了其完整 API。在build()主流程中,self.app.display_debug(...)会被用于输出每个版本构建的调试日志(见 interface.py)。
metadata 与 project_config / hatch_config
虽然参考文档的成员清单没有单独列出metadata,但它是构建器一切配置的源头:raw_config、project_config、hatch_config都分别对应ProjectMetadata的原始配置、project表与tool.hatch表。测试 test_interface.py 中TestMetadata系列用例(test_build_config、test_target_config、test_build_config_not_table)直接验证了这些属性与pyproject.toml的映射关系,例如当tool.hatch.build不是表结构时会抛出TypeError: Fieldtool.hatch.buildmust be a table。
build_config 与 target_config:全局与目标级配置
@property def build_config(self) -> dict[str, Any]: """ ```toml config-example [tool.hatch.build] ``` """build_config对应tool.hatch.build表——按 构建配置文档 的说法,可以在其中定义全局构建配置(虽然不推荐),随后被目标级配置覆盖。
@property def target_config(self) -> dict[str, Any]: """ ```toml config-example [tool.hatch.build.targets.<PLUGIN_NAME>] ``` """target_config对应tool.hatch.build.targets.<PLUGIN_NAME>表,是构建器专属配置的存放处。源码中对非表结构会直接抛错(见 interface.py):
if not isinstance(target_config, dict): message = f"Field `tool.hatch.build.targets.{self.PLUGIN_NAME}` must be a table" raise TypeError(message)config:BuilderConfig 实例
@property def config(self) -> BuilderConfigBound: """An instance of BuilderConfig."""config是get_config_class()返回的配置类实例,将root、PLUGIN_NAME、build_config、target_config组合在一起,封装了 include/exclude 文件选择、目录、版本、钩子配置等全部构建参数(实现见 config.py)。
2.3 get_config_class:自定义配置类
@classmethod def get_config_class(cls) -> type[BuilderConfigBound]: """Must return a subclass of BuilderConfig.""" return cast(type[BuilderConfigBound], BuilderConfig)默认返回BuilderConfig本身;如果你的构建器需要额外的配置项,就应返回一个BuilderConfig的子类。参考文档中BuilderInterface的示例用法展示了这一点:
from hatchling.builders.config import BuilderConfig from hatchling.builders.plugin.interface import BuilderInterface from hatchling.plugin.manager import PluginManager class SpecialBuilderConfig(BuilderConfig[PluginManager]): ... class SpecialBuilder(BuilderInterface[SpecialBuilderConfig, PluginManager]): PLUGIN_NAME = "special" def get_config_class(self) -> type[SpecialBuilderConfig]: return SpecialBuilderConfig ...2.4 构建流程核心方法
build()是BuilderInterface上驱动整个构建流程的公共方法(参考文档虽未列入成员清单,但它是理解其余方法调用关系的钥匙),其调用顺序可概括为(interface.py):
self.metadata.validate_fields()—— 先校验项目元数据,尽早失败;- 确定输出目录(优先环境变量
HATCH_BUILD_LOCATION,否则config.directory); version_api = self.get_version_api()—— 获取“版本名 → 构建函数”映射,并校验config.versions中不存在未知版本(否则抛ValueError: Unknown versions for target ...);- 通过
self.get_build_hooks(directory)实例化所有已配置的构建钩子; - 依据
HATCH_BUILD_CLEAN/-c标志调用self.clean(directory, versions)与每个钩子的clean(versions); - 对每个版本依次:
get_default_build_data()→set_build_data_defaults(build_data)→ 依次执行所有钩子的initialize(version, build_data)→ 调用version_apiversion产出产物 → 依次执行所有钩子的finalize(...)→ 可选地clean_hooks_after→yield产物路径。
get_version_api:版本到构建函数的映射(抽象方法)
@abstractmethod def get_version_api(self) -> dict[str, Callable]: """ A mapping of `str` versions to a callable that is used for building. Each callable must have the following signature: def ...(build_dir: str, **build_data: dict) -> str: The return value must be the absolute path to the built artifact. """这是构建器插件必须实现的抽象方法。返回值是“版本字符串 → 构建可调用对象”的字典,每个可调用对象接收构建目录与**build_data关键字参数,并返回构建产物的绝对路径。例如 wheel 构建器提供standard与editable两个版本(见 wheel 文档),sdist 构建器在 sdist.py 中同样实现了get_version_api。
get_default_versions:未指定时的默认版本
def get_default_versions(self) -> list[str]: """A list of versions to build when users do not specify any, defaulting to all versions.""" return list(self.get_version_api())默认返回get_version_api()的所有键。用户在tool.hatch.build.targets.<TARGET_NAME>.versions中未指定任何版本时,构建器将使用该方法的返回值(见 config.py)。
get_default_build_data 与 set_build_data_defaults:供构建钩子修改的构建数据
def get_default_build_data(self) -> dict[str, Any]: """A mapping that can be modified by build hooks to influence the behavior of builds.""" return {} def set_build_data_defaults(self, build_data: dict[str, Any]) -> None: build_data.setdefault("artifacts", []) build_data.setdefault("force_include", {})get_default_build_data返回一个可由构建钩子修改、进而影响构建行为的字典;set_build_data_defaults为其注入两个默认键:artifacts(构建期制品)与force_include(强制包含映射)。这两个键在 config.py 的set_build_data上下文管理器中被消费:钩子声明的制品会形成build_artifact_spec,强制包含文件会被合并进build_force_include,同时把已被占用的路径登记为build_reserved_paths以避免冲突。
clean:构建前的清理钩子
def clean(self, directory: str, versions: list[str]) -> None: """ Called before builds if the `-c`/`--clean` flag was passed to the build command. """当hatch build命令传入-c/--clean标志(或设置HATCH_BUILD_CLEAN=true)时,会在构建前调用该方法清理已存在的产物。测试 test_interface.py 中的TestClean用例验证了默认实现可直接调用而不报错。
2.5 recurse_included_files:文件收集的统一入口
def recurse_included_files(self) -> Iterable[IncludedFile]: """ Returns a consistently generated series of file objects for every file that should be distributed. Each file object has three `str` attributes: - `path` - the absolute path - `relative_path` - the path relative to the project root; will be an empty string for external files - `distribution_path` - the path to be distributed as """该方法为所有应当分发的文件生成一致的IncludedFile对象序列,每个对象携带三个字符串属性:path(绝对路径)、relative_path(相对项目根的路径,外部文件为空串)、distribution_path(分发路径)。sdist 构建器在 sdist.py 中正是遍历recurse_included_files()来收集源码包内容。
其内部由两条路径组成(interface.py):
yield from self.recurse_selected_project_files() yield from self.recurse_forced_files(self.config.get_force_include())recurse_selected_project_files():当配置了only-include时走recurse_explicit_files(显式文件),否则走recurse_project_files(基于 include/exclude 模式遍历项目树);recurse_forced_files():处理force-include指定的、可能位于项目根目录之外的强制包含文件。
这两条路径与 构建配置文档 中的“文件选择”选项(include/exclude、only-include/packages、force-include、artifacts)一一对应,并由BuilderConfig中的include_spec、exclude_spec、artifact_spec、only_include、force_include、packages、sources等属性驱动(见 config.py)。对路径过滤的实现细节还包括默认排除的全局模式*.py[cdo]与构建目录、EXCLUDED_DIRECTORIES/EXCLUDED_FILES常量(如.git、.hg等目录以及缓存文件),以及 VCS 排除规则(首个.gitignore/.hgignore会被自动尊重,可通过ignore-vcs = true关闭)。
三、构建器插件的注册方式
参考文档在类文档字符串中给出了标准的“插件 + 钩子”注册范式。首先在插件模块中定义构建器类(前文已展示SpecialBuilder),然后在同包的hooks.py中通过hookimpl暴露注册函数:
from hatchling.plugin import hookimpl from .plugin import SpecialBuilder @hookimpl def hatch_register_builder() -> type[SpecialBuilder]: return SpecialBuilder内置构建器正是通过完全相同的hatch_register_builder钩子向插件管理器登记(见 hooks.py)。注册之后,[tool.hatch.build.targets.special]即可直接使用该构建目标。
四、实战:用 custom 构建器落地一个自定义构建目标
如果你不想单独发布一个插件包,Hatchling 还提供了custom构建器:在项目根目录放置一个 Python 文件(默认hatch_build.py,可用tool.hatch.build.targets.custom.path覆盖),定义一个继承自BuilderInterface的类即可:
from hatchling.builders.plugin.interface import BuilderInterface class CustomBuilder(BuilderInterface): ...相关的约束与注意点(详见 custom 构建器文档):
- 若文件中存在多个
BuilderInterface子类,必须定义名为get_builder的函数返回期望的那个类; - 自定义类中的
PLUGIN_NAME会被忽略并强制设为custom; - 加载逻辑在 custom.py 中实现:读取目标配置的
path选项(默认DEFAULT_BUILD_SCRIPT即hatch_build.py),校验文件存在后通过load_plugin_from_script动态加载类,并以与常规构建器相同的构造参数实例化。
五、与其他文档的关联
- 构建目标的全局配置与文件选择细节(
include/exclude/artifacts/only-include/packages/force-include/sources/reproducible/directory/dev-mode-dirs等)均见 构建配置文档; - 各内置构建目标的专属选项分别见 wheel 构建器文档、sdist 构建器文档、binary 构建器文档;
- 构建器与构建钩子的协作方式见 构建钩子插件文档;
hatch build命令的 CLI 用法(含-c/--clean等标志)见 CLI 参考文档。
六、小结
BuilderInterface是 Hatch 构建体系的插件基石:PLUGIN_NAME定义身份,get_config_class/config/build_config/target_config定义配置形态,get_version_api与get_default_versions定义“版本化”的构建策略,recurse_included_files统一了文件收集语义,而get_default_build_data/clean则为构建钩子与清理流程留出了扩展点。理解了这些成员的职责与调用顺序,无论是编写一个自定义构建目标、打包非标准制品,还是为特定平台定制发行物,都能以最小的成本接入 Hatch 的构建管线——这也是 Hatch 作为“现代、可扩展的 Python 项目管理工具”在设计上的核心所在。
- 开发工具
- 构建工具
【免费下载链接】hatch
Modern, extensible Python project management
相关推荐
如何快速掌握VCV Rack插件开发:从零开始的完整API架构指南
如何快速掌握VCV Rack插件开发:从零开始的完整API架构指南 VCV Rack作为一款功能强大的虚拟Eurorack模块化合成器,其开放的插件生态系统为音
音频处理桌面应用如何快速掌握Orbit性能分析器:从入门到精通的完整指南
如何快速掌握Orbit性能分析器:从入门到精通的完整指南 Orbit是一款强大的C/C++性能分析器,能够帮助开发者深入理解程序运行时行为,识别性能瓶颈,优化应
如何快速掌握JavaCPP Presets:从入门到精通的完整指南
如何快速掌握JavaCPP Presets:从入门到精通的完整指南 JavaCPP Presets是Java开发者访问原生C++库的终极解决方案,它提供了一系列
开发工具跨平台
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考