def update_editable(self): # type: () -> None if not self.link: logger.debug( "Cannot update repository at %s; repository location is " "unknown", self.source_dir, ) return assert self.editable assert self.source_dir if self.link.scheme == 'file': # Static paths don't get updated return assert '+' in self.link.url, \ "bad url: {self.link.url!r}".format(**locals()) vc_type, url = self.link.url.split('+', 1) vcs_backend = vcs.get_backend(vc_type) if vcs_backend: if not self.link.is_vcs: reason = ( "This form of VCS requirement is being deprecated: {}." ).format(self.link.url) replacement = None if self.link.url.startswith("git+git@"): replacement = ( "git+https://[email protected]/..., " "git+ssh://[email protected]/..., " "or the insecure git+git://[email protected]/...") deprecated(reason, replacement, gone_in="21.0", issue=7554) hidden_url = hide_url(self.link.url) vcs_backend.obtain(self.source_dir, url=hidden_url) else: assert 0, ('Unexpected version control type (in {}): {}'.format( self.link, vc_type))
def check_invalid_constraint_type(req): # type: (InstallRequirement) -> str # Check for unsupported forms problem = "" if not req.name: problem = "Unnamed requirements are not allowed as constraints" elif req.link: problem = "Links are not allowed as constraints" elif req.extras: problem = "Constraints cannot have extras" if problem: deprecated( reason=( "Constraints are only allowed to take the form of a package " "name and a version specifier. Other forms were originally " "permitted as an accident of the implementation, but were " "undocumented. The new implementation of the resolver no " "longer supports these forms."), replacement=("replacing the constraint with a requirement."), # No plan yet for when the new resolver becomes default gone_in=None, issue=8210) return problem
def unpack_url( link, # type: Link location, # type: str download, # type: Downloader download_dir=None, # type: Optional[str] hashes=None, # type: Optional[Hashes] ): # type: (...) -> Optional[File] """Unpack link into location, downloading if required. :param hashes: A Hashes object, one of whose embedded hashes must match, or HashMismatch will be raised. If the Hashes is empty, no matches are required, and unhashable types of requirements (like VCS ones, which would ordinarily raise HashUnsupported) are allowed. """ # non-editable vcs urls if link.is_vcs: unpack_vcs_link(link, location) return None # Once out-of-tree-builds are no longer supported, could potentially # replace the below condition with `assert not link.is_existing_dir` # - unpack_url does not need to be called for in-tree-builds. # # As further cleanup, _copy_source_tree and accompanying tests can # be removed. if link.is_existing_dir(): deprecated( "A future pip version will change local packages to be built " "in-place without first copying to a temporary directory. " "We recommend you use --use-feature=in-tree-build to test " "your packages with this new behavior before it becomes the " "default.\n", replacement=None, gone_in="21.3", issue=7555) if os.path.isdir(location): rmtree(location) _copy_source_tree(link.file_path, location) return None # file urls if link.is_file: file = get_file_url(link, download_dir, hashes=hashes) # http urls else: file = get_http_url( link, download, download_dir, hashes=hashes, ) # unpack the archive to the build dir location. even when only downloading # archives, they have to be unpacked to parse dependencies, except wheels if not link.is_wheel: unpack_file(file.path, location, file.content_type) return file
def run(self, options, args): # type: (Values, List[str]) -> int skip = set(stdlib_pkgs) if not options.freeze_all: skip.update(DEV_PKGS) if options.excludes: skip.update(options.excludes) cmdoptions.check_list_path_option(options) if options.find_links: deprecated( "--find-links option in pip freeze is deprecated.", replacement=None, gone_in="21.2", issue=9069, ) for line in freeze( requirement=options.requirements, find_links=options.find_links, local_only=options.local, user_only=options.user, paths=options.path, isolated=options.isolated_mode, skip=skip, exclude_editable=options.exclude_editable, ): sys.stdout.write(line + "\n") return SUCCESS
def warn_deprecated_install_options(requirement_set, options): # type: (RequirementSet, Optional[List[str]]) -> None """If any location-changing --install-option arguments were passed for requirements or on the command-line, then show a deprecation warning. """ def format_options(option_names): # type: (Iterable[str]) -> List[str] return ["--{}".format(name.replace("_", "-")) for name in option_names] requirements = ( requirement_set.unnamed_requirements + list(requirement_set.requirements.values()) ) offenders = [] for requirement in requirements: install_options = requirement.options.get("install_options", []) location_options = parse_distutils_args(install_options) if location_options: offenders.append( "{!r} from {}".format( format_options(location_options.keys()), requirement ) ) if options: location_options = parse_distutils_args(options) if location_options: offenders.append( "{!r} from command line".format( format_options(location_options.keys()) ) ) if not offenders: return deprecated( reason=( "Location-changing options found in --install-option: {}. " "This configuration may cause unexpected behavior and is " "unsupported.".format( "; ".join(offenders) ) ), replacement=( "using pip-level options like --user, --prefix, --root, and " "--target" ), gone_in="20.2", issue=7309, )
def add_dependency_links(self, links): # FIXME: this shouldn't be global list this, it should only # apply to requirements of the package that specifies the # dependency_links value # FIXME: also, we should track comes_from (i.e., use Link) if self.process_dependency_links: deprecated( "Dependency Links processing has been deprecated and will be " "removed in a future release.", replacement="PEP 508 URL dependencies", gone_in="19.0", issue=4187, ) self.dependency_links.extend(links)
def test_deprecated_message_reads_well(): with pytest.raises(PipDeprecationWarning) as exception: deprecated( "Stop doing this!", gone_in="1.0", # this matches the patched version. replacement="to be nicer", issue="100000", # I hope we never reach this number. ) message = exception.value.args[0] assert message == ("DEPRECATION: Stop doing this! " "pip 1.0 will remove support for this functionality. " "A possible replacement is to be nicer. " "You can find discussion regarding this at " "https://github.com/pypa/pip/issues/100000.")
def test_deprecated_message_reads_well(): with pytest.raises(PipDeprecationWarning) as exception: deprecated( "Stop doing this!", gone_in="1.0", # this matches the patched version. replacement="to be nicer", issue="100000", # I hope we never reach this number. ) message = exception.value.args[0] assert message == ("DEPRECATION: Stop doing this! " "This behavior change has been enforced since pip 1.0. " "A possible replacement is to be nicer. " "Discussion can be found at " "https://github.com/pypa/pip/issues/100000.")
def test_deprecated_message_reads_well_past() -> None: with pytest.raises(PipDeprecationWarning) as exception: deprecated( reason="Stop doing this!", gone_in="1.0", # this matches the patched version. replacement="to be nicer", feature_flag="magic-8-ball", issue=100000, ) message = exception.value.args[0] assert message == ( "DEPRECATION: Stop doing this! " "Since pip 1.0, this is no longer supported. " "A possible replacement is to be nicer. " "Discussion can be found at https://github.com/pypa/pip/issues/100000")
def test_deprecated_message_contains_information(gone_in, replacement, issue): with pytest.warns(PipDeprecationWarning) as record: deprecated( "Stop doing this!", replacement=replacement, gone_in=gone_in, issue=issue, ) assert len(record) == 1 message = record[0].message.args[0] assert "DEPRECATION: Stop doing this!" in message # Ensure non-None values are mentioned. for item in [gone_in, replacement, issue]: if item is not None: assert str(item) in message
def test_deprecated_raises_error_if_too_old(replacement, issue): with pytest.raises(PipDeprecationWarning) as exception: deprecated( "Stop doing this!", gone_in="1.0", # this matches the patched version. replacement=replacement, issue=issue, ) message = exception.value.args[0] assert "DEPRECATION: Stop doing this!" in message assert "1.0" in message # Ensure non-None values are mentioned. for item in [replacement, issue]: if item is not None: assert str(item) in message
def get_pep_518_info(self): """Get PEP 518 build-time requirements. Returns the list of the packages required to build the project, specified as per PEP 518 within the package. If `pyproject.toml` is not present, returns None to signify not using the same. """ if not os.path.isfile(self.pyproject_toml): return None with io.open(self.pyproject_toml, encoding="utf-8") as f: pp_toml = pytoml.load(f) # Extract the build requirements requires = pp_toml.get("build-system", {}).get("requires", None) template = ( "%s does not comply with PEP 518 since pyproject.toml " "does not contain a valid '[build-system].requires' key: %s") if requires is None: logging.warn(template, self, "it is missing.") deprecated( "Future versions of pip may reject packages with " "pyproject.toml files that do not contain the [build-system]" "table and the requires key, as specified in PEP 518.", replacement=None, gone_in="18.2", issue=5416, ) # Currently, we're isolating the build based on the presence of the # pyproject.toml file. If the user doesn't specify # build-system.requires, assume they intended to use setuptools and # wheel for now. return ["setuptools", "wheel"] else: # Error out if it's not a list of strings is_list_of_str = isinstance(requires, list) and all( isinstance(req, six.string_types) for req in requires) if not is_list_of_str: raise InstallationError(template % (self, "it is not a list of strings.")) # If control flow reaches here, we're good to go. return requires
def parse_links(page: "HTMLPage", use_deprecated_html5lib: bool) -> Iterable[Link]: """ Parse an HTML document, and yield its anchor elements as Link objects. """ encoding = page.encoding or "utf-8" # Check if the page starts with a valid doctype, to decide whether to use # http.parser or (deprecated) html5lib for parsing -- unless explicitly # requested to use html5lib. if not use_deprecated_html5lib: expected_doctype = "<!doctype html>".encode(encoding) actual_start = page.content[:len(expected_doctype)] if actual_start.decode(encoding).lower() != "<!doctype html>": deprecated( reason= (f"The HTML index page being used ({page.url}) is not a proper " "HTML 5 document. This is in violation of PEP 503 which requires " "these pages to be well-formed HTML 5 documents. Please reach out " "to the owners of this index page, and ask them to update this " "index page to a valid HTML 5 document."), replacement=None, gone_in="22.2", issue=10825, ) use_deprecated_html5lib = True if use_deprecated_html5lib: yield from _parse_links_html5lib(page) return parser = HTMLLinkParser() parser.feed(page.content.decode(encoding)) url = page.url base_url = parser.base_url or url for anchor in parser.anchors: link = _create_link_from_element( anchor, page_url=url, base_url=base_url, ) if link is None: continue yield link
def get_prefixed_libs(prefix: str) -> List[str]: """Return the lib locations under ``prefix``.""" new_pure, new_plat = _sysconfig.get_prefixed_libs(prefix) if _USE_SYSCONFIG: return _deduplicated(new_pure, new_plat) from . import _distutils old_pure, old_plat = _distutils.get_prefixed_libs(prefix) old_lib_paths = _deduplicated(old_pure, old_plat) # Apple's Python (shipped with Xcode and Command Line Tools) hard-code # platlib and purelib to '/Library/Python/X.Y/site-packages'. This will # cause serious build isolation bugs when Apple starts shipping 3.10 because # pip will install build backends to the wrong location. This tells users # who is at fault so Apple may notice it and fix the issue in time. if all(_looks_like_apple_library(p) for p in old_lib_paths): deprecated( reason=( "Python distributed by Apple's Command Line Tools incorrectly " "patches sysconfig to always point to '/Library/Python'. This " "will cause build isolation to operate incorrectly on Python " "3.10 or later. Please help report this to Apple so they can " "fix this. https://developer.apple.com/bug-reporting/"), replacement=None, gone_in=None, ) return old_lib_paths warned = [ _warn_if_mismatch( pathlib.Path(old_pure), pathlib.Path(new_pure), key="prefixed-purelib", ), _warn_if_mismatch( pathlib.Path(old_plat), pathlib.Path(new_plat), key="prefixed-platlib", ), ] if any(warned): _log_context(prefix=prefix) return old_lib_paths
def _determine_file(self, options, need_value): # Convert legacy venv_file option to site_file or error if options.venv_file and not options.site_file: if running_under_virtualenv(): options.site_file = True deprecated( "The --venv option has been deprecated.", replacement="--site", gone_in="19.3", ) else: raise PipError( "Legacy --venv option requires a virtual environment. " "Use --site instead." ) file_options = [ key for key, value in ( (kinds.USER, options.user_file), (kinds.GLOBAL, options.global_file), (kinds.SITE, options.site_file), ) if value ] if not file_options: if not need_value: return None # Default to user, unless there's a site file. elif any( os.path.exists(site_config_file) for site_config_file in get_configuration_files()[kinds.SITE] ): return kinds.SITE else: return kinds.USER elif len(file_options) == 1: return file_options[0] raise PipError( "Need exactly one file to operate upon " "(--user, --site, --global) to perform." )
def test_deprecated_message_reads_well_future() -> None: with pytest.warns(PipDeprecationWarning) as record: deprecated( reason="Stop doing this!", gone_in="2.0", # this is greater than the patched version. replacement="to be nicer", feature_flag="crisis", issue=100000, ) assert len(record) == 1 assert isinstance(record[0].message, PipDeprecationWarning) message = record[0].message.args[0] assert message == ( "DEPRECATION: Stop doing this! " "pip 2.0 will enforce this behaviour change. " "A possible replacement is to be nicer. " "You can use the flag --use-feature=crisis to test the upcoming behaviour. " "Discussion can be found at https://github.com/pypa/pip/issues/100000")
def determine_build_failure_suppression(options: Values) -> bool: """Determines whether build failures should be suppressed and backtracked on.""" if "backtrack-on-build-failures" not in options.deprecated_features_enabled: return False if "legacy-resolver" in options.deprecated_features_enabled: raise CommandError("Cannot backtrack with legacy resolver.") deprecated( reason= ("Backtracking on build failures can mask issues related to how " "a package generates metadata or builds a wheel. This flag will " "be removed in pip 22.2."), gone_in=None, replacement= ("avoiding known-bad versions by explicitly telling pip to ignore them " "(either directly as requirements, or via a constraints file)"), feature_flag=None, issue=10655, ) return True
def update_editable(self, obtain=True): # type: (bool) -> None if not self.link: logger.debug( "Cannot update repository at %s; repository location is " "unknown", self.source_dir, ) return assert self.editable assert self.source_dir if self.link.scheme == "file": # Static paths don't get updated return assert "+" in self.link.url, "bad url: %r" % self.link.url vc_type, url = self.link.url.split("+", 1) vcs_backend = vcs.get_backend(vc_type) if vcs_backend: if not self.link.is_vcs: reason = ( "This form of VCS requirement is being deprecated: {}." ).format(self.link.url) replacement = None if self.link.url.startswith("git+git@"): replacement = ( "git+https://[email protected]/..., " "git+ssh://[email protected]/..., " "or the insecure git+git://[email protected]/..." ) deprecated(reason, replacement, gone_in="21.0", issue=7554) hidden_url = hide_url(self.link.url) if obtain: vcs_backend.obtain(self.source_dir, url=hidden_url) else: vcs_backend.export(self.source_dir, url=hidden_url) else: assert 0, "Unexpected version control type (in %s): %s" % ( self.link, vc_type, )
def _determine_file(self, options, need_value): # Convert legacy venv_file option to site_file or error if options.venv_file and not options.site_file: if running_under_virtualenv(): options.site_file = True deprecated( "The --venv option has been deprecated.", replacement="--site", gone_in="19.3", ) else: raise PipError( "Legacy --venv option requires a virtual environment. " "Use --site instead." ) file_options = [key for key, value in ( (kinds.USER, options.user_file), (kinds.GLOBAL, options.global_file), (kinds.SITE, options.site_file), ) if value] if not file_options: if not need_value: return None # Default to user, unless there's a site file. elif os.path.exists(site_config_file): return kinds.SITE else: return kinds.USER elif len(file_options) == 1: return file_options[0] raise PipError( "Need exactly one file to operate upon " "(--user, --site, --global) to perform." )
def test_deprecated_message_contains_information( gone_in: Optional[str], replacement: Optional[str], issue: Optional[int], feature_flag: Optional[str], ) -> None: with pytest.warns(PipDeprecationWarning) as record: deprecated( reason="Stop doing this!", replacement=replacement, gone_in=gone_in, feature_flag=feature_flag, issue=issue, ) assert len(record) == 1 assert isinstance(record[0].message, PipDeprecationWarning) message = record[0].message.args[0] assert "DEPRECATION: Stop doing this!" in message # Ensure non-None values are mentioned. for item in [gone_in, replacement, issue, feature_flag]: if item is not None: assert str(item) in message
def run(self, options, args): # type: (Values, List[str]) -> int format_control = FormatControl(set(), set()) wheel_cache = WheelCache(options.cache_dir, format_control) skip = set(stdlib_pkgs) if not options.freeze_all: skip.update(DEV_PKGS) if options.excludes: skip.update(options.excludes) cmdoptions.check_list_path_option(options) if options.find_links: deprecated( "--find-links option in pip freeze is deprecated.", replacement=None, gone_in="21.2", issue=9069, ) freeze_kwargs = dict( requirement=options.requirements, find_links=options.find_links, local_only=options.local, user_only=options.user, paths=options.path, isolated=options.isolated_mode, wheel_cache=wheel_cache, skip=skip, exclude_editable=options.exclude_editable, ) for line in freeze(**freeze_kwargs): sys.stdout.write(line + '\n') return SUCCESS
def resolve(self, root_reqs, check_supported_wheels): # type: (List[InstallRequirement], bool) -> RequirementSet constraints = {} # type: Dict[str, Constraint] user_requested = set() # type: Set[str] requirements = [] for req in root_reqs: if req.constraint: # Ensure we only accept valid constraints problem = check_invalid_constraint_type(req) if problem: raise InstallationError(problem) if not req.match_markers(): continue name = canonicalize_name(req.name) if name in constraints: constraints[name] &= req else: constraints[name] = Constraint.from_ireq(req) else: if req.user_supplied and req.name: user_requested.add(canonicalize_name(req.name)) r = self.factory.make_requirement_from_install_req( req, requested_extras=(), ) if r is not None: requirements.append(r) provider = PipProvider( factory=self.factory, constraints=constraints, ignore_dependencies=self.ignore_dependencies, upgrade_strategy=self.upgrade_strategy, user_requested=user_requested, ) if "PIP_RESOLVER_DEBUG" in os.environ: reporter = PipDebuggingReporter() else: reporter = PipReporter() resolver = RLResolver(provider, reporter) try: try_to_avoid_resolution_too_deep = 2000000 self._result = resolver.resolve( requirements, max_rounds=try_to_avoid_resolution_too_deep, ) except ResolutionImpossible as e: error = self.factory.get_installation_error(e) six.raise_from(error, e) req_set = RequirementSet(check_supported_wheels=check_supported_wheels) for candidate in self._result.mapping.values(): ireq = candidate.get_install_requirement() if ireq is None: continue # Check if there is already an installation under the same name, # and set a flag for later stages to uninstall it, if needed. installed_dist = self.factory.get_dist_to_uninstall(candidate) if installed_dist is None: # There is no existing installation -- nothing to uninstall. ireq.should_reinstall = False elif self.factory.force_reinstall: # The --force-reinstall flag is set -- reinstall. ireq.should_reinstall = True elif installed_dist.parsed_version != candidate.version: # The installation is different in version -- reinstall. ireq.should_reinstall = True elif candidate.is_editable or dist_is_editable(installed_dist): # The incoming distribution is editable, or different in # editable-ness to installation -- reinstall. ireq.should_reinstall = True elif candidate.source_link.is_file: # The incoming distribution is under file:// if candidate.source_link.is_wheel: # is a local wheel -- do nothing. logger.info( "%s is already installed with the same version as the " "provided wheel. Use --force-reinstall to force an " "installation of the wheel.", ireq.name, ) continue looks_like_sdist = (is_archive_file( candidate.source_link.file_path) and candidate.source_link.ext != ".zip") if looks_like_sdist: # is a local sdist -- show a deprecation warning! reason = ( "Source distribution is being reinstalled despite an " "installed package having the same name and version as " "the installed package.") replacement = "use --force-reinstall" deprecated( reason=reason, replacement=replacement, gone_in="21.1", issue=8711, ) # is a local sdist or path -- reinstall ireq.should_reinstall = True else: continue link = candidate.source_link if link and link.is_yanked: # The reason can contain non-ASCII characters, Unicode # is required for Python 2. msg = ('The candidate selected for download or install is a ' 'yanked version: {name!r} candidate (version {version} ' 'at {link})\nReason for being yanked: {reason}').format( name=candidate.name, version=candidate.version, link=link, reason=link.yanked_reason or '<none given>', ) logger.warning(msg) req_set.add_named_requirement(ireq) reqs = req_set.all_requirements self.factory.preparer.prepare_linked_requirements_more(reqs) return req_set
def parse_req_from_line(name, line_source): # type: (str, Optional[str]) -> RequirementParts if is_url(name): marker_sep = "; " else: marker_sep = ";" if marker_sep in name: name, markers_as_string = name.split(marker_sep, 1) markers_as_string = markers_as_string.strip() if not markers_as_string: markers = None else: markers = Marker(markers_as_string) else: markers = None name = name.strip() req_as_string = None path = os.path.normpath(os.path.abspath(name)) link = None extras_as_string = None if is_url(name): link = Link(name) else: p, extras_as_string = _strip_extras(path) url = _get_url_from_path(p, name) if url is not None: link = Link(url) # it's a local file, dir, or url if link: # Handle relative file URLs if link.scheme == "file" and re.search(r"\.\./", link.url): link = Link(path_to_url(os.path.normpath(os.path.abspath(link.path)))) # wheel file if link.is_wheel: wheel = Wheel(link.filename) # can raise InvalidWheelFilename req_as_string = "{wheel.name}=={wheel.version}".format(**locals()) else: # set the req to the egg fragment. when it's not there, this # will become an 'unnamed' requirement req_as_string = link.egg_fragment # a requirement specifier else: req_as_string = name extras = convert_extras(extras_as_string) def with_source(text): # type: (str) -> str if not line_source: return text return "{} (from {})".format(text, line_source) if req_as_string is not None: try: req = Requirement(req_as_string) except InvalidRequirement: if os.path.sep in req_as_string: add_msg = "It looks like a path." add_msg += deduce_helpful_msg(req_as_string) elif "=" in req_as_string and not any( op in req_as_string for op in operators ): add_msg = "= is not a valid operator. Did you mean == ?" else: add_msg = "" msg = with_source("Invalid requirement: {!r}".format(req_as_string)) if add_msg: msg += "\nHint: {}".format(add_msg) raise InstallationError(msg) else: # Deprecate extras after specifiers: "name>=1.0[extras]" # This currently works by accident because _strip_extras() parses # any extras in the end of the string and those are saved in # RequirementParts for spec in req.specifier: spec_str = str(spec) if spec_str.endswith("]"): msg = "Extras after version '{}'.".format(spec_str) replace = "moving the extras before version specifiers" deprecated(msg, replacement=replace, gone_in="21.0") else: req = None return RequirementParts(req, link, markers, extras)
def _main(self, args): # type: (List[str]) -> int # We must initialize this before the tempdir manager, otherwise the # configuration would not be accessible by the time we clean up the # tempdir manager. tempdir_registry = enter_context(tempdir_registry()) # Intentionally set as early as possible so globally-managed temporary # directories are available to the rest of the code. enter_context(global_tempdir_manager()) options, args = parse_args(args) # Set verbosity so that it can be used elsewhere. verbosity = options.verbose - options.quiet level_number = setup_logging( verbosity=verbosity, no_color=options.no_color, user_log_file=options.log, ) if (sys.version_info[:2] == (2, 7) and not options.no_python_version_warning): message = ( "pip 21.0 will drop support for Python 2.7 in January 2021. " "More details about Python 2 support in pip can be found at " "https://pip.pypa.io/en/latest/development/release-process/#python-2-support" # noqa ) if platform.python_implementation() == "CPython": message = ( "Python 2.7 reached the end of its life on January " "1st, 2020. Please upgrade your Python as Python 2.7 " "is no longer maintained. ") + message deprecated(message, replacement=None, gone_in="21.0") if (sys.version_info[:2] == (3, 5) and not options.no_python_version_warning): message = ("Python 3.5 reached the end of its life on September " "13th, 2020. Please upgrade your Python as Python 3.5 " "is no longer maintained. pip 21.0 will drop support " "for Python 3.5 in January 2021.") deprecated(message, replacement=None, gone_in="21.0") # TODO: Try to get these passing down from the command? # without resorting to os.environ to hold these. # This also affects isolated builds and it should. if options.no_input: os.environ['PIP_NO_INPUT'] = '1' if options.exists_action: os.environ['PIP_EXISTS_ACTION'] = ' '.join(options.exists_action) if options.require_venv and not ignore_require_venv: # If a venv is required check if it can really be found if not running_under_virtualenv(): logger.critical( 'Could not find an activated virtualenv (required).') sys.exit(VIRTUALENV_NOT_FOUND) if options.cache_dir: options.cache_dir = normalize_path(options.cache_dir) if not check_path_owner(options.cache_dir): logger.warning( "The directory '%s' or its parent directory is not owned " "or is not writable by the current user. The cache " "has been disabled. Check the permissions and owner of " "that directory. If executing pip with sudo, you may want " "sudo's -H flag.", options.cache_dir, ) options.cache_dir = None if '2020-resolver' in options.features_enabled and not PY2: logger.warning( "--use-feature=2020-resolver no longer has any effect, " "since it is now the default dependency resolver in pip. " "This will become an error in pip 21.0.") try: status = run(options, args) assert isinstance(status, int) return status except PreviousBuildDirError as exc: logger.critical(str(exc)) logger.debug('Exception information:', exc_info=True) return PREVIOUS_BUILD_DIR_ERROR except (InstallationError, UninstallationError, BadCommand, SubProcessError, NetworkConnectionError) as exc: logger.critical(str(exc)) logger.debug('Exception information:', exc_info=True) return ERROR except CommandError as exc: logger.critical('%s', exc) logger.debug('Exception information:', exc_info=True) return ERROR except BrokenStdoutLoggingError: # Bypass our logger and write any remaining messages to stderr # because stdout no longer works. print('ERROR: Pipe to stdout was broken', file=sys.stderr) if level_number <= logging.DEBUG: traceback.print_exc(file=sys.stderr) return ERROR except KeyboardInterrupt: logger.critical('Operation cancelled by user') logger.debug('Exception information:', exc_info=True) return ERROR except BaseException: logger.critical('Exception:', exc_info=True) return UNKNOWN_ERROR finally: handle_pip_version_check(options)
def _emit_egg_deprecation(location: Optional[str]) -> None: deprecated( reason=f"Loading egg at {location} is deprecated.", replacement="to use pip for package installation.", gone_in=None, )
def get_scheme( dist_name: str, user: bool = False, home: Optional[str] = None, root: Optional[str] = None, isolated: bool = False, prefix: Optional[str] = None, ) -> Scheme: old = _distutils.get_scheme( dist_name, user=user, home=home, root=root, isolated=isolated, prefix=prefix, ) new = _sysconfig.get_scheme( dist_name, user=user, home=home, root=root, isolated=isolated, prefix=prefix, ) base = prefix or home or _default_base(user=user) warning_contexts = [] for k in SCHEME_KEYS: # Extra join because distutils can return relative paths. old_v = pathlib.Path(base, getattr(old, k)) new_v = pathlib.Path(getattr(new, k)) if old_v == new_v: continue # distutils incorrectly put PyPy packages under ``site-packages/python`` # in the ``posix_home`` scheme, but PyPy devs said they expect the # directory name to be ``pypy`` instead. So we treat this as a bug fix # and not warn about it. See bpo-43307 and python/cpython#24628. skip_pypy_special_case = (sys.implementation.name == "pypy" and home is not None and k in ("platlib", "purelib") and old_v.parent == new_v.parent and old_v.name.startswith("python") and new_v.name.startswith("pypy")) if skip_pypy_special_case: continue # sysconfig's ``osx_framework_user`` does not include ``pythonX.Y`` in # the ``include`` value, but distutils's ``headers`` does. We'll let # CPython decide whether this is a bug or feature. See bpo-43948. skip_osx_framework_user_special_case = ( user and is_osx_framework() and k == "headers" and old_v.parent.parent == new_v.parent and old_v.parent.name.startswith("python")) if skip_osx_framework_user_special_case: continue # On Red Hat and derived Linux distributions, distutils is patched to # use "lib64" instead of "lib" for platlib. if k == "platlib" and _looks_like_red_hat_patched(): continue # Both Debian and Red Hat patch Python to place the system site under # /usr/local instead of /usr. Debian also places lib in dist-packages # instead of site-packages, but the /usr/local check should cover it. skip_linux_system_special_case = ( not (user or home or prefix) and old_v.parts[1:3] == ("usr", "local") and len(new_v.parts) > 1 and new_v.parts[1] == "usr" and (len(new_v.parts) < 3 or new_v.parts[2] != "local") and (_looks_like_red_hat_patched() or _looks_like_debian_patched())) if skip_linux_system_special_case: continue # On Python 3.7 and earlier, sysconfig does not include sys.abiflags in # the "pythonX.Y" part of the path, but distutils does. skip_sysconfig_abiflag_bug = ( sys.version_info < (3, 8) and not WINDOWS and k in ("headers", "platlib", "purelib") and tuple(_fix_abiflags(old_v.parts)) == new_v.parts) if skip_sysconfig_abiflag_bug: continue warning_contexts.append((old_v, new_v, f"scheme.{k}")) if not warning_contexts: return old # Check if this path mismatch is caused by distutils config files. Those # files will no longer work once we switch to sysconfig, so this raises a # deprecation message for them. default_old = _distutils.distutils_scheme( dist_name, user, home, root, isolated, prefix, ignore_config_files=True, ) if any(default_old[k] != getattr(old, k) for k in SCHEME_KEYS): deprecated( "Configuring installation scheme with distutils config files " "is deprecated and will no longer work in the near future. If you " "are using a Homebrew or Linuxbrew Python, please see discussion " "at https://github.com/Homebrew/homebrew-core/issues/76621", replacement=None, gone_in=None, ) return old # Post warnings about this mismatch so user can report them back. for old_v, new_v, key in warning_contexts: _warn_mismatched(old_v, new_v, key=key) _log_context(user=user, home=home, root=root, prefix=prefix) return old
def main(self, args): # type: (List[str]) -> int options, args = self.parse_args(args) # Set verbosity so that it can be used elsewhere. self.verbosity = options.verbose - options.quiet level_number = setup_logging( verbosity=self.verbosity, no_color=options.no_color, user_log_file=options.log, ) if sys.version_info[:2] == (3, 4): deprecated( "Python 3.4 support has been deprecated. pip 19.1 will be the " "last one supporting it. Please upgrade your Python as Python " "3.4 won't be maintained after March 2019 (cf PEP 429).", replacement=None, gone_in='19.2', ) elif sys.version_info[:2] == (2, 7): message = ( "A future version of pip will drop support for Python 2.7." ) if platform.python_implementation() == "CPython": message = ( "Python 2.7 will reach the end of its life on January " "1st, 2020. Please upgrade your Python as Python 2.7 " "won't be maintained after that date. " ) + message deprecated(message, replacement=None, gone_in=None) # TODO: Try to get these passing down from the command? # without resorting to os.environ to hold these. # This also affects isolated builds and it should. if options.no_input: os.environ['PIP_NO_INPUT'] = '1' if options.exists_action: os.environ['PIP_EXISTS_ACTION'] = ' '.join(options.exists_action) if options.require_venv and not self.ignore_require_venv: # If a venv is required check if it can really be found if not running_under_virtualenv(): logger.critical( 'Could not find an activated virtualenv (required).' ) sys.exit(VIRTUALENV_NOT_FOUND) try: status = self.run(options, args) # FIXME: all commands should return an exit status # and when it is done, isinstance is not needed anymore if isinstance(status, int): return status except PreviousBuildDirError as exc: logger.critical(str(exc)) logger.debug('Exception information:', exc_info=True) return PREVIOUS_BUILD_DIR_ERROR except (InstallationError, UninstallationError, BadCommand) as exc: logger.critical(str(exc)) logger.debug('Exception information:', exc_info=True) return ERROR except CommandError as exc: logger.critical('%s', exc) logger.debug('Exception information:', exc_info=True) return ERROR except BrokenStdoutLoggingError: # Bypass our logger and write any remaining messages to stderr # because stdout no longer works. print('ERROR: Pipe to stdout was broken', file=sys.stderr) if level_number <= logging.DEBUG: traceback.print_exc(file=sys.stderr) return ERROR except KeyboardInterrupt: logger.critical('Operation cancelled by user') logger.debug('Exception information:', exc_info=True) return ERROR except BaseException: logger.critical('Exception:', exc_info=True) return UNKNOWN_ERROR finally: allow_version_check = ( # Does this command have the index_group options? hasattr(options, "no_index") and # Is this command allowed to perform this check? not (options.disable_pip_version_check or options.no_index) ) # Check if we're using the latest version of pip available if allow_version_check: session = self._build_session( options, retries=0, timeout=min(5, options.timeout) ) with session: pip_version_check(session, options) # Shutdown the logging module logging.shutdown() return SUCCESS
def from_dist(cls, dist, dependency_links): location = os.path.normcase(os.path.abspath(dist.location)) comments = [] from pip._internal.vcs import vcs, get_src_requirement if dist_is_editable(dist) and vcs.get_backend_name(location): editable = True try: req = get_src_requirement(dist, location) except InstallationError as exc: logger.warning( "Error when trying to get requirement for VCS system %s, " "falling back to uneditable format", exc) req = None if req is None: logger.warning('Could not determine repository location of %s', location) comments.append( '## !! Could not determine repository location') req = dist.as_requirement() editable = False else: editable = False req = dist.as_requirement() specs = req.specs assert len(specs) == 1 and specs[0][0] in ["==", "==="], \ 'Expected 1 spec with == or ===; specs = %r; dist = %r' % \ (specs, dist) version = specs[0][1] ver_match = cls._rev_re.search(version) date_match = cls._date_re.search(version) if ver_match or date_match: svn_backend = vcs.get_backend('svn') if svn_backend: svn_location = svn_backend().get_location( dist, dependency_links, ) if not svn_location: logger.warning( 'Warning: cannot find svn location for %s', req, ) comments.append( '## FIXME: could not find svn URL in dependency_links ' 'for this package:') else: deprecated( "SVN editable detection based on dependency links " "will be dropped in the future.", replacement=None, gone_in="18.2", issue=4187, ) comments.append( '# Installing as editable to satisfy requirement %s:' % req) if ver_match: rev = ver_match.group(1) else: rev = '{%s}' % date_match.group(1) editable = True req = '%s@%s#egg=%s' % (svn_location, rev, cls.egg_name(dist)) return cls(dist.project_name, req, editable, comments)
def from_dist(cls, dist, dependency_links): location = os.path.normcase(os.path.abspath(dist.location)) comments = [] from pip._internal.vcs import vcs, get_src_requirement if dist_is_editable(dist) and vcs.get_backend_name(location): editable = True try: req = get_src_requirement(dist, location) except InstallationError as exc: logger.warning( "Error when trying to get requirement for VCS system %s, " "falling back to uneditable format", exc ) req = None if req is None: logger.warning( 'Could not determine repository location of %s', location ) comments.append( '## !! Could not determine repository location' ) req = dist.as_requirement() editable = False else: editable = False req = dist.as_requirement() specs = req.specs assert len(specs) == 1 and specs[0][0] in ["==", "==="], \ 'Expected 1 spec with == or ===; specs = %r; dist = %r' % \ (specs, dist) version = specs[0][1] ver_match = cls._rev_re.search(version) date_match = cls._date_re.search(version) if ver_match or date_match: svn_backend = vcs.get_backend('svn') if svn_backend: svn_location = svn_backend().get_location( dist, dependency_links, ) if not svn_location: logger.warning( 'Warning: cannot find svn location for %s', req, ) comments.append( '## FIXME: could not find svn URL in dependency_links ' 'for this package:' ) else: deprecated( "SVN editable detection based on dependency links " "will be dropped in the future.", replacement=None, gone_in="18.2", issue=4187, ) comments.append( '# Installing as editable to satisfy requirement %s:' % req ) if ver_match: rev = ver_match.group(1) else: rev = '{%s}' % date_match.group(1) editable = True req = '%s@%s#egg=%s' % ( svn_location, rev, cls.egg_name(dist) ) return cls(dist.project_name, req, editable, comments)
def install( install_req, # type: InstallRequirement install_options, # type: List[str] global_options, # type: Sequence[str] root, # type: Optional[str] home, # type: Optional[str] prefix, # type: Optional[str] use_user_site, # type: bool pycompile, # type: bool scheme, # type: Scheme ): # type: (...) -> None # Extend the list of global and install options passed on to # the setup.py call with the ones from the requirements file. # Options specified in requirements file override those # specified on the command line, since the last option given # to setup.py is the one that is used. global_options = list(global_options) + install_req.options.get( "global_options", []) install_options = list(install_options) + install_req.options.get( "install_options", []) header_dir = scheme.headers with TempDirectory(kind="record") as temp_dir: record_filename = os.path.join(temp_dir.path, "install-record.txt") install_args = make_setuptools_install_args( install_req.setup_py_path, global_options=global_options, install_options=install_options, record_filename=record_filename, root=root, prefix=prefix, header_dir=header_dir, home=home, use_user_site=use_user_site, no_user_config=install_req.isolated, pycompile=pycompile, ) runner = runner_with_spinner_message( "Running setup.py install for {}".format(install_req.name)) with indent_log(), install_req.build_env: runner( cmd=install_args, cwd=install_req.unpacked_source_directory, ) if not os.path.exists(record_filename): logger.debug("Record file %s not found", record_filename) return install_req.install_succeeded = True # We intentionally do not use any encoding to read the file because # setuptools writes the file using distutils.file_util.write_file, # which does not specify an encoding. with open(record_filename) as f: record_lines = f.read().splitlines() def prepend_root(path): # type: (str) -> str if root is None or not os.path.isabs(path): return path else: return change_root(root, path) for line in record_lines: directory = os.path.dirname(line) if directory.endswith(".egg-info"): egg_info_dir = prepend_root(directory) break else: deprecated( reason=("{} did not indicate that it installed an " ".egg-info directory. Only setup.py projects " "generating .egg-info directories are supported." ).format(install_req), replacement=("for maintainers: updating the setup.py of {0}. " "For users: contact the maintainers of {0} to let " "them know to update their setup.py.".format( install_req.name)), gone_in="20.2", issue=6998, ) # FIXME: put the record somewhere return new_lines = [] for line in record_lines: filename = line.strip() if os.path.isdir(filename): filename += os.path.sep new_lines.append(os.path.relpath(prepend_root(filename), egg_info_dir)) new_lines.sort() ensure_dir(egg_info_dir) inst_files_path = os.path.join(egg_info_dir, "installed-files.txt") with open(inst_files_path, "w") as f: f.write("\n".join(new_lines) + "\n")
def _main(self, args): # type: (List[str]) -> int # We must initialize this before the tempdir manager, otherwise the # configuration would not be accessible by the time we clean up the # tempdir manager. self.tempdir_registry = self.enter_context(tempdir_registry()) # Intentionally set as early as possible so globally-managed temporary # directories are available to the rest of the code. self.enter_context(global_tempdir_manager()) options, args = self.parse_args(args) # Set verbosity so that it can be used elsewhere. self.verbosity = options.verbose - options.quiet level_number = setup_logging( verbosity=self.verbosity, no_color=options.no_color, user_log_file=options.log, ) # TODO: Try to get these passing down from the command? # without resorting to os.environ to hold these. # This also affects isolated builds and it should. if options.no_input: os.environ['PIP_NO_INPUT'] = '1' if options.exists_action: os.environ['PIP_EXISTS_ACTION'] = ' '.join(options.exists_action) if options.require_venv and not self.ignore_require_venv: # If a venv is required check if it can really be found if not running_under_virtualenv(): logger.critical( 'Could not find an activated virtualenv (required).' ) sys.exit(VIRTUALENV_NOT_FOUND) if options.cache_dir: options.cache_dir = normalize_path(options.cache_dir) if not check_path_owner(options.cache_dir): logger.warning( "The directory '%s' or its parent directory is not owned " "or is not writable by the current user. The cache " "has been disabled. Check the permissions and owner of " "that directory. If executing pip with sudo, you may want " "sudo's -H flag.", options.cache_dir, ) options.cache_dir = None if getattr(options, "build_dir", None): deprecated( reason=( "The -b/--build/--build-dir/--build-directory " "option is deprecated and has no effect anymore." ), replacement=( "use the TMPDIR/TEMP/TMP environment variable, " "possibly combined with --no-clean" ), gone_in="21.1", issue=8333, ) if '2020-resolver' in options.features_enabled: logger.warning( "--use-feature=2020-resolver no longer has any effect, " "since it is now the default dependency resolver in pip. " "This will become an error in pip 21.0." ) try: status = self.run(options, args) assert isinstance(status, int) return status except PreviousBuildDirError as exc: logger.critical(str(exc)) logger.debug('Exception information:', exc_info=True) return PREVIOUS_BUILD_DIR_ERROR except (InstallationError, UninstallationError, BadCommand, NetworkConnectionError) as exc: logger.critical(str(exc)) logger.debug('Exception information:', exc_info=True) return ERROR except CommandError as exc: logger.critical('%s', exc) logger.debug('Exception information:', exc_info=True) return ERROR except BrokenStdoutLoggingError: # Bypass our logger and write any remaining messages to stderr # because stdout no longer works. print('ERROR: Pipe to stdout was broken', file=sys.stderr) if level_number <= logging.DEBUG: traceback.print_exc(file=sys.stderr) return ERROR except KeyboardInterrupt: logger.critical('Operation cancelled by user') logger.debug('Exception information:', exc_info=True) return ERROR except BaseException: logger.critical('Exception:', exc_info=True) return UNKNOWN_ERROR finally: self.handle_pip_version_check(options)
def install( self, install_options, # type: List[str] global_options=None, # type: Optional[Sequence[str]] root=None, # type: Optional[str] home=None, # type: Optional[str] prefix=None, # type: Optional[str] warn_script_location=True, # type: bool use_user_site=False, # type: bool pycompile=True # type: bool ): # type: (...) -> None scheme = get_scheme( self.name, user=use_user_site, home=home, root=root, isolated=self.isolated, prefix=prefix, ) global_options = global_options if global_options is not None else [] if self.editable: install_editable_legacy( install_options, global_options, prefix=prefix, home=home, use_user_site=use_user_site, name=self.name, setup_py_path=self.setup_py_path, isolated=self.isolated, build_env=self.build_env, unpacked_source_directory=self.unpacked_source_directory, ) self.install_succeeded = True return if self.is_wheel: assert self.local_file_path direct_url = None if self.original_link: direct_url = direct_url_from_link( self.original_link, self.source_dir, self.original_link_is_in_wheel_cache, ) install_wheel( self.name, self.local_file_path, scheme=scheme, req_description=str(self.req), pycompile=pycompile, warn_script_location=warn_script_location, direct_url=direct_url, requested=self.user_supplied, ) self.install_succeeded = True return # TODO: Why don't we do this for editable installs? # Extend the list of global and install options passed on to # the setup.py call with the ones from the requirements file. # Options specified in requirements file override those # specified on the command line, since the last option given # to setup.py is the one that is used. global_options = list(global_options) + self.global_options install_options = list(install_options) + self.install_options try: success = install_legacy( install_options=install_options, global_options=global_options, root=root, home=home, prefix=prefix, use_user_site=use_user_site, pycompile=pycompile, scheme=scheme, setup_py_path=self.setup_py_path, isolated=self.isolated, req_name=self.name, build_env=self.build_env, unpacked_source_directory=self.unpacked_source_directory, req_description=str(self.req), ) except LegacyInstallFailure as exc: self.install_succeeded = False six.reraise(*exc.parent) except Exception: self.install_succeeded = True raise self.install_succeeded = success if success and self.legacy_install_reason == 8368: deprecated( reason=("{} was installed using the legacy 'setup.py install' " "method, because a wheel could not be built for it.". format(self.name)), replacement="to fix the wheel build issue reported above", gone_in=None, issue=8368, )
def _main(self, args): # type: (List[str]) -> int # Intentionally set as early as possible so globally-managed temporary # directories are available to the rest of the code. self.enter_context(global_tempdir_manager()) options, args = self.parse_args(args) # Set verbosity so that it can be used elsewhere. self.verbosity = options.verbose - options.quiet level_number = setup_logging( verbosity=self.verbosity, no_color=options.no_color, user_log_file=options.log, ) if (sys.version_info[:2] == (2, 7) and not options.no_python_version_warning): message = ( "A future version of pip will drop support for Python 2.7. " "More details about Python 2 support in pip, can be found at " "https://pip.pypa.io/en/latest/development/release-process/#python-2-support" # noqa ) if platform.python_implementation() == "CPython": message = ( "Python 2.7 reached the end of its life on January " "1st, 2020. Please upgrade your Python as Python 2.7 " "is no longer maintained. ") + message deprecated(message, replacement=None, gone_in=None) if options.skip_requirements_regex: deprecated( "--skip-requirements-regex is unsupported and will be removed", replacement=( "manage requirements/constraints files explicitly, " "possibly generating them from metadata"), gone_in="20.1", issue=7297, ) # TODO: Try to get these passing down from the command? # without resorting to os.environ to hold these. # This also affects isolated builds and it should. if options.no_input: os.environ['PIP_NO_INPUT'] = '1' if options.exists_action: os.environ['PIP_EXISTS_ACTION'] = ' '.join(options.exists_action) if options.require_venv and not self.ignore_require_venv: # If a venv is required check if it can really be found if not running_under_virtualenv(): logger.critical( 'Could not find an activated virtualenv (required).') sys.exit(VIRTUALENV_NOT_FOUND) if options.cache_dir: options.cache_dir = normalize_path(options.cache_dir) if not check_path_owner(options.cache_dir): logger.warning( "The directory '%s' or its parent directory is not owned " "or is not writable by the current user. The cache " "has been disabled. Check the permissions and owner of " "that directory. If executing pip with sudo, you may want " "sudo's -H flag.", options.cache_dir, ) options.cache_dir = None try: status = self.run(options, args) # FIXME: all commands should return an exit status # and when it is done, isinstance is not needed anymore if isinstance(status, int): return status except PreviousBuildDirError as exc: logger.critical(str(exc)) logger.debug('Exception information:', exc_info=True) return PREVIOUS_BUILD_DIR_ERROR except (InstallationError, UninstallationError, BadCommand) as exc: logger.critical(str(exc)) logger.debug('Exception information:', exc_info=True) return ERROR except CommandError as exc: logger.critical('%s', exc) logger.debug('Exception information:', exc_info=True) return ERROR except BrokenStdoutLoggingError: # Bypass our logger and write any remaining messages to stderr # because stdout no longer works. print('ERROR: Pipe to stdout was broken', file=sys.stderr) if level_number <= logging.DEBUG: traceback.print_exc(file=sys.stderr) return ERROR except KeyboardInterrupt: logger.critical('Operation cancelled by user') logger.debug('Exception information:', exc_info=True) return ERROR except BaseException: logger.critical('Exception:', exc_info=True) return UNKNOWN_ERROR finally: self.handle_pip_version_check(options) return SUCCESS