| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447 |
- """Contains the RequirementCommand base class.
- This class is in a separate module so the commands that do not always
- need PackageFinder capability don't unnecessarily import the
- PackageFinder machinery and all its vendored dependencies, etc.
- """
- from __future__ import annotations
- import logging
- import os
- from functools import partial
- from optparse import Values
- from typing import Any, Callable, TypeVar
- from pip._internal.build_env import (
- BuildEnvironmentInstaller,
- InprocessBuildEnvironmentInstaller,
- SubprocessBuildEnvironmentInstaller,
- )
- from pip._internal.cache import WheelCache
- from pip._internal.cli import cmdoptions
- from pip._internal.cli.cmdoptions import make_target_python
- from pip._internal.cli.index_command import IndexGroupCommand
- from pip._internal.cli.index_command import SessionCommandMixin as SessionCommandMixin
- from pip._internal.exceptions import (
- CommandError,
- PreviousBuildDirError,
- UnsupportedPythonVersion,
- )
- from pip._internal.index.collector import LinkCollector
- from pip._internal.index.package_finder import PackageFinder
- from pip._internal.models.selection_prefs import SelectionPreferences
- from pip._internal.models.target_python import TargetPython
- from pip._internal.network.session import PipSession
- from pip._internal.operations.build.build_tracker import BuildTracker
- from pip._internal.operations.prepare import RequirementPreparer
- from pip._internal.req.constructors import (
- install_req_from_editable,
- install_req_from_line,
- install_req_from_parsed_requirement,
- install_req_from_req_string,
- )
- from pip._internal.req.pep723 import PEP723Exception, pep723_metadata
- from pip._internal.req.req_dependency_group import parse_dependency_groups
- from pip._internal.req.req_file import parse_requirements
- from pip._internal.req.req_install import InstallRequirement
- from pip._internal.resolution.base import BaseResolver
- from pip._internal.utils.packaging import check_requires_python
- from pip._internal.utils.temp_dir import (
- TempDirectory,
- TempDirectoryTypeRegistry,
- tempdir_kinds,
- )
- logger = logging.getLogger(__name__)
- def should_ignore_regular_constraints(options: Values) -> bool:
- """
- Check if regular constraints should be ignored because
- we are in a isolated build process and build constraints
- feature is enabled but no build constraints were passed.
- """
- return os.environ.get("_PIP_IN_BUILD_IGNORE_CONSTRAINTS") == "1"
- KEEPABLE_TEMPDIR_TYPES = [
- tempdir_kinds.BUILD_ENV,
- tempdir_kinds.EPHEM_WHEEL_CACHE,
- tempdir_kinds.REQ_BUILD,
- ]
- _CommandT = TypeVar("_CommandT", bound="RequirementCommand")
- def with_cleanup(
- func: Callable[[_CommandT, Values, list[str]], int],
- ) -> Callable[[_CommandT, Values, list[str]], int]:
- """Decorator for common logic related to managing temporary
- directories.
- """
- def configure_tempdir_registry(registry: TempDirectoryTypeRegistry) -> None:
- for t in KEEPABLE_TEMPDIR_TYPES:
- registry.set_delete(t, False)
- def wrapper(self: _CommandT, options: Values, args: list[str]) -> int:
- assert self.tempdir_registry is not None
- if options.no_clean:
- configure_tempdir_registry(self.tempdir_registry)
- try:
- return func(self, options, args)
- except PreviousBuildDirError:
- # This kind of conflict can occur when the user passes an explicit
- # build directory with a pre-existing folder. In that case we do
- # not want to accidentally remove it.
- configure_tempdir_registry(self.tempdir_registry)
- raise
- return wrapper
- def parse_constraint_files(
- constraint_files: list[str],
- finder: PackageFinder,
- options: Values,
- session: PipSession,
- ) -> list[InstallRequirement]:
- requirements = []
- for filename in constraint_files:
- for parsed_req in parse_requirements(
- filename,
- constraint=True,
- finder=finder,
- options=options,
- session=session,
- ):
- req_to_add = install_req_from_parsed_requirement(
- parsed_req,
- isolated=options.isolated_mode,
- user_supplied=False,
- )
- requirements.append(req_to_add)
- return requirements
- class RequirementCommand(IndexGroupCommand):
- def __init__(self, *args: Any, **kw: Any) -> None:
- super().__init__(*args, **kw)
- self.cmd_opts.add_option(cmdoptions.dependency_groups())
- self.cmd_opts.add_option(cmdoptions.no_clean())
- @staticmethod
- def determine_resolver_variant(options: Values) -> str:
- """Determines which resolver should be used, based on the given options."""
- if "legacy-resolver" in options.deprecated_features_enabled:
- return "legacy"
- return "resolvelib"
- @classmethod
- def make_requirement_preparer(
- cls,
- temp_build_dir: TempDirectory,
- options: Values,
- build_tracker: BuildTracker,
- session: PipSession,
- finder: PackageFinder,
- use_user_site: bool,
- download_dir: str | None = None,
- verbosity: int = 0,
- ) -> RequirementPreparer:
- """
- Create a RequirementPreparer instance for the given parameters.
- """
- temp_build_dir_path = temp_build_dir.path
- assert temp_build_dir_path is not None
- legacy_resolver = False
- resolver_variant = cls.determine_resolver_variant(options)
- if resolver_variant == "resolvelib":
- lazy_wheel = "fast-deps" in options.features_enabled
- if lazy_wheel:
- logger.warning(
- "pip is using lazily downloaded wheels using HTTP "
- "range requests to obtain dependency information. "
- "This experimental feature is enabled through "
- "--use-feature=fast-deps and it is not ready for "
- "production."
- )
- else:
- legacy_resolver = True
- lazy_wheel = False
- if "fast-deps" in options.features_enabled:
- logger.warning(
- "fast-deps has no effect when used with the legacy resolver."
- )
- # Handle build constraints
- build_constraints = getattr(options, "build_constraints", [])
- build_constraint_feature_enabled = (
- "build-constraint" in options.features_enabled
- )
- env_installer: BuildEnvironmentInstaller
- if "inprocess-build-deps" in options.features_enabled:
- build_constraint_reqs = parse_constraint_files(
- build_constraints, finder, options, session
- )
- env_installer = InprocessBuildEnvironmentInstaller(
- finder=finder,
- build_tracker=build_tracker,
- build_constraints=build_constraint_reqs,
- verbosity=verbosity,
- wheel_cache=WheelCache(options.cache_dir),
- )
- else:
- env_installer = SubprocessBuildEnvironmentInstaller(
- finder,
- build_constraints=build_constraints,
- build_constraint_feature_enabled=build_constraint_feature_enabled,
- )
- return RequirementPreparer(
- build_dir=temp_build_dir_path,
- src_dir=options.src_dir,
- download_dir=download_dir,
- build_isolation=options.build_isolation,
- build_isolation_installer=env_installer,
- check_build_deps=options.check_build_deps,
- build_tracker=build_tracker,
- session=session,
- progress_bar=options.progress_bar,
- finder=finder,
- require_hashes=options.require_hashes,
- use_user_site=use_user_site,
- lazy_wheel=lazy_wheel,
- verbosity=verbosity,
- legacy_resolver=legacy_resolver,
- )
- @classmethod
- def make_resolver(
- cls,
- preparer: RequirementPreparer,
- finder: PackageFinder,
- options: Values,
- wheel_cache: WheelCache | None = None,
- use_user_site: bool = False,
- ignore_installed: bool = True,
- ignore_requires_python: bool = False,
- force_reinstall: bool = False,
- upgrade_strategy: str = "to-satisfy-only",
- py_version_info: tuple[int, ...] | None = None,
- ) -> BaseResolver:
- """
- Create a Resolver instance for the given parameters.
- """
- make_install_req = partial(
- install_req_from_req_string,
- isolated=options.isolated_mode,
- )
- resolver_variant = cls.determine_resolver_variant(options)
- # The long import name and duplicated invocation is needed to convince
- # Mypy into correctly typechecking. Otherwise it would complain the
- # "Resolver" class being redefined.
- if resolver_variant == "resolvelib":
- import pip._internal.resolution.resolvelib.resolver
- return pip._internal.resolution.resolvelib.resolver.Resolver(
- preparer=preparer,
- finder=finder,
- wheel_cache=wheel_cache,
- make_install_req=make_install_req,
- use_user_site=use_user_site,
- ignore_dependencies=options.ignore_dependencies,
- ignore_installed=ignore_installed,
- ignore_requires_python=ignore_requires_python,
- force_reinstall=force_reinstall,
- upgrade_strategy=upgrade_strategy,
- py_version_info=py_version_info,
- )
- import pip._internal.resolution.legacy.resolver
- return pip._internal.resolution.legacy.resolver.Resolver(
- preparer=preparer,
- finder=finder,
- wheel_cache=wheel_cache,
- make_install_req=make_install_req,
- use_user_site=use_user_site,
- ignore_dependencies=options.ignore_dependencies,
- ignore_installed=ignore_installed,
- ignore_requires_python=ignore_requires_python,
- force_reinstall=force_reinstall,
- upgrade_strategy=upgrade_strategy,
- py_version_info=py_version_info,
- )
- def get_requirements(
- self,
- args: list[str],
- options: Values,
- finder: PackageFinder,
- session: PipSession,
- ) -> list[InstallRequirement]:
- """
- Parse command-line arguments into the corresponding requirements.
- """
- requirements: list[InstallRequirement] = []
- if not should_ignore_regular_constraints(options):
- constraints = parse_constraint_files(
- options.constraints, finder, options, session
- )
- requirements.extend(constraints)
- for req in args:
- if not req.strip():
- continue
- req_to_add = install_req_from_line(
- req,
- comes_from=None,
- isolated=options.isolated_mode,
- user_supplied=True,
- config_settings=getattr(options, "config_settings", None),
- )
- requirements.append(req_to_add)
- if options.dependency_groups:
- for req in parse_dependency_groups(options.dependency_groups):
- req_to_add = install_req_from_req_string(
- req,
- isolated=options.isolated_mode,
- user_supplied=True,
- )
- requirements.append(req_to_add)
- for req in options.editables:
- req_to_add = install_req_from_editable(
- req,
- user_supplied=True,
- isolated=options.isolated_mode,
- config_settings=getattr(options, "config_settings", None),
- )
- requirements.append(req_to_add)
- # NOTE: options.require_hashes may be set if --require-hashes is True
- for filename in options.requirements:
- for parsed_req in parse_requirements(
- filename, finder=finder, options=options, session=session
- ):
- req_to_add = install_req_from_parsed_requirement(
- parsed_req,
- isolated=options.isolated_mode,
- user_supplied=True,
- config_settings=(
- parsed_req.options.get("config_settings")
- if parsed_req.options
- else None
- ),
- )
- requirements.append(req_to_add)
- if options.requirements_from_scripts:
- if len(options.requirements_from_scripts) > 1:
- raise CommandError("--requirements-from-script can only be given once")
- script = options.requirements_from_scripts[0]
- try:
- script_metadata = pep723_metadata(script)
- except PEP723Exception as exc:
- raise CommandError(exc.msg)
- script_requires_python = script_metadata.get("requires-python", "")
- if script_requires_python and not options.ignore_requires_python:
- target_python = make_target_python(options)
- if not check_requires_python(
- requires_python=script_requires_python,
- version_info=target_python.py_version_info,
- ):
- raise UnsupportedPythonVersion(
- f"Script {script!r} requires a different Python: "
- f"{target_python.py_version} not in {script_requires_python!r}"
- )
- for req in script_metadata.get("dependencies", []):
- req_to_add = install_req_from_req_string(
- req,
- isolated=options.isolated_mode,
- user_supplied=True,
- )
- requirements.append(req_to_add)
- # If any requirement has hash options, enable hash checking.
- if any(req.has_hash_options for req in requirements):
- options.require_hashes = True
- if not (
- args
- or options.editables
- or options.requirements
- or options.dependency_groups
- or options.requirements_from_scripts
- ):
- opts = {"name": self.name}
- if options.find_links:
- raise CommandError(
- "You must give at least one requirement to {name} "
- '(maybe you meant "pip {name} {links}"?)'.format(
- **dict(opts, links=" ".join(options.find_links))
- )
- )
- else:
- raise CommandError(
- "You must give at least one requirement to {name} "
- '(see "pip help {name}")'.format(**opts)
- )
- return requirements
- @staticmethod
- def trace_basic_info(finder: PackageFinder) -> None:
- """
- Trace basic information about the provided objects.
- """
- # Display where finder is looking for packages
- search_scope = finder.search_scope
- locations = search_scope.get_formatted_locations()
- if locations:
- logger.info(locations)
- def _build_package_finder(
- self,
- options: Values,
- session: PipSession,
- target_python: TargetPython | None = None,
- ignore_requires_python: bool | None = None,
- ) -> PackageFinder:
- """
- Create a package finder appropriate to this requirement command.
- :param ignore_requires_python: Whether to ignore incompatible
- "Requires-Python" values in links. Defaults to False.
- """
- link_collector = LinkCollector.create(session, options=options)
- selection_prefs = SelectionPreferences(
- allow_yanked=True,
- format_control=options.format_control,
- release_control=options.release_control,
- prefer_binary=options.prefer_binary,
- ignore_requires_python=ignore_requires_python,
- )
- return PackageFinder.create(
- link_collector=link_collector,
- selection_prefs=selection_prefs,
- target_python=target_python,
- uploaded_prior_to=options.uploaded_prior_to,
- )
|