compileall.py revision b13d04c99143ea326d1c74ba537ff43cbd04175b
1"""Module/script to "compile" all .py files to .pyc (or .pyo) file.
2
3When called as a script with arguments, this compiles the directories
4given as arguments recursively; the -l option prevents it from
5recursing into directories.
6
7Without arguments, if compiles all modules on sys.path, without
8recursing into subdirectories.  (Even though it should do so for
9packages -- for now, you'll have to deal with packages separately.)
10
11See module py_compile for details of the actual byte-compilation.
12
13"""
14import os
15import sys
16import py_compile
17import struct
18import imp
19
20__all__ = ["compile_dir","compile_files","compile_path"]
21
22def compile_dir(dir, maxlevels=10, ddir=None,
23                force=0, rx=None, quiet=0):
24    """Byte-compile all modules in the given directory tree.
25
26    Arguments (only dir is required):
27
28    dir:       the directory to byte-compile
29    maxlevels: maximum recursion level (default 10)
30    ddir:      if given, purported directory name (this is the
31               directory name that will show up in error messages)
32    force:     if 1, force compilation, even if timestamps are up-to-date
33    quiet:     if 1, be quiet during compilation
34
35    """
36    if not quiet:
37        print 'Listing', dir, '...'
38    try:
39        names = os.listdir(dir)
40    except os.error:
41        print "Can't list", dir
42        names = []
43    names.sort()
44    success = 1
45    for name in names:
46        fullname = os.path.join(dir, name)
47        if ddir is not None:
48            dfile = os.path.join(ddir, name)
49        else:
50            dfile = None
51        if not os.path.isdir(fullname):
52            if not compile_file(fullname, ddir, force, rx, quiet):
53                success = 0
54        elif maxlevels > 0 and \
55             name != os.curdir and name != os.pardir and \
56             os.path.isdir(fullname) and \
57             not os.path.islink(fullname):
58            if not compile_dir(fullname, maxlevels - 1, dfile, force, rx,
59                               quiet):
60                success = 0
61    return success
62
63def compile_file(fullname, ddir=None, force=0, rx=None, quiet=0):
64    """Byte-compile file.
65    file:      the file to byte-compile
66    ddir:      if given, purported directory name (this is the
67               directory name that will show up in error messages)
68    force:     if 1, force compilation, even if timestamps are up-to-date
69    quiet:     if 1, be quiet during compilation
70
71    """
72
73    success = 1
74    name = os.path.basename(fullname)
75    if ddir is not None:
76        dfile = os.path.join(ddir, name)
77    else:
78        dfile = None
79    if rx is not None:
80        mo = rx.search(fullname)
81        if mo:
82            return success
83    if os.path.isfile(fullname):
84        head, tail = name[:-3], name[-3:]
85        if tail == '.py':
86            if not force:
87                try:
88                    mtime = int(os.stat(fullname).st_mtime)
89                    expect = struct.pack('<4sl', imp.get_magic(), mtime)
90                    cfile = fullname + (__debug__ and 'c' or 'o')
91                    with open(cfile, 'rb') as chandle:
92                        actual = chandle.read(8)
93                    if expect == actual:
94                        return success
95                except IOError:
96                    pass
97            if not quiet:
98                print 'Compiling', fullname, '...'
99            try:
100                ok = py_compile.compile(fullname, None, dfile, True)
101            except py_compile.PyCompileError,err:
102                if quiet:
103                    print 'Compiling', fullname, '...'
104                print err.msg
105                success = 0
106            except IOError, e:
107                print "Sorry", e
108                success = 0
109            else:
110                if ok == 0:
111                    success = 0
112    return success
113
114def compile_path(skip_curdir=1, maxlevels=0, force=0, quiet=0):
115    """Byte-compile all module on sys.path.
116
117    Arguments (all optional):
118
119    skip_curdir: if true, skip current directory (default true)
120    maxlevels:   max recursion level (default 0)
121    force: as for compile_dir() (default 0)
122    quiet: as for compile_dir() (default 0)
123
124    """
125    success = 1
126    for dir in sys.path:
127        if (not dir or dir == os.curdir) and skip_curdir:
128            print 'Skipping current directory'
129        else:
130            success = success and compile_dir(dir, maxlevels, None,
131                                              force, quiet=quiet)
132    return success
133
134def expand_args(args, flist):
135    """read names in flist and append to args"""
136    expanded = args[:]
137    if flist:
138        try:
139            if flist == '-':
140                fd = sys.stdin
141            else:
142                fd = open(flist)
143            while 1:
144                line = fd.readline()
145                if not line:
146                    break
147                expanded.append(line[:-1])
148        except IOError:
149            print "Error reading file list %s" % flist
150            raise
151    return expanded
152
153def main():
154    """Script main program."""
155    import getopt
156    try:
157        opts, args = getopt.getopt(sys.argv[1:], 'lfqd:x:i:')
158    except getopt.error, msg:
159        print msg
160        print "usage: python compileall.py [-l] [-f] [-q] [-d destdir] " \
161              "[-x regexp] [-i list] [directory|file ...]"
162        print "-l: don't recurse down"
163        print "-f: force rebuild even if timestamps are up-to-date"
164        print "-q: quiet operation"
165        print "-d destdir: purported directory name for error messages"
166        print "   if no directory arguments, -l sys.path is assumed"
167        print "-x regexp: skip files matching the regular expression regexp"
168        print "   the regexp is searched for in the full path of the file"
169        print "-i list: expand list with its content (file and directory names)"
170        sys.exit(2)
171    maxlevels = 10
172    ddir = None
173    force = 0
174    quiet = 0
175    rx = None
176    flist = None
177    for o, a in opts:
178        if o == '-l': maxlevels = 0
179        if o == '-d': ddir = a
180        if o == '-f': force = 1
181        if o == '-q': quiet = 1
182        if o == '-x':
183            import re
184            rx = re.compile(a)
185        if o == '-i': flist = a
186    if ddir:
187        if len(args) != 1 and not os.path.isdir(args[0]):
188            print "-d destdir require exactly one directory argument"
189            sys.exit(2)
190    success = 1
191    try:
192        if args or flist:
193            try:
194                if flist:
195                    args = expand_args(args, flist)
196            except IOError:
197                success = 0
198            if success:
199                for arg in args:
200                    if os.path.isdir(arg):
201                        if not compile_dir(arg, maxlevels, ddir,
202                                           force, rx, quiet):
203                            success = 0
204                    else:
205                        if not compile_file(arg, ddir, force, rx, quiet):
206                            success = 0
207        else:
208            success = compile_path()
209    except KeyboardInterrupt:
210        print "\n[interrupt]"
211        success = 0
212    return success
213
214if __name__ == '__main__':
215    exit_status = int(not main())
216    sys.exit(exit_status)
217