| # Copyright 2015 Pants project contributors (see CONTRIBUTORS.md). |
| # Licensed under the Apache License, Version 2.0 (see LICENSE). |
| |
| from __future__ import absolute_import |
| |
| from .compatibility import to_bytes |
| from .executor import Executor |
| from .util import named_temporary_file |
| |
| |
| _COMPILER_MAIN = """ |
| from __future__ import print_function |
| |
| import os |
| import py_compile |
| import sys |
| |
| |
| def compile(root, relpaths): |
| compiled = [] |
| errored = {} |
| for relpath in relpaths: |
| abspath = os.path.join(root, relpath) |
| # NB: We give the compiled bytecode file a `.pyc` extension, but if PYTHONOPTIMIZE is in play |
| # the generated bytecode will be optimized. Traditionally these optimized bytecode files would |
| # have a `.pyo` extension, but the extension only matters for location of the file to execute |
| # for a given module and not on the interpretation of its bytecode contents. As such we're |
| # safe to pick the `.pyc` extension for all bytecode file cases without a need to interpret the |
| # current optimization setting for the active python interpreter. |
| pyc_relpath = relpath + 'c' |
| pyc_abspath = os.path.join(root, pyc_relpath) |
| try: |
| py_compile.compile(abspath, cfile=pyc_abspath, dfile=relpath, doraise=True) |
| compiled.append(pyc_relpath) |
| except py_compile.PyCompileError as e: |
| errored[e.file] = e.msg |
| return compiled, errored |
| |
| |
| def main(root, relpaths): |
| compiled, errored = compile(root, relpaths) |
| if not errored: |
| for path in compiled: |
| print(path) |
| sys.exit(0) |
| |
| print('Encountered %%d errors compiling %%d files:' %% (len(errored), len(relpaths)), |
| file=sys.stderr) |
| for file, msg in errored.items(): |
| print(' %%s: %%s' %% (file, msg), file=sys.stderr) |
| sys.exit(1) |
| |
| root = %(root)r |
| relpaths = %(relpaths)r |
| |
| main(root, relpaths) |
| """ |
| |
| |
| class Compiler(object): |
| class Error(Exception): pass |
| class CompilationFailure(Error): # N.B. This subclasses `Error` only for backwards compatibility. |
| """Indicates an error compiling one or more python source files.""" |
| |
| def __init__(self, interpreter): |
| """Creates a bytecode compiler for the given `interpreter`. |
| |
| :param interpreter: The interpreter to use to compile sources with. |
| :type interpreter: :class:`pex.interpreter.PythonInterpreter` |
| """ |
| self._interpreter = interpreter |
| |
| def compile(self, root, relpaths): |
| """Compiles the given python source files using this compiler's interpreter. |
| |
| :param string root: The root path all the source files are found under. |
| :param list relpaths: The realtive paths from the `root` of the source files to compile. |
| :returns: A list of relative paths of the compiled bytecode files. |
| :raises: A :class:`Compiler.Error` if there was a problem bytecode compiling any of the files. |
| """ |
| with named_temporary_file() as fp: |
| fp.write(to_bytes(_COMPILER_MAIN % {'root': root, 'relpaths': relpaths}, encoding='utf-8')) |
| fp.flush() |
| |
| try: |
| out, _ = Executor.execute([self._interpreter.binary, fp.name]) |
| except Executor.NonZeroExit as e: |
| raise self.CompilationFailure( |
| 'encountered %r during bytecode compilation.\nstderr was:\n%s\n' % (e, e.stderr) |
| ) |
| |
| return out.splitlines() |