Author: Christian Heimes (tiran)
Branch: tiran:fromager:patchsettings
Commit: 0879376fa0a4c1c4dd761fdbb85c3118332af8d5
Date: January 12, 2026
This document describes the new patchsettings module, which introduces a declarative, YAML-based configuration system for applying patches and modifications to source distributions (sdists) and wheel metadata during the build process.
Fromager currently supports patching packages through:
- Patch files (
.patchfiles inpatches/<package>-<version>/) project_overridesettings in YAML configuration forpyproject.tomlmodifications- Plugin hooks for programmatic customization
- Patch files are inflexible:
.patchfiles require exact line matching and are brittle across versions - Limited declarative options:
project_overrideonly handlespyproject.toml[build-system]modifications - Plugin overhead: Simple modifications require writing Python code in a plugin module
- Version targeting is coarse: Patches are either version-specific (directory-based) or apply to all versions
- No metadata patching: No declarative way to modify wheel
METADATA(install requirements)
- Provide a declarative, YAML-based approach for common patching operations
- Support version-conditional patches using specifier sets (e.g.,
>=1.2,<2.0) - Enable sdist modifications (source files,
pyproject.toml,PKG-INFO) - Enable dist-info metadata modifications (wheel
METADATA, dependencies) - Reduce the need for custom plugin code for common scenarios
src/fromager/patchsettings.py
PatchBase (abstract base)
├── SdistPatchBase (abstract base for sdist operations)
│ ├── PatchReplaceLine - regex line replacement
│ ├── PatchDeleteLine - regex line deletion
│ ├── PatchPyProjectBuildSystem - pyproject.toml [build-system] modifications
│ └── FixPkgInfoVersion - PKG-INFO metadata version fix
│
└── DistInfoMetadataPatchBase (abstract base for wheel metadata operations)
├── RemoveInstallRequires - remove install dependencies
└── PinInstallRequiresToBuildVersion - pin dep to build env version
The module uses Pydantic's discriminated union pattern with the op field as the discriminator:
Patch = typing.Annotated[
PatchReplaceLine
| PatchDeleteLine
| PatchPyProjectBuildSystem
| FixPkgInfoVersion
| RemoveInstallRequires
| PinInstallRequiresToBuildVersion,
pydantic.Field(..., discriminator="op"),
]This allows YAML configurations to specify the operation type via the op key, with Pydantic automatically selecting the correct model class.
Common fields for all patch operations:
| Field | Type | Description |
|---|---|---|
step |
ClassVar | Build phase: "sdist" or "dist-info-metadata" |
op |
str | Operation discriminator (e.g., "replace-line") |
title |
str | Human-readable description |
when_version |
SpecifierSet | None | Version specifier for conditional application |
ignore_missing |
bool | Don't fail if operation doesn't modify anything |
Base for operations on unpacked source distributions. Defines execute() method signature:
def execute(
self,
*,
ctx: context.WorkContext,
req: Requirement,
version: Version,
sdist_root_dir: pathlib.Path,
) -> None:Base for operations on wheel metadata. Defines execute() method signature:
def execute(
self,
*,
ctx: context.WorkContext,
req: Requirement,
version: Version,
wheel_root_dir: pathlib.Path,
build_env: build_environment.BuildEnvironment,
) -> None:Regex-based line replacement in source files.
Fields:
files: List of file glob patternssearch: Regex pattern to matchreplace: Replacement string (supports backreferences)
Use Case: Comment out problematic requirements, fix version strings
Regex-based line deletion from source files.
Fields:
files: List of file glob patternssearch: Regex pattern to match
Use Case: Remove unwanted dependencies from requirements files
Modify pyproject.toml [build-system] section.
Fields:
update_build_requires: List of requirements to add/updateremove_build_requires: List of package names to remove
Replaces: Existing project_override setting in package settings
Use Case: Add missing build dependencies, remove incompatible build tools
Fix PKG-INFO metadata version in sdist.
Fields:
metadata_version: Target metadata version (default:"2.4")
Use Case: Fix sdists with invalid or outdated metadata versions
Remove an install dependency from wheel METADATA.
Fields:
requirement: Requirement specifier to removename_only: If true, match only by package name (ignore version specifiers)
Use Case: Remove optional/problematic runtime dependencies
Pin an install requirement to the version in the build environment.
Fields:
requirement: Requirement to pin
Use Case: Ensure wheel uses exact versions of build-time dependencies
Pydantic-aware wrapper for packaging.specifiers.SpecifierSet:
class SpecifierSetType(SpecifierSet):
@classmethod
def __get_pydantic_core_schema__(cls, source_type, handler) -> CoreSchema:
return core_schema.with_info_plain_validator_function(
lambda v, _: SpecifierSet(v),
serialization=core_schema.plain_serializer_function_ser_schema(
str, when_used="json"
),
)Pydantic-aware wrapper for packaging.requirements.Requirement:
class RequirementType(Requirement):
@classmethod
def __get_pydantic_core_schema__(cls, source_type, handler) -> CoreSchema:
return core_schema.with_info_plain_validator_function(
lambda v, _: Requirement(v),
serialization=core_schema.plain_serializer_function_ser_schema(
str, when_used="json"
),
)patch:
- title: Comment out 'foo' requirement for version >= 1.2
op: replace-line
files:
- 'requirements.txt'
search: '^(foo.*)$'
replace: '# \1'
when_version: '>=1.2'
ignore_missing: true
- title: Remove 'bar' from constraints.txt
op: delete-line
files:
- 'constraints.txt'
search: 'bar.*'
- title: Remove 'somepackage' installation requirement
op: remove-install-requires
requirement: somepackage
- title: Fix PKG-INFO metadata version
op: fix-pkg-info
metadata_version: '2.4'
when_version: '<1.0'
- title: Add missing setuptools to pyproject.toml
op: pyproject-build-system
update_build_requires:
- setuptools
- title: Remove Ray installation requirement
op: remove-install-requires
requirement: ray
name_only: true
- title: Update Torch install requirement to version in build env
op: pin-install-requires-to-build
requirement: torchclass Root(pydantic.BaseModel):
model_config = MODEL_CONFIG
patch: pydantic.RootModel[list[Patch]]| Component | Integration |
|---|---|
packagesettings.py |
Could embed Patches as a field in PackageSettings |
sources.py:prepare_new_source() |
Execute sdist step patches after unpacking |
wheels.py:add_extra_metadata_to_wheels() |
Execute dist-info-metadata step patches |
pyproject.py |
PatchPyProjectBuildSystem supersedes project_override |
1. Download sdist
2. Unpack sdist
3. Apply .patch files (existing)
4. Apply sdist-step patch settings (NEW)
- PatchReplaceLine
- PatchDeleteLine
- PatchPyProjectBuildSystem
- FixPkgInfoVersion
5. Build wheel
6. Apply dist-info-metadata-step patch settings (NEW)
- RemoveInstallRequires
- PinInstallRequiresToBuildVersion
7. Pack wheel
The commit introduces the data models only. The following work remains:
- Execution Logic: Implement
execute()methods for each patch class - Integration: Add patch execution calls to
sources.pyandwheels.py - Settings Integration: Add
patchfield toPackageSettingsmodel - Deprecation: Plan migration from
project_overridetoPatchPyProjectBuildSystem - Documentation: Add user documentation and examples
- Tests: Unit tests for each patch operation
- Validation: File existence checks, regex compilation validation
| Feature | project_override |
patchsettings |
|---|---|---|
| Scope | pyproject.toml only |
Files, pyproject, PKG-INFO, wheel metadata |
| Version targeting | None | when_version specifier sets |
| Failure handling | None | ignore_missing option |
| Discoverability | Limited | title field for documentation |
| Extensibility | Fixed | Discriminated union pattern |
- Regex DoS: Complex regex patterns could cause performance issues
- Path traversal: File patterns should be validated to stay within sdist root
- Build environment access:
PinInstallRequiresToBuildVersionrequires build env access
-
Additional operations:
add-line- append lines to filesupdate-install-requires- modify version specifiersadd-entry-point- add console scripts
-
Conditional operations:
when_python- Python version conditionswhen_platform- Platform-specific patches
-
Operation chaining:
- Reference previous operation results
- Conditional execution based on prior outcomes