diff --git a/colcon_core/python_project/__init__.py b/colcon_core/python_project/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/colcon_core/python_project/spec.py b/colcon_core/python_project/spec.py new file mode 100644 index 00000000..2fcf8303 --- /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 3928d02e..e20b5115 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/test/spell_check.words b/test/spell_check.words index 18c2677b..9bde54cf 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 00000000..072eb0a9 --- /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'], + }