1*cda5da8dSAndroid Build Coastguard Worker"""Routine to "compile" a .py file to a .pyc file. 2*cda5da8dSAndroid Build Coastguard Worker 3*cda5da8dSAndroid Build Coastguard WorkerThis module has intimate knowledge of the format of .pyc files. 4*cda5da8dSAndroid Build Coastguard Worker""" 5*cda5da8dSAndroid Build Coastguard Worker 6*cda5da8dSAndroid Build Coastguard Workerimport enum 7*cda5da8dSAndroid Build Coastguard Workerimport importlib._bootstrap_external 8*cda5da8dSAndroid Build Coastguard Workerimport importlib.machinery 9*cda5da8dSAndroid Build Coastguard Workerimport importlib.util 10*cda5da8dSAndroid Build Coastguard Workerimport os 11*cda5da8dSAndroid Build Coastguard Workerimport os.path 12*cda5da8dSAndroid Build Coastguard Workerimport sys 13*cda5da8dSAndroid Build Coastguard Workerimport traceback 14*cda5da8dSAndroid Build Coastguard Worker 15*cda5da8dSAndroid Build Coastguard Worker__all__ = ["compile", "main", "PyCompileError", "PycInvalidationMode"] 16*cda5da8dSAndroid Build Coastguard Worker 17*cda5da8dSAndroid Build Coastguard Worker 18*cda5da8dSAndroid Build Coastguard Workerclass PyCompileError(Exception): 19*cda5da8dSAndroid Build Coastguard Worker """Exception raised when an error occurs while attempting to 20*cda5da8dSAndroid Build Coastguard Worker compile the file. 21*cda5da8dSAndroid Build Coastguard Worker 22*cda5da8dSAndroid Build Coastguard Worker To raise this exception, use 23*cda5da8dSAndroid Build Coastguard Worker 24*cda5da8dSAndroid Build Coastguard Worker raise PyCompileError(exc_type,exc_value,file[,msg]) 25*cda5da8dSAndroid Build Coastguard Worker 26*cda5da8dSAndroid Build Coastguard Worker where 27*cda5da8dSAndroid Build Coastguard Worker 28*cda5da8dSAndroid Build Coastguard Worker exc_type: exception type to be used in error message 29*cda5da8dSAndroid Build Coastguard Worker type name can be accesses as class variable 30*cda5da8dSAndroid Build Coastguard Worker 'exc_type_name' 31*cda5da8dSAndroid Build Coastguard Worker 32*cda5da8dSAndroid Build Coastguard Worker exc_value: exception value to be used in error message 33*cda5da8dSAndroid Build Coastguard Worker can be accesses as class variable 'exc_value' 34*cda5da8dSAndroid Build Coastguard Worker 35*cda5da8dSAndroid Build Coastguard Worker file: name of file being compiled to be used in error message 36*cda5da8dSAndroid Build Coastguard Worker can be accesses as class variable 'file' 37*cda5da8dSAndroid Build Coastguard Worker 38*cda5da8dSAndroid Build Coastguard Worker msg: string message to be written as error message 39*cda5da8dSAndroid Build Coastguard Worker If no value is given, a default exception message will be 40*cda5da8dSAndroid Build Coastguard Worker given, consistent with 'standard' py_compile output. 41*cda5da8dSAndroid Build Coastguard Worker message (or default) can be accesses as class variable 42*cda5da8dSAndroid Build Coastguard Worker 'msg' 43*cda5da8dSAndroid Build Coastguard Worker 44*cda5da8dSAndroid Build Coastguard Worker """ 45*cda5da8dSAndroid Build Coastguard Worker 46*cda5da8dSAndroid Build Coastguard Worker def __init__(self, exc_type, exc_value, file, msg=''): 47*cda5da8dSAndroid Build Coastguard Worker exc_type_name = exc_type.__name__ 48*cda5da8dSAndroid Build Coastguard Worker if exc_type is SyntaxError: 49*cda5da8dSAndroid Build Coastguard Worker tbtext = ''.join(traceback.format_exception_only( 50*cda5da8dSAndroid Build Coastguard Worker exc_type, exc_value)) 51*cda5da8dSAndroid Build Coastguard Worker errmsg = tbtext.replace('File "<string>"', 'File "%s"' % file) 52*cda5da8dSAndroid Build Coastguard Worker else: 53*cda5da8dSAndroid Build Coastguard Worker errmsg = "Sorry: %s: %s" % (exc_type_name,exc_value) 54*cda5da8dSAndroid Build Coastguard Worker 55*cda5da8dSAndroid Build Coastguard Worker Exception.__init__(self,msg or errmsg,exc_type_name,exc_value,file) 56*cda5da8dSAndroid Build Coastguard Worker 57*cda5da8dSAndroid Build Coastguard Worker self.exc_type_name = exc_type_name 58*cda5da8dSAndroid Build Coastguard Worker self.exc_value = exc_value 59*cda5da8dSAndroid Build Coastguard Worker self.file = file 60*cda5da8dSAndroid Build Coastguard Worker self.msg = msg or errmsg 61*cda5da8dSAndroid Build Coastguard Worker 62*cda5da8dSAndroid Build Coastguard Worker def __str__(self): 63*cda5da8dSAndroid Build Coastguard Worker return self.msg 64*cda5da8dSAndroid Build Coastguard Worker 65*cda5da8dSAndroid Build Coastguard Worker 66*cda5da8dSAndroid Build Coastguard Workerclass PycInvalidationMode(enum.Enum): 67*cda5da8dSAndroid Build Coastguard Worker TIMESTAMP = 1 68*cda5da8dSAndroid Build Coastguard Worker CHECKED_HASH = 2 69*cda5da8dSAndroid Build Coastguard Worker UNCHECKED_HASH = 3 70*cda5da8dSAndroid Build Coastguard Worker 71*cda5da8dSAndroid Build Coastguard Worker 72*cda5da8dSAndroid Build Coastguard Workerdef _get_default_invalidation_mode(): 73*cda5da8dSAndroid Build Coastguard Worker if os.environ.get('SOURCE_DATE_EPOCH'): 74*cda5da8dSAndroid Build Coastguard Worker return PycInvalidationMode.CHECKED_HASH 75*cda5da8dSAndroid Build Coastguard Worker else: 76*cda5da8dSAndroid Build Coastguard Worker return PycInvalidationMode.TIMESTAMP 77*cda5da8dSAndroid Build Coastguard Worker 78*cda5da8dSAndroid Build Coastguard Worker 79*cda5da8dSAndroid Build Coastguard Workerdef compile(file, cfile=None, dfile=None, doraise=False, optimize=-1, 80*cda5da8dSAndroid Build Coastguard Worker invalidation_mode=None, quiet=0): 81*cda5da8dSAndroid Build Coastguard Worker """Byte-compile one Python source file to Python bytecode. 82*cda5da8dSAndroid Build Coastguard Worker 83*cda5da8dSAndroid Build Coastguard Worker :param file: The source file name. 84*cda5da8dSAndroid Build Coastguard Worker :param cfile: The target byte compiled file name. When not given, this 85*cda5da8dSAndroid Build Coastguard Worker defaults to the PEP 3147/PEP 488 location. 86*cda5da8dSAndroid Build Coastguard Worker :param dfile: Purported file name, i.e. the file name that shows up in 87*cda5da8dSAndroid Build Coastguard Worker error messages. Defaults to the source file name. 88*cda5da8dSAndroid Build Coastguard Worker :param doraise: Flag indicating whether or not an exception should be 89*cda5da8dSAndroid Build Coastguard Worker raised when a compile error is found. If an exception occurs and this 90*cda5da8dSAndroid Build Coastguard Worker flag is set to False, a string indicating the nature of the exception 91*cda5da8dSAndroid Build Coastguard Worker will be printed, and the function will return to the caller. If an 92*cda5da8dSAndroid Build Coastguard Worker exception occurs and this flag is set to True, a PyCompileError 93*cda5da8dSAndroid Build Coastguard Worker exception will be raised. 94*cda5da8dSAndroid Build Coastguard Worker :param optimize: The optimization level for the compiler. Valid values 95*cda5da8dSAndroid Build Coastguard Worker are -1, 0, 1 and 2. A value of -1 means to use the optimization 96*cda5da8dSAndroid Build Coastguard Worker level of the current interpreter, as given by -O command line options. 97*cda5da8dSAndroid Build Coastguard Worker :param invalidation_mode: 98*cda5da8dSAndroid Build Coastguard Worker :param quiet: Return full output with False or 0, errors only with 1, 99*cda5da8dSAndroid Build Coastguard Worker and no output with 2. 100*cda5da8dSAndroid Build Coastguard Worker 101*cda5da8dSAndroid Build Coastguard Worker :return: Path to the resulting byte compiled file. 102*cda5da8dSAndroid Build Coastguard Worker 103*cda5da8dSAndroid Build Coastguard Worker Note that it isn't necessary to byte-compile Python modules for 104*cda5da8dSAndroid Build Coastguard Worker execution efficiency -- Python itself byte-compiles a module when 105*cda5da8dSAndroid Build Coastguard Worker it is loaded, and if it can, writes out the bytecode to the 106*cda5da8dSAndroid Build Coastguard Worker corresponding .pyc file. 107*cda5da8dSAndroid Build Coastguard Worker 108*cda5da8dSAndroid Build Coastguard Worker However, if a Python installation is shared between users, it is a 109*cda5da8dSAndroid Build Coastguard Worker good idea to byte-compile all modules upon installation, since 110*cda5da8dSAndroid Build Coastguard Worker other users may not be able to write in the source directories, 111*cda5da8dSAndroid Build Coastguard Worker and thus they won't be able to write the .pyc file, and then 112*cda5da8dSAndroid Build Coastguard Worker they would be byte-compiling every module each time it is loaded. 113*cda5da8dSAndroid Build Coastguard Worker This can slow down program start-up considerably. 114*cda5da8dSAndroid Build Coastguard Worker 115*cda5da8dSAndroid Build Coastguard Worker See compileall.py for a script/module that uses this module to 116*cda5da8dSAndroid Build Coastguard Worker byte-compile all installed files (or all files in selected 117*cda5da8dSAndroid Build Coastguard Worker directories). 118*cda5da8dSAndroid Build Coastguard Worker 119*cda5da8dSAndroid Build Coastguard Worker Do note that FileExistsError is raised if cfile ends up pointing at a 120*cda5da8dSAndroid Build Coastguard Worker non-regular file or symlink. Because the compilation uses a file renaming, 121*cda5da8dSAndroid Build Coastguard Worker the resulting file would be regular and thus not the same type of file as 122*cda5da8dSAndroid Build Coastguard Worker it was previously. 123*cda5da8dSAndroid Build Coastguard Worker """ 124*cda5da8dSAndroid Build Coastguard Worker if invalidation_mode is None: 125*cda5da8dSAndroid Build Coastguard Worker invalidation_mode = _get_default_invalidation_mode() 126*cda5da8dSAndroid Build Coastguard Worker if cfile is None: 127*cda5da8dSAndroid Build Coastguard Worker if optimize >= 0: 128*cda5da8dSAndroid Build Coastguard Worker optimization = optimize if optimize >= 1 else '' 129*cda5da8dSAndroid Build Coastguard Worker cfile = importlib.util.cache_from_source(file, 130*cda5da8dSAndroid Build Coastguard Worker optimization=optimization) 131*cda5da8dSAndroid Build Coastguard Worker else: 132*cda5da8dSAndroid Build Coastguard Worker cfile = importlib.util.cache_from_source(file) 133*cda5da8dSAndroid Build Coastguard Worker if os.path.islink(cfile): 134*cda5da8dSAndroid Build Coastguard Worker msg = ('{} is a symlink and will be changed into a regular file if ' 135*cda5da8dSAndroid Build Coastguard Worker 'import writes a byte-compiled file to it') 136*cda5da8dSAndroid Build Coastguard Worker raise FileExistsError(msg.format(cfile)) 137*cda5da8dSAndroid Build Coastguard Worker elif os.path.exists(cfile) and not os.path.isfile(cfile): 138*cda5da8dSAndroid Build Coastguard Worker msg = ('{} is a non-regular file and will be changed into a regular ' 139*cda5da8dSAndroid Build Coastguard Worker 'one if import writes a byte-compiled file to it') 140*cda5da8dSAndroid Build Coastguard Worker raise FileExistsError(msg.format(cfile)) 141*cda5da8dSAndroid Build Coastguard Worker loader = importlib.machinery.SourceFileLoader('<py_compile>', file) 142*cda5da8dSAndroid Build Coastguard Worker source_bytes = loader.get_data(file) 143*cda5da8dSAndroid Build Coastguard Worker try: 144*cda5da8dSAndroid Build Coastguard Worker code = loader.source_to_code(source_bytes, dfile or file, 145*cda5da8dSAndroid Build Coastguard Worker _optimize=optimize) 146*cda5da8dSAndroid Build Coastguard Worker except Exception as err: 147*cda5da8dSAndroid Build Coastguard Worker py_exc = PyCompileError(err.__class__, err, dfile or file) 148*cda5da8dSAndroid Build Coastguard Worker if quiet < 2: 149*cda5da8dSAndroid Build Coastguard Worker if doraise: 150*cda5da8dSAndroid Build Coastguard Worker raise py_exc 151*cda5da8dSAndroid Build Coastguard Worker else: 152*cda5da8dSAndroid Build Coastguard Worker sys.stderr.write(py_exc.msg + '\n') 153*cda5da8dSAndroid Build Coastguard Worker return 154*cda5da8dSAndroid Build Coastguard Worker try: 155*cda5da8dSAndroid Build Coastguard Worker dirname = os.path.dirname(cfile) 156*cda5da8dSAndroid Build Coastguard Worker if dirname: 157*cda5da8dSAndroid Build Coastguard Worker os.makedirs(dirname) 158*cda5da8dSAndroid Build Coastguard Worker except FileExistsError: 159*cda5da8dSAndroid Build Coastguard Worker pass 160*cda5da8dSAndroid Build Coastguard Worker if invalidation_mode == PycInvalidationMode.TIMESTAMP: 161*cda5da8dSAndroid Build Coastguard Worker source_stats = loader.path_stats(file) 162*cda5da8dSAndroid Build Coastguard Worker bytecode = importlib._bootstrap_external._code_to_timestamp_pyc( 163*cda5da8dSAndroid Build Coastguard Worker code, source_stats['mtime'], source_stats['size']) 164*cda5da8dSAndroid Build Coastguard Worker else: 165*cda5da8dSAndroid Build Coastguard Worker source_hash = importlib.util.source_hash(source_bytes) 166*cda5da8dSAndroid Build Coastguard Worker bytecode = importlib._bootstrap_external._code_to_hash_pyc( 167*cda5da8dSAndroid Build Coastguard Worker code, 168*cda5da8dSAndroid Build Coastguard Worker source_hash, 169*cda5da8dSAndroid Build Coastguard Worker (invalidation_mode == PycInvalidationMode.CHECKED_HASH), 170*cda5da8dSAndroid Build Coastguard Worker ) 171*cda5da8dSAndroid Build Coastguard Worker mode = importlib._bootstrap_external._calc_mode(file) 172*cda5da8dSAndroid Build Coastguard Worker importlib._bootstrap_external._write_atomic(cfile, bytecode, mode) 173*cda5da8dSAndroid Build Coastguard Worker return cfile 174*cda5da8dSAndroid Build Coastguard Worker 175*cda5da8dSAndroid Build Coastguard Worker 176*cda5da8dSAndroid Build Coastguard Workerdef main(): 177*cda5da8dSAndroid Build Coastguard Worker import argparse 178*cda5da8dSAndroid Build Coastguard Worker 179*cda5da8dSAndroid Build Coastguard Worker description = 'A simple command-line interface for py_compile module.' 180*cda5da8dSAndroid Build Coastguard Worker parser = argparse.ArgumentParser(description=description) 181*cda5da8dSAndroid Build Coastguard Worker parser.add_argument( 182*cda5da8dSAndroid Build Coastguard Worker '-q', '--quiet', 183*cda5da8dSAndroid Build Coastguard Worker action='store_true', 184*cda5da8dSAndroid Build Coastguard Worker help='Suppress error output', 185*cda5da8dSAndroid Build Coastguard Worker ) 186*cda5da8dSAndroid Build Coastguard Worker parser.add_argument( 187*cda5da8dSAndroid Build Coastguard Worker 'filenames', 188*cda5da8dSAndroid Build Coastguard Worker nargs='+', 189*cda5da8dSAndroid Build Coastguard Worker help='Files to compile', 190*cda5da8dSAndroid Build Coastguard Worker ) 191*cda5da8dSAndroid Build Coastguard Worker args = parser.parse_args() 192*cda5da8dSAndroid Build Coastguard Worker if args.filenames == ['-']: 193*cda5da8dSAndroid Build Coastguard Worker filenames = [filename.rstrip('\n') for filename in sys.stdin.readlines()] 194*cda5da8dSAndroid Build Coastguard Worker else: 195*cda5da8dSAndroid Build Coastguard Worker filenames = args.filenames 196*cda5da8dSAndroid Build Coastguard Worker for filename in filenames: 197*cda5da8dSAndroid Build Coastguard Worker try: 198*cda5da8dSAndroid Build Coastguard Worker compile(filename, doraise=True) 199*cda5da8dSAndroid Build Coastguard Worker except PyCompileError as error: 200*cda5da8dSAndroid Build Coastguard Worker if args.quiet: 201*cda5da8dSAndroid Build Coastguard Worker parser.exit(1) 202*cda5da8dSAndroid Build Coastguard Worker else: 203*cda5da8dSAndroid Build Coastguard Worker parser.exit(1, error.msg) 204*cda5da8dSAndroid Build Coastguard Worker except OSError as error: 205*cda5da8dSAndroid Build Coastguard Worker if args.quiet: 206*cda5da8dSAndroid Build Coastguard Worker parser.exit(1) 207*cda5da8dSAndroid Build Coastguard Worker else: 208*cda5da8dSAndroid Build Coastguard Worker parser.exit(1, str(error)) 209*cda5da8dSAndroid Build Coastguard Worker 210*cda5da8dSAndroid Build Coastguard Worker 211*cda5da8dSAndroid Build Coastguard Workerif __name__ == "__main__": 212*cda5da8dSAndroid Build Coastguard Worker main() 213