From d57afd6c3eed88703d09bdcc12957d5e2a6929cb Mon Sep 17 00:00:00 2001 From: Scott K Logan Date: Fri, 20 Sep 2024 13:07:52 -0500 Subject: [PATCH] Add function for reading and caching PEP 518 spec --- colcon_core/python_project/__init__.py | 0 colcon_core/python_project/spec.py | 49 ++++++++++++++++++++++++ setup.cfg | 1 + stdeb.cfg | 2 +- test/spell_check.words | 5 +++ test/test_pyproject_spec.py | 53 ++++++++++++++++++++++++++ 6 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 colcon_core/python_project/__init__.py create mode 100644 colcon_core/python_project/spec.py create mode 100644 test/test_pyproject_spec.py diff --git a/colcon_core/python_project/__init__.py b/colcon_core/python_project/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/colcon_core/python_project/spec.py b/colcon_core/python_project/spec.py new file mode 100644 index 000000000..2fcf8303d --- /dev/null +++ b/colcon_core/python_project/spec.py @@ -0,0 +1,49 @@ +# Copyright 2022 Open Source Robotics Foundation, Inc. +# Licensed under the Apache License, Version 2.0 + +try: + # Python 3.11+ + from tomllib import load as toml_load +except ImportError: + from tomli import load as toml_load + + +SPEC_NAME = 'pyproject.toml' + +_DEFAULT_BUILD_SYSTEM = { + 'build-backend': 'setuptools.build_meta:__legacy__', + 'requires': ['setuptools >= 40.8.0', 'wheel'], +} + + +def load_spec(project_path): + """ + Load build system specifications for a Python project. + + :param project_path: Path to the root directory of the project + """ + spec_file = project_path / SPEC_NAME + try: + with spec_file.open('rb') as f: + spec = toml_load(f) + except FileNotFoundError: + spec = {} + + spec.setdefault('build-system', _DEFAULT_BUILD_SYSTEM) + + return spec + + +def load_and_cache_spec(desc): + """ + Get the cached spec for a package descriptor. + + If the spec has not been loaded yet, load and cache it. + + :param desc: The package descriptor + """ + spec = desc.metadata.get('python_project_spec') + if spec is None: + spec = load_spec(desc.path) + desc.metadata['python_project_spec'] = spec + return spec diff --git a/setup.cfg b/setup.cfg index 3928d02e7..2ca02ee6c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -39,6 +39,7 @@ install_requires = pytest-repeat pytest-rerunfailures setuptools>=30.3.0 + tomli>=1.0.0; python_version < "3.11" packages = find: zip_safe = false diff --git a/stdeb.cfg b/stdeb.cfg index 9eb1227d1..551873bb5 100644 --- a/stdeb.cfg +++ b/stdeb.cfg @@ -1,6 +1,6 @@ [colcon-core] No-Python2: -Depends3: python3-distlib, python3-empy (<4), python3-packaging, python3-pytest, python3-setuptools, python3 (>= 3.8) | python3-importlib-metadata +Depends3: python3-distlib, python3-empy (<4), python3-packaging, python3-pytest, python3-setuptools, python3 (>= 3.8) | python3-importlib-metadata, python3 (>= 3.11) | python3-tomli (>= 1) Recommends3: python3-pytest-cov Suggests3: python3-pytest-repeat, python3-pytest-rerunfailures Replaces3: colcon diff --git a/test/spell_check.words b/test/spell_check.words index 18c2677bf..9bde54cf7 100644 --- a/test/spell_check.words +++ b/test/spell_check.words @@ -4,6 +4,7 @@ apache argparse asyncio autouse +backend backported basepath bazqux @@ -85,6 +86,7 @@ prepending proactor purelib pydocstyle +pyproject pytest pytests pythondontwritebytecode @@ -136,6 +138,9 @@ testsuite thomas tmpdir todo +toml +tomli +tomllib traceback tryfirst tuples diff --git a/test/test_pyproject_spec.py b/test/test_pyproject_spec.py new file mode 100644 index 000000000..072eb0a9d --- /dev/null +++ b/test/test_pyproject_spec.py @@ -0,0 +1,53 @@ +# Copyright 2024 Open Source Robotics Foundation, Inc. +# Licensed under the Apache License, Version 2.0 + +from colcon_core.package_descriptor import PackageDescriptor +from colcon_core.python_project.spec import load_and_cache_spec + + +def test_pyproject_missing(tmp_path): + desc = PackageDescriptor(tmp_path) + + spec = load_and_cache_spec(desc) + assert spec.get('build-system') == { + 'build-backend': 'setuptools.build_meta:__legacy__', + 'requires': ['setuptools >= 40.8.0', 'wheel'], + } + + +def test_pyproject_empty(tmp_path): + desc = PackageDescriptor(tmp_path) + + (tmp_path / 'pyproject.toml').write_text('') + + spec = load_and_cache_spec(desc) + assert spec.get('build-system') == { + 'build-backend': 'setuptools.build_meta:__legacy__', + 'requires': ['setuptools >= 40.8.0', 'wheel'], + } + + +def test_specified(tmp_path): + desc = PackageDescriptor(tmp_path) + + (tmp_path / 'pyproject.toml').write_text('\n'.join(( + '[build-system]', + 'build-backend = "my_build_backend.meta"', + 'requires = ["my-build-backend"]', + ))) + + spec = load_and_cache_spec(desc) + assert spec.get('build-system') == { + 'build-backend': 'my_build_backend.meta', + 'requires': ['my-build-backend'], + } + + # truncate the pyproject.toml and call again + # this verifies that the spec is cached + (tmp_path / 'pyproject.toml').write_text('') + + spec = load_and_cache_spec(desc) + assert spec.get('build-system') == { + 'build-backend': 'my_build_backend.meta', + 'requires': ['my-build-backend'], + }