From db1ac1c2a0dbcac72a98b152949ef859e3f56f9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filipe=20La=C3=ADns?= Date: Wed, 22 Mar 2023 03:57:46 +0000 Subject: [PATCH] inspection.info: simplify get_pep517_metadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Filipe LaĆ­ns --- src/poetry/inspection/info.py | 147 +++++++++++++++++----------------- 1 file changed, 75 insertions(+), 72 deletions(-) diff --git a/src/poetry/inspection/info.py b/src/poetry/inspection/info.py index fe7ce6864e1..bcee16e2937 100644 --- a/src/poetry/inspection/info.py +++ b/src/poetry/inspection/info.py @@ -4,7 +4,7 @@ import functools import glob import logging -import os +import re import tarfile import zipfile @@ -12,7 +12,9 @@ from typing import TYPE_CHECKING from typing import Any +import build import pkginfo +import pyproject_hooks from poetry.core.factory import Factory from poetry.core.packages.dependency import Dependency @@ -38,28 +40,6 @@ logger = logging.getLogger(__name__) -PEP517_META_BUILD = """\ -import build -import build.env -import pyproject_hooks - -source = '{source}' -dest = '{dest}' - -with build.env.IsolatedEnvBuilder() as env: - builder = build.ProjectBuilder( - srcdir=source, - scripts_dir=env.scripts_dir, - python_executable=env.executable, - runner=pyproject_hooks.quiet_subprocess_runner, - ) - env.install(builder.build_system_requires) - env.install(builder.get_requires_for_build('wheel')) - builder.metadata_path(dest) -""" - -PEP517_META_BUILD_DEPS = ["build==0.9.0", "pyproject_hooks==1.0.0"] - class PackageInfoError(ValueError): def __init__(self, path: Path | str, *reasons: BaseException | str) -> None: @@ -565,20 +545,7 @@ def from_path(cls, path: Path) -> PackageInfo: return cls.from_sdist(path=path) -@functools.lru_cache(maxsize=None) -def get_pep517_metadata(path: Path) -> PackageInfo: - """ - Helper method to use PEP-517 library to build and read package metadata. - - :param path: Path to package source to build and read metadata for. - """ - info = None - - with contextlib.suppress(PackageInfoError): - info = PackageInfo.from_setup_files(path) - if all([info.version, info.name, info.requires_dist]): - return info - +def _get_pep517_metadata_from_backend(path: Path) -> PackageInfo | None: with ephemeral_environment( flags={"no-pip": False, "no-setuptools": False, "no-wheel": False} ) as venv: @@ -586,51 +553,87 @@ def get_pep517_metadata(path: Path) -> PackageInfo: dest_dir = venv.path.parent / "dist" dest_dir.mkdir() - pep517_meta_build_script = PEP517_META_BUILD.format( - source=path.as_posix(), dest=dest_dir.as_posix() + builder = build.ProjectBuilder( + source_dir=path, + python_executable=venv.python, + runner=pyproject_hooks.quiet_subprocess_runner, ) + # install build backend dependencies try: - venv.run_pip( - "install", - "--disable-pip-version-check", - "--ignore-installed", - "--no-input", - *PEP517_META_BUILD_DEPS, - ) - venv.run( - "python", - "-", - input_=pep517_meta_build_script, - ) - info = PackageInfo.from_metadata(dest_dir) + # install build system dependencies first + venv.run_pip_install(*builder.build_system_requires) + # then install extra dependencies needed to build a wheel + # (this needs to be done in a separate step because we need the + # build system dependencies to run the backend) + venv.run_pip_install(*builder.get_requires_for_build("wheel")) except EnvCommandError as e: - # something went wrong while attempting pep517 metadata build - # fallback to egg_info if setup.py available - logger.debug("PEP517 build failed: %s", e) - setup_py = path / "setup.py" - if not setup_py.exists(): - raise PackageInfoError( - path, - e, - "No fallback setup.py file was found to generate egg_info.", - ) + # if it's a legacy project (uses setuptools), the dependencies + # should already be met, as we set no-setuptools=False, so the pip + # install calls should never fail. if they do, it means there really + # is a missing build dependency, and pip wasn't able to install it. + # we should simply return an error in this case. + raise PackageInfoError( + path, "Failed to provision the build environment.", e + ) - cwd = Path.cwd() - os.chdir(path.as_posix()) + # try the prepare_metadata_for_build_wheel hook + metadata = builder.prepare("wheel", dest_dir) + if metadata: + logger.debug("got metadata from prepare_metadata_for_build_wheel") + return PackageInfo.from_metadata(dest_dir) + + logger.debug("prepare_metadata_for_build_wheel failed or it's not supported") + + # next, try setup.py egg_info if setup.py exists + setup_py = path / "setup.py" + if setup_py.is_file(): try: venv.run("python", "setup.py", "egg_info") - info = PackageInfo.from_metadata(path) - except EnvCommandError as fbe: - raise PackageInfoError( - path, "Fallback egg_info generation failed.", fbe + except EnvCommandError as e: + logger.debug("setup.py egg_info failed: %s", e) + else: + return PackageInfo.from_metadata(builder.metadata_path(path)) + + # finally, try the build_wheel hook + wheel_name = builder.build("wheel", dest_dir) + if wheel_name: + with zipfile.ZipFile(dest_dir / wheel_name) as w: + # extract the .dist_info directory + w.extractall( + dest_dir, + ( + member + for member in w.filelist() + if re.match(r".*\.dist-info\/", member) + ), ) - finally: - os.chdir(cwd.as_posix()) + logger.debug("got metadata from build_wheel") + return PackageInfo.from_metadata(dest_dir) + + logger.debug("build_wheel failed") + + + +@functools.lru_cache(maxsize=None) +def get_pep517_metadata(path: Path) -> PackageInfo: + """ + Helper method to use PEP-517 library to build and read package metadata. + + :param path: Path to package source to build and read metadata for. + """ + info = None + + with contextlib.suppress(PackageInfoError): + info = PackageInfo.from_setup_files(path) + if all([info.version, info.name, info.requires_dist]): + return info + + # try fetching the metadata from the backend, but fall back to reading + # from the setup files (PackageInfo.from_setup_files) if we fail + info = _get_pep517_metadata_from_backend(path) or info if info: - logger.debug("Falling back to parsed setup.py file for %s", path) return info - # if we reach here, everything has failed and all hope is lost raise PackageInfoError(path, "Exhausted all core metadata sources.")