py_compile.py revision e537d6e93e0b5c37a96090df9ec831bd1bf8e2fb
1"""Routine to "compile" a .py file to a .pyc (or .pyo) file.
2
3This module has intimate knowledge of the format of .pyc files.
4"""
5
6import __builtin__
7import imp
8import marshal
9import os
10import sys
11import traceback
12
13MAGIC = imp.get_magic()
14
15__all__ = ["compile", "main", "PyCompileError"]
16
17
18class PyCompileError(Exception):
19    """Exception raised when an error occurs while attempting to
20    compile the file.
21
22    To raise this exception, use
23
24        raise PyCompileError(exc_type,exc_value,file[,msg])
25
26    where
27
28        exc_type:   exception type to be used in error message
29                    type name can be accesses as class variable
30                    'exc_type_name'
31
32        exc_value:  exception value to be used in error message
33                    can be accesses as class variable 'exc_value'
34
35        file:       name of file being compiled to be used in error message
36                    can be accesses as class variable 'file'
37
38        msg:        string message to be written as error message
39                    If no value is given, a default exception message will be given,
40                    consistent with 'standard' py_compile output.
41                    message (or default) can be accesses as class variable 'msg'
42
43    """
44
45    def __init__(self, exc_type, exc_value, file, msg=''):
46        exc_type_name = exc_type.__name__
47        if exc_type is SyntaxError:
48            tbtext = ''.join(traceback.format_exception_only(exc_type, exc_value))
49            errmsg = tbtext.replace('File "<string>"', 'File "%s"' % file)
50        else:
51            errmsg = "Sorry: %s: %s" % (exc_type_name,exc_value)
52
53        Exception.__init__(self,msg or errmsg,exc_type_name,exc_value,file)
54
55        self.exc_type_name = exc_type_name
56        self.exc_value = exc_value
57        self.file = file
58        self.msg = msg or errmsg
59
60    def __str__(self):
61        return self.msg
62
63
64# Define an internal helper according to the platform
65if os.name == "mac":
66    import MacOS
67    def set_creator_type(file):
68        MacOS.SetCreatorAndType(file, 'Pyth', 'PYC ')
69else:
70    def set_creator_type(file):
71        pass
72
73def wr_long(f, x):
74    """Internal; write a 32-bit int to a file in little-endian order."""
75    f.write(chr( x        & 0xff))
76    f.write(chr((x >> 8)  & 0xff))
77    f.write(chr((x >> 16) & 0xff))
78    f.write(chr((x >> 24) & 0xff))
79
80def compile(file, cfile=None, dfile=None, doraise=False):
81    """Byte-compile one Python source file to Python bytecode.
82
83    Arguments:
84
85    file:    source filename
86    cfile:   target filename; defaults to source with 'c' or 'o' appended
87             ('c' normally, 'o' in optimizing mode, giving .pyc or .pyo)
88    dfile:   purported filename; defaults to source (this is the filename
89             that will show up in error messages)
90    doraise: flag indicating whether or not an exception should be
91             raised when a compile error is found. If an exception
92             occurs and this flag is set to False, a string
93             indicating the nature of the exception will be printed,
94             and the function will return to the caller. If an
95             exception occurs and this flag is set to True, a
96             PyCompileError exception will be raised.
97
98    Note that it isn't necessary to byte-compile Python modules for
99    execution efficiency -- Python itself byte-compiles a module when
100    it is loaded, and if it can, writes out the bytecode to the
101    corresponding .pyc (or .pyo) file.
102
103    However, if a Python installation is shared between users, it is a
104    good idea to byte-compile all modules upon installation, since
105    other users may not be able to write in the source directories,
106    and thus they won't be able to write the .pyc/.pyo file, and then
107    they would be byte-compiling every module each time it is loaded.
108    This can slow down program start-up considerably.
109
110    See compileall.py for a script/module that uses this module to
111    byte-compile all installed files (or all files in selected
112    directories).
113
114    """
115    f = open(file, 'U')
116    try:
117        timestamp = long(os.fstat(f.fileno()).st_mtime)
118    except AttributeError:
119        timestamp = long(os.stat(file).st_mtime)
120    codestring = f.read()
121    f.close()
122    if codestring and codestring[-1] != '\n':
123        codestring = codestring + '\n'
124    try:
125        codeobject = __builtin__.compile(codestring, dfile or file,'exec')
126    except Exception,err:
127        py_exc = PyCompileError(err.__class__,err.args,dfile or file)
128        if doraise:
129            raise py_exc
130        else:
131            sys.stderr.write(py_exc.msg + '\n')
132            return
133    if cfile is None:
134        cfile = file + (__debug__ and 'c' or 'o')
135    fc = open(cfile, 'wb')
136    fc.write('\0\0\0\0')
137    wr_long(fc, timestamp)
138    marshal.dump(codeobject, fc)
139    fc.flush()
140    fc.seek(0, 0)
141    fc.write(MAGIC)
142    fc.close()
143    set_creator_type(cfile)
144
145def main(args=None):
146    """Compile several source files.
147
148    The files named in 'args' (or on the command line, if 'args' is
149    not specified) are compiled and the resulting bytecode is cached
150    in the normal manner.  This function does not search a directory
151    structure to locate source files; it only compiles files named
152    explicitly.
153
154    """
155    if args is None:
156        args = sys.argv[1:]
157    for filename in args:
158        try:
159            compile(filename, doraise=True)
160        except PyCompileError,err:
161            sys.stderr.write(err.msg)
162
163if __name__ == "__main__":
164    main()
165