This repository has been archived on 2026-05-18. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
miscutils/pycc

291 lines
11 KiB
Plaintext
Raw Normal View History

2026-05-18 12:31:43 -04:00
#!/usr/bin/python
# python script that compiles files and directories to python bytecode,
# possibly with optimizations
# TODO:
# Distinguish between files and directories
# Accept the optimization flag
# Enforce command option rules
import py_compile
import compileall
import sys
import os
import re
import getopt
import traceback
usage = r"""
pycc : Python byteCode Compiler (pronounced "pie-sees")
(C) Andrew Kesterson 2006, <andrew@aklabs.net>
usage: pycc <options> src_files ...
options:
-L dir : Add the following directory to the sys.path; similar to
the -L directive in gcc
-l lib : Specifically import the given module before compiling
any modules (successive -l directives are processed in
the order received); similar to -l directive in gcc. The
argument should be in standard python 'import' notation.
"from xxx import y' should be enclosed in quotes. Otherwise
provide 'xxx' alone for the module name to import
-O : Enable the python compiler's optimizations; default output
will have a .pyo extension instead of a .pyc. NOTE: FOR
SOME REASON, this option doesn't work on single files.
Only directories. This is a limitation of the python
compiler libraries, not this program.
-o : When compiling a single file, specify the output filename.
When compiling a directory or multiple files, this directive
has no effect.
-r : Recurse beyond the first level of directories given on the
command line, so that for example passing src/dir on the
command line would compile src/dir/src1.py and also
src/dir/subdir/src2.py.
src_files This is a list of single files and directories. Directories
are not recursed into (beyond the first level) unless the
-r option is specified.
-E : Stop compilation of all source files when one source file
has an error (otherwise, an error message is printed to
stderr and compilation continues to the next file). Error
messages are printed regardless of this flag.
-d n : Tells the compiler to descend up to (at maximum) n levels of
directories when running recursively
-F : Force the generation of code, even when the .pyc/.pyo
timestamps are up to date
-v : Be verbose (errors are always printed regardless)
-h : display this help
"""
shortopts = "L:l:Oo:rd:EvhF"
class ArgChecker:
def printErr(self, msg):
if ( not msg.endswith("\n") ):
msg += "\n"
sys.__stdout__.write(msg)
def importPaths (self, importStr):
fromRE = re.compile(r"""^from\s+([\w.]+)\s+import\s+(\w+).*$""")
importRE = re.compile(r"""^import\s+([.\w]+).*$""")
fromparts = fromRE.findall(importStr)
importparts = importRE.findall(importStr)
try:
if ( (len(fromparts) != 1 or len(fromparts[0]) != 2 ) and len(importparts) == 0):
self.printErr("Bad option: -l %s : invalid syntax : not enough arguments!" % (importStr))
self.printErr("%s : %s" % (fromparts, importparts))
return False
elif ( len(fromparts) == 1 and len(fromparts[0]) == 2 ):
__import__(fromparts[0][1], globals(), None, fromparts[0][0].split("."))
return True
elif ( len(importparts) == 1):
__import__(importparts[0][0], globals(), None, None)
return True
return False
except Exception, e:
self.printErr("Import error:%s: %s" % (importStr, str(e)))
return False
def checkArgs (self, argc, argv):
global shortopts
global usage
compiler = PYCompiler()
opts, args = getopt.getopt(argv, shortopts)
# self.printErr("%s : %s" % (opts, args))
errors = False
if ( len(args) == 0 ):
self.printErr(usage)
return None
for file in args:
if ( os.path.exists(file) ):
if ( os.path.isfile(file) ):
compiler.queueFile(file)
elif ( os.path.isdir(file) ):
compiler.queueDir(file)
else:
self.printErr("Bad Option: Unable to determine type of file %s" % file)
errors = True
else:
self.printErr("Bad Option: File or directory does not exist %s" % file)
errors = True
for opt in opts:
if ( opt[0] == "-L" ):
# add library path
if ( os.path.exists(opt[1]) and os.path.isdir(opt[1]) ):
sys.path.append(opt[1])
else:
self.printErr("Bad option: -L %s : Path is not a directory or is nonexistant" % opt[1])
errors = True
elif ( opt[0] == "-l" ):
# specifically import the given module before continuing
if ( not self.importPaths(opt[1]) ):
errors = True
elif ( opt[0] == "-O" ) :
compiler.optimize(True)
elif ( opt[0] == "-o" ) :
if ( len(compiler.files) == 1 and len(compiler.dirs) == 0 ):
compiler.ofile = opt[1]
else:
if ( compiler.verbose ):
self.printErr("Ignoring option -o with multiple files or directories...")
elif ( opt[0] == "-r" ):
compiler.recursive(True)
elif ( opt[0] == "-E" ):
compiler.stopOnError(True)
elif ( opt[0] == "-v" ):
compiler.setVerbose(True)
elif ( opt[0] == "-F" ):
compiler.forceGen(True)
elif ( opt[0] == "-h" ):
self.printErr("HELP FOUND")
return False
elif ( opt[0] == "-d" ):
try:
compiler.setMaxDepth(int(opt[1]))
except Exception, e:
self.printErr("Bad Option: %s : must pass an integer" % opt[0])
else:
self.printErr(usage)
errors = True
if ( not errors ):
return compiler
else:
#self.printErr("%s : %s" % (opts, args))
return None
class PYCompiler :
def __init__ (self):
self.files = []
self.dirs = []
self.opt = False
self.recurse = False
self.stopErr = False
self.verbose = 0
self.depth = 10
self.force = 0
self.ofile = ""
def setVerbose (self, opt):
if ( opt ):
self.verbose = 1
else:
self.verbose = 0
def forceGen (self, opt):
if ( opt ):
self.force = 1
else:
self.force = 0
def setMaxDepth (self, depth):
self.depth = depth
def printErr(self, msg):
sys.__stdout__.write(msg)
if ( (not msg.endswith("\n")) ):
sys.__stdout__.write("\n")
def queueFile (self, fname):
self.files.append(fname)
def queueDir (self, dirname):
self.dirs.append(dirname)
def printCompileErr (self, e):
#self.printErr("printErr called with exception %s" % str(e))
efile = e.file
etype = e.exc_type_name
if ( len(e.exc_value) > 1 ) :
eline = e.exc_value[1][1]
ecode = e.exc_value[1][3]
else:
eline = -1
ecode = "(no code given)"
edesc = e.exc_value[0]
msg = "%s:%d: %s : %s : %s" % (efile, eline, etype, edesc, str(ecode))
self.printErr(msg)
def compile (self):
for fname in self.files:
ofile = ""
if ( len(self.files) == 1 and len(self.dirs) == 0 and self.ofile) :
ofile = self.ofile
else:
ofile = fname + "c"
if ( not self.__compile_file(fname, ofile) ):
if ( self.stopErr or ( len(self.files) + len(self.dirs)) == 1):
return False
for dirname in self.dirs :
if ( not os.path.walk(dirname, self.__compile_dir, None) ):
if ( self.stopErr ):
return False
return True
def __compile_file (self, fname, ofile = None):
try:
if ( self.force == 1 and len(self.files) == 1 and len(self.dirs) == 0):
if ( os.path.exists(ofile) and os.path.isfile(ofile) ):
try:
os.remove(ofile)
except OSError, e:
if ( self.verbose ):
self.printErr("%s : Failed to remove original - ignoring ... " % fname)
else:
if ( self.verbose ):
self.printErr("%s : Original output does not exist - ignoring ... " % fname)
#self.printErr("%s -> %s ..." % (fname, ofile))
py_compile.compile(fname, ofile, None, True)
if ( (fname in self.files) and self.verbose ):
# don't print this if we're being called from __compile_dir
self.printErr("%s -> %s ... OK" % (fname, ofile))
return True
except py_compile.PyCompileError, e:
self.printCompileErr(e)
if ( fname in self.files ):
self.printErr("%s -> %s ... FAILED" % (fname, ofile))
return False
def __compile_dir (self, arg, dirname, fnames):
try:
for fname in fnames:
if ( not (fname.endswith("py") or fname.endswith("PY")) ):
continue
tocompile = os.path.join(dirname, fname)
if ( os.path.isdir(tocompile) ):
os.path.walk(dirname, self.__compile_dir, None)
outfile = tocompile+"c"
if ( not self.__compile_file(tocompile, outfile) ):
self.printErr("%s -> %s ... FAILED" % (tocompile, outfile))
if ( self.stopErr ):
return False
else:
continue
else:
if ( self.verbose ):
self.printErr("%s -> %s ... OK" % (tocompile, outfile))
return True
except py_compile.PyCompileError, e:
self.printCompileErr(e)
return False
def optimize (self, opt):
self.opt = opt
def recursive (self, opt):
self.recurse = opt
def stopOnError (self, opt):
self.stopErr = opt
def main (argc, argv):
checker = ArgChecker()
compiler = checker.checkArgs(argc, argv)
if ( not compiler ):
sys.exit(1)
else:
if ( not compiler.compile() ):
sys.exit(2)
else:
sys.exit(0)
if ( __name__ == "__main__" ):
main(len(sys.argv)-1, sys.argv[1:])