34d8b22f922b05c422e242044731f15c289d7820
[cython.git] / Cython / Compiler / Main.py
1 #
2 #   Cython Top Level
3 #
4
5 import os, sys, re, codecs
6 if sys.version_info[:2] < (2, 3):
7     sys.stderr.write("Sorry, Cython requires Python 2.3 or later\n")
8     sys.exit(1)
9
10 try:
11     set
12 except NameError:
13     # Python 2.3
14     from sets import Set as set
15
16 import itertools
17 from time import time
18
19 import Code
20 import Errors
21 # Do not import Parsing here, import it when needed, because Parsing imports
22 # Nodes, which globally needs debug command line options initialized to set a
23 # conditional metaclass. These options are processed by CmdLine called from
24 # main() in this file.
25 # import Parsing
26 import Version
27 from Scanning import PyrexScanner, FileSourceDescriptor
28 from Errors import PyrexError, CompileError, InternalError, AbortError, error, warning
29 from Symtab import BuiltinScope, ModuleScope
30 from Cython import Utils
31 from Cython.Utils import open_new_file, replace_suffix
32 import CythonScope
33 import DebugFlags
34
35 module_name_pattern = re.compile(r"[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$")
36
37 verbose = 0
38
39 def dumptree(t):
40     # For quick debugging in pipelines
41     print t.dump()
42     return t
43
44 def abort_on_errors(node):
45     # Stop the pipeline if there are any errors.
46     if Errors.num_errors != 0:
47         raise AbortError, "pipeline break"
48     return node
49
50 class CompilationData(object):
51     #  Bundles the information that is passed from transform to transform.
52     #  (For now, this is only)
53
54     #  While Context contains every pxd ever loaded, path information etc.,
55     #  this only contains the data related to a single compilation pass
56     #
57     #  pyx                   ModuleNode              Main code tree of this compilation.
58     #  pxds                  {string : ModuleNode}   Trees for the pxds used in the pyx.
59     #  codewriter            CCodeWriter             Where to output final code.
60     #  options               CompilationOptions
61     #  result                CompilationResult
62     pass
63
64 class Context(object):
65     #  This class encapsulates the context needed for compiling
66     #  one or more Cython implementation files along with their
67     #  associated and imported declaration files. It includes
68     #  the root of the module import namespace and the list
69     #  of directories to search for include files.
70     #
71     #  modules               {string : ModuleScope}
72     #  include_directories   [string]
73     #  future_directives     [object]
74     #  language_level        int     currently 2 or 3 for Python 2/3
75
76     def __init__(self, include_directories, compiler_directives, cpp=False, language_level=2):
77         import Builtin, CythonScope
78         self.modules = {"__builtin__" : Builtin.builtin_scope}
79         self.modules["cython"] = CythonScope.create_cython_scope(self)
80         self.include_directories = include_directories
81         self.future_directives = set()
82         self.compiler_directives = compiler_directives
83         self.cpp = cpp
84
85         self.pxds = {} # full name -> node tree
86
87         standard_include_path = os.path.abspath(os.path.normpath(
88             os.path.join(os.path.dirname(__file__), os.path.pardir, 'Includes')))
89         self.include_directories = include_directories + [standard_include_path]
90
91         self.set_language_level(language_level)
92
93         self.gdb_debug_outputwriter = None
94
95     def set_language_level(self, level):
96         self.language_level = level
97         if level >= 3:
98             from Future import print_function, unicode_literals
99             self.future_directives.add(print_function)
100             self.future_directives.add(unicode_literals)
101             self.modules['builtins'] = self.modules['__builtin__']
102
103     def create_pipeline(self, pxd, py=False):
104         from Visitor import PrintTree
105         from ParseTreeTransforms import NormalizeTree, PostParse, PxdPostParse
106         from ParseTreeTransforms import AnalyseDeclarationsTransform, AnalyseExpressionsTransform
107         from ParseTreeTransforms import CreateClosureClasses, MarkClosureVisitor, DecoratorTransform
108         from ParseTreeTransforms import InterpretCompilerDirectives, TransformBuiltinMethods
109         from ParseTreeTransforms import ExpandInplaceOperators
110         from TypeInference import MarkAssignments, MarkOverflowingArithmetic
111         from ParseTreeTransforms import AlignFunctionDefinitions, GilCheck
112         from AnalysedTreeTransforms import AutoTestDictTransform
113         from AutoDocTransforms import EmbedSignature
114         from Optimize import FlattenInListTransform, SwitchTransform, IterationTransform
115         from Optimize import EarlyReplaceBuiltinCalls, OptimizeBuiltinCalls
116         from Optimize import ConstantFolding, FinalOptimizePhase
117         from Optimize import DropRefcountingTransform
118         from Buffer import IntroduceBufferAuxiliaryVars
119         from ModuleNode import check_c_declarations, check_c_declarations_pxd
120
121         if pxd:
122             _check_c_declarations = check_c_declarations_pxd
123             _specific_post_parse = PxdPostParse(self)
124         else:
125             _check_c_declarations = check_c_declarations
126             _specific_post_parse = None
127
128         if py and not pxd:
129             _align_function_definitions = AlignFunctionDefinitions(self)
130         else:
131             _align_function_definitions = None
132
133         return [
134             NormalizeTree(self),
135             PostParse(self),
136             _specific_post_parse,
137             InterpretCompilerDirectives(self, self.compiler_directives),
138             MarkClosureVisitor(self),
139             _align_function_definitions,
140             ConstantFolding(),
141             FlattenInListTransform(),
142             DecoratorTransform(self),
143             AnalyseDeclarationsTransform(self),
144             AutoTestDictTransform(self),
145             EmbedSignature(self),
146             EarlyReplaceBuiltinCalls(self),  ## Necessary?
147             MarkAssignments(self),
148             MarkOverflowingArithmetic(self),
149             TransformBuiltinMethods(self),  ## Necessary?
150             IntroduceBufferAuxiliaryVars(self),
151             _check_c_declarations,
152             AnalyseExpressionsTransform(self),
153             CreateClosureClasses(self),  ## After all lookups and type inference
154             ExpandInplaceOperators(self),
155             OptimizeBuiltinCalls(self),  ## Necessary?
156             IterationTransform(),
157             SwitchTransform(),
158             DropRefcountingTransform(),
159             FinalOptimizePhase(self),
160             GilCheck(),
161             ]
162
163     def create_pyx_pipeline(self, options, result, py=False):
164         def generate_pyx_code(module_node):
165             module_node.process_implementation(options, result)
166             result.compilation_source = module_node.compilation_source
167             return result
168
169         def inject_pxd_code(module_node):
170             from textwrap import dedent
171             stats = module_node.body.stats
172             for name, (statlistnode, scope) in self.pxds.iteritems():
173                 # Copy over function nodes to the module
174                 # (this seems strange -- I believe the right concept is to split
175                 # ModuleNode into a ModuleNode and a CodeGenerator, and tell that
176                 # CodeGenerator to generate code both from the pyx and pxd ModuleNodes.
177                  stats.append(statlistnode)
178                  # Until utility code is moved to code generation phase everywhere,
179                  # we need to copy it over to the main scope
180                  module_node.scope.utility_code_list.extend(scope.utility_code_list)
181             return module_node
182
183         test_support = []
184         if options.evaluate_tree_assertions:
185             from Cython.TestUtils import TreeAssertVisitor
186             test_support.append(TreeAssertVisitor())
187
188         if options.gdb_debug:
189             from Cython.Debugger import DebugWriter
190             from ParseTreeTransforms import DebugTransform
191             self.gdb_debug_outputwriter = DebugWriter.CythonDebugWriter(
192                 options.output_dir)
193             debug_transform = [DebugTransform(self, options, result)]
194         else:
195             debug_transform = []
196
197         return list(itertools.chain(
198             [create_parse(self)],
199             self.create_pipeline(pxd=False, py=py),
200             test_support,
201             [inject_pxd_code, abort_on_errors],
202             debug_transform,
203             [generate_pyx_code]))
204
205     def create_pxd_pipeline(self, scope, module_name):
206         def parse_pxd(source_desc):
207             tree = self.parse(source_desc, scope, pxd=True,
208                               full_module_name=module_name)
209             tree.scope = scope
210             tree.is_pxd = True
211             return tree
212
213         from CodeGeneration import ExtractPxdCode
214
215         # The pxd pipeline ends up with a CCodeWriter containing the
216         # code of the pxd, as well as a pxd scope.
217         return [parse_pxd] + self.create_pipeline(pxd=True) + [
218             ExtractPxdCode(self),
219             ]
220
221     def create_py_pipeline(self, options, result):
222         return self.create_pyx_pipeline(options, result, py=True)
223
224
225     def process_pxd(self, source_desc, scope, module_name):
226         pipeline = self.create_pxd_pipeline(scope, module_name)
227         result = self.run_pipeline(pipeline, source_desc)
228         return result
229
230     def nonfatal_error(self, exc):
231         return Errors.report_error(exc)
232
233     def run_pipeline(self, pipeline, source):
234         error = None
235         data = source
236         try:
237             try:
238                 for phase in pipeline:
239                     if phase is not None:
240                         if DebugFlags.debug_verbose_pipeline:
241                             t = time()
242                             print "Entering pipeline phase %r" % phase
243                         data = phase(data)
244                         if DebugFlags.debug_verbose_pipeline:
245                             print "    %.3f seconds" % (time() - t)
246             except CompileError, err:
247                 # err is set
248                 Errors.report_error(err)
249                 error = err
250         except InternalError, err:
251             # Only raise if there was not an earlier error
252             if Errors.num_errors == 0:
253                 raise
254             error = err
255         except AbortError, err:
256             error = err
257         return (error, data)
258
259     def find_module(self, module_name,
260             relative_to = None, pos = None, need_pxd = 1):
261         # Finds and returns the module scope corresponding to
262         # the given relative or absolute module name. If this
263         # is the first time the module has been requested, finds
264         # the corresponding .pxd file and process it.
265         # If relative_to is not None, it must be a module scope,
266         # and the module will first be searched for relative to
267         # that module, provided its name is not a dotted name.
268         debug_find_module = 0
269         if debug_find_module:
270             print("Context.find_module: module_name = %s, relative_to = %s, pos = %s, need_pxd = %s" % (
271                     module_name, relative_to, pos, need_pxd))
272
273         scope = None
274         pxd_pathname = None
275         if not module_name_pattern.match(module_name):
276             if pos is None:
277                 pos = (module_name, 0, 0)
278             raise CompileError(pos,
279                 "'%s' is not a valid module name" % module_name)
280         if "." not in module_name and relative_to:
281             if debug_find_module:
282                 print("...trying relative import")
283             scope = relative_to.lookup_submodule(module_name)
284             if not scope:
285                 qualified_name = relative_to.qualify_name(module_name)
286                 pxd_pathname = self.find_pxd_file(qualified_name, pos)
287                 if pxd_pathname:
288                     scope = relative_to.find_submodule(module_name)
289         if not scope:
290             if debug_find_module:
291                 print("...trying absolute import")
292             scope = self
293             for name in module_name.split("."):
294                 scope = scope.find_submodule(name)
295         if debug_find_module:
296             print("...scope =", scope)
297         if not scope.pxd_file_loaded:
298             if debug_find_module:
299                 print("...pxd not loaded")
300             scope.pxd_file_loaded = 1
301             if not pxd_pathname:
302                 if debug_find_module:
303                     print("...looking for pxd file")
304                 pxd_pathname = self.find_pxd_file(module_name, pos)
305                 if debug_find_module:
306                     print("......found ", pxd_pathname)
307                 if not pxd_pathname and need_pxd:
308                     package_pathname = self.search_include_directories(module_name, ".py", pos)
309                     if package_pathname and package_pathname.endswith('__init__.py'):
310                         pass
311                     else:
312                         error(pos, "'%s.pxd' not found" % module_name)
313             if pxd_pathname:
314                 try:
315                     if debug_find_module:
316                         print("Context.find_module: Parsing %s" % pxd_pathname)
317                     rel_path = module_name.replace('.', os.sep) + os.path.splitext(pxd_pathname)[1]
318                     if not pxd_pathname.endswith(rel_path):
319                         rel_path = pxd_pathname # safety measure to prevent printing incorrect paths
320                     source_desc = FileSourceDescriptor(pxd_pathname, rel_path)
321                     err, result = self.process_pxd(source_desc, scope, module_name)
322                     if err:
323                         raise err
324                     (pxd_codenodes, pxd_scope) = result
325                     self.pxds[module_name] = (pxd_codenodes, pxd_scope)
326                 except CompileError:
327                     pass
328         return scope
329
330     def find_pxd_file(self, qualified_name, pos):
331         # Search include path for the .pxd file corresponding to the
332         # given fully-qualified module name.
333         # Will find either a dotted filename or a file in a
334         # package directory. If a source file position is given,
335         # the directory containing the source file is searched first
336         # for a dotted filename, and its containing package root
337         # directory is searched first for a non-dotted filename.
338         pxd = self.search_include_directories(qualified_name, ".pxd", pos)
339         if pxd is None: # XXX Keep this until Includes/Deprecated is removed
340             if (qualified_name.startswith('python') or
341                 qualified_name in ('stdlib', 'stdio', 'stl')):
342                 standard_include_path = os.path.abspath(os.path.normpath(
343                         os.path.join(os.path.dirname(__file__), os.path.pardir, 'Includes')))
344                 deprecated_include_path = os.path.join(standard_include_path, 'Deprecated')
345                 self.include_directories.append(deprecated_include_path)
346                 try:
347                     pxd = self.search_include_directories(qualified_name, ".pxd", pos)
348                 finally:
349                     self.include_directories.pop()
350                 if pxd:
351                     name = qualified_name
352                     if name.startswith('python'):
353                         warning(pos, "'%s' is deprecated, use 'cpython'" % name, 1)
354                     elif name in ('stdlib', 'stdio'):
355                         warning(pos, "'%s' is deprecated, use 'libc.%s'" % (name, name), 1)
356                     elif name in ('stl'):
357                         warning(pos, "'%s' is deprecated, use 'libcpp.*.*'" % name, 1)
358         return pxd
359
360     def find_pyx_file(self, qualified_name, pos):
361         # Search include path for the .pyx file corresponding to the
362         # given fully-qualified module name, as for find_pxd_file().
363         return self.search_include_directories(qualified_name, ".pyx", pos)
364
365     def find_include_file(self, filename, pos):
366         # Search list of include directories for filename.
367         # Reports an error and returns None if not found.
368         path = self.search_include_directories(filename, "", pos,
369                                                include=True)
370         if not path:
371             error(pos, "'%s' not found" % filename)
372         return path
373
374     def search_include_directories(self, qualified_name, suffix, pos,
375                                    include=False):
376         # Search the list of include directories for the given
377         # file name. If a source file position is given, first
378         # searches the directory containing that file. Returns
379         # None if not found, but does not report an error.
380         # The 'include' option will disable package dereferencing.
381         dirs = self.include_directories
382         if pos:
383             file_desc = pos[0]
384             if not isinstance(file_desc, FileSourceDescriptor):
385                 raise RuntimeError("Only file sources for code supported")
386             if include:
387                 dirs = [os.path.dirname(file_desc.filename)] + dirs
388             else:
389                 dirs = [self.find_root_package_dir(file_desc.filename)] + dirs
390
391         dotted_filename = qualified_name
392         if suffix:
393             dotted_filename += suffix
394         if not include:
395             names = qualified_name.split('.')
396             package_names = names[:-1]
397             module_name = names[-1]
398             module_filename = module_name + suffix
399             package_filename = "__init__" + suffix
400
401         for dir in dirs:
402             path = os.path.join(dir, dotted_filename)
403             if Utils.path_exists(path):
404                 return path
405             if not include:
406                 package_dir = self.check_package_dir(dir, package_names)
407                 if package_dir is not None:
408                     path = os.path.join(package_dir, module_filename)
409                     if Utils.path_exists(path):
410                         return path
411                     path = os.path.join(dir, package_dir, module_name,
412                                         package_filename)
413                     if Utils.path_exists(path):
414                         return path
415         return None
416
417     def find_root_package_dir(self, file_path):
418         dir = os.path.dirname(file_path)
419         while self.is_package_dir(dir):
420             parent = os.path.dirname(dir)
421             if parent == dir:
422                 break
423             dir = parent
424         return dir
425
426     def check_package_dir(self, dir, package_names):
427         for dirname in package_names:
428             dir = os.path.join(dir, dirname)
429             if not self.is_package_dir(dir):
430                 return None
431         return dir
432
433     def c_file_out_of_date(self, source_path):
434         c_path = Utils.replace_suffix(source_path, ".c")
435         if not os.path.exists(c_path):
436             return 1
437         c_time = Utils.modification_time(c_path)
438         if Utils.file_newer_than(source_path, c_time):
439             return 1
440         pos = [source_path]
441         pxd_path = Utils.replace_suffix(source_path, ".pxd")
442         if os.path.exists(pxd_path) and Utils.file_newer_than(pxd_path, c_time):
443             return 1
444         for kind, name in self.read_dependency_file(source_path):
445             if kind == "cimport":
446                 dep_path = self.find_pxd_file(name, pos)
447             elif kind == "include":
448                 dep_path = self.search_include_directories(name, pos)
449             else:
450                 continue
451             if dep_path and Utils.file_newer_than(dep_path, c_time):
452                 return 1
453         return 0
454
455     def find_cimported_module_names(self, source_path):
456         return [ name for kind, name in self.read_dependency_file(source_path)
457                  if kind == "cimport" ]
458
459     def is_package_dir(self, dir_path):
460         #  Return true if the given directory is a package directory.
461         for filename in ("__init__.py",
462                          "__init__.pyx",
463                          "__init__.pxd"):
464             path = os.path.join(dir_path, filename)
465             if Utils.path_exists(path):
466                 return 1
467
468     def read_dependency_file(self, source_path):
469         dep_path = Utils.replace_suffix(source_path, ".dep")
470         if os.path.exists(dep_path):
471             f = open(dep_path, "rU")
472             chunks = [ line.strip().split(" ", 1)
473                        for line in f.readlines()
474                        if " " in line.strip() ]
475             f.close()
476             return chunks
477         else:
478             return ()
479
480     def lookup_submodule(self, name):
481         # Look up a top-level module. Returns None if not found.
482         return self.modules.get(name, None)
483
484     def find_submodule(self, name):
485         # Find a top-level module, creating a new one if needed.
486         scope = self.lookup_submodule(name)
487         if not scope:
488             scope = ModuleScope(name,
489                 parent_module = None, context = self)
490             self.modules[name] = scope
491         return scope
492
493     def parse(self, source_desc, scope, pxd, full_module_name):
494         if not isinstance(source_desc, FileSourceDescriptor):
495             raise RuntimeError("Only file sources for code supported")
496         source_filename = source_desc.filename
497         scope.cpp = self.cpp
498         # Parse the given source file and return a parse tree.
499         try:
500             f = Utils.open_source_file(source_filename, "rU")
501             try:
502                 import Parsing
503                 s = PyrexScanner(f, source_desc, source_encoding = f.encoding,
504                                  scope = scope, context = self)
505                 tree = Parsing.p_module(s, pxd, full_module_name)
506             finally:
507                 f.close()
508         except UnicodeDecodeError, msg:
509             #import traceback
510             #traceback.print_exc()
511             error((source_desc, 0, 0), "Decoding error, missing or incorrect coding=<encoding-name> at top of source (%s)" % msg)
512         if Errors.num_errors > 0:
513             raise CompileError
514         return tree
515
516     def extract_module_name(self, path, options):
517         # Find fully_qualified module name from the full pathname
518         # of a source file.
519         dir, filename = os.path.split(path)
520         module_name, _ = os.path.splitext(filename)
521         if "." in module_name:
522             return module_name
523         if module_name == "__init__":
524             dir, module_name = os.path.split(dir)
525         names = [module_name]
526         while self.is_package_dir(dir):
527             parent, package_name = os.path.split(dir)
528             if parent == dir:
529                 break
530             names.append(package_name)
531             dir = parent
532         names.reverse()
533         return ".".join(names)
534
535     def setup_errors(self, options, result):
536         Errors.reset() # clear any remaining error state
537         if options.use_listing_file:
538             result.listing_file = Utils.replace_suffix(source, ".lis")
539             path = result.listing_file
540         else:
541             path = None
542         Errors.open_listing_file(path=path,
543                                  echo_to_stderr=options.errors_to_stderr)
544
545     def teardown_errors(self, err, options, result):
546         source_desc = result.compilation_source.source_desc
547         if not isinstance(source_desc, FileSourceDescriptor):
548             raise RuntimeError("Only file sources for code supported")
549         Errors.close_listing_file()
550         result.num_errors = Errors.num_errors
551         if result.num_errors > 0:
552             err = True
553         if err and result.c_file:
554             try:
555                 Utils.castrate_file(result.c_file, os.stat(source_desc.filename))
556             except EnvironmentError:
557                 pass
558             result.c_file = None
559
560 def create_parse(context):
561     def parse(compsrc):
562         source_desc = compsrc.source_desc
563         full_module_name = compsrc.full_module_name
564         initial_pos = (source_desc, 1, 0)
565         scope = context.find_module(full_module_name, pos = initial_pos, need_pxd = 0)
566         tree = context.parse(source_desc, scope, pxd = 0, full_module_name = full_module_name)
567         tree.compilation_source = compsrc
568         tree.scope = scope
569         tree.is_pxd = False
570         return tree
571     return parse
572
573 def create_default_resultobj(compilation_source, options):
574     result = CompilationResult()
575     result.main_source_file = compilation_source.source_desc.filename
576     result.compilation_source = compilation_source
577     source_desc = compilation_source.source_desc
578     if options.output_file:
579         result.c_file = os.path.join(compilation_source.cwd, options.output_file)
580     else:
581         if options.cplus:
582             c_suffix = ".cpp"
583         else:
584             c_suffix = ".c"
585         result.c_file = Utils.replace_suffix(source_desc.filename, c_suffix)
586     return result
587
588 def run_pipeline(source, options, full_module_name = None):
589     # Set up context
590     context = options.create_context()
591
592     # Set up source object
593     cwd = os.getcwd()
594     abs_path = os.path.abspath(source)
595     source_ext = os.path.splitext(source)[1]
596     full_module_name = full_module_name or context.extract_module_name(source, options)
597     if options.relative_path_in_code_position_comments:
598         rel_path = full_module_name.replace('.', os.sep) + source_ext
599         if not abs_path.endswith(rel_path):
600             rel_path = source # safety measure to prevent printing incorrect paths
601     else:
602         rel_path = abs_path
603     source_desc = FileSourceDescriptor(abs_path, rel_path)
604     source = CompilationSource(source_desc, full_module_name, cwd)
605
606     # Set up result object
607     result = create_default_resultobj(source, options)
608
609     if options.annotate is None:
610         # By default, decide based on whether an html file already exists.
611         html_filename = os.path.splitext(result.c_file)[0] + ".html"
612         if os.path.exists(html_filename):
613             line = codecs.open(html_filename, "r", encoding="UTF-8").readline()
614             if line.startswith(u'<!-- Generated by Cython'):
615                 options.annotate = True
616
617     # Get pipeline
618     if source_ext.lower() == '.py':
619         pipeline = context.create_py_pipeline(options, result)
620     else:
621         pipeline = context.create_pyx_pipeline(options, result)
622
623     context.setup_errors(options, result)
624     err, enddata = context.run_pipeline(pipeline, source)
625     context.teardown_errors(err, options, result)
626     return result
627
628
629 #------------------------------------------------------------------------
630 #
631 #  Main Python entry points
632 #
633 #------------------------------------------------------------------------
634
635 class CompilationSource(object):
636     """
637     Contains the data necesarry to start up a compilation pipeline for
638     a single compilation unit.
639     """
640     def __init__(self, source_desc, full_module_name, cwd):
641         self.source_desc = source_desc
642         self.full_module_name = full_module_name
643         self.cwd = cwd
644
645 class CompilationOptions(object):
646     """
647     Options to the Cython compiler:
648
649     show_version      boolean   Display version number
650     use_listing_file  boolean   Generate a .lis file
651     errors_to_stderr  boolean   Echo errors to stderr when using .lis
652     include_path      [string]  Directories to search for include files
653     output_file       string    Name of generated .c file
654     generate_pxi      boolean   Generate .pxi file for public declarations
655     recursive         boolean   Recursively find and compile dependencies
656     timestamps        boolean   Only compile changed source files. If None,
657                                 defaults to true when recursive is true.
658     verbose           boolean   Always print source names being compiled
659     quiet             boolean   Don't print source names in recursive mode
660     compiler_directives  dict      Overrides for pragma options (see Options.py)
661     evaluate_tree_assertions boolean  Test support: evaluate parse tree assertions
662     language_level    integer   The Python language level: 2 or 3
663
664     cplus             boolean   Compile as c++ code
665     """
666
667     def __init__(self, defaults = None, **kw):
668         self.include_path = []
669         if defaults:
670             if isinstance(defaults, CompilationOptions):
671                 defaults = defaults.__dict__
672         else:
673             defaults = default_options
674         self.__dict__.update(defaults)
675         self.__dict__.update(kw)
676
677     def create_context(self):
678         return Context(self.include_path, self.compiler_directives,
679                       self.cplus, self.language_level)
680
681
682 class CompilationResult(object):
683     """
684     Results from the Cython compiler:
685
686     c_file           string or None   The generated C source file
687     h_file           string or None   The generated C header file
688     i_file           string or None   The generated .pxi file
689     api_file         string or None   The generated C API .h file
690     listing_file     string or None   File of error messages
691     object_file      string or None   Result of compiling the C file
692     extension_file   string or None   Result of linking the object file
693     num_errors       integer          Number of compilation errors
694     compilation_source CompilationSource
695     """
696
697     def __init__(self):
698         self.c_file = None
699         self.h_file = None
700         self.i_file = None
701         self.api_file = None
702         self.listing_file = None
703         self.object_file = None
704         self.extension_file = None
705         self.main_source_file = None
706
707
708 class CompilationResultSet(dict):
709     """
710     Results from compiling multiple Pyrex source files. A mapping
711     from source file paths to CompilationResult instances. Also
712     has the following attributes:
713
714     num_errors   integer   Total number of compilation errors
715     """
716
717     num_errors = 0
718
719     def add(self, source, result):
720         self[source] = result
721         self.num_errors += result.num_errors
722
723
724 def compile_single(source, options, full_module_name = None):
725     """
726     compile_single(source, options, full_module_name)
727
728     Compile the given Pyrex implementation file and return a CompilationResult.
729     Always compiles a single file; does not perform timestamp checking or
730     recursion.
731     """
732     return run_pipeline(source, options, full_module_name)
733
734
735 def compile_multiple(sources, options):
736     """
737     compile_multiple(sources, options)
738
739     Compiles the given sequence of Pyrex implementation files and returns
740     a CompilationResultSet. Performs timestamp checking and/or recursion
741     if these are specified in the options.
742     """
743     context = options.create_context()
744     sources = [os.path.abspath(source) for source in sources]
745     processed = set()
746     results = CompilationResultSet()
747     recursive = options.recursive
748     timestamps = options.timestamps
749     if timestamps is None:
750         timestamps = recursive
751     verbose = options.verbose or ((recursive or timestamps) and not options.quiet)
752     for source in sources:
753         if source not in processed:
754             # Compiling multiple sources in one context doesn't quite
755             # work properly yet.
756             if not timestamps or context.c_file_out_of_date(source):
757                 if verbose:
758                     sys.stderr.write("Compiling %s\n" % source)
759
760                 result = run_pipeline(source, options)
761                 results.add(source, result)
762             processed.add(source)
763             if recursive:
764                 for module_name in context.find_cimported_module_names(source):
765                     path = context.find_pyx_file(module_name, [source])
766                     if path:
767                         sources.append(path)
768                     else:
769                         sys.stderr.write(
770                             "Cannot find .pyx file for cimported module '%s'\n" % module_name)
771     return results
772
773 def compile(source, options = None, full_module_name = None, **kwds):
774     """
775     compile(source [, options], [, <option> = <value>]...)
776
777     Compile one or more Pyrex implementation files, with optional timestamp
778     checking and recursing on dependecies. The source argument may be a string
779     or a sequence of strings If it is a string and no recursion or timestamp
780     checking is requested, a CompilationResult is returned, otherwise a
781     CompilationResultSet is returned.
782     """
783     options = CompilationOptions(defaults = options, **kwds)
784     if isinstance(source, basestring) and not options.timestamps \
785             and not options.recursive:
786         return compile_single(source, options, full_module_name)
787     else:
788         return compile_multiple(source, options)
789
790 #------------------------------------------------------------------------
791 #
792 #  Main command-line entry point
793 #
794 #------------------------------------------------------------------------
795 def setuptools_main():
796     return main(command_line = 1)
797
798 def main(command_line = 0):
799     args = sys.argv[1:]
800     any_failures = 0
801     if command_line:
802         from CmdLine import parse_command_line
803         options, sources = parse_command_line(args)
804     else:
805         options = CompilationOptions(default_options)
806         sources = args
807
808     if options.show_version:
809         sys.stderr.write("Cython version %s\n" % Version.version)
810     if options.working_path!="":
811         os.chdir(options.working_path)
812     try:
813         result = compile(sources, options)
814         if result.num_errors > 0:
815             any_failures = 1
816     except (EnvironmentError, PyrexError), e:
817         sys.stderr.write(str(e) + '\n')
818         any_failures = 1
819     if any_failures:
820         sys.exit(1)
821
822
823
824 #------------------------------------------------------------------------
825 #
826 #  Set the default options depending on the platform
827 #
828 #------------------------------------------------------------------------
829
830 default_options = dict(
831     show_version = 0,
832     use_listing_file = 0,
833     errors_to_stderr = 1,
834     cplus = 0,
835     output_file = None,
836     annotate = None,
837     generate_pxi = 0,
838     working_path = "",
839     recursive = 0,
840     timestamps = None,
841     verbose = 0,
842     quiet = 0,
843     compiler_directives = {},
844     evaluate_tree_assertions = False,
845     emit_linenums = False,
846     relative_path_in_code_position_comments = True,
847     c_line_in_traceback = True,
848     language_level = 2,
849     gdb_debug = False,
850 )