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