Eliminado venv y www del repositorio, agrege un requirements igual

This commit is contained in:
2020-11-22 21:14:46 -03:00
parent 18cf2d335a
commit 199a1e2a61
820 changed files with 15495 additions and 22017 deletions

View File

@@ -1,4 +1,4 @@
"""Wrappers to build Python packages using PEP 517 hooks
"""
__version__ = '0.5.0'
__version__ = '0.8.2'

View File

@@ -2,7 +2,9 @@
It expects:
- Command line args: hook_name, control_dir
- Environment variable: PEP517_BUILD_BACKEND=entry.point:spec
- Environment variables:
PEP517_BUILD_BACKEND=entry.point:spec
PEP517_BACKEND_PATH=paths (separated with os.pathsep)
- control_dir/input.json:
- {"kwargs": {...}}
@@ -12,28 +14,86 @@ Results:
"""
from glob import glob
from importlib import import_module
import json
import os
import os.path
from os.path import join as pjoin
import re
import shutil
import sys
import traceback
# This is run as a script, not a module, so it can't do a relative import
import compat
# This file is run as a script, and `import compat` is not zip-safe, so we
# include write_json() and read_json() from compat.py.
#
# Handle reading and writing JSON in UTF-8, on Python 3 and 2.
if sys.version_info[0] >= 3:
# Python 3
def write_json(obj, path, **kwargs):
with open(path, 'w', encoding='utf-8') as f:
json.dump(obj, f, **kwargs)
def read_json(path):
with open(path, 'r', encoding='utf-8') as f:
return json.load(f)
else:
# Python 2
def write_json(obj, path, **kwargs):
with open(path, 'wb') as f:
json.dump(obj, f, encoding='utf-8', **kwargs)
def read_json(path):
with open(path, 'rb') as f:
return json.load(f)
class BackendUnavailable(Exception):
"""Raised if we cannot import the backend"""
def __init__(self, traceback):
self.traceback = traceback
class BackendInvalid(Exception):
"""Raised if the backend is invalid"""
def __init__(self, message):
self.message = message
class HookMissing(Exception):
"""Raised if a hook is missing and we are not executing the fallback"""
def contained_in(filename, directory):
"""Test if a file is located within the given directory."""
filename = os.path.normcase(os.path.abspath(filename))
directory = os.path.normcase(os.path.abspath(directory))
return os.path.commonprefix([filename, directory]) == directory
def _build_backend():
"""Find and load the build backend"""
# Add in-tree backend directories to the front of sys.path.
backend_path = os.environ.get('PEP517_BACKEND_PATH')
if backend_path:
extra_pathitems = backend_path.split(os.pathsep)
sys.path[:0] = extra_pathitems
ep = os.environ['PEP517_BUILD_BACKEND']
mod_path, _, obj_path = ep.partition(':')
try:
obj = import_module(mod_path)
except ImportError:
raise BackendUnavailable
raise BackendUnavailable(traceback.format_exc())
if backend_path:
if not any(
contained_in(obj.__file__, path)
for path in extra_pathitems
):
raise BackendInvalid("Backend was not loaded from backend-path")
if obj_path:
for path_part in obj_path.split('.'):
obj = getattr(obj, path_part)
@@ -54,15 +114,19 @@ def get_requires_for_build_wheel(config_settings):
return hook(config_settings)
def prepare_metadata_for_build_wheel(metadata_directory, config_settings):
def prepare_metadata_for_build_wheel(
metadata_directory, config_settings, _allow_fallback):
"""Invoke optional prepare_metadata_for_build_wheel
Implements a fallback by building a wheel if the hook isn't defined.
Implements a fallback by building a wheel 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_wheel
except AttributeError:
if not _allow_fallback:
raise HookMissing()
return _get_wheel_metadata_from_wheel(backend, metadata_directory,
config_settings)
else:
@@ -161,6 +225,8 @@ class _DummyException(Exception):
class GotUnsupportedOperation(Exception):
"""For internal use when backend raises UnsupportedOperation"""
def __init__(self, traceback):
self.traceback = traceback
def build_sdist(sdist_directory, config_settings):
@@ -169,7 +235,7 @@ def build_sdist(sdist_directory, config_settings):
try:
return backend.build_sdist(sdist_directory, config_settings)
except getattr(backend, 'UnsupportedOperation', _DummyException):
raise GotUnsupportedOperation
raise GotUnsupportedOperation(traceback.format_exc())
HOOK_NAMES = {
@@ -190,17 +256,24 @@ def main():
sys.exit("Unknown hook: %s" % hook_name)
hook = globals()[hook_name]
hook_input = compat.read_json(pjoin(control_dir, 'input.json'))
hook_input = read_json(pjoin(control_dir, 'input.json'))
json_out = {'unsupported': False, 'return_val': None}
try:
json_out['return_val'] = hook(**hook_input['kwargs'])
except BackendUnavailable:
except BackendUnavailable as e:
json_out['no_backend'] = True
except GotUnsupportedOperation:
json_out['traceback'] = e.traceback
except BackendInvalid as e:
json_out['backend_invalid'] = True
json_out['backend_error'] = e.message
except GotUnsupportedOperation as e:
json_out['unsupported'] = True
json_out['traceback'] = e.traceback
except HookMissing:
json_out['hook_missing'] = True
compat.write_json(json_out, pjoin(control_dir, 'output.json'), indent=2)
write_json(json_out, pjoin(control_dir, 'output.json'), indent=2)
if __name__ == '__main__':

View File

@@ -3,25 +3,56 @@
import argparse
import logging
import os
import contextlib
from pip._vendor import pytoml
from pip._vendor import toml
import shutil
import errno
import tempfile
from .envbuild import BuildEnvironment
from .wrappers import Pep517HookCaller
from .dirtools import tempdir, mkdir_p
from .compat import FileNotFoundError
log = logging.getLogger(__name__)
@contextlib.contextmanager
def tempdir():
td = tempfile.mkdtemp()
def validate_system(system):
"""
Ensure build system has the requisite fields.
"""
required = {'requires', 'build-backend'}
if not (required <= set(system)):
message = "Missing required fields: {missing}".format(
missing=required-set(system),
)
raise ValueError(message)
def load_system(source_dir):
"""
Load the build system from a source dir (pyproject.toml).
"""
pyproject = os.path.join(source_dir, 'pyproject.toml')
with open(pyproject) as f:
pyproject_data = toml.load(f)
return pyproject_data['build-system']
def compat_system(source_dir):
"""
Given a source dir, attempt to get a build system backend
and requirements from pyproject.toml. Fallback to
setuptools but only if the file was not found or a build
system was not indicated.
"""
try:
yield td
finally:
shutil.rmtree(td)
system = load_system(source_dir)
except (FileNotFoundError, KeyError):
system = {}
system.setdefault(
'build-backend',
'setuptools.build_meta:__legacy__',
)
system.setdefault('requires', ['setuptools', 'wheel'])
return system
def _do_build(hooks, env, dist, dest):
@@ -42,33 +73,18 @@ def _do_build(hooks, env, dist, dest):
shutil.move(source, os.path.join(dest, os.path.basename(filename)))
def mkdir_p(*args, **kwargs):
"""Like `mkdir`, but does not raise an exception if the
directory already exists.
"""
try:
return os.mkdir(*args, **kwargs)
except OSError as exc:
if exc.errno != errno.EEXIST:
raise
def build(source_dir, dist, dest=None):
pyproject = os.path.join(source_dir, 'pyproject.toml')
def build(source_dir, dist, dest=None, system=None):
system = system or load_system(source_dir)
dest = os.path.join(source_dir, dest or 'dist')
mkdir_p(dest)
with open(pyproject) as f:
pyproject_data = pytoml.load(f)
# Ensure the mandatory data can be loaded
buildsys = pyproject_data['build-system']
requires = buildsys['requires']
backend = buildsys['build-backend']
hooks = Pep517HookCaller(source_dir, backend)
validate_system(system)
hooks = Pep517HookCaller(
source_dir, system['build-backend'], system.get('backend-path')
)
with BuildEnvironment() as env:
env.pip_install(requires)
env.pip_install(system['requires'])
_do_build(hooks, env, dist, dest)

View File

@@ -4,7 +4,7 @@ import argparse
import logging
import os
from os.path import isfile, join as pjoin
from pip._vendor.pytoml import TomlError, load as toml_load
from pip._vendor.toml import TomlDecodeError, load as toml_load
import shutil
from subprocess import CalledProcessError
import sys
@@ -147,12 +147,13 @@ def check(source_dir):
buildsys = pyproject_data['build-system']
requires = buildsys['requires']
backend = buildsys['build-backend']
backend_path = buildsys.get('backend-path')
log.info('Loaded pyproject.toml')
except (TomlError, KeyError):
except (TomlDecodeError, KeyError):
log.error("Invalid pyproject.toml", exc_info=True)
return False
hooks = Pep517HookCaller(source_dir, backend)
hooks = Pep517HookCaller(source_dir, backend, backend_path)
sdist_ok = check_build_sdist(hooks, requires)
wheel_ok = check_build_wheel(hooks, requires)

View File

@@ -1,7 +1,10 @@
"""Handle reading and writing JSON in UTF-8, on Python 3 and 2."""
"""Python 2/3 compatibility"""
import json
import sys
# Handle reading and writing JSON in UTF-8, on Python 3 and 2.
if sys.version_info[0] >= 3:
# Python 3
def write_json(obj, path, **kwargs):
@@ -21,3 +24,11 @@ else:
def read_json(path):
with open(path, 'rb') as f:
return json.load(f)
# FileNotFoundError
try:
FileNotFoundError = FileNotFoundError
except NameError:
FileNotFoundError = IOError

View File

@@ -3,23 +3,27 @@
import os
import logging
from pip._vendor import pytoml
from pip._vendor import toml
import shutil
from subprocess import check_call
import sys
from sysconfig import get_paths
from tempfile import mkdtemp
from .wrappers import Pep517HookCaller
from .wrappers import Pep517HookCaller, LoggerWrapper
log = logging.getLogger(__name__)
def _load_pyproject(source_dir):
with open(os.path.join(source_dir, 'pyproject.toml')) as f:
pyproject_data = pytoml.load(f)
pyproject_data = toml.load(f)
buildsys = pyproject_data['build-system']
return buildsys['requires'], buildsys['build-backend']
return (
buildsys['requires'],
buildsys['build-backend'],
buildsys.get('backend-path'),
)
class BuildEnvironment(object):
@@ -90,9 +94,14 @@ class BuildEnvironment(object):
if not reqs:
return
log.info('Calling pip to install %s', reqs)
check_call([
cmd = [
sys.executable, '-m', 'pip', 'install', '--ignore-installed',
'--prefix', self.path] + list(reqs))
'--prefix', self.path] + list(reqs)
check_call(
cmd,
stdout=LoggerWrapper(log, logging.INFO),
stderr=LoggerWrapper(log, logging.ERROR),
)
def __exit__(self, exc_type, exc_val, exc_tb):
needs_cleanup = (
@@ -126,8 +135,8 @@ def build_wheel(source_dir, wheel_dir, config_settings=None):
"""
if config_settings is None:
config_settings = {}
requires, backend = _load_pyproject(source_dir)
hooks = Pep517HookCaller(source_dir, backend)
requires, backend, backend_path = _load_pyproject(source_dir)
hooks = Pep517HookCaller(source_dir, backend, backend_path)
with BuildEnvironment() as env:
env.pip_install(requires)
@@ -148,8 +157,8 @@ def build_sdist(source_dir, sdist_dir, config_settings=None):
"""
if config_settings is None:
config_settings = {}
requires, backend = _load_pyproject(source_dir)
hooks = Pep517HookCaller(source_dir, backend)
requires, backend, backend_path = _load_pyproject(source_dir)
hooks = Pep517HookCaller(source_dir, backend, backend_path)
with BuildEnvironment() as env:
env.pip_install(requires)

View File

@@ -1,14 +1,24 @@
import threading
from contextlib import contextmanager
import os
from os.path import dirname, abspath, join as pjoin
import shutil
from subprocess import check_call
from subprocess import check_call, check_output, STDOUT
import sys
from tempfile import mkdtemp
from . import compat
_in_proc_script = pjoin(dirname(abspath(__file__)), '_in_process.py')
try:
import importlib.resources as resources
def _in_proc_script_path():
return resources.path(__package__, '_in_process.py')
except ImportError:
@contextmanager
def _in_proc_script_path():
yield pjoin(dirname(abspath(__file__)), '_in_process.py')
@contextmanager
@@ -22,10 +32,29 @@ def tempdir():
class BackendUnavailable(Exception):
"""Will be raised if the backend cannot be imported in the hook process."""
def __init__(self, traceback):
self.traceback = traceback
class BackendInvalid(Exception):
"""Will be raised if the backend is invalid."""
def __init__(self, backend_name, backend_path, message):
self.backend_name = backend_name
self.backend_path = backend_path
self.message = message
class HookMissing(Exception):
"""Will be raised on missing hooks."""
def __init__(self, hook_name):
super(HookMissing, self).__init__(hook_name)
self.hook_name = hook_name
class UnsupportedOperation(Exception):
"""May be raised by build_sdist if the backend indicates that it can't."""
def __init__(self, traceback):
self.traceback = traceback
def default_subprocess_runner(cmd, cwd=None, extra_environ=None):
@@ -37,25 +66,86 @@ def default_subprocess_runner(cmd, cwd=None, extra_environ=None):
check_call(cmd, cwd=cwd, env=env)
def quiet_subprocess_runner(cmd, cwd=None, extra_environ=None):
"""A method of calling the wrapper subprocess while suppressing output."""
env = os.environ.copy()
if extra_environ:
env.update(extra_environ)
check_output(cmd, cwd=cwd, env=env, stderr=STDOUT)
def norm_and_check(source_tree, requested):
"""Normalise and check a backend path.
Ensure that the requested backend path is specified as a relative path,
and resolves to a location under the given source tree.
Return an absolute version of the requested path.
"""
if os.path.isabs(requested):
raise ValueError("paths must be relative")
abs_source = os.path.abspath(source_tree)
abs_requested = os.path.normpath(os.path.join(abs_source, requested))
# We have to use commonprefix for Python 2.7 compatibility. So we
# normalise case to avoid problems because commonprefix is a character
# based comparison :-(
norm_source = os.path.normcase(abs_source)
norm_requested = os.path.normcase(abs_requested)
if os.path.commonprefix([norm_source, norm_requested]) != norm_source:
raise ValueError("paths must be inside source tree")
return abs_requested
class Pep517HookCaller(object):
"""A wrapper around a source directory to be built with a PEP 517 backend.
source_dir : The path to the source directory, containing pyproject.toml.
backend : The build backend spec, as per PEP 517, from pyproject.toml.
build_backend : The build backend spec, as per PEP 517, from
pyproject.toml.
backend_path : The backend path, as per PEP 517, from pyproject.toml.
runner : A callable that invokes the wrapper subprocess.
The 'runner', if provided, must expect the following:
cmd : a list of strings representing the command and arguments to
execute, as would be passed to e.g. 'subprocess.check_call'.
cwd : a string representing the working directory that must be
used for the subprocess. Corresponds to the provided source_dir.
extra_environ : a dict mapping environment variable names to values
which must be set for the subprocess execution.
"""
def __init__(self, source_dir, build_backend):
def __init__(
self,
source_dir,
build_backend,
backend_path=None,
runner=None,
):
if runner is None:
runner = default_subprocess_runner
self.source_dir = abspath(source_dir)
self.build_backend = build_backend
self._subprocess_runner = default_subprocess_runner
if backend_path:
backend_path = [
norm_and_check(self.source_dir, p) for p in backend_path
]
self.backend_path = backend_path
self._subprocess_runner = runner
# TODO: Is this over-engineered? Maybe frontends only need to
# set this when creating the wrapper, not on every call.
@contextmanager
def subprocess_runner(self, runner):
"""A context manager for temporarily overriding the default subprocess
runner.
"""
prev = self._subprocess_runner
self._subprocess_runner = runner
yield
self._subprocess_runner = prev
try:
yield
finally:
self._subprocess_runner = prev
def get_requires_for_build_wheel(self, config_settings=None):
"""Identify packages required for building a wheel
@@ -72,18 +162,21 @@ class Pep517HookCaller(object):
})
def prepare_metadata_for_build_wheel(
self, metadata_directory, config_settings=None):
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.
and the dist-info extracted from that (unless _allow_fallback is
False).
"""
return self._call_hook('prepare_metadata_for_build_wheel', {
'metadata_directory': abspath(metadata_directory),
'config_settings': config_settings,
'_allow_fallback': _allow_fallback,
})
def build_wheel(
@@ -139,25 +232,77 @@ class Pep517HookCaller(object):
# letters, digits and _, . and : characters, and will be used as a
# Python identifier, so non-ASCII content is wrong on Python 2 in
# any case).
# For backend_path, we use sys.getfilesystemencoding.
if sys.version_info[0] == 2:
build_backend = self.build_backend.encode('ASCII')
else:
build_backend = self.build_backend
extra_environ = {'PEP517_BUILD_BACKEND': build_backend}
if self.backend_path:
backend_path = os.pathsep.join(self.backend_path)
if sys.version_info[0] == 2:
backend_path = backend_path.encode(sys.getfilesystemencoding())
extra_environ['PEP517_BACKEND_PATH'] = backend_path
with tempdir() as td:
compat.write_json({'kwargs': kwargs}, pjoin(td, 'input.json'),
hook_input = {'kwargs': kwargs}
compat.write_json(hook_input, pjoin(td, 'input.json'),
indent=2)
# Run the hook in a subprocess
self._subprocess_runner(
[sys.executable, _in_proc_script, hook_name, td],
cwd=self.source_dir,
extra_environ={'PEP517_BUILD_BACKEND': build_backend}
)
with _in_proc_script_path() as script:
self._subprocess_runner(
[sys.executable, str(script), hook_name, td],
cwd=self.source_dir,
extra_environ=extra_environ
)
data = compat.read_json(pjoin(td, 'output.json'))
if data.get('unsupported'):
raise UnsupportedOperation
raise UnsupportedOperation(data.get('traceback', ''))
if data.get('no_backend'):
raise BackendUnavailable
raise BackendUnavailable(data.get('traceback', ''))
if data.get('backend_invalid'):
raise BackendInvalid(
backend_name=self.build_backend,
backend_path=self.backend_path,
message=data.get('backend_error', '')
)
if data.get('hook_missing'):
raise HookMissing(hook_name)
return data['return_val']
class LoggerWrapper(threading.Thread):
"""
Read messages from a pipe and redirect them
to a logger (see python's logging module).
"""
def __init__(self, logger, level):
threading.Thread.__init__(self)
self.daemon = True
self.logger = logger
self.level = level
# create the pipe and reader
self.fd_read, self.fd_write = os.pipe()
self.reader = os.fdopen(self.fd_read)
self.start()
def fileno(self):
return self.fd_write
@staticmethod
def remove_newline(msg):
return msg[:-1] if msg.endswith(os.linesep) else msg
def run(self):
for line in self.reader:
self._write(self.remove_newline(line))
def _write(self, message):
self.logger.log(self.level, message)