merged in Vitja's tab removals
[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     testing = '_test_files' in globals()
116     dependfile = os.path.splitext(pyxfilename)[0] + PYXDEP_EXT
117
118     # by default let distutils decide whether to rebuild on its own
119     # (it has a better idea of what the output file will be)
120
121     # but we know more about dependencies so force a rebuild if 
122     # some of the dependencies are newer than the pyxfile.
123     if os.path.exists(dependfile):
124         depends = open(dependfile).readlines()
125         depends = [depend.strip() for depend in depends]
126
127         # gather dependencies in the "files" variable
128         # the dependency file is itself a dependency
129         files = [dependfile]
130         for depend in depends:
131             fullpath = os.path.join(os.path.dirname(dependfile),
132                                     depend) 
133             files.extend(glob.glob(fullpath))
134
135         # only for unit testing to see we did the right thing
136         if testing:
137             _test_files[:] = []  #$pycheck_no
138
139         # if any file that the pyxfile depends upon is newer than
140         # the pyx file, 'touch' the pyx file so that distutils will
141         # be tricked into rebuilding it.
142         for file in files:
143             from distutils.dep_util import newer
144             if newer(file, pyxfilename):
145                 print("Rebuilding because of ", file)
146                 filetime = os.path.getmtime(file)
147                 os.utime(pyxfilename, (filetime, filetime))
148                 if testing:
149                     _test_files.append(file)
150
151 def build_module(name, pyxfilename, pyxbuild_dir=None):
152     assert os.path.exists(pyxfilename), (
153         "Path does not exist: %s" % pyxfilename)
154     handle_dependencies(pyxfilename)
155
156     extension_mod,setup_args = get_distutils_extension(name, pyxfilename)
157     build_in_temp=pyxargs.build_in_temp
158     sargs=pyxargs.setup_args.copy()
159     sargs.update(setup_args)
160     build_in_temp=sargs.pop('build_in_temp',build_in_temp)
161
162     import pyxbuild
163     so_path = pyxbuild.pyx_to_dll(pyxfilename, extension_mod,
164                                   build_in_temp=build_in_temp,
165                                   pyxbuild_dir=pyxbuild_dir,
166                                   setup_args=sargs,
167                                   reload_support=pyxargs.reload_support)
168     assert os.path.exists(so_path), "Cannot find: %s" % so_path
169     
170     junkpath = os.path.join(os.path.dirname(so_path), name+"_*") #very dangerous with --inplace ?
171     junkstuff = glob.glob(junkpath)
172     for path in junkstuff:
173         if path!=so_path:
174             try:
175                 os.remove(path)
176             except IOError:
177                 print("Couldn't remove ", path)
178
179     return so_path
180
181 def load_module(name, pyxfilename, pyxbuild_dir=None):
182     try:
183         so_path = build_module(name, pyxfilename, pyxbuild_dir)
184         mod = imp.load_dynamic(name, so_path)
185         assert mod.__file__ == so_path, (mod.__file__, so_path)
186     except Exception, e:
187         import traceback
188         raise ImportError("Building module failed: %s" %
189                           traceback.format_exception_only(*sys.exc_info()[:2])),None,sys.exc_info()[2]
190     return mod
191
192
193 # import hooks
194
195 class PyxImporter(object):
196     """A meta-path importer for .pyx files.
197     """
198     def __init__(self, extension=PYX_EXT, pyxbuild_dir=None):
199         self.extension = extension
200         self.pyxbuild_dir = pyxbuild_dir
201
202     def find_module(self, fullname, package_path=None):
203         if fullname in sys.modules  and  not pyxargs.reload_support:
204             return None  # only here when reload() 
205         try:
206             fp, pathname, (ext,mode,ty) = imp.find_module(fullname,package_path)
207             if fp: fp.close()  # Python should offer a Default-Loader to avoid this double find/open!
208             if pathname.endswith(self.extension):
209                 return PyxLoader(fullname, pathname,
210                                  pyxbuild_dir=self.pyxbuild_dir)
211             if ty != imp.C_EXTENSION: # only when an extension, check if we have a .pyx next!
212                 return None
213
214             # find .pyx fast, when .so/.pyd exist --inplace
215             pyxpath = os.path.splitext(pathname)[0]+self.extension
216             if os.path.isfile(pyxpath):
217                 return PyxLoader(fullname, pyxpath,
218                                  pyxbuild_dir=self.pyxbuild_dir)
219             
220             # .so/.pyd's on PATH should not be remote from .pyx's
221             # think no need to implement PyxArgs.importer_search_remote here?
222                 
223         except ImportError:
224             pass
225
226         # searching sys.path ...
227                 
228         #if DEBUG_IMPORT:  print "SEARCHING", fullname, package_path
229         if '.' in fullname: # only when package_path anyway?
230             mod_parts = fullname.split('.')
231             module_name = mod_parts[-1]
232         else:
233             module_name = fullname
234         pyx_module_name = module_name + self.extension
235         # this may work, but it returns the file content, not its path
236         #import pkgutil
237         #pyx_source = pkgutil.get_data(package, pyx_module_name)
238
239         if package_path:
240             paths = package_path
241         else:
242             paths = sys.path
243         join_path = os.path.join
244         is_file = os.path.isfile
245         #is_dir = os.path.isdir
246         sep = os.path.sep
247         for path in paths:
248             if not path:
249                 path = os.getcwd()
250             if is_file(path+sep+pyx_module_name):
251                 return PyxLoader(fullname, join_path(path, pyx_module_name),
252                                  pyxbuild_dir=self.pyxbuild_dir)
253                 
254         # not found, normal package, not a .pyx file, none of our business
255         return None
256
257 class PyImporter(PyxImporter):
258     """A meta-path importer for normal .py files.
259     """
260     def __init__(self, pyxbuild_dir=None):
261         self.super = super(PyImporter, self)
262         self.super.__init__(extension='.py', pyxbuild_dir=pyxbuild_dir)
263         self.uncompilable_modules = {}
264         self.blocked_modules = ['Cython', 'distutils.extension',
265                                 'distutils.sysconfig']
266
267     def find_module(self, fullname, package_path=None):
268         if fullname in sys.modules:
269             return None
270         if fullname.startswith('Cython.'):
271             return None
272         if fullname in self.blocked_modules:
273             # prevent infinite recursion
274             return None
275         if DEBUG_IMPORT:
276             print("trying import of module '%s'" % fullname)
277         if fullname in self.uncompilable_modules:
278             path, last_modified = self.uncompilable_modules[fullname]
279             try:
280                 new_last_modified = os.stat(path).st_mtime
281                 if new_last_modified > last_modified:
282                     # import would fail again
283                     return None
284             except OSError:
285                 # module is no longer where we found it, retry the import
286                 pass
287
288         self.blocked_modules.append(fullname)
289         try:
290             importer = self.super.find_module(fullname, package_path)
291             if importer is not None:
292                 try:
293                     if importer.init_path:
294                         path = importer.init_path
295                     else:
296                         path = importer.path
297                     if DEBUG_IMPORT:
298                         print("importer found path %s" % path)
299                     build_module(fullname, path,
300                                  pyxbuild_dir=self.pyxbuild_dir)
301                 except Exception, e:
302                     if DEBUG_IMPORT:
303                         import traceback
304                         traceback.print_exc()
305                     # build failed, not a compilable Python module
306                     try:
307                         last_modified = os.stat(path).st_mtime
308                     except OSError:
309                         last_modified = 0
310                     self.uncompilable_modules[fullname] = (path, last_modified)
311                     importer = None
312         finally:
313             self.blocked_modules.pop()
314         return importer
315
316 class PyxLoader(object):
317     def __init__(self, fullname, path, init_path=None, pyxbuild_dir=None):
318         self.fullname = fullname
319         self.path, self.init_path = path, init_path
320         self.pyxbuild_dir = pyxbuild_dir
321
322     def load_module(self, fullname):
323         assert self.fullname == fullname, (
324             "invalid module, expected %s, got %s" % (
325             self.fullname, fullname))
326         if self.init_path:
327             # package
328             #print "PACKAGE", fullname
329             module = load_module(fullname, self.init_path,
330                                  self.pyxbuild_dir)
331             module.__path__ = [self.path]
332         else:
333             #print "MODULE", fullname
334             module = load_module(fullname, self.path,
335                                  self.pyxbuild_dir)
336         return module
337
338
339 #install args
340 class PyxArgs(object):
341     build_dir=True
342     build_in_temp=True
343     setup_args={}   #None
344
345 ##pyxargs=None   
346     
347 def install(pyximport=True, pyimport=False, build_dir=None, build_in_temp=True,
348             setup_args={}, reload_support=False ):
349     """Main entry point. Call this to install the .pyx import hook in
350     your meta-path for a single Python process.  If you want it to be
351     installed whenever you use Python, add it to your sitecustomize
352     (as described above).
353
354     You can pass ``pyimport=True`` to also install the .py import hook
355     in your meta-path.  Note, however, that it is highly experimental,
356     will not work for most .py files, and will therefore only slow
357     down your imports.  Use at your own risk.
358
359     By default, compiled modules will end up in a ``.pyxbld``
360     directory in the user's home directory.  Passing a different path
361     as ``build_dir`` will override this.
362
363     ``build_in_temp=False`` will produce the C files locally. Working
364     with complex dependencies and debugging becomes more easy. This
365     can principally interfere with existing files of the same name.
366     build_in_temp can be overriden by <modulename>.pyxbld/make_setup_args()
367     by a dict item of 'build_in_temp'
368
369     ``setup_args``: dict of arguments for Distribution - see
370     distutils.core.setup() . They are extended/overriden by those of
371     <modulename>.pyxbld/make_setup_args()
372
373     ``reload_support``:  Enables support for dynamic
374     reload(<pyxmodulename>), e.g. after a change in the Cython code.
375     Additional files <so_path>.reloadNN may arise on that account, when
376     the previously loaded module file cannot be overwritten.
377     """
378     if not build_dir:
379         build_dir = os.path.expanduser('~/.pyxbld')
380         
381     global pyxargs
382     pyxargs = PyxArgs()  #$pycheck_no
383     pyxargs.build_dir = build_dir
384     pyxargs.build_in_temp = build_in_temp
385     pyxargs.setup_args = (setup_args or {}).copy()
386     pyxargs.reload_support = reload_support
387
388     has_py_importer = False
389     has_pyx_importer = False
390     for importer in sys.meta_path:
391         if isinstance(importer, PyxImporter):
392             if isinstance(importer, PyImporter):
393                 has_py_importer = True
394             else:
395                 has_pyx_importer = True
396
397     if pyimport and not has_py_importer:
398         importer = PyImporter(pyxbuild_dir=build_dir)
399         sys.meta_path.insert(0, importer)
400
401     if pyximport and not has_pyx_importer:
402         importer = PyxImporter(pyxbuild_dir=build_dir)
403         sys.meta_path.append(importer)
404
405
406 # MAIN
407
408 def show_docs():
409     import __main__
410     __main__.__name__ = mod_name
411     for name in dir(__main__):
412         item = getattr(__main__, name)
413         try:
414             setattr(item, "__module__", mod_name)
415         except (AttributeError, TypeError):
416             pass
417     help(__main__)
418
419 if __name__ == '__main__':
420     show_docs()