Skip to content

Commit b13d04c

Browse files
committed
- Issue #8140: extend compileall to compile single files. Add -i option.
1 parent dc36472 commit b13d04c

3 files changed

Lines changed: 116 additions & 42 deletions

File tree

Lib/compileall.py

Lines changed: 95 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import struct
1818
import imp
1919

20-
__all__ = ["compile_dir","compile_path"]
20+
__all__ = ["compile_dir","compile_files","compile_path"]
2121

2222
def compile_dir(dir, maxlevels=10, ddir=None,
2323
force=0, rx=None, quiet=0):
@@ -48,39 +48,9 @@ def compile_dir(dir, maxlevels=10, ddir=None,
4848
dfile = os.path.join(ddir, name)
4949
else:
5050
dfile = None
51-
if rx is not None:
52-
mo = rx.search(fullname)
53-
if mo:
54-
continue
55-
if os.path.isfile(fullname):
56-
head, tail = name[:-3], name[-3:]
57-
if tail == '.py':
58-
if not force:
59-
try:
60-
mtime = int(os.stat(fullname).st_mtime)
61-
expect = struct.pack('<4sl', imp.get_magic(), mtime)
62-
cfile = fullname + (__debug__ and 'c' or 'o')
63-
with open(cfile, 'rb') as chandle:
64-
actual = chandle.read(8)
65-
if expect == actual:
66-
continue
67-
except IOError:
68-
pass
69-
if not quiet:
70-
print 'Compiling', fullname, '...'
71-
try:
72-
ok = py_compile.compile(fullname, None, dfile, True)
73-
except py_compile.PyCompileError,err:
74-
if quiet:
75-
print 'Compiling', fullname, '...'
76-
print err.msg
77-
success = 0
78-
except IOError, e:
79-
print "Sorry", e
80-
success = 0
81-
else:
82-
if ok == 0:
83-
success = 0
51+
if not os.path.isdir(fullname):
52+
if not compile_file(fullname, ddir, force, rx, quiet):
53+
success = 0
8454
elif maxlevels > 0 and \
8555
name != os.curdir and name != os.pardir and \
8656
os.path.isdir(fullname) and \
@@ -90,6 +60,57 @@ def compile_dir(dir, maxlevels=10, ddir=None,
9060
success = 0
9161
return success
9262

63+
def 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+
93114
def compile_path(skip_curdir=1, maxlevels=0, force=0, quiet=0):
94115
"""Byte-compile all module on sys.path.
95116
@@ -110,28 +131,49 @@ def compile_path(skip_curdir=1, maxlevels=0, force=0, quiet=0):
110131
force, quiet=quiet)
111132
return success
112133

134+
def 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+
113153
def main():
114154
"""Script main program."""
115155
import getopt
116156
try:
117-
opts, args = getopt.getopt(sys.argv[1:], 'lfqd:x:')
157+
opts, args = getopt.getopt(sys.argv[1:], 'lfqd:x:i:')
118158
except getopt.error, msg:
119159
print msg
120160
print "usage: python compileall.py [-l] [-f] [-q] [-d destdir] " \
121-
"[-x regexp] [directory ...]"
161+
"[-x regexp] [-i list] [directory|file ...]"
122162
print "-l: don't recurse down"
123163
print "-f: force rebuild even if timestamps are up-to-date"
124164
print "-q: quiet operation"
125165
print "-d destdir: purported directory name for error messages"
126166
print " if no directory arguments, -l sys.path is assumed"
127167
print "-x regexp: skip files matching the regular expression regexp"
128168
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)"
129170
sys.exit(2)
130171
maxlevels = 10
131172
ddir = None
132173
force = 0
133174
quiet = 0
134175
rx = None
176+
flist = None
135177
for o, a in opts:
136178
if o == '-l': maxlevels = 0
137179
if o == '-d': ddir = a
@@ -140,17 +182,28 @@ def main():
140182
if o == '-x':
141183
import re
142184
rx = re.compile(a)
185+
if o == '-i': flist = a
143186
if ddir:
144-
if len(args) != 1:
187+
if len(args) != 1 and not os.path.isdir(args[0]):
145188
print "-d destdir require exactly one directory argument"
146189
sys.exit(2)
147190
success = 1
148191
try:
149-
if args:
150-
for dir in args:
151-
if not compile_dir(dir, maxlevels, ddir,
152-
force, rx, quiet):
153-
success = 0
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
154207
else:
155208
success = compile_path()
156209
except KeyboardInterrupt:

Lib/test/test_compileall.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ def setUp(self):
1717
self.bc_path = self.source_path + ('c' if __debug__ else 'o')
1818
with open(self.source_path, 'w') as file:
1919
file.write('x = 123\n')
20+
self.source_path2 = os.path.join(self.directory, '_test2.py')
21+
self.bc_path2 = self.source_path2 + ('c' if __debug__ else 'o')
22+
shutil.copyfile(self.source_path, self.source_path2)
2023

2124
def tearDown(self):
2225
shutil.rmtree(self.directory)
@@ -52,6 +55,22 @@ def test_magic_number(self):
5255
# Test a change in mtime leads to a new .pyc.
5356
self.recreation_check(b'\0\0\0\0')
5457

58+
def test_compile_files(self):
59+
# Test compiling a single file, and complete directory
60+
for fn in (self.bc_path, self.bc_path2):
61+
try:
62+
os.unlink(fn)
63+
except:
64+
pass
65+
compileall.compile_file(self.source_path, force=False, quiet=True)
66+
self.assertTrue(os.path.isfile(self.bc_path) \
67+
and not os.path.isfile(self.bc_path2))
68+
os.unlink(self.bc_path)
69+
compileall.compile_dir(self.directory, force=False, quiet=True)
70+
self.assertTrue(os.path.isfile(self.bc_path) \
71+
and os.path.isfile(self.bc_path2))
72+
os.unlink(self.bc_path)
73+
os.unlink(self.bc_path2)
5574

5675
def test_main():
5776
test_support.run_unittest(CompileallTests)

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ Core and Builtins
2020
Library
2121
-------
2222

23+
- Issue #8140: extend compileall to compile single files. Add -i option.
24+
2325
- Issue #7356: ctypes.util: Make parsing of ldconfig output independent of
2426
the locale.
2527

0 commit comments

Comments
 (0)