merged in Vitja's tab removals
[cython.git] / pyximport / pyxbuild.py
1 """Build a Pyrex file from .pyx source to .so loadable module using
2 the installed distutils infrastructure. Call:
3
4 out_fname = pyx_to_dll("foo.pyx")
5 """
6 import os
7 import sys
8
9 from distutils.dist import Distribution
10 from distutils.errors import DistutilsArgError, DistutilsError, CCompilerError
11 from distutils.extension import Extension
12 from distutils.util import grok_environment_error
13 try:
14     from Cython.Distutils import build_ext
15     HAS_CYTHON = True
16 except ImportError:
17     HAS_CYTHON = False
18
19 DEBUG = 0
20
21 _reloads={}
22
23 def pyx_to_dll(filename, ext = None, force_rebuild = 0,
24                build_in_temp=False, pyxbuild_dir=None, setup_args={}, reload_support=False):
25     """Compile a PYX file to a DLL and return the name of the generated .so 
26        or .dll ."""
27     assert os.path.exists(filename), "Could not find %s" % os.path.abspath(filename)
28
29     path, name = os.path.split(filename)
30
31     if not ext:
32         modname, extension = os.path.splitext(name)
33         assert extension in (".pyx", ".py"), extension
34         if not HAS_CYTHON:
35             filename = filename[:-len(extension)] + '.c'
36         ext = Extension(name=modname, sources=[filename])
37
38     if not pyxbuild_dir:
39         pyxbuild_dir = os.path.join(path, "_pyxbld")
40
41     script_args=setup_args.get("script_args",[])
42     if DEBUG or "--verbose" in script_args:
43         quiet = "--verbose"
44     else:
45         quiet = "--quiet"
46     args = [quiet, "build_ext"]
47     if force_rebuild:
48         args.append("--force")
49     if HAS_CYTHON and build_in_temp:
50         args.append("--pyrex-c-in-temp")
51     sargs = setup_args.copy()
52     sargs.update(
53         {"script_name": None,
54          "script_args": args + script_args} )
55     dist = Distribution(sargs)
56     if not dist.ext_modules:
57         dist.ext_modules = []
58     dist.ext_modules.append(ext)
59     if HAS_CYTHON:
60         dist.cmdclass = {'build_ext': build_ext}
61     build = dist.get_command_obj('build')
62     build.build_base = pyxbuild_dir
63
64     config_files = dist.find_config_files()
65     try: config_files.remove('setup.cfg')
66     except ValueError: pass
67     dist.parse_config_files(config_files)
68
69     cfgfiles = dist.find_config_files()
70     try: cfgfiles.remove('setup.cfg')
71     except ValueError: pass
72     dist.parse_config_files(cfgfiles)
73     try:
74         ok = dist.parse_command_line()
75     except DistutilsArgError:
76         raise
77
78     if DEBUG:
79         print("options (after parsing command line):")
80         dist.dump_option_dicts()
81     assert ok
82
83
84     try:
85         dist.run_commands()
86         obj_build_ext = dist.get_command_obj("build_ext")
87         so_path = obj_build_ext.get_outputs()[0]
88         if obj_build_ext.inplace:
89             # Python distutils get_outputs()[ returns a wrong so_path 
90             # when --inplace ; see http://bugs.python.org/issue5977
91             # workaround:
92             so_path = os.path.join(os.path.dirname(filename),
93                                    os.path.basename(so_path))
94         if reload_support:
95             org_path = so_path
96             timestamp = os.path.getmtime(org_path)
97             global _reloads
98             last_timestamp, last_path, count = _reloads.get(org_path, (None,None,0) )
99             if last_timestamp == timestamp:
100                 so_path = last_path
101             else:
102                 basename = os.path.basename(org_path)
103                 while count < 100:
104                     count += 1
105                     r_path = os.path.join(obj_build_ext.build_lib,
106                                           basename + '.reload%s'%count)
107                     try:
108                         import shutil # late import / reload_support is: debugging
109                         shutil.copy2(org_path, r_path)
110                         so_path = r_path
111                     except IOError:
112                         continue
113                     break
114                 else:
115                     # used up all 100 slots 
116                     raise ImportError("reload count for %s reached maximum"%org_path)
117                 _reloads[org_path]=(timestamp, so_path, count)
118         return so_path
119     except KeyboardInterrupt:
120         sys.exit(1)
121     except (IOError, os.error):
122         exc = sys.exc_info()[1]
123         error = grok_environment_error(exc)
124
125         if DEBUG:
126             sys.stderr.write(error + "\n")
127         raise
128
129 if __name__=="__main__":
130     pyx_to_dll("dummy.pyx")
131     import test
132