setup.py option '--cython-compile-all' to compile more Cython modules during installa...
[cython.git] / pyximport / pyximport.py
1 """
2 Import hooks; when installed with the install() function, these hooks 
3 allow importing .pyx files as if they were Python modules.
4
5 If you want the hook installed every time you run Python
6 you can add it to your Python version by adding these lines to
7 sitecustomize.py (which you can create from scratch in site-packages 
8 if it doesn't exist there or somewhere else on your python path)::
9
10     import pyximport
11     pyximport.install()
12
13 For instance on the Mac with a non-system Python 2.3, you could create
14 sitecustomize.py with only those two lines at
15 /usr/local/lib/python2.3/site-packages/sitecustomize.py .
16
17 A custom distutils.core.Extension instance and setup() args
18 (Distribution) for for the build can be defined by a <modulename>.pyxbld
19 file like:
20
21 # examplemod.pyxbdl
22 def make_ext(modname, pyxfilename):
23     from distutils.extension import Extension
24     return Extension(name = modname,
25                      sources=[pyxfilename, 'hello.c'],
26                      include_dirs=['/myinclude'] )
27 def make_setup_args():
28     return dict(script_args=["--compiler=mingw32"])
29
30 Extra dependencies can be defined by a <modulename>.pyxdep .
31 See README.
32
33 Since Cython 0.11, the :mod:`pyximport` module also has experimental
34 compilation support for normal Python modules.  This allows you to
35 automatically run Cython on every .pyx and .py module that Python
36 imports, including parts of the standard library and installed
37 packages.  Cython will still fail to compile a lot of Python modules,
38 in which case the import mechanism will fall back to loading the
39 Python source modules instead.  The .py import mechanism is installed
40 like this::
41
42     pyximport.install(pyimport = True)
43
44 Running this module as a top-level script will run a test and then print
45 the documentation.
46
47 This code is based on the Py2.3+ import protocol as described in PEP 302.
48 """
49
50 import sys
51 import os
52 import glob
53 import imp
54
55 mod_name = "pyximport"
56
57 assert sys.hexversion >= 0x2030000, "need Python 2.3 or later"
58
59 PYX_EXT = ".pyx"
60 PYXDEP_EXT = ".pyxdep"
61 PYXBLD_EXT = ".pyxbld"
62
63 DEBUG_IMPORT = False
64
65 # Performance problem: for every PYX file that is imported, we will 
66 # invoke the whole distutils infrastructure even if the module is 
67 # already built. It might be more efficient to only do it when the 
68 # mod time of the .pyx is newer than the mod time of the .so but
69 # the question is how to get distutils to tell me the name of the .so
70 # before it builds it. Maybe it is easy...but maybe the peformance
71 # issue isn't real.
72 def _load_pyrex(name, filename):
73     "Load a pyrex file given a name and filename."
74
75 def get_distutils_extension(modname, pyxfilename):
76 #    try:
77 #        import hashlib
78 #    except ImportError:
79 #        import md5 as hashlib
80 #    extra = "_" + hashlib.md5(open(pyxfilename).read()).hexdigest()  
81 #    modname = modname + extra
82     extension_mod,setup_args = handle_special_build(modname, pyxfilename)
83     if not extension_mod:
84         from distutils.extension import Extension
85         extension_mod = Extension(name = modname, sources=[pyxfilename])
86     return extension_mod,setup_args
87
88 def handle_special_build(modname, pyxfilename):
89     special_build = os.path.splitext(pyxfilename)[0] + PYXBLD_EXT
90     ext = None
91     setup_args={}
92     if os.path.exists(special_build): 
93         # globls = {}
94         # locs = {}
95         # execfile(special_build, globls, locs)
96         # ext = locs["make_ext"](modname, pyxfilename)
97         mod = imp.load_source("XXXX", special_build, open(special_build))
98         make_ext = getattr(mod,'make_ext',None)
99         if make_ext:
100             ext = make_ext(modname, pyxfilename)
101             assert ext and ext.sources, ("make_ext in %s did not return Extension" 
102                                          % special_build)
103         make_setup_args = getattr(mod,'make_setup_args',None)
104         if make_setup_args:
105             setup_args = make_setup_args()
106             assert isinstance(setup_args,dict), ("make_setup_args in %s did not return a dict" 
107                                          % special_build)
108         assert set or setup_args, ("neither make_ext nor make_setup_args %s" 
109                                          % special_build)
110         ext.sources = [os.path.join(os.path.dirname(special_build), source) 
111                        for source in ext.sources]
112     return ext, setup_args
113
114 def handle_dependencies(pyxfilename):
115     dependfile = os.path.splitext(pyxfilename)[0] + PYXDEP_EXT
116
117     # by default let distutils decide whether to rebuild on its own
118     # (it has a better idea of what the output file will be)
119
120     # but we know more about dependencies so force a rebuild if 
121     # some of the dependencies are newer than the pyxfile.
122     if os.path.exists(dependfile):
123         depends = open(dependfile).readlines()
124         depends = [depend.strip() for depend in depends]
125
126         # gather dependencies in the "files" variable
127         # the dependency file is itself a dependency
128         files = [dependfile]
129         for depend in depends:
130             fullpath = os.path.join(os.path.dirname(dependfile),
131                                     depend) 
132             files.extend(glob.glob(fullpath))
133
134         # only for unit testing to see we did the right thing
135         _test_files[:] = []  #$pycheck_no
136
137         # if any file that the pyxfile depends upon is newer than
138         # the pyx file, 'touch' the pyx file so that distutils will
139         # be tricked into rebuilding it.
140         for file in files:
141             from distutils.dep_util import newer
142             if newer(file, pyxfilename):
143                 print("Rebuilding because of ", file)
144                 filetime = os.path.getmtime(file)
145                 os.utime(pyxfilename, (filetime, filetime))
146                 _test_files.append(file)
147
148 def build_module(name, pyxfilename, pyxbuild_dir=None):
149     assert os.path.exists(pyxfilename), (
150         "Path does not exist: %s" % pyxfilename)
151     handle_dependencies(pyxfilename)
152
153     extension_mod,setup_args = get_distutils_extension(name, pyxfilename)
154     build_in_temp=pyxargs.build_in_temp
155     sargs=pyxargs.setup_args.copy()
156     sargs.update(setup_args)
157     build_in_temp=sargs.pop('build_in_temp',build_in_temp)
158
159     import pyxbuild
160     so_path = pyxbuild.pyx_to_dll(pyxfilename, extension_mod,
161                                   build_in_temp=build_in_temp,
162                                   pyxbuild_dir=pyxbuild_dir,
163                                   setup_args=sargs,
164                                   reload_support=pyxargs.reload_support)
165     assert os.path.exists(so_path), "Cannot find: %s" % so_path
166     
167     junkpath = os.path.join(os.path.dirname(so_path), name+"_*") #very dangerous with --inplace ?
168     junkstuff = glob.glob(junkpath)
169     for path in junkstuff:
170         if path!=so_path:
171             try:
172                 os.remove(path)
173             except IOError:
174                 print("Couldn't remove ", path)
175
176     return so_path
177
178 def load_module(name, pyxfilename, pyxbuild_dir=None):
179     try:
180         so_path = build_module(name, pyxfilename, pyxbuild_dir)
181         mod = imp.load_dynamic(name, so_path)
182         assert mod.__file__ == so_path, (mod.__file__, so_path)
183     except Exception, e:
184         import traceback
185         raise ImportError("Building module failed: %s" %
186                           traceback.format_exception_only(*sys.exc_info()[:2])),None,sys.exc_info()[2]
187     return mod
188
189
190 # import hooks
191
192 class PyxImporter(object):
193     """A meta-path importer for .pyx files.
194     """
195     def __init__(self, extension=PYX_EXT, pyxbuild_dir=None):
196         self.extension = extension
197         self.pyxbuild_dir = pyxbuild_dir
198
199     def find_module(self, fullname, package_path=None):
200         if fullname in sys.modules  and  not pyxargs.reload_support:
201             return None  # only here when reload() 
202         try:
203             fp, pathname, (ext,mode,ty) = imp.find_module(fullname,package_path)
204             if fp: fp.close()  # Python should offer a Default-Loader to avoid this double find/open!
205             if ty!=imp.C_EXTENSION: # only when an extension, check if we have a .pyx next!
206                 return None
207
208             # find .pyx fast, when .so/.pyd exist --inplace
209             pyxpath = os.path.splitext(pathname)[0]+self.extension
210             if os.path.isfile(pyxpath):
211                 return PyxLoader(fullname, pyxpath,
212                                  pyxbuild_dir=self.pyxbuild_dir)
213             
214             # .so/.pyd's on PATH should not be remote from .pyx's
215             # think no need to implement PyxArgs.importer_search_remote here?
216                 
217         except ImportError:
218             pass
219
220         # searching sys.path ...
221                 
222         #if DEBUG_IMPORT:  print "SEARCHING", fullname, package_path
223         if '.' in fullname: # only when package_path anyway?
224             mod_parts = fullname.split('.')
225             module_name = mod_parts[-1]
226         else:
227             module_name = fullname
228         pyx_module_name = module_name + self.extension
229         # this may work, but it returns the file content, not its path
230         #import pkgutil
231         #pyx_source = pkgutil.get_data(package, pyx_module_name)
232
233         if package_path:
234             paths = package_path
235         else:
236             paths = sys.path
237         join_path = os.path.join
238         is_file = os.path.isfile
239         #is_dir = os.path.isdir
240         sep = os.path.sep
241         for path in paths:
242             if not path:
243                 path = os.getcwd()
244             if is_file(path+sep+pyx_module_name):
245                 return PyxLoader(fullname, join_path(path, pyx_module_name),
246                                  pyxbuild_dir=self.pyxbuild_dir)
247                 
248         # not found, normal package, not a .pyx file, none of our business
249         return None
250
251 class PyImporter(PyxImporter):
252     """A meta-path importer for normal .py files.
253     """
254     def __init__(self, pyxbuild_dir=None):
255         self.super = super(PyImporter, self)
256         self.super.__init__(extension='.py', pyxbuild_dir=pyxbuild_dir)
257         self.uncompilable_modules = {}
258         self.blocked_modules = ['Cython']
259
260     def find_module(self, fullname, package_path=None):
261         if fullname in sys.modules:
262             return None
263         if fullname.startswith('Cython.'):
264             return None
265         if fullname in self.blocked_modules:
266             # prevent infinite recursion
267             return None
268         if DEBUG_IMPORT:
269             print("trying import of module", fullname)
270         if fullname in self.uncompilable_modules:
271             path, last_modified = self.uncompilable_modules[fullname]
272             try:
273                 new_last_modified = os.stat(path).st_mtime
274                 if new_last_modified > last_modified:
275                     # import would fail again
276                     return None
277             except OSError:
278                 # module is no longer where we found it, retry the import
279                 pass
280
281         self.blocked_modules.append(fullname)
282         try:
283             importer = self.super.find_module(fullname, package_path)
284             if importer is not None:
285                 if DEBUG_IMPORT:
286                     print("importer found")
287                 try:
288                     if importer.init_path:
289                         path = importer.init_path
290                     else:
291                         path = importer.path
292                     build_module(fullname, path,
293                                  pyxbuild_dir=self.pyxbuild_dir)
294                 except Exception, e:
295                     if DEBUG_IMPORT:
296                         import traceback
297                         traceback.print_exc()
298                     # build failed, not a compilable Python module
299                     try:
300                         last_modified = os.stat(path).st_mtime
301                     except OSError:
302                         last_modified = 0
303                     self.uncompilable_modules[fullname] = (path, last_modified)
304                     importer = None
305         finally:
306             self.blocked_modules.pop()
307         return importer
308
309 class PyxLoader(object):
310     def __init__(self, fullname, path, init_path=None, pyxbuild_dir=None):
311         self.fullname = fullname
312         self.path, self.init_path = path, init_path
313         self.pyxbuild_dir = pyxbuild_dir
314
315     def load_module(self, fullname):
316         assert self.fullname == fullname, (
317             "invalid module, expected %s, got %s" % (
318             self.fullname, fullname))
319         if self.init_path:
320             # package
321             #print "PACKAGE", fullname
322             module = load_module(fullname, self.init_path,
323                                  self.pyxbuild_dir)
324             module.__path__ = [self.path]
325         else:
326             #print "MODULE", fullname
327             module = load_module(fullname, self.path,
328                                  self.pyxbuild_dir)
329         return module
330
331
332 #install args
333 class PyxArgs(object):
334     build_dir=True
335     build_in_temp=True
336     setup_args={}   #None
337
338 ##pyxargs=None   
339     
340 def install(pyximport=True, pyimport=False, build_dir=None, build_in_temp=True,
341             setup_args={}, reload_support=False ):
342     """Main entry point. Call this to install the .pyx import hook in
343     your meta-path for a single Python process.  If you want it to be
344     installed whenever you use Python, add it to your sitecustomize
345     (as described above).
346
347     You can pass ``pyimport=True`` to also install the .py import hook
348     in your meta-path.  Note, however, that it is highly experimental,
349     will not work for most .py files, and will therefore only slow
350     down your imports.  Use at your own risk.
351
352     By default, compiled modules will end up in a ``.pyxbld``
353     directory in the user's home directory.  Passing a different path
354     as ``build_dir`` will override this.
355
356     ``build_in_temp=False`` will produce the C files locally. Working
357     with complex dependencies and debugging becomes more easy. This
358     can principally interfere with existing files of the same name.
359     build_in_temp can be overriden by <modulename>.pyxbld/make_setup_args()
360     by a dict item of 'build_in_temp'
361
362     ``setup_args``: dict of arguments for Distribution - see
363     distutils.core.setup() . They are extended/overriden by those of
364     <modulename>.pyxbld/make_setup_args()
365
366     ``reload_support``:  Enables support for dynamic
367     reload(<pyxmodulename>), e.g. after a change in the Cython code.
368     Additional files <so_path>.reloadNN may arise on that account, when
369     the previously loaded module file cannot be overwritten.
370     """
371     if not build_dir:
372         build_dir = os.path.expanduser('~/.pyxbld')
373         
374     global pyxargs
375     pyxargs = PyxArgs()  #$pycheck_no
376     pyxargs.build_dir = build_dir
377     pyxargs.build_in_temp = build_in_temp
378     pyxargs.setup_args = (setup_args or {}).copy()
379     pyxargs.reload_support = reload_support
380
381     has_py_importer = False
382     has_pyx_importer = False
383     for importer in sys.meta_path:
384         if isinstance(importer, PyxImporter):
385             if isinstance(importer, PyImporter):
386                 has_py_importer = True
387             else:
388                 has_pyx_importer = True
389
390     if pyimport and not has_py_importer:
391         importer = PyImporter(pyxbuild_dir=build_dir)
392         sys.meta_path.insert(0, importer)
393
394     if pyximport and not has_pyx_importer:
395         importer = PyxImporter(pyxbuild_dir=build_dir)
396         sys.meta_path.append(importer)
397
398
399 # MAIN
400
401 def show_docs():
402     import __main__
403     __main__.__name__ = mod_name
404     for name in dir(__main__):
405         item = getattr(__main__, name)
406         try:
407             setattr(item, "__module__", mod_name)
408         except (AttributeError, TypeError):
409             pass
410     help(__main__)
411
412 if __name__ == '__main__':
413     show_docs()