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
21 from Scanning import PyrexScanner, FileSourceDescriptor
22 from Errors import PyrexError, CompileError, InternalError, error, warning
23 from Symtab import BuiltinScope, ModuleScope
24 from Cython import Utils
25 from Cython.Utils import open_new_file, replace_suffix
29 module_name_pattern = re.compile(r"[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$")
34 # For quick debugging in pipelines
38 def abort_on_errors(node):
39 # Stop the pipeline if there are any errors.
40 if Errors.num_errors != 0:
41 raise InternalError, "abort"
44 class CompilationData(object):
45 # Bundles the information that is passed from transform to transform.
46 # (For now, this is only)
48 # While Context contains every pxd ever loaded, path information etc.,
49 # this only contains the data related to a single compilation pass
51 # pyx ModuleNode Main code tree of this compilation.
52 # pxds {string : ModuleNode} Trees for the pxds used in the pyx.
53 # codewriter CCodeWriter Where to output final code.
54 # options CompilationOptions
55 # result CompilationResult
58 class Context(object):
59 # This class encapsulates the context needed for compiling
60 # one or more Cython implementation files along with their
61 # associated and imported declaration files. It includes
62 # the root of the module import namespace and the list
63 # of directories to search for include files.
65 # modules {string : ModuleScope}
66 # include_directories [string]
67 # future_directives [object]
68 # language_level int currently 2 or 3 for Python 2/3
70 def __init__(self, include_directories, compiler_directives, cpp=False, language_level=2):
71 #self.modules = {"__builtin__" : BuiltinScope()}
72 import Builtin, CythonScope
73 self.modules = {"__builtin__" : Builtin.builtin_scope}
74 self.modules["cython"] = CythonScope.create_cython_scope(self)
75 self.include_directories = include_directories
76 self.future_directives = set()
77 self.compiler_directives = compiler_directives
80 self.pxds = {} # full name -> node tree
82 standard_include_path = os.path.abspath(os.path.normpath(
83 os.path.join(os.path.dirname(__file__), os.path.pardir, 'Includes')))
84 self.include_directories = include_directories + [standard_include_path]
86 self.set_language_level(language_level)
88 def set_language_level(self, level):
89 self.language_level = level
91 from Future import print_function, unicode_literals
92 self.future_directives.add(print_function)
93 self.future_directives.add(unicode_literals)
95 def create_pipeline(self, pxd, py=False):
96 from Visitor import PrintTree
97 from ParseTreeTransforms import WithTransform, NormalizeTree, PostParse, PxdPostParse
98 from ParseTreeTransforms import AnalyseDeclarationsTransform, AnalyseExpressionsTransform
99 from ParseTreeTransforms import CreateClosureClasses, MarkClosureVisitor, DecoratorTransform
100 from ParseTreeTransforms import InterpretCompilerDirectives, TransformBuiltinMethods
101 from ParseTreeTransforms import ExpandInplaceOperators
102 from TypeInference import MarkAssignments, MarkOverflowingArithmetic
103 from ParseTreeTransforms import AlignFunctionDefinitions, GilCheck
104 from AnalysedTreeTransforms import AutoTestDictTransform
105 from AutoDocTransforms import EmbedSignature
106 from Optimize import FlattenInListTransform, SwitchTransform, IterationTransform
107 from Optimize import EarlyReplaceBuiltinCalls, OptimizeBuiltinCalls
108 from Optimize import ConstantFolding, FinalOptimizePhase
109 from Optimize import DropRefcountingTransform
110 from Buffer import IntroduceBufferAuxiliaryVars
111 from ModuleNode import check_c_declarations, check_c_declarations_pxd
114 _check_c_declarations = check_c_declarations_pxd
115 _specific_post_parse = PxdPostParse(self)
117 _check_c_declarations = check_c_declarations
118 _specific_post_parse = None
121 _align_function_definitions = AlignFunctionDefinitions(self)
123 _align_function_definitions = None
128 _specific_post_parse,
129 InterpretCompilerDirectives(self, self.compiler_directives),
130 _align_function_definitions,
131 MarkClosureVisitor(self),
133 FlattenInListTransform(),
135 DecoratorTransform(self),
136 AnalyseDeclarationsTransform(self),
137 CreateClosureClasses(self),
138 AutoTestDictTransform(self),
139 EmbedSignature(self),
140 EarlyReplaceBuiltinCalls(self), ## Necessary?
141 MarkAssignments(self),
142 MarkOverflowingArithmetic(self),
143 TransformBuiltinMethods(self), ## Necessary?
144 IntroduceBufferAuxiliaryVars(self),
145 _check_c_declarations,
146 AnalyseExpressionsTransform(self),
147 ExpandInplaceOperators(self),
148 OptimizeBuiltinCalls(self), ## Necessary?
149 IterationTransform(),
151 DropRefcountingTransform(),
152 FinalOptimizePhase(self),
156 def create_pyx_pipeline(self, options, result, py=False):
157 def generate_pyx_code(module_node):
158 module_node.process_implementation(options, result)
159 result.compilation_source = module_node.compilation_source
162 def inject_pxd_code(module_node):
163 from textwrap import dedent
164 stats = module_node.body.stats
165 for name, (statlistnode, scope) in self.pxds.iteritems():
166 # Copy over function nodes to the module
167 # (this seems strange -- I believe the right concept is to split
168 # ModuleNode into a ModuleNode and a CodeGenerator, and tell that
169 # CodeGenerator to generate code both from the pyx and pxd ModuleNodes.
170 stats.append(statlistnode)
171 # Until utility code is moved to code generation phase everywhere,
172 # we need to copy it over to the main scope
173 module_node.scope.utility_code_list.extend(scope.utility_code_list)
177 if options.evaluate_tree_assertions:
178 from Cython.TestUtils import TreeAssertVisitor
179 test_support.append(TreeAssertVisitor())
183 ] + self.create_pipeline(pxd=False, py=py) + test_support + [
189 def create_pxd_pipeline(self, scope, module_name):
190 def parse_pxd(source_desc):
191 tree = self.parse(source_desc, scope, pxd=True,
192 full_module_name=module_name)
197 from CodeGeneration import ExtractPxdCode
199 # The pxd pipeline ends up with a CCodeWriter containing the
200 # code of the pxd, as well as a pxd scope.
201 return [parse_pxd] + self.create_pipeline(pxd=True) + [
202 ExtractPxdCode(self),
205 def create_py_pipeline(self, options, result):
206 return self.create_pyx_pipeline(options, result, py=True)
209 def process_pxd(self, source_desc, scope, module_name):
210 pipeline = self.create_pxd_pipeline(scope, module_name)
211 result = self.run_pipeline(pipeline, source_desc)
214 def nonfatal_error(self, exc):
215 return Errors.report_error(exc)
217 def run_pipeline(self, pipeline, source):
221 for phase in pipeline:
222 if phase is not None:
223 if DebugFlags.debug_verbose_pipeline:
225 print "Entering pipeline phase %r" % phase
227 if DebugFlags.debug_verbose_pipeline:
228 print " %.3f seconds" % (time() - t)
229 except CompileError, err:
231 Errors.report_error(err)
233 except InternalError, err:
234 # Only raise if there was not an earlier error
235 if Errors.num_errors == 0:
240 def find_module(self, module_name,
241 relative_to = None, pos = None, need_pxd = 1):
242 # Finds and returns the module scope corresponding to
243 # the given relative or absolute module name. If this
244 # is the first time the module has been requested, finds
245 # the corresponding .pxd file and process it.
246 # If relative_to is not None, it must be a module scope,
247 # and the module will first be searched for relative to
248 # that module, provided its name is not a dotted name.
249 debug_find_module = 0
250 if debug_find_module:
251 print("Context.find_module: module_name = %s, relative_to = %s, pos = %s, need_pxd = %s" % (
252 module_name, relative_to, pos, need_pxd))
256 if not module_name_pattern.match(module_name):
258 pos = (module_name, 0, 0)
259 raise CompileError(pos,
260 "'%s' is not a valid module name" % module_name)
261 if "." not in module_name and relative_to:
262 if debug_find_module:
263 print("...trying relative import")
264 scope = relative_to.lookup_submodule(module_name)
266 qualified_name = relative_to.qualify_name(module_name)
267 pxd_pathname = self.find_pxd_file(qualified_name, pos)
269 scope = relative_to.find_submodule(module_name)
271 if debug_find_module:
272 print("...trying absolute import")
274 for name in module_name.split("."):
275 scope = scope.find_submodule(name)
276 if debug_find_module:
277 print("...scope =", scope)
278 if not scope.pxd_file_loaded:
279 if debug_find_module:
280 print("...pxd not loaded")
281 scope.pxd_file_loaded = 1
283 if debug_find_module:
284 print("...looking for pxd file")
285 pxd_pathname = self.find_pxd_file(module_name, pos)
286 if debug_find_module:
287 print("......found ", pxd_pathname)
288 if not pxd_pathname and need_pxd:
289 package_pathname = self.search_include_directories(module_name, ".py", pos)
290 if package_pathname and package_pathname.endswith('__init__.py'):
293 error(pos, "'%s.pxd' not found" % module_name)
296 if debug_find_module:
297 print("Context.find_module: Parsing %s" % pxd_pathname)
298 source_desc = FileSourceDescriptor(pxd_pathname)
299 err, result = self.process_pxd(source_desc, scope, module_name)
302 (pxd_codenodes, pxd_scope) = result
303 self.pxds[module_name] = (pxd_codenodes, pxd_scope)
308 def find_pxd_file(self, qualified_name, pos):
309 # Search include path for the .pxd file corresponding to the
310 # given fully-qualified module name.
311 # Will find either a dotted filename or a file in a
312 # package directory. If a source file position is given,
313 # the directory containing the source file is searched first
314 # for a dotted filename, and its containing package root
315 # directory is searched first for a non-dotted filename.
316 pxd = self.search_include_directories(qualified_name, ".pxd", pos)
317 if pxd is None: # XXX Keep this until Includes/Deprecated is removed
318 if (qualified_name.startswith('python') or
319 qualified_name in ('stdlib', 'stdio', 'stl')):
320 standard_include_path = os.path.abspath(os.path.normpath(
321 os.path.join(os.path.dirname(__file__), os.path.pardir, 'Includes')))
322 deprecated_include_path = os.path.join(standard_include_path, 'Deprecated')
323 self.include_directories.append(deprecated_include_path)
325 pxd = self.search_include_directories(qualified_name, ".pxd", pos)
327 self.include_directories.pop()
329 name = qualified_name
330 if name.startswith('python'):
331 warning(pos, "'%s' is deprecated, use 'cpython'" % name, 1)
332 elif name in ('stdlib', 'stdio'):
333 warning(pos, "'%s' is deprecated, use 'libc.%s'" % (name, name), 1)
334 elif name in ('stl'):
335 warning(pos, "'%s' is deprecated, use 'libcpp.*.*'" % name, 1)
338 def find_pyx_file(self, qualified_name, pos):
339 # Search include path for the .pyx file corresponding to the
340 # given fully-qualified module name, as for find_pxd_file().
341 return self.search_include_directories(qualified_name, ".pyx", pos)
343 def find_include_file(self, filename, pos):
344 # Search list of include directories for filename.
345 # Reports an error and returns None if not found.
346 path = self.search_include_directories(filename, "", pos,
349 error(pos, "'%s' not found" % filename)
352 def search_include_directories(self, qualified_name, suffix, pos,
354 # Search the list of include directories for the given
355 # file name. If a source file position is given, first
356 # searches the directory containing that file. Returns
357 # None if not found, but does not report an error.
358 # The 'include' option will disable package dereferencing.
359 dirs = self.include_directories
362 if not isinstance(file_desc, FileSourceDescriptor):
363 raise RuntimeError("Only file sources for code supported")
365 dirs = [os.path.dirname(file_desc.filename)] + dirs
367 dirs = [self.find_root_package_dir(file_desc.filename)] + dirs
369 dotted_filename = qualified_name
371 dotted_filename += suffix
373 names = qualified_name.split('.')
374 package_names = names[:-1]
375 module_name = names[-1]
376 module_filename = module_name + suffix
377 package_filename = "__init__" + suffix
380 path = os.path.join(dir, dotted_filename)
381 if Utils.path_exists(path):
384 package_dir = self.check_package_dir(dir, package_names)
385 if package_dir is not None:
386 path = os.path.join(package_dir, module_filename)
387 if Utils.path_exists(path):
389 path = os.path.join(dir, package_dir, module_name,
391 if Utils.path_exists(path):
395 def find_root_package_dir(self, file_path):
396 dir = os.path.dirname(file_path)
397 while self.is_package_dir(dir):
398 parent = os.path.dirname(dir)
404 def check_package_dir(self, dir, package_names):
405 for dirname in package_names:
406 dir = os.path.join(dir, dirname)
407 if not self.is_package_dir(dir):
411 def c_file_out_of_date(self, source_path):
412 c_path = Utils.replace_suffix(source_path, ".c")
413 if not os.path.exists(c_path):
415 c_time = Utils.modification_time(c_path)
416 if Utils.file_newer_than(source_path, c_time):
419 pxd_path = Utils.replace_suffix(source_path, ".pxd")
420 if os.path.exists(pxd_path) and Utils.file_newer_than(pxd_path, c_time):
422 for kind, name in self.read_dependency_file(source_path):
423 if kind == "cimport":
424 dep_path = self.find_pxd_file(name, pos)
425 elif kind == "include":
426 dep_path = self.search_include_directories(name, pos)
429 if dep_path and Utils.file_newer_than(dep_path, c_time):
433 def find_cimported_module_names(self, source_path):
434 return [ name for kind, name in self.read_dependency_file(source_path)
435 if kind == "cimport" ]
437 def is_package_dir(self, dir_path):
438 # Return true if the given directory is a package directory.
439 for filename in ("__init__.py",
442 path = os.path.join(dir_path, filename)
443 if Utils.path_exists(path):
446 def read_dependency_file(self, source_path):
447 dep_path = Utils.replace_suffix(source_path, ".dep")
448 if os.path.exists(dep_path):
449 f = open(dep_path, "rU")
450 chunks = [ line.strip().split(" ", 1)
451 for line in f.readlines()
452 if " " in line.strip() ]
458 def lookup_submodule(self, name):
459 # Look up a top-level module. Returns None if not found.
460 return self.modules.get(name, None)
462 def find_submodule(self, name):
463 # Find a top-level module, creating a new one if needed.
464 scope = self.lookup_submodule(name)
466 scope = ModuleScope(name,
467 parent_module = None, context = self)
468 self.modules[name] = scope
471 def parse(self, source_desc, scope, pxd, full_module_name):
472 if not isinstance(source_desc, FileSourceDescriptor):
473 raise RuntimeError("Only file sources for code supported")
474 source_filename = source_desc.filename
476 # Parse the given source file and return a parse tree.
478 f = Utils.open_source_file(source_filename, "rU")
480 s = PyrexScanner(f, source_desc, source_encoding = f.encoding,
481 scope = scope, context = self)
482 tree = Parsing.p_module(s, pxd, full_module_name)
485 except UnicodeDecodeError, msg:
487 #traceback.print_exc()
488 error((source_desc, 0, 0), "Decoding error, missing or incorrect coding=<encoding-name> at top of source (%s)" % msg)
489 if Errors.num_errors > 0:
493 def extract_module_name(self, path, options):
494 # Find fully_qualified module name from the full pathname
496 dir, filename = os.path.split(path)
497 module_name, _ = os.path.splitext(filename)
498 if "." in module_name:
500 if module_name == "__init__":
501 dir, module_name = os.path.split(dir)
502 names = [module_name]
503 while self.is_package_dir(dir):
504 parent, package_name = os.path.split(dir)
507 names.append(package_name)
510 return ".".join(names)
512 def setup_errors(self, options, result):
513 Errors.reset() # clear any remaining error state
514 if options.use_listing_file:
515 result.listing_file = Utils.replace_suffix(source, ".lis")
516 path = result.listing_file
519 Errors.open_listing_file(path=path,
520 echo_to_stderr=options.errors_to_stderr)
522 def teardown_errors(self, err, options, result):
523 source_desc = result.compilation_source.source_desc
524 if not isinstance(source_desc, FileSourceDescriptor):
525 raise RuntimeError("Only file sources for code supported")
526 Errors.close_listing_file()
527 result.num_errors = Errors.num_errors
528 if result.num_errors > 0:
530 if err and result.c_file:
532 Utils.castrate_file(result.c_file, os.stat(source_desc.filename))
533 except EnvironmentError:
537 def create_parse(context):
539 source_desc = compsrc.source_desc
540 full_module_name = compsrc.full_module_name
541 initial_pos = (source_desc, 1, 0)
542 scope = context.find_module(full_module_name, pos = initial_pos, need_pxd = 0)
543 tree = context.parse(source_desc, scope, pxd = 0, full_module_name = full_module_name)
544 tree.compilation_source = compsrc
550 def create_default_resultobj(compilation_source, options):
551 result = CompilationResult()
552 result.main_source_file = compilation_source.source_desc.filename
553 result.compilation_source = compilation_source
554 source_desc = compilation_source.source_desc
555 if options.output_file:
556 result.c_file = os.path.join(compilation_source.cwd, options.output_file)
562 result.c_file = Utils.replace_suffix(source_desc.filename, c_suffix)
565 def run_pipeline(source, options, full_module_name = None):
567 context = Context(options.include_path, options.compiler_directives,
568 options.cplus, options.language_level)
570 # Set up source object
572 source_desc = FileSourceDescriptor(os.path.join(cwd, source))
573 full_module_name = full_module_name or context.extract_module_name(source, options)
574 source = CompilationSource(source_desc, full_module_name, cwd)
576 # Set up result object
577 result = create_default_resultobj(source, options)
580 if source_desc.filename.endswith(".py"):
581 pipeline = context.create_py_pipeline(options, result)
583 pipeline = context.create_pyx_pipeline(options, result)
585 context.setup_errors(options, result)
586 err, enddata = context.run_pipeline(pipeline, source)
587 context.teardown_errors(err, options, result)
591 #------------------------------------------------------------------------
593 # Main Python entry points
595 #------------------------------------------------------------------------
597 class CompilationSource(object):
599 Contains the data necesarry to start up a compilation pipeline for
600 a single compilation unit.
602 def __init__(self, source_desc, full_module_name, cwd):
603 self.source_desc = source_desc
604 self.full_module_name = full_module_name
607 class CompilationOptions(object):
609 Options to the Cython compiler:
611 show_version boolean Display version number
612 use_listing_file boolean Generate a .lis file
613 errors_to_stderr boolean Echo errors to stderr when using .lis
614 include_path [string] Directories to search for include files
615 output_file string Name of generated .c file
616 generate_pxi boolean Generate .pxi file for public declarations
617 recursive boolean Recursively find and compile dependencies
618 timestamps boolean Only compile changed source files. If None,
619 defaults to true when recursive is true.
620 verbose boolean Always print source names being compiled
621 quiet boolean Don't print source names in recursive mode
622 compiler_directives dict Overrides for pragma options (see Options.py)
623 evaluate_tree_assertions boolean Test support: evaluate parse tree assertions
624 language_level integer The Python language level: 2 or 3
626 cplus boolean Compile as c++ code
629 def __init__(self, defaults = None, **kw):
630 self.include_path = []
632 if isinstance(defaults, CompilationOptions):
633 defaults = defaults.__dict__
635 defaults = default_options
636 self.__dict__.update(defaults)
637 self.__dict__.update(kw)
640 class CompilationResult(object):
642 Results from the Cython compiler:
644 c_file string or None The generated C source file
645 h_file string or None The generated C header file
646 i_file string or None The generated .pxi file
647 api_file string or None The generated C API .h file
648 listing_file string or None File of error messages
649 object_file string or None Result of compiling the C file
650 extension_file string or None Result of linking the object file
651 num_errors integer Number of compilation errors
652 compilation_source CompilationSource
660 self.listing_file = None
661 self.object_file = None
662 self.extension_file = None
663 self.main_source_file = None
666 class CompilationResultSet(dict):
668 Results from compiling multiple Pyrex source files. A mapping
669 from source file paths to CompilationResult instances. Also
670 has the following attributes:
672 num_errors integer Total number of compilation errors
677 def add(self, source, result):
678 self[source] = result
679 self.num_errors += result.num_errors
682 def compile_single(source, options, full_module_name = None):
684 compile_single(source, options, full_module_name)
686 Compile the given Pyrex implementation file and return a CompilationResult.
687 Always compiles a single file; does not perform timestamp checking or
690 return run_pipeline(source, options, full_module_name)
693 def compile_multiple(sources, options):
695 compile_multiple(sources, options)
697 Compiles the given sequence of Pyrex implementation files and returns
698 a CompilationResultSet. Performs timestamp checking and/or recursion
699 if these are specified in the options.
701 sources = [os.path.abspath(source) for source in sources]
703 results = CompilationResultSet()
704 recursive = options.recursive
705 timestamps = options.timestamps
706 if timestamps is None:
707 timestamps = recursive
708 verbose = options.verbose or ((recursive or timestamps) and not options.quiet)
709 for source in sources:
710 if source not in processed:
711 # Compiling multiple sources in one context doesn't quite
713 if not timestamps or context.c_file_out_of_date(source):
715 sys.stderr.write("Compiling %s\n" % source)
717 result = run_pipeline(source, options)
718 results.add(source, result)
719 processed.add(source)
721 for module_name in context.find_cimported_module_names(source):
722 path = context.find_pyx_file(module_name, [source])
727 "Cannot find .pyx file for cimported module '%s'\n" % module_name)
730 def compile(source, options = None, full_module_name = None, **kwds):
732 compile(source [, options], [, <option> = <value>]...)
734 Compile one or more Pyrex implementation files, with optional timestamp
735 checking and recursing on dependecies. The source argument may be a string
736 or a sequence of strings If it is a string and no recursion or timestamp
737 checking is requested, a CompilationResult is returned, otherwise a
738 CompilationResultSet is returned.
740 options = CompilationOptions(defaults = options, **kwds)
741 if isinstance(source, basestring) and not options.timestamps \
742 and not options.recursive:
743 return compile_single(source, options, full_module_name)
745 return compile_multiple(source, options)
747 #------------------------------------------------------------------------
749 # Main command-line entry point
751 #------------------------------------------------------------------------
752 def setuptools_main():
753 return main(command_line = 1)
755 def main(command_line = 0):
759 from CmdLine import parse_command_line
760 options, sources = parse_command_line(args)
762 options = CompilationOptions(default_options)
765 if options.show_version:
766 sys.stderr.write("Cython version %s\n" % Version.version)
767 if options.working_path!="":
768 os.chdir(options.working_path)
770 result = compile(sources, options)
771 if result.num_errors > 0:
773 except (EnvironmentError, PyrexError), e:
774 sys.stderr.write(str(e) + '\n')
781 #------------------------------------------------------------------------
783 # Set the default options depending on the platform
785 #------------------------------------------------------------------------
787 default_options = dict(
789 use_listing_file = 0,
790 errors_to_stderr = 1,
800 compiler_directives = {},
801 evaluate_tree_assertions = False,
802 emit_linenums = False,