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

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

6
import importlib._bootstrap_external
7
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
    :param file: The source file name.
    :param cfile: The target byte compiled file name.  When not given, this
70
        defaults to the PEP 3147/PEP 488 location.
Barry Warsaw's avatar
Barry Warsaw committed
71 72 73 74 75 76 77 78
    :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
    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
88
    corresponding .pyc file.
Guido van Rossum's avatar
Guido van Rossum committed
89 90 91 92

    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,
93
    and thus they won't be able to write the .pyc file, and then
Guido van Rossum's avatar
Guido van Rossum committed
94 95 96 97 98 99
    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
            optimization = optimize if optimize >= 1 else ''
109
            cfile = importlib.util.cache_from_source(file,
110
                                                     optimization=optimization)
111
        else:
112
            cfile = importlib.util.cache_from_source(file)
113 114 115
    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')
116
        raise FileExistsError(msg.format(cfile))
117 118 119
    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')
120
        raise FileExistsError(msg.format(cfile))
121 122
    loader = importlib.machinery.SourceFileLoader('<py_compile>', file)
    source_bytes = loader.get_data(file)
123
    try:
124
        code = loader.source_to_code(source_bytes, dfile or file,
125
                                     _optimize=optimize)
126
    except Exception as err:
127
        py_exc = PyCompileError(err.__class__, err, dfile or file)
128 129 130
        if doraise:
            raise py_exc
        else:
131
            sys.stderr.write(py_exc.msg + '\n')
132
            return
133
    try:
134 135 136
        dirname = os.path.dirname(cfile)
        if dirname:
            os.makedirs(dirname)
137 138 139
    except FileExistsError:
        pass
    source_stats = loader.path_stats(file)
140
    bytecode = importlib._bootstrap_external._code_to_bytecode(
141
            code, source_stats['mtime'], source_stats['size'])
142 143
    mode = importlib._bootstrap_external._calc_mode(file)
    importlib._bootstrap_external._write_atomic(cfile, bytecode, mode)
Barry Warsaw's avatar
Barry Warsaw committed
144
    return cfile
145

146

147 148 149 150 151 152 153
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
154 155
    explicitly.  If '-' is the only parameter in args, the list of
    files is taken from standard input.
156 157 158 159

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

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