Skip to content

Commit

Permalink
inspection.info: simplify get_pep517_metadata
Browse files Browse the repository at this point in the history
Signed-off-by: Filipe Laíns <[email protected]>
  • Loading branch information
FFY00 committed Mar 23, 2023
1 parent d1458a6 commit 975f1ad
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 74 deletions.
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ module = [
'cachecontrol.*',
'lockfile.*',
'pexpect.*',
'pyproject_hooks.*',
'requests_toolbelt.*',
'shellingham.*',
'virtualenv.*',
Expand Down
138 changes: 65 additions & 73 deletions src/poetry/inspection/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
import functools
import glob
import logging
import os
import re
import tarfile
import zipfile

from pathlib import Path
from typing import TYPE_CHECKING
from typing import Any

import build
import pkginfo

from poetry.core.factory import Factory
Expand All @@ -22,6 +23,7 @@
from poetry.core.utils.helpers import temporary_directory
from poetry.core.version.markers import InvalidMarker
from poetry.core.version.requirements import InvalidRequirement
from pyproject_hooks import quiet_subprocess_runner

from poetry.utils.env import EnvCommandError
from poetry.utils.env import ephemeral_environment
Expand All @@ -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:
Expand Down Expand Up @@ -565,72 +545,84 @@ 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:
# TODO: cache PEP 517 build environment corresponding to each project venv
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(
path, venv.python, runner=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
)

# 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")

cwd = Path.cwd()
os.chdir(path.as_posix())
# 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
)
finally:
os.chdir(cwd.as_posix())
except EnvCommandError as e:
logger.debug("setup.py egg_info failed: %s", e)
else:
return PackageInfo.from_metadata(Path(builder.metadata_path(path)))

# finally, try the build_wheel hook
wheel_name = builder.build("wheel", dest_dir)
if wheel_name:
logger.debug("got metadata from build_wheel")
return PackageInfo.from_wheel(dest_dir / wheel_name)

logger.debug("build_wheel failed")

return None


@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.")
2 changes: 1 addition & 1 deletion src/poetry/installation/chef.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from build import ProjectBuilder
from build.env import IsolatedEnv as BaseIsolatedEnv
from poetry.core.utils.helpers import temporary_directory
from pyproject_hooks import quiet_subprocess_runner # type: ignore[import]
from pyproject_hooks import quiet_subprocess_runner

from poetry.utils.env import ephemeral_environment

Expand Down

0 comments on commit 975f1ad

Please sign in to comment.