diff --git a/src/fromager/dependencies.py b/src/fromager/dependencies.py index 18dcdbef..51aa174d 100644 --- a/src/fromager/dependencies.py +++ b/src/fromager/dependencies.py @@ -5,11 +5,11 @@ import os import pathlib import tempfile +import tomllib import typing import zipfile import pyproject_hooks -import tomlkit from packaging.metadata import Metadata from packaging.requirements import Requirement from packaging.utils import NormalizedName, canonicalize_name @@ -492,7 +492,9 @@ def get_pyproject_contents(sdist_root_dir: pathlib.Path) -> dict[str, typing.Any pyproject_toml_filename = sdist_root_dir / "pyproject.toml" if not os.path.exists(pyproject_toml_filename): return {} - return tomlkit.loads(pyproject_toml_filename.read_text()) + # Read-only parse: use stdlib tomllib, not tomlkit. tomlkit rejects some + # spec-valid files (out-of-order tables), e.g. #1356. + return tomllib.loads(pyproject_toml_filename.read_text()) # From pypa/build/src/build/__main__.py diff --git a/tests/test_dependencies.py b/tests/test_dependencies.py index 34a68ee8..f97850fd 100644 --- a/tests/test_dependencies.py +++ b/tests/test_dependencies.py @@ -129,6 +129,33 @@ def test_get_build_backend( assert expected_results == actual +def test_get_pyproject_contents_out_of_order_tables(tmp_path: pathlib.Path) -> None: + # Regression test for #1356: a spec-valid pyproject.toml that re-opens a + # super-table (created via dotted keys) out of order. tomlkit rejects this, + # stdlib tomllib accepts it. + tmp_path.joinpath("pyproject.toml").write_text( + textwrap.dedent("""\ + [build-system] + requires = ["setuptools", "wheel"] + build-backend = "setuptools.build_meta" + + [tool.ruff] + lint.extend-select = ["UP"] + lint.ignore = ["E402"] + + [[tool.mypy.overrides]] + module = ["pkg.mod"] + + [tool.ruff.lint.per-file-ignores] + "__init__.py" = ["F401"] + """) + ) + + contents = dependencies.get_pyproject_contents(tmp_path) + + assert contents["build-system"]["requires"] == ["setuptools", "wheel"] + + def _clean_build_artifacts(f: typing.Callable[..., None]) -> typing.Callable[..., None]: @functools.wraps(f) def _with_cleanup(*args: typing.Any, **kwds: typing.Any) -> None: