py_compile.py 6.42 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
import builtins
Barry Warsaw's avatar
Barry Warsaw committed
7
import errno
8
import imp
9 10 11
import marshal
import os
import sys
12
import tokenize
13 14
import traceback

15
MAGIC = imp.get_magic()
16

17 18 19 20 21 22 23 24 25 26 27 28
__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
29

30 31 32
        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
33

34 35
        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
36

37 38
        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
39

40
        msg:        string message to be written as error message
Barry Warsaw's avatar
Barry Warsaw committed
41 42 43 44
                    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
45

46
    """
Tim Peters's avatar
Tim Peters committed
47

48 49 50
    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
51 52
            tbtext = ''.join(traceback.format_exception_only(
                exc_type, exc_value))
53 54 55
            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
56

57 58 59 60 61 62 63 64 65 66
        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

67

68
def wr_long(f, x):
69
    """Internal; write a 32-bit int to a file in little-endian order."""
Barry Warsaw's avatar
Barry Warsaw committed
70
    f.write(bytes([x         & 0xff,
71 72 73
                   (x >> 8)  & 0xff,
                   (x >> 16) & 0xff,
                   (x >> 24) & 0xff]))
Guido van Rossum's avatar
Guido van Rossum committed
74

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

Barry Warsaw's avatar
Barry Warsaw committed
78 79 80 81 82 83 84 85 86 87 88
    :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.
89 90 91 92
    :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
93
    :return: Path to the resulting byte compiled file.
Tim Peters's avatar
Tim Peters committed
94

Guido van Rossum's avatar
Guido van Rossum committed
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
    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).
    """
111
    with tokenize.open(file) as f:
112 113 114 115 116
        try:
            timestamp = int(os.fstat(f.fileno()).st_mtime)
        except AttributeError:
            timestamp = int(os.stat(file).st_mtime)
        codestring = f.read()
117
    try:
118 119
        codeobject = builtins.compile(codestring, dfile or file, 'exec',
                                      optimize=optimize)
120
    except Exception as err:
121
        py_exc = PyCompileError(err.__class__, err, dfile or file)
122 123 124
        if doraise:
            raise py_exc
        else:
125
            sys.stderr.write(py_exc.msg + '\n')
126
            return
127
    if cfile is None:
128 129 130 131
        if optimize >= 0:
            cfile = imp.cache_from_source(file, debug_override=not optimize)
        else:
            cfile = imp.cache_from_source(file)
132 133 134 135 136
    try:
        os.makedirs(os.path.dirname(cfile))
    except OSError as error:
        if error.errno != errno.EEXIST:
            raise
137 138 139 140 141 142 143
    with open(cfile, 'wb') as fc:
        fc.write(b'\0\0\0\0')
        wr_long(fc, timestamp)
        marshal.dump(codeobject, fc)
        fc.flush()
        fc.seek(0, 0)
        fc.write(MAGIC)
Barry Warsaw's avatar
Barry Warsaw committed
144
    return cfile
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 171 172 173 174 175 176 177
    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)
            except IOError as error:
                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())