6 if sys.version_info[:2] < (2, 3):
7 sys.stderr.write("Sorry, Cython requires Python 2.3 or later\n")
14 from sets import Set as set
23 from Scanning import PyrexScanner, FileSourceDescriptor
24 from Errors import PyrexError, CompileError, InternalError, AbortError, error, warning
25 from Symtab import BuiltinScope, ModuleScope
26 from Cython import Utils
27 from Cython.Utils import open_new_file, replace_suffix
31 module_name_pattern = re.compile(r"[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$")
36 # For quick debugging in pipelines
40 def abort_on_errors(node):
41 # Stop the pipeline if there are any errors.
42 if Errors.num_errors != 0:
43 raise AbortError, "pipeline break"
46 class CompilationData(object):
47 # Bundles the information that is passed from transform to transform.
48 # (For now, this is only)
50 # While Context contains every pxd ever loaded, path information etc.,
51 # this only contains the data related to a single compilation pass
53 # pyx ModuleNode Main code tree of this compilation.
54 # pxds {string : ModuleNode} Trees for the pxds used in the pyx.
55 # codewriter CCodeWriter Where to output final code.
56 # options CompilationOptions
57 # result CompilationResult
60 class Context(object):
61 # This class encapsulates the context needed for compiling
62 # one or more Cython implementation files along with their
63 # associated and imported declaration files. It includes
64 # the root of the module import namespace and the list
65 # of directories to search for include files.
67 # modules {string : ModuleScope}
68 # include_directories [string]
69 # future_directives [object]
70 # language_level int currently 2 or 3 for Python 2/3
72 def __init__(self, include_directories, compiler_directives, cpp=False, language_level=2):
73 import Builtin, CythonScope
74 self.modules = {"__builtin__" : Builtin.builtin_scope}
75 self.modules["cython"] = CythonScope.create_cython_scope(self)
76 self.include_directories = include_directories
77 self.future_directives = set()
78 self.compiler_directives = compiler_directives
81 self.pxds = {} # full name -> node tree
83 standard_include_path = os.path.abspath(os.path.normpath(
84 os.path.join(os.path.dirname(__file__), os.path.pardir, 'Includes')))
85 self.include_directories = include_directories + [standard_include_path]
87 self.set_language_level(language_level)
89 self.gdb_debug_outputwriter = None
91 def set_language_level(self, level):
92 self.language_level = level
94 from Future import print_function, unicode_literals
95 self.future_directives.add(print_function)
96 self.future_directives.add(unicode_literals)
97 self.modules['builtins'] = self.modules['__builtin__']
99 def create_pipeline(self, pxd, py=False):
100 from Visitor import PrintTree
101 from ParseTreeTransforms import WithTransform, NormalizeTree, PostParse, PxdPostParse
102 from ParseTreeTransforms import AnalyseDeclarationsTransform, AnalyseExpressionsTransform
103 from ParseTreeTransforms import CreateClosureClasses, MarkClosureVisitor, DecoratorTransform
104 from ParseTreeTransforms import InterpretCompilerDirectives, TransformBuiltinMethods
105 from ParseTreeTransforms import ExpandInplaceOperators
106 from TypeInference import MarkAssignments, MarkOverflowingArithmetic
107 from ParseTreeTransforms import AlignFunctionDefinitions, GilCheck
108 from AnalysedTreeTransforms import AutoTestDictTransform
109 from AutoDocTransforms import EmbedSignature
110 from Optimize import FlattenInListTransform, SwitchTransform, IterationTransform
111 from Optimize import EarlyReplaceBuiltinCalls, OptimizeBuiltinCalls
112 from Optimize import ConstantFolding, FinalOptimizePhase
113 from Optimize import DropRefcountingTransform
114 from Buffer import IntroduceBufferAuxiliaryVars
115 from ModuleNode import check_c_declarations, check_c_declarations_pxd
118 _check_c_declarations = check_c_declarations_pxd
119 _specific_post_parse = PxdPostParse(self)
121 _check_c_declarations = check_c_declarations
122 _specific_post_parse = None
125 _align_function_definitions = AlignFunctionDefinitions(self)
127 _align_function_definitions = None
132 _specific_post_parse,
133 InterpretCompilerDirectives(self, self.compiler_directives),
134 _align_function_definitions,
135 MarkClosureVisitor(self),
137 FlattenInListTransform(),
139 DecoratorTransform(self),
140 AnalyseDeclarationsTransform(self),
141 AutoTestDictTransform(self),
142 EmbedSignature(self),
143 EarlyReplaceBuiltinCalls(self), ## Necessary?
144 MarkAssignments(self),
145 MarkOverflowingArithmetic(self),
146 TransformBuiltinMethods(self), ## Necessary?
147 IntroduceBufferAuxiliaryVars(self),
148 _check_c_declarations,
149 AnalyseExpressionsTransform(self),
150 CreateClosureClasses(self), ## After all lookups and type inference
151 ExpandInplaceOperators(self),
152 OptimizeBuiltinCalls(self), ## Necessary?
153 IterationTransform(),
155 DropRefcountingTransform(),
156 FinalOptimizePhase(self),
160 def create_pyx_pipeline(self, options, result, py=False):
161 def generate_pyx_code(module_node):
162 module_node.process_implementation(options, result)
163 result.compilation_source = module_node.compilation_source
166 def inject_pxd_code(module_node):
167 from textwrap import dedent
168 stats = module_node.body.stats
169 for name, (statlistnode, scope) in self.pxds.iteritems():
170 # Copy over function nodes to the module
171 # (this seems strange -- I believe the right concept is to split
172 # ModuleNode into a ModuleNode and a CodeGenerator, and tell that
173 # CodeGenerator to generate code both from the pyx and pxd ModuleNodes.
174 stats.append(statlistnode)
175 # Until utility code is moved to code generation phase everywhere,
176 # we need to copy it over to the main scope
177 module_node.scope.utility_code_list.extend(scope.utility_code_list)
181 if options.evaluate_tree_assertions:
182 from Cython.TestUtils import TreeAssertVisitor
183 test_support.append(TreeAssertVisitor())
185 if options.gdb_debug:
186 from Cython.Debugger import DebugWriter
187 from ParseTreeTransforms import DebugTransform
188 self.gdb_debug_outputwriter = DebugWriter.CythonDebugWriter(
190 debug_transform = [DebugTransform(self, options, result)]
194 return list(itertools.chain(
195 [create_parse(self)],
196 self.create_pipeline(pxd=False, py=py),
198 [inject_pxd_code, abort_on_errors],
200 [generate_pyx_code]))
202 def create_pxd_pipeline(self, scope, module_name):
203 def parse_pxd(source_desc):
204 tree = self.parse(source_desc, scope, pxd=True,
205 full_module_name=module_name)
210 from CodeGeneration import ExtractPxdCode
212 # The pxd pipeline ends up with a CCodeWriter containing the
213 # code of the pxd, as well as a pxd scope.
214 return [parse_pxd] + self.create_pipeline(pxd=True) + [
215 ExtractPxdCode(self),
218 def create_py_pipeline(self, options, result):
219 return self.create_pyx_pipeline(options, result, py=True)
222 def process_pxd(self, source_desc, scope, module_name):
223 pipeline = self.create_pxd_pipeline(scope, module_name)
224 result = self.run_pipeline(pipeline, source_desc)
227 def nonfatal_error(self, exc):
228 return Errors.report_error(exc)
230 def run_pipeline(self, pipeline, source):
235 for phase in pipeline:
236 if phase is not None:
237 if DebugFlags.debug_verbose_pipeline:
239 print "Entering pipeline phase %r" % phase
241 if DebugFlags.debug_verbose_pipeline:
242 print " %.3f seconds" % (time() - t)
243 except CompileError, err:
245 Errors.report_error(err)
247 except InternalError, err:
248 # Only raise if there was not an earlier error
249 if Errors.num_errors == 0:
252 except AbortError, err:
256 def find_module(self, module_name,
257 relative_to = None, pos = None, need_pxd = 1):
258 # Finds and returns the module scope corresponding to
259 # the given relative or absolute module name. If this
260 # is the first time the module has been requested, finds
261 # the corresponding .pxd file and process it.
262 # If relative_to is not None, it must be a module scope,
263 # and the module will first be searched for relative to
264 # that module, provided its name is not a dotted name.
265 debug_find_module = 0
266 if debug_find_module:
267 print("Context.find_module: module_name = %s, relative_to = %s, pos = %s, need_pxd = %s" % (
268 module_name, relative_to, pos, need_pxd))
272 if not module_name_pattern.match(module_name):
274 pos = (module_name, 0, 0)
275 raise CompileError(pos,
276 "'%s' is not a valid module name" % module_name)
277 if "." not in module_name and relative_to:
278 if debug_find_module:
279 print("...trying relative import")
280 scope = relative_to.lookup_submodule(module_name)
282 qualified_name = relative_to.qualify_name(module_name)
283 pxd_pathname = self.find_pxd_file(qualified_name, pos)
285 scope = relative_to.find_submodule(module_name)
287 if debug_find_module:
288 print("...trying absolute import")
290 for name in module_name.split("."):
291 scope = scope.find_submodule(name)
292 if debug_find_module:
293 print("...scope =", scope)
294 if not scope.pxd_file_loaded:
295 if debug_find_module:
296 print("...pxd not loaded")
297 scope.pxd_file_loaded = 1
299 if debug_find_module:
300 print("...looking for pxd file")
301 pxd_pathname = self.find_pxd_file(module_name, pos)
302 if debug_find_module:
303 print("......found ", pxd_pathname)
304 if not pxd_pathname and need_pxd:
305 package_pathname = self.search_include_directories(module_name, ".py", pos)
306 if package_pathname and package_pathname.endswith('__init__.py'):
309 error(pos, "'%s.pxd' not found" % module_name)
312 if debug_find_module:
313 print("Context.find_module: Parsing %s" % pxd_pathname)
314 rel_path = module_name.replace('.', os.sep) + os.path.splitext(pxd_pathname)[1]
315 if not pxd_pathname.endswith(rel_path):
316 rel_path = pxd_pathname # safety measure to prevent printing incorrect paths
317 source_desc = FileSourceDescriptor(pxd_pathname, rel_path)
318 err, result = self.process_pxd(source_desc, scope, module_name)
321 (pxd_codenodes, pxd_scope) = result
322 self.pxds[module_name] = (pxd_codenodes, pxd_scope)
327 def find_pxd_file(self, qualified_name, pos):
328 # Search include path for the .pxd file corresponding to the
329 # given fully-qualified module name.
330 # Will find either a dotted filename or a file in a
331 # package directory. If a source file position is given,
332 # the directory containing the source file is searched first
333 # for a dotted filename, and its containing package root
334 # directory is searched first for a non-dotted filename.
335 pxd = self.search_include_directories(qualified_name, ".pxd", pos)
336 if pxd is None: # XXX Keep this until Includes/Deprecated is removed
337 if (qualified_name.startswith('python') or
338 qualified_name in ('stdlib', 'stdio', 'stl')):
339 standard_include_path = os.path.abspath(os.path.normpath(
340 os.path.join(os.path.dirname(__file__), os.path.pardir, 'Includes')))
341 deprecated_include_path = os.path.join(standard_include_path, 'Deprecated')
342 self.include_directories.append(deprecated_include_path)
344 pxd = self.search_include_directories(qualified_name, ".pxd", pos)
346 self.include_directories.pop()
348 name = qualified_name
349 if name.startswith('python'):
350 warning(pos, "'%s' is deprecated, use 'cpython'" % name, 1)
351 elif name in ('stdlib', 'stdio'):
352 warning(pos, "'%s' is deprecated, use 'libc.%s'" % (name, name), 1)
353 elif name in ('stl'):
354 warning(pos, "'%s' is deprecated, use 'libcpp.*.*'" % name, 1)
357 def find_pyx_file(self, qualified_name, pos):
358 # Search include path for the .pyx file corresponding to the
359 # given fully-qualified module name, as for find_pxd_file().
360 return self.search_include_directories(qualified_name, ".pyx", pos)
362 def find_include_file(self, filename, pos):
363 # Search list of include directories for filename.
364 # Reports an error and returns None if not found.
365 path = self.search_include_directories(filename, "", pos,
368 error(pos, "'%s' not found" % filename)
371 def search_include_directories(self, qualified_name, suffix, pos,
373 # Search the list of include directories for the given
374 # file name. If a source file position is given, first
375 # searches the directory containing that file. Returns
376 # None if not found, but does not report an error.
377 # The 'include' option will disable package dereferencing.
378 dirs = self.include_directories
381 if not isinstance(file_desc, FileSourceDescriptor):
382 raise RuntimeError("Only file sources for code supported")
384 dirs = [os.path.dirname(file_desc.filename)] + dirs
386 dirs = [self.find_root_package_dir(file_desc.filename)] + dirs
388 dotted_filename = qualified_name
390 dotted_filename += suffix
392 names = qualified_name.split('.')
393 package_names = names[:-1]
394 module_name = names[-1]
395 module_filename = module_name + suffix
396 package_filename = "__init__" + suffix
399 path = os.path.join(dir, dotted_filename)
400 if Utils.path_exists(path):
403 package_dir = self.check_package_dir(dir, package_names)
404 if package_dir is not None:
405 path = os.path.join(package_dir, module_filename)
406 if Utils.path_exists(path):
408 path = os.path.join(dir, package_dir, module_name,
410 if Utils.path_exists(path):
414 def find_root_package_dir(self, file_path):
415 dir = os.path.dirname(file_path)
416 while self.is_package_dir(dir):
417 parent = os.path.dirname(dir)
423 def check_package_dir(self, dir, package_names):
424 for dirname in package_names:
425 dir = os.path.join(dir, dirname)
426 if not self.is_package_dir(dir):
430 def c_file_out_of_date(self, source_path):
431 c_path = Utils.replace_suffix(source_path, ".c")
432 if not os.path.exists(c_path):
434 c_time = Utils.modification_time(c_path)
435 if Utils.file_newer_than(source_path, c_time):
438 pxd_path = Utils.replace_suffix(source_path, ".pxd")
439 if os.path.exists(pxd_path) and Utils.file_newer_than(pxd_path, c_time):
441 for kind, name in self.read_dependency_file(source_path):
442 if kind == "cimport":
443 dep_path = self.find_pxd_file(name, pos)
444 elif kind == "include":
445 dep_path = self.search_include_directories(name, pos)
448 if dep_path and Utils.file_newer_than(dep_path, c_time):
452 def find_cimported_module_names(self, source_path):
453 return [ name for kind, name in self.read_dependency_file(source_path)
454 if kind == "cimport" ]
456 def is_package_dir(self, dir_path):
457 # Return true if the given directory is a package directory.
458 for filename in ("__init__.py",
461 path = os.path.join(dir_path, filename)
462 if Utils.path_exists(path):
465 def read_dependency_file(self, source_path):
466 dep_path = Utils.replace_suffix(source_path, ".dep")
467 if os.path.exists(dep_path):
468 f = open(dep_path, "rU")
469 chunks = [ line.strip().split(" ", 1)
470 for line in f.readlines()
471 if " " in line.strip() ]
477 def lookup_submodule(self, name):
478 # Look up a top-level module. Returns None if not found.
479 return self.modules.get(name, None)
481 def find_submodule(self, name):
482 # Find a top-level module, creating a new one if needed.
483 scope = self.lookup_submodule(name)
485 scope = ModuleScope(name,
486 parent_module = None, context = self)
487 self.modules[name] = scope
490 def parse(self, source_desc, scope, pxd, full_module_name):
491 if not isinstance(source_desc, FileSourceDescriptor):
492 raise RuntimeError("Only file sources for code supported")
493 source_filename = source_desc.filename
495 # Parse the given source file and return a parse tree.
497 f = Utils.open_source_file(source_filename, "rU")
499 s = PyrexScanner(f, source_desc, source_encoding = f.encoding,
500 scope = scope, context = self)
501 tree = Parsing.p_module(s, pxd, full_module_name)
504 except UnicodeDecodeError, msg:
506 #traceback.print_exc()
507 error((source_desc, 0, 0), "Decoding error, missing or incorrect coding=<encoding-name> at top of source (%s)" % msg)
508 if Errors.num_errors > 0:
512 def extract_module_name(self, path, options):
513 # Find fully_qualified module name from the full pathname
515 dir, filename = os.path.split(path)
516 module_name, _ = os.path.splitext(filename)
517 if "." in module_name:
519 if module_name == "__init__":
520 dir, module_name = os.path.split(dir)
521 names = [module_name]
522 while self.is_package_dir(dir):
523 parent, package_name = os.path.split(dir)
526 names.append(package_name)
529 return ".".join(names)
531 def setup_errors(self, options, result):
532 Errors.reset() # clear any remaining error state
533 if options.use_listing_file:
534 result.listing_file = Utils.replace_suffix(source, ".lis")
535 path = result.listing_file
538 Errors.open_listing_file(path=path,
539 echo_to_stderr=options.errors_to_stderr)
541 def teardown_errors(self, err, options, result):
542 source_desc = result.compilation_source.source_desc
543 if not isinstance(source_desc, FileSourceDescriptor):
544 raise RuntimeError("Only file sources for code supported")
545 Errors.close_listing_file()
546 result.num_errors = Errors.num_errors
547 if result.num_errors > 0:
549 if err and result.c_file:
551 Utils.castrate_file(result.c_file, os.stat(source_desc.filename))
552 except EnvironmentError:
556 def create_parse(context):
558 source_desc = compsrc.source_desc
559 full_module_name = compsrc.full_module_name
560 initial_pos = (source_desc, 1, 0)
561 scope = context.find_module(full_module_name, pos = initial_pos, need_pxd = 0)
562 tree = context.parse(source_desc, scope, pxd = 0, full_module_name = full_module_name)
563 tree.compilation_source = compsrc
569 def create_default_resultobj(compilation_source, options):
570 result = CompilationResult()
571 result.main_source_file = compilation_source.source_desc.filename
572 result.compilation_source = compilation_source
573 source_desc = compilation_source.source_desc
574 if options.output_file:
575 result.c_file = os.path.join(compilation_source.cwd, options.output_file)
581 result.c_file = Utils.replace_suffix(source_desc.filename, c_suffix)
584 def run_pipeline(source, options, full_module_name = None):
586 context = options.create_context()
588 # Set up source object
590 abs_path = os.path.abspath(source)
591 source_ext = os.path.splitext(source)[1]
592 full_module_name = full_module_name or context.extract_module_name(source, options)
593 if options.relative_path_in_code_position_comments:
594 rel_path = full_module_name.replace('.', os.sep) + source_ext
595 if not abs_path.endswith(rel_path):
596 rel_path = source # safety measure to prevent printing incorrect paths
599 source_desc = FileSourceDescriptor(abs_path, rel_path)
600 source = CompilationSource(source_desc, full_module_name, cwd)
602 # Set up result object
603 result = create_default_resultobj(source, options)
606 if source_ext.lower() == '.py':
607 pipeline = context.create_py_pipeline(options, result)
609 pipeline = context.create_pyx_pipeline(options, result)
611 context.setup_errors(options, result)
612 err, enddata = context.run_pipeline(pipeline, source)
613 context.teardown_errors(err, options, result)
617 #------------------------------------------------------------------------
619 # Main Python entry points
621 #------------------------------------------------------------------------
623 class CompilationSource(object):
625 Contains the data necesarry to start up a compilation pipeline for
626 a single compilation unit.
628 def __init__(self, source_desc, full_module_name, cwd):
629 self.source_desc = source_desc
630 self.full_module_name = full_module_name
633 class CompilationOptions(object):
635 Options to the Cython compiler:
637 show_version boolean Display version number
638 use_listing_file boolean Generate a .lis file
639 errors_to_stderr boolean Echo errors to stderr when using .lis
640 include_path [string] Directories to search for include files
641 output_file string Name of generated .c file
642 generate_pxi boolean Generate .pxi file for public declarations
643 recursive boolean Recursively find and compile dependencies
644 timestamps boolean Only compile changed source files. If None,
645 defaults to true when recursive is true.
646 verbose boolean Always print source names being compiled
647 quiet boolean Don't print source names in recursive mode
648 compiler_directives dict Overrides for pragma options (see Options.py)
649 evaluate_tree_assertions boolean Test support: evaluate parse tree assertions
650 language_level integer The Python language level: 2 or 3
652 cplus boolean Compile as c++ code
655 def __init__(self, defaults = None, **kw):
656 self.include_path = []
658 if isinstance(defaults, CompilationOptions):
659 defaults = defaults.__dict__
661 defaults = default_options
662 self.__dict__.update(defaults)
663 self.__dict__.update(kw)
665 def create_context(self):
666 return Context(self.include_path, self.compiler_directives,
667 self.cplus, self.language_level)
670 class CompilationResult(object):
672 Results from the Cython compiler:
674 c_file string or None The generated C source file
675 h_file string or None The generated C header file
676 i_file string or None The generated .pxi file
677 api_file string or None The generated C API .h file
678 listing_file string or None File of error messages
679 object_file string or None Result of compiling the C file
680 extension_file string or None Result of linking the object file
681 num_errors integer Number of compilation errors
682 compilation_source CompilationSource
690 self.listing_file = None
691 self.object_file = None
692 self.extension_file = None
693 self.main_source_file = None
696 class CompilationResultSet(dict):
698 Results from compiling multiple Pyrex source files. A mapping
699 from source file paths to CompilationResult instances. Also
700 has the following attributes:
702 num_errors integer Total number of compilation errors
707 def add(self, source, result):
708 self[source] = result
709 self.num_errors += result.num_errors
712 def compile_single(source, options, full_module_name = None):
714 compile_single(source, options, full_module_name)
716 Compile the given Pyrex implementation file and return a CompilationResult.
717 Always compiles a single file; does not perform timestamp checking or
720 return run_pipeline(source, options, full_module_name)
723 def compile_multiple(sources, options):
725 compile_multiple(sources, options)
727 Compiles the given sequence of Pyrex implementation files and returns
728 a CompilationResultSet. Performs timestamp checking and/or recursion
729 if these are specified in the options.
731 context = options.create_context()
732 sources = [os.path.abspath(source) for source in sources]
734 results = CompilationResultSet()
735 recursive = options.recursive
736 timestamps = options.timestamps
737 if timestamps is None:
738 timestamps = recursive
739 verbose = options.verbose or ((recursive or timestamps) and not options.quiet)
740 for source in sources:
741 if source not in processed:
742 # Compiling multiple sources in one context doesn't quite
744 if not timestamps or context.c_file_out_of_date(source):
746 sys.stderr.write("Compiling %s\n" % source)
748 result = run_pipeline(source, options)
749 results.add(source, result)
750 processed.add(source)
752 for module_name in context.find_cimported_module_names(source):
753 path = context.find_pyx_file(module_name, [source])
758 "Cannot find .pyx file for cimported module '%s'\n" % module_name)
761 def compile(source, options = None, full_module_name = None, **kwds):
763 compile(source [, options], [, <option> = <value>]...)
765 Compile one or more Pyrex implementation files, with optional timestamp
766 checking and recursing on dependecies. The source argument may be a string
767 or a sequence of strings If it is a string and no recursion or timestamp
768 checking is requested, a CompilationResult is returned, otherwise a
769 CompilationResultSet is returned.
771 options = CompilationOptions(defaults = options, **kwds)
772 if isinstance(source, basestring) and not options.timestamps \
773 and not options.recursive:
774 return compile_single(source, options, full_module_name)
776 return compile_multiple(source, options)
778 #------------------------------------------------------------------------
780 # Main command-line entry point
782 #------------------------------------------------------------------------
783 def setuptools_main():
784 return main(command_line = 1)
786 def main(command_line = 0):
790 from CmdLine import parse_command_line
791 options, sources = parse_command_line(args)
793 options = CompilationOptions(default_options)
796 if options.show_version:
797 sys.stderr.write("Cython version %s\n" % Version.version)
798 if options.working_path!="":
799 os.chdir(options.working_path)
801 result = compile(sources, options)
802 if result.num_errors > 0:
804 except (EnvironmentError, PyrexError), e:
805 sys.stderr.write(str(e) + '\n')
812 #------------------------------------------------------------------------
814 # Set the default options depending on the platform
816 #------------------------------------------------------------------------
818 default_options = dict(
820 use_listing_file = 0,
821 errors_to_stderr = 1,
831 compiler_directives = {},
832 evaluate_tree_assertions = False,
833 emit_linenums = False,
834 relative_path_in_code_position_comments = True,