def build_wheel_editable( name: str, backend: Pep517HookCaller, metadata_directory: str, tempd: str, ) -> Optional[str]: """Build one InstallRequirement using the PEP 660 build process. Returns path to wheel if successfully built. Otherwise, returns None. """ assert metadata_directory is not None try: logger.debug("Destination directory: %s", tempd) runner = runner_with_spinner_message( f"Building editable for {name} (pyproject.toml)") with backend.subprocess_runner(runner): try: wheel_name = backend.build_editable( tempd, metadata_directory=metadata_directory, ) except HookMissing as e: logger.error( "Cannot build editable %s because the build " "backend does not have the %s hook", name, e, ) return None except Exception: logger.error("Failed building editable for %s", name) return None return os.path.join(tempd, wheel_name)
def build_wheel_pep517( name, # type: str backend, # type: Pep517HookCaller metadata_directory, # type: str tempd, # type: str ): # type: (...) -> Optional[str] """Build one InstallRequirement using the PEP 517 build process. Returns path to wheel if successfully built. Otherwise, returns None. """ assert metadata_directory is not None try: logger.debug('Destination directory: %s', tempd) runner = runner_with_spinner_message( f'Building wheel for {name} (PEP 517)') with backend.subprocess_runner(runner): wheel_name = backend.build_wheel( tempd, metadata_directory=metadata_directory, ) except Exception: logger.error('Failed building wheel for %s', name) return None return os.path.join(tempd, wheel_name)
def build_wheel_pep517( name: str, backend: Pep517HookCaller, metadata_directory: str, tempd: str, ) -> Optional[str]: """Build one InstallRequirement using the PEP 517 build process. Returns path to wheel if successfully built. Otherwise, returns None. """ assert metadata_directory is not None try: logger.debug("Destination directory: %s", tempd) runner = runner_with_spinner_message( f"Building wheel for {name} (pyproject.toml)") with backend.subprocess_runner(runner): wheel_name = backend.build_wheel( tempd, metadata_directory=metadata_directory, ) except Exception: logger.error("Failed building wheel for %s", name) return None return os.path.join(tempd, wheel_name)
def generate_editable_metadata( build_env: BuildEnvironment, backend: Pep517HookCaller, details: str ) -> str: """Generate metadata using mechanisms described in PEP 660. Returns the generated metadata directory. """ metadata_tmpdir = TempDirectory(kind="modern-metadata", globally_managed=True) metadata_dir = metadata_tmpdir.path with build_env: # Note that Pep517HookCaller implements a fallback for # prepare_metadata_for_build_wheel/editable, so we don't have to # consider the possibility that this hook doesn't exist. runner = runner_with_spinner_message( "Preparing editable metadata (pyproject.toml)" ) with backend.subprocess_runner(runner): try: distinfo_dir = backend.prepare_metadata_for_build_editable(metadata_dir) except InstallationSubprocessError as error: raise MetadataGenerationFailed(package_details=details) from error return os.path.join(metadata_dir, distinfo_dir)
def _build_one_pep517(self, req, tempd, python_tag=None): """Build one InstallRequirement using the PEP 517 build process. Returns path to wheel if successfully built. Otherwise, returns None. """ assert req.metadata_directory is not None if self.build_options: # PEP 517 does not support --build-options logger.error('Cannot build wheel for %s using PEP 517 when ' '--build-options is present' % (req.name, )) return None try: logger.debug('Destination directory: %s', tempd) runner = runner_with_spinner_message( 'Building wheel for {} (PEP 517)'.format(req.name)) backend = req.pep517_backend with backend.subprocess_runner(runner): wheel_name = backend.build_wheel( tempd, metadata_directory=req.metadata_directory, ) if python_tag: # General PEP 517 backends don't necessarily support # a "--python-tag" option, so we rename the wheel # file directly. new_name = replace_python_tag(wheel_name, python_tag) os.rename(os.path.join(tempd, wheel_name), os.path.join(tempd, new_name)) # Reassign to simplify the return at the end of function wheel_name = new_name except Exception: logger.error('Failed building wheel for %s', req.name) return None return os.path.join(tempd, wheel_name)
def build_wheel_pep517( name, # type: str backend, # type: Pep517HookCaller metadata_directory, # type: str build_options, # type: List[str] tempd, # type: str ): # type: (...) -> Optional[str] """Build one InstallRequirement using the PEP 517 build process. Returns path to wheel if successfully built. Otherwise, returns None. """ assert metadata_directory is not None if build_options: # PEP 517 does not support --build-options logger.error('Cannot build wheel for %s using PEP 517 when ' '--build-option is present' % (name, )) return None try: logger.debug('Destination directory: %s', tempd) runner = runner_with_spinner_message( 'Building wheel for {} (PEP 517)'.format(name)) with backend.subprocess_runner(runner): wheel_name = backend.build_wheel( tempd, metadata_directory=metadata_directory, ) except Exception: logger.error('Failed building wheel for %s', name) return None return os.path.join(tempd, wheel_name)
def _get_build_requires_editable(self) -> Iterable[str]: with self.req.build_env: runner = runner_with_spinner_message( "Getting requirements to build editable") backend = self.req.pep517_backend assert backend is not None with backend.subprocess_runner(runner): return backend.get_requires_for_build_editable()
def _setup_isolation(self, finder): def _raise_conflicts(conflicting_with, conflicting_reqs): format_string = ( "Some build dependencies for {requirement} " "conflict with {conflicting_with}: {description}." ) error_message = format_string.format( requirement=self.req, conflicting_with=conflicting_with, description=', '.join( '%s is incompatible with %s' % (installed, wanted) for installed, wanted in sorted(conflicting) ) ) raise InstallationError(error_message) # Isolate in a BuildEnvironment and install the build-time # requirements. self.req.build_env = BuildEnvironment() self.req.build_env.install_requirements( finder, self.req.pyproject_requires, 'overlay', "Installing build dependencies" ) conflicting, missing = self.req.build_env.check_requirements( self.req.requirements_to_check ) if conflicting: _raise_conflicts("PEP 517/518 supported requirements", conflicting) if missing: logger.warning( "Missing build requirements in pyproject.toml for %s.", self.req, ) logger.warning( "The project does not specify a build backend, and " "pip cannot fall back to setuptools without %s.", " and ".join(map(repr, sorted(missing))) ) # Install any extra build dependencies that the backend requests. # This must be done in a second pass, as the pyproject.toml # dependencies must be installed before we can call the backend. with self.req.build_env: runner = runner_with_spinner_message( "Getting requirements to build wheel" ) backend = self.req.pep517_backend with backend.subprocess_runner(runner): reqs = backend.get_requires_for_build_wheel() conflicting, missing = self.req.build_env.check_requirements(reqs) if conflicting: _raise_conflicts("the backend dependencies", conflicting) self.req.build_env.install_requirements( finder, missing, 'normal', "Installing backend dependencies" )
def supports_pyproject_editable(self) -> bool: if not self.use_pep517: return False assert self.pep517_backend with self.build_env: runner = runner_with_spinner_message( "Checking if build backend supports build_editable") with self.pep517_backend.subprocess_runner(runner): return "build_editable" in self.pep517_backend._supported_features( )
def prepare_pep517_metadata(self): # type: () -> str assert self.pep517_backend is not None # NOTE: This needs to be refactored to stop using atexit metadata_tmpdir = TempDirectory(kind="modern-metadata") atexit.register(metadata_tmpdir.cleanup) metadata_dir = metadata_tmpdir.path with self.build_env: # Note that Pep517HookCaller implements a fallback for # prepare_metadata_for_build_wheel, so we don't have to # consider the possibility that this hook doesn't exist. runner = runner_with_spinner_message("Preparing wheel metadata") backend = self.pep517_backend with backend.subprocess_runner(runner): distinfo_dir = backend.prepare_metadata_for_build_wheel( metadata_dir ) return os.path.join(metadata_dir, distinfo_dir)
def generate_metadata(build_env, backend): # type: (BuildEnvironment, Pep517HookCaller) -> str """Generate metadata using mechanisms described in PEP 517. Returns the generated metadata directory. """ metadata_tmpdir = TempDirectory( kind="modern-metadata", globally_managed=True ) metadata_dir = metadata_tmpdir.path with build_env: # Note that Pep517HookCaller implements a fallback for # prepare_metadata_for_build_wheel, so we don't have to # consider the possibility that this hook doesn't exist. runner = runner_with_spinner_message("Preparing wheel metadata") with backend.subprocess_runner(runner): distinfo_dir = backend.prepare_metadata_for_build_wheel( metadata_dir ) return os.path.join(metadata_dir, distinfo_dir)
def install( install_options: List[str], global_options: Sequence[str], root: Optional[str], home: Optional[str], prefix: Optional[str], use_user_site: bool, pycompile: bool, scheme: Scheme, setup_py_path: str, isolated: bool, req_name: str, build_env: BuildEnvironment, unpacked_source_directory: str, req_description: str, ) -> 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( f"Running setup.py install for {req_name}") with 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 as e: # Signal to the caller that we didn't install the new package raise LegacyInstallFailure(package_details=req_name) from e # 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() write_installed_files_from_setuptools_record(record_lines, root, req_description) return True
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 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 global_options = global_options if global_options is not None else [] if self.editable: self.install_editable( install_options, global_options, prefix=prefix, ) return if self.is_wheel: version = wheel.wheel_version(self.source_dir) wheel.check_compatibility(version, self.name) self.move_wheel_files( self.source_dir, root=root, prefix=prefix, home=home, warn_script_location=warn_script_location, use_user_site=use_user_site, pycompile=pycompile, ) self.install_succeeded = True return # 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.options.get('global_options', []) install_options = list(install_options) + \ self.options.get('install_options', []) with TempDirectory(kind="record") as temp_dir: record_filename = os.path.join(temp_dir.path, 'install-record.txt') install_args = self.get_install_args( global_options, record_filename, root, prefix, pycompile, ) runner = runner_with_spinner_message( "Running setup.py install for {}".format(self.name) ) with indent_log(), self.build_env: runner( cmd=install_args + install_options, cwd=self.unpacked_source_directory, ) if not os.path.exists(record_filename): logger.debug('Record file %s not found', record_filename) return self.install_succeeded = True 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) with open(record_filename) as f: for line in f: directory = os.path.dirname(line) if directory.endswith('.egg-info'): egg_info_dir = prepend_root(directory) break else: logger.warning( 'Could not find .egg-info directory in install record' ' for %s', self, ) # FIXME: put the record somewhere return new_lines = [] with open(record_filename) as f: for line in f: 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')