BuildExecutable: use correct executable extension, build dynamically instead of stati...
[cython.git] / Cython / Build / BuildExecutable.py
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):
17     return sysconfig.get_config_var(name) or ''
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')
31 CFLAGS = get_config_var('CFLAGS') + ' ' + os.environ.get('CFLAGS', '')
32 LINKCC = get_config_var('LINKCC')
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=()):
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     cycompile(input_file, compiler_args)
102     ccompile(basename)
103     clink(basename)
104     return basename + EXE_EXT
105
106 def build_and_run(args):
107     """
108     Build an executable program from a Cython module and runs it.
109
110     Arguments after the module name will be passed verbatimely to the
111     program.
112     """
113     cy_args = []
114     last_arg = None
115     for i, arg in enumerate(args):
116         if arg.startswith('-'):
117             cy_args.append(arg)
118         elif last_arg in ('-X', '--directive'):
119             cy_args.append(arg)
120         else:
121             input_file = arg
122             args = args[i+1:]
123             break
124         last_arg = arg
125     else:
126         raise ValueError('no input file provided')
127
128     program_name = build(input_file, cy_args)
129     exec_file(program_name, args)
130
131 if __name__ == '__main__':
132     build_and_run(sys.argv[1:])