diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 16:08:06 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 16:08:06 +0000 |
commit | ba6d96469df143b52295f8e79da648bf8a597407 (patch) | |
tree | 5ea0c3374f5c53209ad02008dcdddfc8ccae92e5 /dhpython/build/plugin_pyproject.py | |
parent | Initial commit. (diff) | |
download | dh-python-upstream.tar.xz dh-python-upstream.zip |
Adding upstream version 5.20230130+deb12u1.upstream/5.20230130+deb12u1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dhpython/build/plugin_pyproject.py')
-rw-r--r-- | dhpython/build/plugin_pyproject.py | 201 |
1 files changed, 201 insertions, 0 deletions
diff --git a/dhpython/build/plugin_pyproject.py b/dhpython/build/plugin_pyproject.py new file mode 100644 index 0000000..b24aa9a --- /dev/null +++ b/dhpython/build/plugin_pyproject.py @@ -0,0 +1,201 @@ +# Copyright © 2012-2020 Piotr Ożarowski <piotr@debian.org> +# © 2020 Scott Kitterman <scott@kitterman.com> +# © 2021 Stuart Prescott <stuart@debian.org> +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +from pathlib import Path +import logging +import os.path as osp +import shutil +import sysconfig +try: + import tomli +except ModuleNotFoundError: + # Plugin still works, only needed for autodetection + pass +try: + from installer import install + from installer.destinations import SchemeDictionaryDestination + from installer.sources import WheelFile +except ModuleNotFoundError: + SchemeDictionaryDestination = WheelFile = install = None + +from dhpython.build.base import Base, shell_command + +log = logging.getLogger('dhpython') + + +class BuildSystem(Base): + DESCRIPTION = 'Generic PEP517 build system' + SUPPORTED_INTERPRETERS = {'python3', 'python{version}'} + REQUIRED_FILES = ['pyproject.toml'] + OPTIONAL_FILES = {} + CLEAN_FILES = Base.CLEAN_FILES | {'build'} + + def detect(self, context): + """Return certainty level that this plugin describes the right build + system + + This method uses cls.{REQUIRED}_FILES (pyroject.toml) only; any + other PEP517 compliant builder (such as the flit) builder should + indicate higher specificity than this plugin. + + :return: 0 <= certainty <= 100 + :rtype: int + """ + result = super().detect(context) + # Temporarily reduce the threshold while we're in beta + result -= 20 + + try: + with open('pyproject.toml', 'rb') as f: + pyproject = tomli.load(f) + if pyproject.get('build-system', {}).get('build-backend'): + result += 10 + else: + # Not a PEP517 built package + result = 0 + except NameError: + # No toml, no autdetection + result = 0 + except FileNotFoundError: + # Not a PEP517 package + result = 0 + if result > 100: + return 100 + return result + + def clean(self, context, args): + super().clean(context, args) + if osp.exists(args['interpreter'].binary()): + log.debug("removing '%s' (and everything under it)", + args['build_dir']) + osp.isdir(args['build_dir']) and shutil.rmtree(args['build_dir']) + return 0 # no need to invoke anything + + def configure(self, context, args): + if install is None: + raise Exception("PEP517 plugin dependencies are not available. " + "Please Build-Depend on pybuild-plugin-pyproject.") + # No separate configure step + return 0 + + def build(self, context, args): + self.build_step1(context, args) + self.build_step2(context, args) + + @shell_command + def build_step1(self, context, args): + """ build a wheel using the PEP517 builder defined by upstream """ + log.info('Building wheel for %s with "build" module', + args['interpreter']) + context['ENV']['FLIT_NO_NETWORK'] = '1' + context['ENV']['HOME'] = args['home_dir'] + return ('{interpreter} -m build ' + '--skip-dependency-check --no-isolation --wheel ' + '--outdir ' + args['home_dir'] + + ' {args}' + ) + + def build_step2(self, context, args): + """ unpack the wheel into pybuild's normal """ + log.info('Unpacking wheel built for %s with "installer" module', + args['interpreter']) + extras = {} + for extra in ('scripts', 'data'): + path = Path(args["home_dir"]) / extra + if osp.exists(path): + log.warning(f'{extra.title()} directory already exists, ' + 'skipping unpack. ' + 'Is the Python package being built twice?') + return + extras[extra] = path + destination = SchemeDictionaryDestination( + { + 'platlib': args['build_dir'], + 'purelib': args['build_dir'], + 'scripts': extras['scripts'], + 'data': extras['data'], + }, + interpreter=args['interpreter'].binary_dv, + script_kind='posix', + ) + + # FIXME this next step will unpack every single wheel file it finds + # which is probably ok since each wheel is built in a separate + # directory; but perhaps it should only accept the correctly named + # wheels that match the current interpreter? + # python-packaging has relevant utilities in + # - packaging/utils.py::parse_wheel_filename + # - packaging/tags.py (although it is current-interpreter-centric) + wheels = Path(args['home_dir']).glob('*.whl') + for wheel in wheels: + if wheel.name.startswith('UNKNOWN'): + raise Exception(f'UNKNOWN wheel found: {wheel.name}. Does ' + 'pyproject.toml specify a build-backend?') + with WheelFile.open(wheel) as source: + install( + source=source, + destination=destination, + additional_metadata={}, + ) + + def install(self, context, args): + log.info('Copying package built for %s to destdir', + args['interpreter']) + try: + paths = sysconfig.get_paths(scheme='deb_system') + except KeyError: + # Debian hasn't patched sysconfig schemes until 3.10 + # TODO: Introduce a version check once sysconfig is patched. + paths = sysconfig.get_paths(scheme='posix_prefix') + + # start by copying the data and scripts + for extra in ('data', 'scripts'): + src_dir = Path(args['home_dir']) / extra + if not src_dir.exists(): + continue + target_dir = args['destdir'] + paths[extra] + log.debug('Copying %s directory contents from %s -> %s', + extra, src_dir, target_dir) + shutil.copytree( + src_dir, + target_dir, + dirs_exist_ok=True, + ) + + # then copy the modules + module_dir = args['build_dir'] + target_dir = args['destdir'] + args['install_dir'] + log.debug('Copying module contents from %s -> %s', + module_dir, target_dir) + shutil.copytree( + module_dir, + target_dir, + dirs_exist_ok=True, + ) + + @shell_command + def test(self, context, args): + scripts = Path(args["home_dir"]) / 'scripts' + if scripts.exists(): + context['ENV']['PATH'] = f"{scripts}:{context['ENV']['PATH']}" + context['ENV']['HOME'] = args['home_dir'] + return super().test(context, args) |