def install_given_reqs( to_install, # type: List[InstallRequirement] install_options, # type: List[str] global_options=(), # type: Sequence[str] *args, # type: Any **kwargs # type: Any ): # type: (...) -> List[InstallationResult] """ Install everything in the given list. (to be called after having downloaded and unpacked the packages) """ if to_install: logger.info( 'Installing collected packages: %s', ', '.join([req.name for req in to_install]), ) installed = [] with indent_log(): for requirement in to_install: if requirement.should_reinstall: logger.info('Attempting uninstall: %s', requirement.name) with indent_log(): uninstalled_pathset = requirement.uninstall( auto_confirm=True) try: requirement.install(install_options, global_options, *args, **kwargs) except Exception: should_rollback = (requirement.should_reinstall and not requirement.install_succeeded) # if install did not succeed, rollback previous uninstall if should_rollback: uninstalled_pathset.rollback() raise else: should_commit = (requirement.should_reinstall and requirement.install_succeeded) if should_commit: uninstalled_pathset.commit() installed.append(InstallationResult(requirement.name)) return installed
def prepare_editable_requirement( self, req, # type: InstallRequirement ): # type: (...) -> AbstractDistribution """Prepare an editable requirement """ assert req.editable, "cannot prepare a non-editable req as editable" logger.info('Obtaining %s', req) with indent_log(): if self.require_hashes: raise InstallationError( 'The editable requirement {} cannot be installed when ' 'requiring hashes, because there is no single file to ' 'hash.'.format(req)) req.ensure_has_source_dir(self.src_dir) req.update_editable(not self._download_should_save) abstract_dist = _get_prepared_distribution( req, self.req_tracker, self.finder, self.build_isolation, ) if self._download_should_save: req.archive(self.download_dir) req.check_if_exists(self.use_user_site) return abstract_dist
def show_tags(options): # type: (Values) -> None tag_limit = 10 target_python = make_target_python(options) tags = target_python.get_tags() # Display the target options that were explicitly provided. formatted_target = target_python.format_given() suffix = '' if formatted_target: suffix = ' (target: {})'.format(formatted_target) msg = 'Compatible tags: {}{}'.format(len(tags), suffix) logger.info(msg) if options.verbose < 1 and len(tags) > tag_limit: tags_limited = True tags = tags[:tag_limit] else: tags_limited = False with indent_log(): for tag in tags: logger.info(str(tag)) if tags_limited: msg = ( '...\n' '[First {tag_limit} tags shown. Pass --verbose to show all.]' ).format(tag_limit=tag_limit) logger.info(msg)
def remove(self, auto_confirm=False, verbose=False): # type: (bool, bool) -> None """Remove paths in ``self.paths`` with confirmation (unless ``auto_confirm`` is True).""" if not self.paths: logger.info( "Can't uninstall '%s'. No files were found to uninstall.", self.dist.project_name, ) return dist_name_version = (self.dist.project_name + "-" + self.dist.version) logger.info('Uninstalling %s:', dist_name_version) with indent_log(): if auto_confirm or self._allowed_to_proceed(verbose): moved = self._moved_paths for_rename = compress_for_rename(self.paths) for path in sorted(compact(for_rename)): moved.stash(path) logger.debug('Removing file or directory %s', path) for pth in self.pth.values(): pth.remove() logger.info('Successfully uninstalled %s', dist_name_version)
def install_editable( install_options, # type: List[str] global_options, # type: Sequence[str] prefix, # type: Optional[str] home, # type: Optional[str] use_user_site, # type: bool name, # type: str setup_py_path, # type: str isolated, # type: bool build_env, # type: BuildEnvironment unpacked_source_directory, # type: str ): # type: (...) -> None """Install a package in editable mode. Most arguments are pass-through to setuptools. """ logger.info('Running setup.py develop for %s', name) args = make_setuptools_develop_args( setup_py_path, global_options=global_options, install_options=install_options, no_user_config=isolated, prefix=prefix, home=home, use_user_site=use_user_site, ) with indent_log(): with build_env: call_subprocess( args, cwd=unpacked_source_directory, )
def show_vendor_versions(): # type: () -> None logger.info('vendored library versions:') vendor_txt_versions = create_vendor_txt_map() with indent_log(): show_actual_vendor_versions(vendor_txt_versions)
def _display(msg, paths): # type: (str, Iterable[str]) -> None if not paths: return logger.info(msg) with indent_log(): for path in sorted(compact(paths)): logger.info(path)
def show_sys_implementation(): # type: () -> None logger.info('sys.implementation:') if hasattr(sys, 'implementation'): implementation = sys.implementation # type: ignore implementation_name = implementation.name else: implementation_name = '' with indent_log(): show_value('name', implementation_name)
def build( requirements, # type: Iterable[InstallRequirement] wheel_cache, # type: WheelCache build_options, # type: List[str] global_options, # type: List[str] ): # type: (...) -> BuildResult """Build wheels. :return: The list of InstallRequirement that succeeded to build and the list of InstallRequirement that failed to build. """ if not requirements: return [], [] # Build the wheels. logger.info( 'Building wheels for collected packages: %s', ', '.join(req.name for req in requirements), ) with indent_log(): build_successes, build_failures = [], [] for req in requirements: cache_dir = _get_cache_dir(req, wheel_cache) wheel_file = _build_one( req, cache_dir, build_options, global_options ) if wheel_file: # Update the link for this. req.link = Link(path_to_url(wheel_file)) req.local_file_path = req.link.file_path assert req.link.is_wheel build_successes.append(req) else: build_failures.append(req) # notify success/failure if build_successes: logger.info( 'Successfully built %s', ' '.join([req.name for req in build_successes]), ) if build_failures: logger.info( 'Failed to build %s', ' '.join([req.name for req in build_failures]), ) # Return a list of requirements that failed to build return build_successes, build_failures
def export(self, location, url): # type: (str, HiddenText) -> None """Export the svn repository at the url to the destination location""" url, rev_options = self.get_url_rev_options(url) logger.info('Exporting svn repository %s to %s', url, location) with indent_log(): if os.path.exists(location): # Subversion doesn't like to check out over an existing # directory --force fixes this, but was only added in svn 1.5 rmtree(location) cmd_args = make_command( 'export', self.get_remote_call_options(), rev_options.to_args(), url, location, ) self.run_command(cmd_args, show_stdout=False)
def prepare_metadata(self): # type: () -> None """Ensure that project metadata is available. Under PEP 517, call the backend hook to prepare the metadata. Under legacy processing, call setup.py egg-info. """ assert self.source_dir with indent_log(): self.metadata_directory = self._generate_metadata() # Act on the newly generated metadata, based on the name and version. if not self.name: self._set_requirement() else: self.warn_on_mismatching_name() self.assert_source_matches_version()
def process_project_url(self, project_url, link_evaluator): # type: (Link, LinkEvaluator) -> List[InstallationCandidate] logger.debug( 'Fetching project page and analyzing links: %s', project_url, ) html_page = self._link_collector.fetch_page(project_url) if html_page is None: return [] page_links = list(parse_links(html_page)) with indent_log(): package_links = self.evaluate_links( link_evaluator, links=page_links, ) return package_links
def print_results(hits, name_column_width=None, terminal_width=None): if not hits: return if name_column_width is None: name_column_width = max([ len(hit['name']) + len(highest_version(hit.get('versions', ['-']))) for hit in hits ]) + 4 installed_packages = [p.project_name for p in pkg_resources.working_set] for hit in hits: name = hit['name'] summary = hit['summary'] or '' latest = highest_version(hit.get('versions', ['-'])) if terminal_width is not None: target_width = terminal_width - name_column_width - 5 if target_width > 10: # wrap and indent summary to fit terminal summary = textwrap.wrap(summary, target_width) summary = ('\n' + ' ' * (name_column_width + 3)).join(summary) line = '{name_latest:{name_column_width}} - {summary}'.format( name_latest='{name} ({latest})'.format(**locals()), **locals()) try: write_output(line) if name in installed_packages: dist = pkg_resources.get_distribution(name) with indent_log(): if dist.version == latest: write_output('INSTALLED: %s (latest)', dist.version) else: write_output('INSTALLED: %s', dist.version) if parse_version(latest).pre: write_output( 'LATEST: %s (pre-release; install' ' with "pip install --pre")', latest) else: write_output('LATEST: %s', latest) except UnicodeEncodeError: pass
def prepare_installed_requirement( self, req, # type: InstallRequirement skip_reason # type: str ): # type: (...) -> AbstractDistribution """Prepare an already-installed requirement """ assert req.satisfied_by, "req should have been satisfied but isn't" assert skip_reason is not None, ( "did not get skip reason skipped but req.satisfied_by " "is set to {}".format(req.satisfied_by)) logger.info('Requirement %s: %s (%s)', skip_reason, req, req.satisfied_by.version) with indent_log(): if self.require_hashes: logger.debug( 'Since it is already installed, we are trusting this ' 'package without checking its hash. To ensure a ' 'completely repeatable environment, install into an ' 'empty virtualenv.') abstract_dist = InstalledDistribution(req) return abstract_dist
def install( 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 setup_py_path, # type: str isolated, # type: bool req_name, # type: str build_env, # type: BuildEnvironment unpacked_source_directory, # type: str req_description, # type: str ): # type: (...) -> bool header_dir = scheme.headers with TempDirectory(kind="record") as temp_dir: try: record_filename = os.path.join(temp_dir.path, 'install-record.txt') install_args = make_setuptools_install_args( 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=isolated, pycompile=pycompile, ) runner = runner_with_spinner_message( "Running setup.py install for {}".format(req_name)) with indent_log(), build_env: runner( cmd=install_args, cwd=unpacked_source_directory, ) if not os.path.exists(record_filename): logger.debug('Record file %s not found', record_filename) # Signal to the caller that we didn't install the new package return False except Exception: # Signal to the caller that we didn't install the new package raise LegacyInstallFailure # At this point, we have successfully installed the requirement. # 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(req_description), 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(req_name)), gone_in="20.2", issue=6998, ) # FIXME: put the record somewhere return True 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') return True
def _resolve_one( self, requirement_set, # type: RequirementSet req_to_install, # type: InstallRequirement ): # type: (...) -> List[InstallRequirement] """Prepare a single requirements file. :return: A list of additional InstallRequirements to also install. """ # Tell user what we are doing for this requirement: # obtain (editable), skipping, processing (local url), collecting # (remote url or package name) if req_to_install.constraint or req_to_install.prepared: return [] req_to_install.prepared = True abstract_dist = self._get_abstract_dist_for(req_to_install) # Parse and return dependencies dist = abstract_dist.get_pkg_resources_distribution() # This will raise UnsupportedPythonVersion if the given Python # version isn't compatible with the distribution's Requires-Python. _check_dist_requires_python( dist, version_info=self._py_version_info, ignore_requires_python=self.ignore_requires_python, ) more_reqs = [] # type: List[InstallRequirement] def add_req(subreq, extras_requested): sub_install_req = self._make_install_req( str(subreq), req_to_install, ) parent_req_name = req_to_install.name to_scan_again, add_to_parent = requirement_set.add_requirement( sub_install_req, parent_req_name=parent_req_name, extras_requested=extras_requested, ) if parent_req_name and add_to_parent: self._discovered_dependencies[parent_req_name].append( add_to_parent ) more_reqs.extend(to_scan_again) with indent_log(): # We add req_to_install before its dependencies, so that we # can refer to it when adding dependencies. if not requirement_set.has_requirement(req_to_install.name): # 'unnamed' requirements will get added here # 'unnamed' requirements can only come from being directly # provided by the user. assert req_to_install.is_direct requirement_set.add_requirement( req_to_install, parent_req_name=None, ) if not self.ignore_dependencies: if req_to_install.extras: logger.debug( "Installing extra requirements: %r", ','.join(req_to_install.extras), ) missing_requested = sorted( set(req_to_install.extras) - set(dist.extras) ) for missing in missing_requested: logger.warning( '%s does not provide the extra \'%s\'', dist, missing ) available_requested = sorted( set(dist.extras) & set(req_to_install.extras) ) for subreq in dist.requires(available_requested): add_req(subreq, extras_requested=available_requested) if not req_to_install.editable and not req_to_install.satisfied_by: # XXX: --no-install leads this to report 'Successfully # downloaded' for only non-editable reqs, even though we took # action on them. req_to_install.successfully_downloaded = True return more_reqs
def prepare_linked_requirement( self, req, # type: InstallRequirement ): # type: (...) -> AbstractDistribution """Prepare a requirement that would be obtained from req.link """ assert req.link link = req.link # TODO: Breakup into smaller functions if link.scheme == 'file': path = link.file_path logger.info('Processing %s', display_path(path)) else: logger.info('Collecting %s', req.req or req) download_dir = self.download_dir if link.is_wheel and self.wheel_download_dir: # when doing 'pip wheel` we download wheels to a # dedicated dir. download_dir = self.wheel_download_dir if link.is_wheel: if download_dir: # When downloading, we only unpack wheels to get # metadata. autodelete_unpacked = True else: # When installing a wheel, we use the unpacked # wheel. autodelete_unpacked = False else: # We always delete unpacked sdists after pip runs. autodelete_unpacked = True with indent_log(): # Since source_dir is only set for editable requirements. assert req.source_dir is None req.ensure_has_source_dir(self.build_dir, autodelete_unpacked) # If a checkout exists, it's unwise to keep going. version # inconsistencies are logged later, but do not fail the # installation. # FIXME: this won't upgrade when there's an existing # package unpacked in `req.source_dir` if os.path.exists(os.path.join(req.source_dir, 'setup.py')): raise PreviousBuildDirError( "pip can't proceed with requirements '{}' due to a" " pre-existing build directory ({}). This is " "likely due to a previous installation that failed" ". pip is being responsible and not assuming it " "can delete this. Please delete it and try again.".format( req, req.source_dir)) # Now that we have the real link, we can tell what kind of # requirements we have and raise some more informative errors # than otherwise. (For example, we can raise VcsHashUnsupported # for a VCS URL rather than HashMissing.) if self.require_hashes: # We could check these first 2 conditions inside # unpack_url and save repetition of conditions, but then # we would report less-useful error messages for # unhashable requirements, complaining that there's no # hash provided. if link.is_vcs: raise VcsHashUnsupported() elif link.is_existing_dir(): raise DirectoryUrlHashUnsupported() if not req.original_link and not req.is_pinned: # Unpinned packages are asking for trouble when a new # version is uploaded. This isn't a security check, but # it saves users a surprising hash mismatch in the # future. # # file:/// URLs aren't pinnable, so don't complain # about them not being pinned. raise HashUnpinned() hashes = req.hashes(trust_internet=not self.require_hashes) if self.require_hashes and not hashes: # Known-good hashes are missing for this requirement, so # shim it with a facade object that will provoke hash # computation and then raise a HashMissing exception # showing the user what the hash should be. hashes = MissingHashes() try: local_file = unpack_url( link, req.source_dir, self.downloader, download_dir, hashes=hashes, ) except requests.HTTPError as exc: logger.critical( 'Could not install requirement %s because of error %s', req, exc, ) raise InstallationError( 'Could not install requirement {} because of HTTP ' 'error {} for URL {}'.format(req, exc, link)) # For use in later processing, preserve the file path on the # requirement. if local_file: req.local_file_path = local_file.path abstract_dist = _get_prepared_distribution( req, self.req_tracker, self.finder, self.build_isolation, ) if download_dir: if link.is_existing_dir(): logger.info('Link is a directory, ignoring download_dir') elif local_file: download_location = os.path.join(download_dir, link.filename) if not os.path.exists(download_location): shutil.copy(local_file.path, download_location) logger.info('Saved %s', display_path(download_location)) if self._download_should_save: # Make a .zip of the source_dir we already created. if link.is_vcs: req.archive(self.download_dir) return abstract_dist