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 22, 2023
1 parent 3370a64 commit db1ac1c
Showing 1 changed file with 75 additions and 72 deletions.
147 changes: 75 additions & 72 deletions src/poetry/inspection/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@
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
import pyproject_hooks

from poetry.core.factory import Factory
from poetry.core.packages.dependency import Dependency
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,95 @@ 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(
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.")

0 comments on commit db1ac1c

Please sign in to comment.