From 0189da7b602752bf587aad63e54072da5299cd9a Mon Sep 17 00:00:00 2001 From: Nick Coghlan Date: Sun, 16 Jul 2017 15:12:00 +1000 Subject: [PATCH] PEP 517: update backend example (#310) - update hook names and signatures - clearly separate sdist building & wheel building - add support for out-of-tree wheel builds - clarify build_wheel spec based on updated example - be explicit that out-of-tree builds should match the results of building via sdist --- pep-0517.txt | 112 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 82 insertions(+), 30 deletions(-) diff --git a/pep-0517.txt b/pep-0517.txt index 1bd87a5bf..980fbbebf 100644 --- a/pep-0517.txt +++ b/pep-0517.txt @@ -190,25 +190,38 @@ metadata. The directory passed in by the build frontend MUST be identical to the directory created by ``prepare_wheel_metadata``, including any unrecognized files it created. -If build_directory is not None, it is a unicode string containing the -path to a directory where intermediate build artifacts may be stored. -This may be empty, or it may contain artifacts from a previous build to -be used as a cache. The backend is responsible for determining whether -any cached artifacts are outdated. When a build_directory is provided, -the backend should not create or modify any files in the source -directory (the working directory where the hook is called). If the -backend cannot reliably avoid modifying the directory it builds from, it -should copy any files it needs to build_directory and perform the build -there. +Backends which do not provide the ``prepare_wheel_metadata`` hook may either +silently ignore the ``metadata_directory`` parameter to ``build_wheel``, or +else raise an exception when it is set to anything other than ``None``. -If build_directory is None, the backend may do an 'in place' build which -modifies the source directory. The semantics of this are not specified -here. +If ``build_directory`` is not None, it is a unicode string containing the +path to a directory other than the source directory (the working directory where +the hook is called) where intermediate build artifacts may be stored. -Whatever the value of build_directory, the backend may also store intermediates -in other cache locations or temporary directories, which it is responsible for -managing. The presence or absence of any caches should not make a -material difference to the final result of the build. +These out-of-tree builds should have the same result as first building an sdist +and then building a wheel from that sdist. If the backend cannot otherwise +ensure that unexpected files won't end up in the built wheel archive, it should +copy any essential input files it needs to ``build_directory`` and perform the +build there. + +The provided build directory may be empty, or it may contain artifacts from a +previous build to be used as a cache. The backend is responsible for determining +whether any cached artifacts are outdated. + +If ``build_directory`` is None, the backend may do an 'in place' build which +modifies the source directory and may produce different results from those that +would be obtained by exporting an sdist first. The exact semantics of this will +depend on the build system in use, and hence are not specified here. + +To guard against frontends that are not fully compliant with this specification, +defensively coded backends may treat +``os.path.samefile(build_directory, os.getcwd())`` as a request for an in place +build. + +Whatever the value of ``build_directory``, the backend may also store +intermediate artifacts in other cache locations or temporary directories, which +it is responsible for managing. The presence or absence of any caches should not +make a material difference to the final result of the build. build_sdist ----------- @@ -618,16 +631,15 @@ build backend:: # mypackage_custom_build_backend.py import os.path import pathlib + import shutil - def get_build_requires(config_settings, config_directory): - return ["wheel"] + SDIST_NAME = "mypackage-0.1" + SDIST_FILENAME = SDIST_NAME + ".tar.gz" + WHEEL_FILENAME = "mypackage-0.1-py2.py3-none-any.whl" - def build_wheel(wheel_directory, config_settings, config_directory=None): - from wheel.archive import archive_wheelfile - filename = "mypackage-0.1-py2.py3-none-any" - path = os.path.join(wheel_directory, filename) - archive_wheelfile(path, "src/") - return filename + ################# + # sdist creation + ################# def _exclude_hidden_and_special_files(archive_entry): """Tarfile filter to exclude hidden and special files from the archive""" @@ -636,14 +648,54 @@ build backend:: return archive_entry return None - def build_sdist(sdist_dir, config_settings): - sdist_subdir = "mypackage-0.1" - sdist_path = pathlib.Path(sdist_dir) / (sdist_subdir + ".tar.gz") + def _make_sdist(sdist_dir): + """Make an sdist and return both the Python object and its filename""" + sdist_path = pathlib.Path(sdist_dir) / SDIST_FILENAME sdist = tarfile.open(sdist_path, "w:gz", format=tarfile.PAX_FORMAT) # Tar up the whole directory, minus hidden and special files - sdist.add(os.getcwd(), arcname=sdist_subdir, + sdist.add(os.getcwd(), arcname=SDIST_NAME, filter=_exclude_hidden_and_special_files) - return sdist_subdir + ".tar.gz" + return sdist, SDIST_FILENAME + + def build_sdist(sdist_dir, config_settings): + """PEP 517 sdist creation hook""" + sdist, sdist_filename = _make_sdist(sdist_dir) + return sdist_filename + + ################# + # wheel creation + ################# + + def get_requires_for_build_wheel(config_settings): + """PEP 517 wheel building dependency definition hook""" + # As a simple static requirement, this could also just be + # listed in the project's build system dependencies instead + return ["wheel"] + + def _prepare_out_of_tree_build(build_directory): + """Prepare out-of-tree build by way of unpacking the sdist""" + sdist, sdist_filename = _make_sdist(build_directory) + os.chdir(build_directory) + if os.path.exists(SDIST_NAME): + # Prevent caching of stale input files + shutil.rmtree(SDIST_NAME) + sdist.extractall() + os.remove(sdist_filename) + os.chdir(SDIST_NAME) + + def build_wheel(wheel_directory, build_directory=None, + metadata_directory=None, config_settings=None): + """PEP 517 wheel creation hook""" + # First check if the frontend has requested an out-of-tree build + out_of_tree_build = (build_directory is not None and + not os.path.samefile(build_directory, os.getcwd()) + if out_of_tree_build: + _prepare_out_of_tree_build(build_directory) + # Continue with assembling the wheel archive + from wheel.archive import archive_wheelfile + path = os.path.join(wheel_directory, WHEEL_FILENAME) + archive_wheelfile(path, "src/") + return WHEEL_FILENAME Of course, this is a *terrible* build backend: it requires the user to have manually set up the wheel metadata in