##// END OF EJS Templates
Removed --libpath long name.
Chris Laumann -
Show More
@@ -1,283 +1,283 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 Cython related magics.
3 Cython related magics.
4
4
5 Author:
5 Author:
6 * Brian Granger
6 * Brian Granger
7
7
8 Parts of this code were taken from Cython.inline.
8 Parts of this code were taken from Cython.inline.
9 """
9 """
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2010-2011, IPython Development Team.
11 # Copyright (C) 2010-2011, IPython Development Team.
12 #
12 #
13 # Distributed under the terms of the Modified BSD License.
13 # Distributed under the terms of the Modified BSD License.
14 #
14 #
15 # The full license is in the file COPYING.txt, distributed with this software.
15 # The full license is in the file COPYING.txt, distributed with this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 from __future__ import print_function
18 from __future__ import print_function
19
19
20 import imp
20 import imp
21 import io
21 import io
22 import os
22 import os
23 import re
23 import re
24 import sys
24 import sys
25 import time
25 import time
26
26
27 try:
27 try:
28 import hashlib
28 import hashlib
29 except ImportError:
29 except ImportError:
30 import md5 as hashlib
30 import md5 as hashlib
31
31
32 from distutils.core import Distribution, Extension
32 from distutils.core import Distribution, Extension
33 from distutils.command.build_ext import build_ext
33 from distutils.command.build_ext import build_ext
34
34
35 from IPython.core import display
35 from IPython.core import display
36 from IPython.core import magic_arguments
36 from IPython.core import magic_arguments
37 from IPython.core.magic import Magics, magics_class, cell_magic
37 from IPython.core.magic import Magics, magics_class, cell_magic
38 from IPython.testing.skipdoctest import skip_doctest
38 from IPython.testing.skipdoctest import skip_doctest
39 from IPython.utils import py3compat
39 from IPython.utils import py3compat
40
40
41 import Cython
41 import Cython
42 from Cython.Compiler.Errors import CompileError
42 from Cython.Compiler.Errors import CompileError
43 from Cython.Build.Dependencies import cythonize
43 from Cython.Build.Dependencies import cythonize
44
44
45
45
46 @magics_class
46 @magics_class
47 class CythonMagics(Magics):
47 class CythonMagics(Magics):
48
48
49 def __init__(self, shell):
49 def __init__(self, shell):
50 super(CythonMagics,self).__init__(shell)
50 super(CythonMagics,self).__init__(shell)
51 self._reloads = {}
51 self._reloads = {}
52 self._code_cache = {}
52 self._code_cache = {}
53
53
54 def _import_all(self, module):
54 def _import_all(self, module):
55 for k,v in module.__dict__.items():
55 for k,v in module.__dict__.items():
56 if not k.startswith('__'):
56 if not k.startswith('__'):
57 self.shell.push({k:v})
57 self.shell.push({k:v})
58
58
59 @cell_magic
59 @cell_magic
60 def cython_inline(self, line, cell):
60 def cython_inline(self, line, cell):
61 """Compile and run a Cython code cell using Cython.inline.
61 """Compile and run a Cython code cell using Cython.inline.
62
62
63 This magic simply passes the body of the cell to Cython.inline
63 This magic simply passes the body of the cell to Cython.inline
64 and returns the result. If the variables `a` and `b` are defined
64 and returns the result. If the variables `a` and `b` are defined
65 in the user's namespace, here is a simple example that returns
65 in the user's namespace, here is a simple example that returns
66 their sum::
66 their sum::
67
67
68 %%cython_inline
68 %%cython_inline
69 return a+b
69 return a+b
70
70
71 For most purposes, we recommend the usage of the `%%cython` magic.
71 For most purposes, we recommend the usage of the `%%cython` magic.
72 """
72 """
73 locs = self.shell.user_global_ns
73 locs = self.shell.user_global_ns
74 globs = self.shell.user_ns
74 globs = self.shell.user_ns
75 return Cython.inline(cell, locals=locs, globals=globs)
75 return Cython.inline(cell, locals=locs, globals=globs)
76
76
77 @cell_magic
77 @cell_magic
78 def cython_pyximport(self, line, cell):
78 def cython_pyximport(self, line, cell):
79 """Compile and import a Cython code cell using pyximport.
79 """Compile and import a Cython code cell using pyximport.
80
80
81 The contents of the cell are written to a `.pyx` file in the current
81 The contents of the cell are written to a `.pyx` file in the current
82 working directory, which is then imported using `pyximport`. This
82 working directory, which is then imported using `pyximport`. This
83 magic requires a module name to be passed::
83 magic requires a module name to be passed::
84
84
85 %%cython_pyximport modulename
85 %%cython_pyximport modulename
86 def f(x):
86 def f(x):
87 return 2.0*x
87 return 2.0*x
88
88
89 The compiled module is then imported and all of its symbols are
89 The compiled module is then imported and all of its symbols are
90 injected into the user's namespace. For most purposes, we recommend
90 injected into the user's namespace. For most purposes, we recommend
91 the usage of the `%%cython` magic.
91 the usage of the `%%cython` magic.
92 """
92 """
93 module_name = line.strip()
93 module_name = line.strip()
94 if not module_name:
94 if not module_name:
95 raise ValueError('module name must be given')
95 raise ValueError('module name must be given')
96 fname = module_name + '.pyx'
96 fname = module_name + '.pyx'
97 with io.open(fname, 'w', encoding='utf-8') as f:
97 with io.open(fname, 'w', encoding='utf-8') as f:
98 f.write(cell)
98 f.write(cell)
99 if 'pyximport' not in sys.modules:
99 if 'pyximport' not in sys.modules:
100 import pyximport
100 import pyximport
101 pyximport.install(reload_support=True)
101 pyximport.install(reload_support=True)
102 if module_name in self._reloads:
102 if module_name in self._reloads:
103 module = self._reloads[module_name]
103 module = self._reloads[module_name]
104 reload(module)
104 reload(module)
105 else:
105 else:
106 __import__(module_name)
106 __import__(module_name)
107 module = sys.modules[module_name]
107 module = sys.modules[module_name]
108 self._reloads[module_name] = module
108 self._reloads[module_name] = module
109 self._import_all(module)
109 self._import_all(module)
110
110
111 @magic_arguments.magic_arguments()
111 @magic_arguments.magic_arguments()
112 @magic_arguments.argument(
112 @magic_arguments.argument(
113 '-c', '--compile-args', action='append', default=[],
113 '-c', '--compile-args', action='append', default=[],
114 help="Extra flags to pass to compiler via the `extra_compile_args` "
114 help="Extra flags to pass to compiler via the `extra_compile_args` "
115 "Extension flag (can be specified multiple times)."
115 "Extension flag (can be specified multiple times)."
116 )
116 )
117 @magic_arguments.argument(
117 @magic_arguments.argument(
118 '-la', '--link-args', action='append', default=[],
118 '-la', '--link-args', action='append', default=[],
119 help="Extra flags to pass to linker via the `extra_link_args` "
119 help="Extra flags to pass to linker via the `extra_link_args` "
120 "Extension flag (can be specified multiple times)."
120 "Extension flag (can be specified multiple times)."
121 )
121 )
122 @magic_arguments.argument(
122 @magic_arguments.argument(
123 '-l', '--lib', action='append', default=[],
123 '-l', '--lib', action='append', default=[],
124 help="Add a library to link the extension against (can be specified "
124 help="Add a library to link the extension against (can be specified "
125 "multiple times)."
125 "multiple times)."
126 )
126 )
127 @magic_arguments.argument(
127 @magic_arguments.argument(
128 '-L', '--libpath', action='append', default=[],
128 '-L', dest='library_dirs', metavar='dir', action='append', default=[],
129 help="Add a path to the list of libary directories (can be specified "
129 help="Add a path to the list of libary directories (can be specified "
130 "multiple times)."
130 "multiple times)."
131 )
131 )
132 @magic_arguments.argument(
132 @magic_arguments.argument(
133 '-I', '--include', action='append', default=[],
133 '-I', '--include', action='append', default=[],
134 help="Add a path to the list of include directories (can be specified "
134 help="Add a path to the list of include directories (can be specified "
135 "multiple times)."
135 "multiple times)."
136 )
136 )
137 @magic_arguments.argument(
137 @magic_arguments.argument(
138 '-+', '--cplus', action='store_true', default=False,
138 '-+', '--cplus', action='store_true', default=False,
139 help="Output a C++ rather than C file."
139 help="Output a C++ rather than C file."
140 )
140 )
141 @magic_arguments.argument(
141 @magic_arguments.argument(
142 '-f', '--force', action='store_true', default=False,
142 '-f', '--force', action='store_true', default=False,
143 help="Force the compilation of a new module, even if the source has been "
143 help="Force the compilation of a new module, even if the source has been "
144 "previously compiled."
144 "previously compiled."
145 )
145 )
146 @magic_arguments.argument(
146 @magic_arguments.argument(
147 '-a', '--annotate', action='store_true', default=False,
147 '-a', '--annotate', action='store_true', default=False,
148 help="Produce a colorized HTML version of the source."
148 help="Produce a colorized HTML version of the source."
149 )
149 )
150 @cell_magic
150 @cell_magic
151 def cython(self, line, cell):
151 def cython(self, line, cell):
152 """Compile and import everything from a Cython code cell.
152 """Compile and import everything from a Cython code cell.
153
153
154 The contents of the cell are written to a `.pyx` file in the
154 The contents of the cell are written to a `.pyx` file in the
155 directory `IPYTHONDIR/cython` using a filename with the hash of the
155 directory `IPYTHONDIR/cython` using a filename with the hash of the
156 code. This file is then cythonized and compiled. The resulting module
156 code. This file is then cythonized and compiled. The resulting module
157 is imported and all of its symbols are injected into the user's
157 is imported and all of its symbols are injected into the user's
158 namespace. The usage is similar to that of `%%cython_pyximport` but
158 namespace. The usage is similar to that of `%%cython_pyximport` but
159 you don't have to pass a module name::
159 you don't have to pass a module name::
160
160
161 %%cython
161 %%cython
162 def f(x):
162 def f(x):
163 return 2.0*x
163 return 2.0*x
164 """
164 """
165 args = magic_arguments.parse_argstring(self.cython, line)
165 args = magic_arguments.parse_argstring(self.cython, line)
166 code = cell if cell.endswith('\n') else cell+'\n'
166 code = cell if cell.endswith('\n') else cell+'\n'
167 lib_dir = os.path.join(self.shell.ipython_dir, 'cython')
167 lib_dir = os.path.join(self.shell.ipython_dir, 'cython')
168 quiet = True
168 quiet = True
169 key = code, sys.version_info, sys.executable, Cython.__version__
169 key = code, sys.version_info, sys.executable, Cython.__version__
170
170
171 if not os.path.exists(lib_dir):
171 if not os.path.exists(lib_dir):
172 os.makedirs(lib_dir)
172 os.makedirs(lib_dir)
173
173
174 if args.force:
174 if args.force:
175 # Force a new module name by adding the current time to the
175 # Force a new module name by adding the current time to the
176 # key which is hashed to determine the module name.
176 # key which is hashed to determine the module name.
177 key += time.time(),
177 key += time.time(),
178
178
179 module_name = "_cython_magic_" + hashlib.md5(str(key).encode('utf-8')).hexdigest()
179 module_name = "_cython_magic_" + hashlib.md5(str(key).encode('utf-8')).hexdigest()
180 module_path = os.path.join(lib_dir, module_name + self.so_ext)
180 module_path = os.path.join(lib_dir, module_name + self.so_ext)
181
181
182 have_module = os.path.isfile(module_path)
182 have_module = os.path.isfile(module_path)
183 need_cythonize = not have_module
183 need_cythonize = not have_module
184
184
185 if args.annotate:
185 if args.annotate:
186 html_file = os.path.join(lib_dir, module_name + '.html')
186 html_file = os.path.join(lib_dir, module_name + '.html')
187 if not os.path.isfile(html_file):
187 if not os.path.isfile(html_file):
188 need_cythonize = True
188 need_cythonize = True
189
189
190 if need_cythonize:
190 if need_cythonize:
191 c_include_dirs = args.include
191 c_include_dirs = args.include
192 if 'numpy' in code:
192 if 'numpy' in code:
193 import numpy
193 import numpy
194 c_include_dirs.append(numpy.get_include())
194 c_include_dirs.append(numpy.get_include())
195 pyx_file = os.path.join(lib_dir, module_name + '.pyx')
195 pyx_file = os.path.join(lib_dir, module_name + '.pyx')
196 pyx_file = py3compat.cast_bytes_py2(pyx_file, encoding=sys.getfilesystemencoding())
196 pyx_file = py3compat.cast_bytes_py2(pyx_file, encoding=sys.getfilesystemencoding())
197 with io.open(pyx_file, 'w', encoding='utf-8') as f:
197 with io.open(pyx_file, 'w', encoding='utf-8') as f:
198 f.write(code)
198 f.write(code)
199 extension = Extension(
199 extension = Extension(
200 name = module_name,
200 name = module_name,
201 sources = [pyx_file],
201 sources = [pyx_file],
202 include_dirs = c_include_dirs,
202 include_dirs = c_include_dirs,
203 library_dirs = args.libpath,
203 library_dirs = args.library_dirs,
204 extra_compile_args = args.compile_args,
204 extra_compile_args = args.compile_args,
205 extra_link_args = args.link_args,
205 extra_link_args = args.link_args,
206 libraries = args.lib,
206 libraries = args.lib,
207 language = 'c++' if args.cplus else 'c',
207 language = 'c++' if args.cplus else 'c',
208 )
208 )
209 build_extension = self._get_build_extension()
209 build_extension = self._get_build_extension()
210 try:
210 try:
211 opts = dict(
211 opts = dict(
212 quiet=quiet,
212 quiet=quiet,
213 annotate = args.annotate,
213 annotate = args.annotate,
214 force = True,
214 force = True,
215 )
215 )
216 build_extension.extensions = cythonize([extension], **opts)
216 build_extension.extensions = cythonize([extension], **opts)
217 except CompileError:
217 except CompileError:
218 return
218 return
219
219
220 if not have_module:
220 if not have_module:
221 build_extension.build_temp = os.path.dirname(pyx_file)
221 build_extension.build_temp = os.path.dirname(pyx_file)
222 build_extension.build_lib = lib_dir
222 build_extension.build_lib = lib_dir
223 build_extension.run()
223 build_extension.run()
224 self._code_cache[key] = module_name
224 self._code_cache[key] = module_name
225
225
226 module = imp.load_dynamic(module_name, module_path)
226 module = imp.load_dynamic(module_name, module_path)
227 self._import_all(module)
227 self._import_all(module)
228
228
229 if args.annotate:
229 if args.annotate:
230 try:
230 try:
231 with io.open(html_file, encoding='utf-8') as f:
231 with io.open(html_file, encoding='utf-8') as f:
232 annotated_html = f.read()
232 annotated_html = f.read()
233 except IOError as e:
233 except IOError as e:
234 # File could not be opened. Most likely the user has a version
234 # File could not be opened. Most likely the user has a version
235 # of Cython before 0.15.1 (when `cythonize` learned the
235 # of Cython before 0.15.1 (when `cythonize` learned the
236 # `force` keyword argument) and has already compiled this
236 # `force` keyword argument) and has already compiled this
237 # exact source without annotation.
237 # exact source without annotation.
238 print('Cython completed successfully but the annotated '
238 print('Cython completed successfully but the annotated '
239 'source could not be read.', file=sys.stderr)
239 'source could not be read.', file=sys.stderr)
240 print(e, file=sys.stderr)
240 print(e, file=sys.stderr)
241 else:
241 else:
242 return display.HTML(self.clean_annotated_html(annotated_html))
242 return display.HTML(self.clean_annotated_html(annotated_html))
243
243
244 @property
244 @property
245 def so_ext(self):
245 def so_ext(self):
246 """The extension suffix for compiled modules."""
246 """The extension suffix for compiled modules."""
247 try:
247 try:
248 return self._so_ext
248 return self._so_ext
249 except AttributeError:
249 except AttributeError:
250 self._so_ext = self._get_build_extension().get_ext_filename('')
250 self._so_ext = self._get_build_extension().get_ext_filename('')
251 return self._so_ext
251 return self._so_ext
252
252
253 def _get_build_extension(self):
253 def _get_build_extension(self):
254 dist = Distribution()
254 dist = Distribution()
255 config_files = dist.find_config_files()
255 config_files = dist.find_config_files()
256 try:
256 try:
257 config_files.remove('setup.cfg')
257 config_files.remove('setup.cfg')
258 except ValueError:
258 except ValueError:
259 pass
259 pass
260 dist.parse_config_files(config_files)
260 dist.parse_config_files(config_files)
261 build_extension = build_ext(dist)
261 build_extension = build_ext(dist)
262 build_extension.finalize_options()
262 build_extension.finalize_options()
263 return build_extension
263 return build_extension
264
264
265 @staticmethod
265 @staticmethod
266 def clean_annotated_html(html):
266 def clean_annotated_html(html):
267 """Clean up the annotated HTML source.
267 """Clean up the annotated HTML source.
268
268
269 Strips the link to the generated C or C++ file, which we do not
269 Strips the link to the generated C or C++ file, which we do not
270 present to the user.
270 present to the user.
271 """
271 """
272 r = re.compile('<p>Raw output: <a href="(.*)">(.*)</a>')
272 r = re.compile('<p>Raw output: <a href="(.*)">(.*)</a>')
273 html = '\n'.join(l for l in html.splitlines() if not r.match(l))
273 html = '\n'.join(l for l in html.splitlines() if not r.match(l))
274 return html
274 return html
275
275
276 _loaded = False
276 _loaded = False
277
277
278 def load_ipython_extension(ip):
278 def load_ipython_extension(ip):
279 """Load the extension in IPython."""
279 """Load the extension in IPython."""
280 global _loaded
280 global _loaded
281 if not _loaded:
281 if not _loaded:
282 ip.register_magics(CythonMagics)
282 ip.register_magics(CythonMagics)
283 _loaded = True
283 _loaded = True
General Comments 0
You need to be logged in to leave comments. Login now