1 """ 2 Compile a Python script into an executable that embeds CPython and run it. 3 Requires CPython to be built as a shared library ('libpythonX.Y'). 4 5 Basic usage: 6 7 python cythonrun somefile.py [ARGS] 8 """ 9 10 DEBUG = True 11 12 import sys 13 import os 14 from distutils import sysconfig 15 16 def get_config_var(name, default=''): 17 return sysconfig.get_config_var(name) or default 18 19 INCDIR = sysconfig.get_python_inc() 20 LIBDIR1 = get_config_var('LIBDIR') 21 LIBDIR2 = get_config_var('LIBPL') 22 PYLIB = get_config_var('LIBRARY') 23 PYLIB_DYN = get_config_var('LDLIBRARY') 24 if PYLIB_DYN == PYLIB: 25 # no shared library 26 PYLIB_DYN = '' 27 else: 28 PYLIB_DYN = os.path.splitext(PYLIB_DYN[3:])[0] # 'lib(XYZ).so' -> XYZ 29 30 CC = get_config_var('CC', os.environ.get('CC', '')) 31 CFLAGS = get_config_var('CFLAGS') + ' ' + os.environ.get('CFLAGS', '') 32 LINKCC = get_config_var('LINKCC', os.environ.get('LINKCC', CC)) 33 LINKFORSHARED = get_config_var('LINKFORSHARED') 34 LIBS = get_config_var('LIBS') 35 SYSLIBS = get_config_var('SYSLIBS') 36 EXE_EXT = sysconfig.get_config_var('EXE') 37 38 def _debug(msg, *args): 39 if DEBUG: 40 if args: 41 msg = msg % args 42 sys.stderr.write(msg + '\n') 43 44 def dump_config(): 45 _debug('INCDIR: %s', INCDIR) 46 _debug('LIBDIR1: %s', LIBDIR1) 47 _debug('LIBDIR2: %s', LIBDIR2) 48 _debug('PYLIB: %s', PYLIB) 49 _debug('PYLIB_DYN: %s', PYLIB_DYN) 50 _debug('CC: %s', CC) 51 _debug('CFLAGS: %s', CFLAGS) 52 _debug('LINKCC: %s', LINKCC) 53 _debug('LINKFORSHARED: %s', LINKFORSHARED) 54 _debug('LIBS: %s', LIBS) 55 _debug('SYSLIBS: %s', SYSLIBS) 56 _debug('EXE_EXT: %s', EXE_EXT) 57 58 def runcmd(cmd, shell=True): 59 if shell: 60 cmd = ' '.join(cmd) 61 _debug(cmd) 62 else: 63 _debug(' '.join(cmd)) 64 65 try: 66 import subprocess 67 except ImportError: # Python 2.3 ... 68 returncode = os.system(cmd) 69 else: 70 returncode = subprocess.call(cmd, shell=shell) 71 72 if returncode: 73 sys.exit(returncode) 74 75 def clink(basename): 76 runcmd([LINKCC, '-o', basename + EXE_EXT, basename+'.o', '-L'+LIBDIR1, '-L'+LIBDIR2] 77 + [PYLIB_DYN and ('-l'+PYLIB_DYN) or os.path.join(LIBDIR1, PYLIB)] 78 + LIBS.split() + SYSLIBS.split() + LINKFORSHARED.split()) 79 80 def ccompile(basename): 81 runcmd([CC, '-c', '-o', basename+'.o', basename+'.c', '-I' + INCDIR] + CFLAGS.split()) 82 83 def cycompile(input_file, options=()): 84 from Cython.Compiler import Version, CmdLine, Main 85 options, sources = CmdLine.parse_command_line(list(options or ()) + ['--embed', input_file]) 86 _debug('Using Cython %s to compile %s', Version.version, input_file) 87 result = Main.compile(sources, options) 88 if result.num_errors > 0: 89 sys.exit(1) 90 91 def exec_file(program_name, args=()): 92 runcmd([os.path.abspath(program_name)] + list(args), shell=False) 93 94 def build(input_file, compiler_args=(), force=False): 95 """ 96 Build an executable program from a Cython module. 97 98 Returns the name of the executable file. 99 """ 100 basename = os.path.splitext(input_file)[0] 101 exe_file = basename + EXE_EXT 102 if not force and os.path.abspath(exe_file) == os.path.abspath(input_file): 103 raise ValueError("Input and output file names are the same, refusing to overwrite") 104 if (not force and os.path.exists(exe_file) and os.path.exists(input_file) 105 and os.path.getmtime(input_file) <= os.path.getmtime(exe_file)): 106 _debug("File is up to date, not regenerating %s", exe_file) 107 return exe_file 108 cycompile(input_file, compiler_args) 109 ccompile(basename) 110 clink(basename) 111 return exe_file 112 113 def build_and_run(args): 114 """ 115 Build an executable program from a Cython module and runs it. 116 117 Arguments after the module name will be passed verbatimely to the 118 program. 119 """ 120 cy_args = [] 121 last_arg = None 122 for i, arg in enumerate(args): 123 if arg.startswith('-'): 124 cy_args.append(arg) 125 elif last_arg in ('-X', '--directive'): 126 cy_args.append(arg) 127 else: 128 input_file = arg 129 args = args[i+1:] 130 break 131 last_arg = arg 132 else: 133 raise ValueError('no input file provided') 134 135 program_name = build(input_file, cy_args) 136 exec_file(program_name, args) 137 138 if __name__ == '__main__': 139 build_and_run(sys.argv[1:]) 140