Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/fromager/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ def get_build_system_dependencies(
sdist_root_dir=sdist_root_dir,
build_dir=pbi.build_dir(sdist_root_dir),
)
hook = ctx.settings.build_system_dependencies_hook
if hook is not None:
orig_deps = hook(
ctx=ctx,
req=req,
sdist_root_dir=sdist_root_dir,
build_dir=pbi.build_dir(sdist_root_dir),
requirements=list(orig_deps),
)
deps = _filter_requirements(req, orig_deps)

_write_requirements_file(
Expand Down
25 changes: 25 additions & 0 deletions src/fromager/packagesettings/_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,26 @@ class SettingsFile(pydantic.BaseModel):
.. versionadded:: 0.92.0
"""

build_system_dependencies_hook: pydantic.ImportString | None = None
"""Post-processing hook for build-system dependencies

A dotted import path to a callable that receives build-system
dependencies after the per-package override (or default) runs
and returns a (possibly modified) list. When not set, no
post-processing is applied.

The callable signature must be::

def hook(
*,
ctx: WorkContext,
req: Requirement,
sdist_root_dir: pathlib.Path,
build_dir: pathlib.Path,
requirements: list[str],
) -> list[str]: ...
"""

@classmethod
def from_string(
cls,
Expand Down Expand Up @@ -193,6 +213,11 @@ def external_commands(self) -> ExternalCommands:
"""
return self._settings.external_commands

@property
def build_system_dependencies_hook(self) -> typing.Callable[..., list[str]] | None:
"""Get the build-system dependencies post-processing hook, if configured."""
return self._settings.build_system_dependencies_hook

def variant_changelog(self) -> list[str]:
"""Get global changelog for current variant"""
return list(self._settings.changelog.get(self.variant, []))
Expand Down
101 changes: 101 additions & 0 deletions tests/test_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,3 +515,104 @@ def test_get_metadata_from_wheel_validation_disabled(tmp_path: pathlib.Path) ->
# Assert: Should still parse the basic fields
assert metadata.name == "testpkg"
assert str(metadata.version) == "1.0.0"


def _sample_build_system_deps_hook(
*,
ctx: context.WorkContext,
req: Requirement,
sdist_root_dir: pathlib.Path,
build_dir: pathlib.Path,
requirements: list[str],
) -> list[str]:
return requirements + ["extra-dep>=1.0"]


@patch("fromager.dependencies._write_requirements_file")
@_clean_build_artifacts
def test_get_build_system_dependencies_with_hook(
_: Mock, tmp_path: pathlib.Path
) -> None:
from fromager import packagesettings

settings = packagesettings.Settings(
settings=packagesettings.SettingsFile(
build_system_dependencies_hook=_sample_build_system_deps_hook,
),
package_settings=[],
variant="cpu",
patches_dir=tmp_path / "patches",
max_jobs=None,
)
ctx = context.WorkContext(
active_settings=settings,
patches_dir=tmp_path / "patches",
sdists_repo=tmp_path / "sdists-repo",
wheels_repo=tmp_path / "wheels-repo",
work_dir=tmp_path / "work-dir",
variant="cpu",
)
ctx.setup()

pyproject_file = _fromager_root / "pyproject.toml"
shutil.copyfile(pyproject_file, tmp_path / "pyproject.toml")

results = dependencies.get_build_system_dependencies(
ctx=ctx,
req=Requirement("fromager"),
version=Version("1.0.0"),
sdist_root_dir=tmp_path,
)
names = set(r.name for r in results)
assert "extra-dep" in names
assert "hatchling" in names


@patch("fromager.dependencies._write_requirements_file")
@_clean_build_artifacts
def test_get_build_system_dependencies_without_hook(
_: Mock, tmp_context: context.WorkContext, tmp_path: pathlib.Path
) -> None:
pyproject_file = _fromager_root / "pyproject.toml"
shutil.copyfile(pyproject_file, tmp_path / "pyproject.toml")

results = dependencies.get_build_system_dependencies(
ctx=tmp_context,
req=Requirement("fromager"),
version=Version("1.0.0"),
sdist_root_dir=tmp_path,
)
names = set(r.name for r in results)
assert "extra-dep" not in names
assert "hatchling" in names


def test_settings_file_build_system_dependencies_hook_default() -> None:
from fromager import packagesettings

sf = packagesettings.SettingsFile()
assert sf.build_system_dependencies_hook is None


def test_settings_file_build_system_dependencies_hook_callable() -> None:
from fromager import packagesettings

sf = packagesettings.SettingsFile(
build_system_dependencies_hook=_sample_build_system_deps_hook,
)
assert sf.build_system_dependencies_hook is _sample_build_system_deps_hook


def test_settings_build_system_dependencies_hook_property() -> None:
from fromager import packagesettings

settings = packagesettings.Settings(
settings=packagesettings.SettingsFile(
build_system_dependencies_hook=_sample_build_system_deps_hook,
),
package_settings=[],
variant="cpu",
patches_dir=pathlib.Path("/tmp/patches"),
max_jobs=None,
)
assert settings.build_system_dependencies_hook is _sample_build_system_deps_hook
Loading