| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308 | 
							- import threading
 
- from contextlib import contextmanager
 
- import os
 
- from os.path import dirname, abspath, join as pjoin
 
- import shutil
 
- from subprocess import check_call, check_output, STDOUT
 
- import sys
 
- from tempfile import mkdtemp
 
- from . import compat
 
- 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
 
- def tempdir():
 
-     td = mkdtemp()
 
-     try:
 
-         yield td
 
-     finally:
 
-         shutil.rmtree(td)
 
- 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):
 
-     """The default method of calling the wrapper subprocess."""
 
-     env = os.environ.copy()
 
-     if extra_environ:
 
-         env.update(extra_environ)
 
-     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.
 
-     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,
 
-             backend_path=None,
 
-             runner=None,
 
-     ):
 
-         if runner is None:
 
-             runner = default_subprocess_runner
 
-         self.source_dir = abspath(source_dir)
 
-         self.build_backend = build_backend
 
-         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
 
-     @contextmanager
 
-     def subprocess_runner(self, runner):
 
-         """A context manager for temporarily overriding the default subprocess
 
-         runner.
 
-         """
 
-         prev = self._subprocess_runner
 
-         self._subprocess_runner = runner
 
-         try:
 
-             yield
 
-         finally:
 
-             self._subprocess_runner = prev
 
-     def get_requires_for_build_wheel(self, config_settings=None):
 
-         """Identify packages required for building a wheel
 
-         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_wheel', {
 
-             'config_settings': config_settings
 
-         })
 
-     def prepare_metadata_for_build_wheel(
 
-             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_wheel', {
 
-             'metadata_directory': abspath(metadata_directory),
 
-             'config_settings': config_settings,
 
-             '_allow_fallback': _allow_fallback,
 
-         })
 
-     def build_wheel(
 
-             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_wheel' hook in the backend.
 
-         However, if that was previously called by
 
-         'prepare_metadata_for_build_wheel', 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_wheel', {
 
-             'wheel_directory': abspath(wheel_directory),
 
-             'config_settings': config_settings,
 
-             'metadata_directory': metadata_directory,
 
-         })
 
-     def get_requires_for_build_sdist(self, config_settings=None):
 
-         """Identify packages required for building a wheel
 
-         Returns a list of dependency specifications, e.g.:
 
-             ["setuptools >= 26"]
 
-         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_sdist', {
 
-             'config_settings': config_settings
 
-         })
 
-     def build_sdist(self, sdist_directory, config_settings=None):
 
-         """Build an sdist from this project.
 
-         Returns the name of the newly created file.
 
-         This calls the 'build_sdist' backend hook in a subprocess.
 
-         """
 
-         return self._call_hook('build_sdist', {
 
-             'sdist_directory': abspath(sdist_directory),
 
-             'config_settings': config_settings,
 
-         })
 
-     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
 
-         # convert here by encoding using ASCII (the backend can only contain
 
-         # 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:
 
-             hook_input = {'kwargs': kwargs}
 
-             compat.write_json(hook_input, pjoin(td, 'input.json'),
 
-                               indent=2)
 
-             # Run the hook in a subprocess
 
-             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(data.get('traceback', ''))
 
-             if data.get('no_backend'):
 
-                 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)
 
 
  |