|
"""Check a project and backend by attempting to build using PEP 517 hooks. |
|
""" |
|
import argparse |
|
import logging |
|
import os |
|
import shutil |
|
import sys |
|
import tarfile |
|
import zipfile |
|
from os.path import isfile |
|
from os.path import join as pjoin |
|
from subprocess import CalledProcessError |
|
from tempfile import mkdtemp |
|
|
|
from ._compat import tomllib |
|
from .colorlog import enable_colourful_output |
|
from .envbuild import BuildEnvironment |
|
from .wrappers import Pep517HookCaller |
|
|
|
log = logging.getLogger(__name__) |
|
|
|
|
|
def check_build_sdist(hooks, build_sys_requires): |
|
with BuildEnvironment() as env: |
|
try: |
|
env.pip_install(build_sys_requires) |
|
log.info('Installed static build dependencies') |
|
except CalledProcessError: |
|
log.error('Failed to install static build dependencies') |
|
return False |
|
|
|
try: |
|
reqs = hooks.get_requires_for_build_sdist({}) |
|
log.info('Got build requires: %s', reqs) |
|
except Exception: |
|
log.error('Failure in get_requires_for_build_sdist', exc_info=True) |
|
return False |
|
|
|
try: |
|
env.pip_install(reqs) |
|
log.info('Installed dynamic build dependencies') |
|
except CalledProcessError: |
|
log.error('Failed to install dynamic build dependencies') |
|
return False |
|
|
|
td = mkdtemp() |
|
log.info('Trying to build sdist in %s', td) |
|
try: |
|
try: |
|
filename = hooks.build_sdist(td, {}) |
|
log.info('build_sdist returned %r', filename) |
|
except Exception: |
|
log.info('Failure in build_sdist', exc_info=True) |
|
return False |
|
|
|
if not filename.endswith('.tar.gz'): |
|
log.error( |
|
"Filename %s doesn't have .tar.gz extension", filename) |
|
return False |
|
|
|
path = pjoin(td, filename) |
|
if isfile(path): |
|
log.info("Output file %s exists", path) |
|
else: |
|
log.error("Output file %s does not exist", path) |
|
return False |
|
|
|
if tarfile.is_tarfile(path): |
|
log.info("Output file is a tar file") |
|
else: |
|
log.error("Output file is not a tar file") |
|
return False |
|
|
|
finally: |
|
shutil.rmtree(td) |
|
|
|
return True |
|
|
|
|
|
def check_build_wheel(hooks, build_sys_requires): |
|
with BuildEnvironment() as env: |
|
try: |
|
env.pip_install(build_sys_requires) |
|
log.info('Installed static build dependencies') |
|
except CalledProcessError: |
|
log.error('Failed to install static build dependencies') |
|
return False |
|
|
|
try: |
|
reqs = hooks.get_requires_for_build_wheel({}) |
|
log.info('Got build requires: %s', reqs) |
|
except Exception: |
|
log.error('Failure in get_requires_for_build_sdist', exc_info=True) |
|
return False |
|
|
|
try: |
|
env.pip_install(reqs) |
|
log.info('Installed dynamic build dependencies') |
|
except CalledProcessError: |
|
log.error('Failed to install dynamic build dependencies') |
|
return False |
|
|
|
td = mkdtemp() |
|
log.info('Trying to build wheel in %s', td) |
|
try: |
|
try: |
|
filename = hooks.build_wheel(td, {}) |
|
log.info('build_wheel returned %r', filename) |
|
except Exception: |
|
log.info('Failure in build_wheel', exc_info=True) |
|
return False |
|
|
|
if not filename.endswith('.whl'): |
|
log.error("Filename %s doesn't have .whl extension", filename) |
|
return False |
|
|
|
path = pjoin(td, filename) |
|
if isfile(path): |
|
log.info("Output file %s exists", path) |
|
else: |
|
log.error("Output file %s does not exist", path) |
|
return False |
|
|
|
if zipfile.is_zipfile(path): |
|
log.info("Output file is a zip file") |
|
else: |
|
log.error("Output file is not a zip file") |
|
return False |
|
|
|
finally: |
|
shutil.rmtree(td) |
|
|
|
return True |
|
|
|
|
|
def check(source_dir): |
|
pyproject = pjoin(source_dir, 'pyproject.toml') |
|
if isfile(pyproject): |
|
log.info('Found pyproject.toml') |
|
else: |
|
log.error('Missing pyproject.toml') |
|
return False |
|
|
|
try: |
|
with open(pyproject, 'rb') as f: |
|
pyproject_data = tomllib.load(f) |
|
|
|
buildsys = pyproject_data['build-system'] |
|
requires = buildsys['requires'] |
|
backend = buildsys['build-backend'] |
|
backend_path = buildsys.get('backend-path') |
|
log.info('Loaded pyproject.toml') |
|
except (tomllib.TOMLDecodeError, KeyError): |
|
log.error("Invalid pyproject.toml", exc_info=True) |
|
return False |
|
|
|
hooks = Pep517HookCaller(source_dir, backend, backend_path) |
|
|
|
sdist_ok = check_build_sdist(hooks, requires) |
|
wheel_ok = check_build_wheel(hooks, requires) |
|
|
|
if not sdist_ok: |
|
log.warning('Sdist checks failed; scroll up to see') |
|
if not wheel_ok: |
|
log.warning('Wheel checks failed') |
|
|
|
return sdist_ok |
|
|
|
|
|
def main(argv=None): |
|
log.warning('pep517.check is deprecated. ' |
|
'Consider switching to https://pypi.org/project/build/') |
|
|
|
ap = argparse.ArgumentParser() |
|
ap.add_argument( |
|
'source_dir', |
|
help="A directory containing pyproject.toml") |
|
args = ap.parse_args(argv) |
|
|
|
enable_colourful_output() |
|
|
|
ok = check(args.source_dir) |
|
|
|
if ok: |
|
print(ansi('Checks passed', 'green')) |
|
else: |
|
print(ansi('Checks failed', 'red')) |
|
sys.exit(1) |
|
|
|
|
|
ansi_codes = { |
|
'reset': '\x1b[0m', |
|
'bold': '\x1b[1m', |
|
'red': '\x1b[31m', |
|
'green': '\x1b[32m', |
|
} |
|
|
|
|
|
def ansi(s, attr): |
|
if os.name != 'nt' and sys.stdout.isatty(): |
|
return ansi_codes[attr] + str(s) + ansi_codes['reset'] |
|
else: |
|
return str(s) |
|
|
|
|
|
if __name__ == '__main__': |
|
main() |
|
|