py_compile.py 6.93 KB
Newer Older
Guido van Rossum's avatar
Guido van Rossum committed
1 2 3 4
"""Routine to "compile" a .py file to a .pyc (or .pyo) file.

This module has intimate knowledge of the format of .pyc files.
"""
5

6 7
import importlib._bootstrap
import importlib.machinery
8
import importlib.util
9
import os
10
import os.path
11 12 13
import sys
import traceback

14 15 16 17 18 19 20 21 22 23 24 25
__all__ = ["compile", "main", "PyCompileError"]


class PyCompileError(Exception):
    """Exception raised when an error occurs while attempting to
    compile the file.

    To raise this exception, use

        raise PyCompileError(exc_type,exc_value,file[,msg])

    where
Tim Peters's avatar
Tim Peters committed
26

27 28 29
        exc_type:   exception type to be used in error message
                    type name can be accesses as class variable
                    'exc_type_name'
Tim Peters's avatar
Tim Peters committed
30

31 32
        exc_value:  exception value to be used in error message
                    can be accesses as class variable 'exc_value'
Tim Peters's avatar
Tim Peters committed
33

34 35
        file:       name of file being compiled to be used in error message
                    can be accesses as class variable 'file'
Tim Peters's avatar
Tim Peters committed
36

37
        msg:        string message to be written as error message
Barry Warsaw's avatar
Barry Warsaw committed
38 39 40 41
                    If no value is given, a default exception message will be
                    given, consistent with 'standard' py_compile output.
                    message (or default) can be accesses as class variable
                    'msg'
Tim Peters's avatar
Tim Peters committed
42

43
    """
Tim Peters's avatar
Tim Peters committed
44

45 46 47
    def __init__(self, exc_type, exc_value, file, msg=''):
        exc_type_name = exc_type.__name__
        if exc_type is SyntaxError:
Barry Warsaw's avatar
Barry Warsaw committed
48 49
            tbtext = ''.join(traceback.format_exception_only(
                exc_type, exc_value))
50 51 52
            errmsg = tbtext.replace('File "<string>"', 'File "%s"' % file)
        else:
            errmsg = "Sorry: %s: %s" % (exc_type_name,exc_value)
Tim Peters's avatar
Tim Peters committed
53

54 55 56 57 58 59 60 61 62 63
        Exception.__init__(self,msg or errmsg,exc_type_name,exc_value,file)

        self.exc_type_name = exc_type_name
        self.exc_value = exc_value
        self.file = file
        self.msg = msg or errmsg

    def __str__(self):
        return self.msg

64

65
def compile(file, cfile=None, dfile=None, doraise=False, optimize=-1):
Guido van Rossum's avatar
Guido van Rossum committed
66 67
    """Byte-compile one Python source file to Python bytecode.

Barry Warsaw's avatar
Barry Warsaw committed
68 69 70 71 72 73 74 75 76 77 78
    :param file: The source file name.
    :param cfile: The target byte compiled file name.  When not given, this
        defaults to the PEP 3147 location.
    :param dfile: Purported file name, i.e. the file name that shows up in
        error messages.  Defaults to the source file name.
    :param doraise: Flag indicating whether or not an exception should be
        raised when a compile error is found.  If an exception occurs and this
        flag is set to False, a string indicating the nature of the exception
        will be printed, and the function will return to the caller. If an
        exception occurs and this flag is set to True, a PyCompileError
        exception will be raised.
79 80 81 82
    :param optimize: The optimization level for the compiler.  Valid values
        are -1, 0, 1 and 2.  A value of -1 means to use the optimization
        level of the current interpreter, as given by -O command line options.

Barry Warsaw's avatar
Barry Warsaw committed
83
    :return: Path to the resulting byte compiled file.
Tim Peters's avatar
Tim Peters committed
84

Guido van Rossum's avatar
Guido van Rossum committed
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
    Note that it isn't necessary to byte-compile Python modules for
    execution efficiency -- Python itself byte-compiles a module when
    it is loaded, and if it can, writes out the bytecode to the
    corresponding .pyc (or .pyo) file.

    However, if a Python installation is shared between users, it is a
    good idea to byte-compile all modules upon installation, since
    other users may not be able to write in the source directories,
    and thus they won't be able to write the .pyc/.pyo file, and then
    they would be byte-compiling every module each time it is loaded.
    This can slow down program start-up considerably.

    See compileall.py for a script/module that uses this module to
    byte-compile all installed files (or all files in selected
    directories).
100 101 102 103 104

    Do note that FileExistsError is raised if cfile ends up pointing at a
    non-regular file or symlink. Because the compilation uses a file renaming,
    the resulting file would be regular and thus not the same type of file as
    it was previously.
Guido van Rossum's avatar
Guido van Rossum committed
105
    """
106 107
    if cfile is None:
        if optimize >= 0:
108 109
            cfile = importlib.util.cache_from_source(file,
                                                     debug_override=not optimize)
110
        else:
111
            cfile = importlib.util.cache_from_source(file)
112 113 114
    if os.path.islink(cfile):
        msg = ('{} is a symlink and will be changed into a regular file if '
               'import writes a byte-compiled file to it')
115
        raise FileExistsError(msg.format(cfile))
116 117 118
    elif os.path.exists(cfile) and not os.path.isfile(cfile):
        msg = ('{} is a non-regular file and will be changed into a regular '
               'one if import writes a byte-compiled file to it')
119
        raise FileExistsError(msg.format(cfile))
120 121
    loader = importlib.machinery.SourceFileLoader('<py_compile>', file)
    source_bytes = loader.get_data(file)
122
    try:
123
        code = loader.source_to_code(source_bytes, dfile or file,
124
                                     _optimize=optimize)
125
    except Exception as err:
126
        py_exc = PyCompileError(err.__class__, err, dfile or file)
127 128 129
        if doraise:
            raise py_exc
        else:
130
            sys.stderr.write(py_exc.msg + '\n')
131
            return
132
    try:
133 134 135
        dirname = os.path.dirname(cfile)
        if dirname:
            os.makedirs(dirname)
136 137 138
    except FileExistsError:
        pass
    source_stats = loader.path_stats(file)
139 140 141 142
    bytecode = importlib._bootstrap._code_to_bytecode(
            code, source_stats['mtime'], source_stats['size'])
    mode = importlib._bootstrap._calc_mode(file)
    importlib._bootstrap._write_atomic(cfile, bytecode, mode)
Barry Warsaw's avatar
Barry Warsaw committed
143
    return cfile
144

145

146 147 148 149 150 151 152
def main(args=None):
    """Compile several source files.

    The files named in 'args' (or on the command line, if 'args' is
    not specified) are compiled and the resulting bytecode is cached
    in the normal manner.  This function does not search a directory
    structure to locate source files; it only compiles files named
153 154
    explicitly.  If '-' is the only parameter in args, the list of
    files is taken from standard input.
155 156 157 158

    """
    if args is None:
        args = sys.argv[1:]
Christian Heimes's avatar
Christian Heimes committed
159
    rv = 0
160 161 162 163 164 165 166 167 168 169 170
    if args == ['-']:
        while True:
            filename = sys.stdin.readline()
            if not filename:
                break
            filename = filename.rstrip('\n')
            try:
                compile(filename, doraise=True)
            except PyCompileError as error:
                rv = 1
                sys.stderr.write("%s\n" % error.msg)
171
            except OSError as error:
172 173 174 175 176 177
                rv = 1
                sys.stderr.write("%s\n" % error)
    else:
        for filename in args:
            try:
                compile(filename, doraise=True)
178
            except PyCompileError as error:
179 180 181
                # return value to indicate at least one failure
                rv = 1
                sys.stderr.write(error.msg)
Christian Heimes's avatar
Christian Heimes committed
182
    return rv
Tim Peters's avatar
Tim Peters committed
183

184
if __name__ == "__main__":
Christian Heimes's avatar
Christian Heimes committed
185
    sys.exit(main())