Home | History | Annotate | Download | only in Build
      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