diff --git a/pep517/in_process/_in_process.py b/pep517/in_process/_in_process.py index a536b03..bc74724 100644 --- a/pep517/in_process/_in_process.py +++ b/pep517/in_process/_in_process.py @@ -149,14 +149,19 @@ def _dist_info_files(whl_zip): def _get_wheel_metadata_from_wheel( - backend, metadata_directory, config_settings): + backend, metadata_directory, config_settings, editable=False): """Build a wheel and extract the metadata from it. Fallback for when the build backend does not - define the 'get_wheel_metadata' hook. + define the 'prepare_metadata' hook. """ from zipfile import ZipFile - whl_basename = backend.build_wheel(metadata_directory, config_settings) + if editable: + whl_basename = backend.build_editable( + metadata_directory, config_settings) + else: + whl_basename = backend.build_wheel( + metadata_directory, config_settings) with open(os.path.join(metadata_directory, WHEEL_BUILT_MARKER), 'wb'): pass # Touch marker file @@ -167,7 +172,7 @@ def _get_wheel_metadata_from_wheel( return dist_info[0].split('/')[0] -def _find_already_built_wheel(metadata_directory): +def _find_already_built_wheel(metadata_directory, editable=False): """Check for a wheel already built during the get_wheel_metadata hook. """ if not metadata_directory: @@ -181,8 +186,9 @@ def _find_already_built_wheel(metadata_directory): print('Found wheel built marker, but no .whl files') return None if len(whl_files) > 1: + hook_name = 'build_editable' if editable else 'build_wheel' print('Found multiple .whl files; unspecified behaviour. ' - 'Will call build_wheel.') + 'Will call {}.'.format(hook_name)) return None # Exactly one .whl file @@ -206,7 +212,7 @@ def build_wheel(wheel_directory, config_settings, metadata_directory=None): def get_requires_for_build_sdist(config_settings): - """Invoke the optional get_requires_for_build_wheel hook + """Invoke the optional get_requires_for_build_sdist hook Returns [] if the hook is not defined. """ @@ -219,6 +225,57 @@ def get_requires_for_build_sdist(config_settings): return hook(config_settings) +def prepare_metadata_for_build_editable( + metadata_directory, config_settings, _allow_fallback): + """Invoke optional prepare_metadata_for_build_editable + + Implements a fallback by building a wheel for editable if the hook + isn't defined, unless _allow_fallback is False in which case + HookMissing is raised. + """ + backend = _build_backend() + try: + hook = backend.prepare_metadata_for_build_editable + except AttributeError: + if not _allow_fallback: + raise HookMissing() + return _get_wheel_metadata_from_wheel(backend, metadata_directory, + config_settings, editable=True) + else: + return hook(metadata_directory, config_settings) + + +def build_editable(wheel_directory, config_settings, metadata_directory=None): + """Invoke the optional build_editable hook. + """ + prebuilt_whl = _find_already_built_wheel(metadata_directory, editable=True) + if prebuilt_whl: + shutil.copy2(prebuilt_whl, wheel_directory) + return os.path.basename(prebuilt_whl) + + backend = _build_backend() + try: + hook = backend.build_wheel_for_editable + except AttributeError: + raise HookMissing() + else: + return hook(wheel_directory, config_settings, metadata_directory) + + +def get_requires_for_build_editable(config_settings): + """Invoke the optional get_requires_for_build_editable hook + + Returns [] if the hook is not defined. + """ + backend = _build_backend() + try: + hook = backend.get_requires_for_build_editable + except AttributeError: + return [] + else: + return hook(config_settings) + + class _DummyException(Exception): """Nothing should ever raise this exception""" @@ -244,6 +301,9 @@ def build_sdist(sdist_directory, config_settings): 'build_wheel', 'get_requires_for_build_sdist', 'build_sdist', + 'get_requires_for_build_editable', + 'prepare_metadata_for_build_editable', + 'build_editable', } diff --git a/pep517/wrappers.py b/pep517/wrappers.py index 00974aa..6ce0208 100644 --- a/pep517/wrappers.py +++ b/pep517/wrappers.py @@ -234,6 +234,59 @@ def build_sdist(self, sdist_directory, config_settings=None): 'config_settings': config_settings, }) + def get_requires_for_build_editable(self, config_settings=None): + """Identify packages required for building a editable + + Returns a list of dependency specifications, e.g.:: + + ["wheel >= 0.25", "setuptools"] + + This does not include requirements specified in pyproject.toml. + It returns the result of calling the equivalently named hook in a + subprocess. + """ + return self._call_hook('get_requires_for_build_editable', { + 'config_settings': config_settings + }) + + def prepare_metadata_for_build_editable( + self, metadata_directory, config_settings=None, + _allow_fallback=True): + """Prepare a ``*.dist-info`` folder with metadata for this project. + + Returns the name of the newly created folder. + + If the build backend defines a hook with this name, it will be called + in a subprocess. If not, the backend will be asked to build a wheel, + and the dist-info extracted from that (unless _allow_fallback is + False). + """ + return self._call_hook('prepare_metadata_for_build_editable', { + 'metadata_directory': abspath(metadata_directory), + 'config_settings': config_settings, + '_allow_fallback': _allow_fallback, + }) + + def build_editable( + self, wheel_directory, config_settings=None, + metadata_directory=None): + """Build a wheel from this project. + + Returns the name of the newly created file. + + In general, this will call the 'build_editable' hook in the backend. + However, if that was previously called by + 'prepare_metadata_for_build_editable', and the same metadata_directory is + used, the previously built wheel will be copied to wheel_directory. + """ + if metadata_directory is not None: + metadata_directory = abspath(metadata_directory) + return self._call_hook('build_editable', { + 'wheel_directory': abspath(wheel_directory), + 'config_settings': config_settings, + 'metadata_directory': metadata_directory, + }) + def _call_hook(self, hook_name, kwargs): # On Python 2, pytoml returns Unicode values (which is correct) but the # environment passed to check_call needs to contain string values. We