fix error handling in sequential cython runs
[cython.git] / Cython / Compiler / Main.py
1 #
2 #   Cython Top Level
3 #
4
5 import os, sys, re
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 from time import time
17 import Code
18 import Errors
19 import Parsing
20 import Version
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
26 import CythonScope
27 import DebugFlags
28
29 module_name_pattern = re.compile(r"[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$")
30
31 verbose = 0
32
33 def dumptree(t):
34     # For quick debugging in pipelines
35     print t.dump()
36     return t
37
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"
42     return node
43
44 class CompilationData(object):
45     #  Bundles the information that is passed from transform to transform.
46     #  (For now, this is only)
47
48     #  While Context contains every pxd ever loaded, path information etc.,
49     #  this only contains the data related to a single compilation pass
50     #
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
56     pass
57
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.
64     #
65     #  modules               {string : ModuleScope}
66     #  include_directories   [string]
67     #  future_directives     [object]
68     #  language_level        int     currently 2 or 3 for Python 2/3
69     
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
78         self.cpp = cpp
79
80         self.pxds = {} # full name -> node tree
81
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]
85
86         self.set_language_level(language_level)
87
88     def set_language_level(self, level):
89         self.language_level = level
90         if level >= 3:
91             from Future import print_function, unicode_literals
92             self.future_directives.add(print_function)
93             self.future_directives.add(unicode_literals)
94
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
112
113         if pxd:
114             _check_c_declarations = check_c_declarations_pxd
115             _specific_post_parse = PxdPostParse(self)
116         else:
117             _check_c_declarations = check_c_declarations
118             _specific_post_parse = None
119             
120         if py and not pxd:
121             _align_function_definitions = AlignFunctionDefinitions(self)
122         else:
123             _align_function_definitions = None
124  
125         return [
126             NormalizeTree(self),
127             PostParse(self),
128             _specific_post_parse,
129             InterpretCompilerDirectives(self, self.compiler_directives),
130             _align_function_definitions,
131             MarkClosureVisitor(self),
132             ConstantFolding(),
133             FlattenInListTransform(),
134             WithTransform(self),
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(),
150             SwitchTransform(),
151             DropRefcountingTransform(),
152             FinalOptimizePhase(self),
153             GilCheck(),
154             ]
155
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
160             return result
161
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)
174             return module_node
175
176         test_support = []
177         if options.evaluate_tree_assertions:
178             from Cython.TestUtils import TreeAssertVisitor
179             test_support.append(TreeAssertVisitor())
180
181         return ([
182                 create_parse(self),
183             ] + self.create_pipeline(pxd=False, py=py) + test_support + [
184                 inject_pxd_code,
185                 abort_on_errors,
186                 generate_pyx_code,
187             ])
188
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)
193             tree.scope = scope
194             tree.is_pxd = True
195             return tree
196
197         from CodeGeneration import ExtractPxdCode
198
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),
203             ]
204             
205     def create_py_pipeline(self, options, result):
206         return self.create_pyx_pipeline(options, result, py=True)
207
208
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)
212         return result
213     
214     def nonfatal_error(self, exc):
215         return Errors.report_error(exc)
216
217     def run_pipeline(self, pipeline, source):
218         error = None
219         data = source
220         try:
221             for phase in pipeline:
222                 if phase is not None:
223                     if DebugFlags.debug_verbose_pipeline:
224                         t = time()
225                         print "Entering pipeline phase %r" % phase
226                     data = phase(data)
227                     if DebugFlags.debug_verbose_pipeline:
228                         print "    %.3f seconds" % (time() - t)
229         except CompileError, err:
230             # err is set
231             Errors.report_error(err)
232             error = err
233         except InternalError, err:
234             # Only raise if there was not an earlier error
235             if Errors.num_errors == 0:
236                 raise
237             error = err
238         return (error, data)
239
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))
253
254         scope = None
255         pxd_pathname = None
256         if not module_name_pattern.match(module_name):
257             if pos is None:
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)
265             if not scope:
266                 qualified_name = relative_to.qualify_name(module_name)
267                 pxd_pathname = self.find_pxd_file(qualified_name, pos)
268                 if pxd_pathname:
269                     scope = relative_to.find_submodule(module_name)
270         if not scope:
271             if debug_find_module:
272                 print("...trying absolute import")
273             scope = self
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
282             if not pxd_pathname:
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'):
291                         pass
292                     else:
293                         error(pos, "'%s.pxd' not found" % module_name)
294             if pxd_pathname:
295                 try:
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)
300                     if err:
301                         raise err
302                     (pxd_codenodes, pxd_scope) = result
303                     self.pxds[module_name] = (pxd_codenodes, pxd_scope)
304                 except CompileError:
305                     pass
306         return scope
307     
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)
324                 try:
325                     pxd = self.search_include_directories(qualified_name, ".pxd", pos)
326                 finally:
327                     self.include_directories.pop()
328                 if pxd:
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)
336         return pxd
337
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)
342     
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,
347                                                include=True)
348         if not path:
349             error(pos, "'%s' not found" % filename)
350         return path
351     
352     def search_include_directories(self, qualified_name, suffix, pos,
353                                    include=False):
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
360         if pos:
361             file_desc = pos[0]
362             if not isinstance(file_desc, FileSourceDescriptor):
363                 raise RuntimeError("Only file sources for code supported")
364             if include:
365                 dirs = [os.path.dirname(file_desc.filename)] + dirs
366             else:
367                 dirs = [self.find_root_package_dir(file_desc.filename)] + dirs
368
369         dotted_filename = qualified_name
370         if suffix:
371             dotted_filename += suffix
372         if not include:
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
378
379         for dir in dirs:
380             path = os.path.join(dir, dotted_filename)
381             if Utils.path_exists(path):
382                 return path
383             if not include:
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):
388                         return path
389                     path = os.path.join(dir, package_dir, module_name,
390                                         package_filename)
391                     if Utils.path_exists(path):
392                         return path
393         return None
394
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)
399             if parent == dir:
400                 break
401             dir = parent
402         return dir
403
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):
408                 return None
409         return dir
410
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):
414             return 1
415         c_time = Utils.modification_time(c_path)
416         if Utils.file_newer_than(source_path, c_time):
417             return 1
418         pos = [source_path]
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):
421             return 1
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)
427             else:
428                 continue
429             if dep_path and Utils.file_newer_than(dep_path, c_time):
430                 return 1
431         return 0
432     
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" ]
436
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", 
440                          "__init__.pyx", 
441                          "__init__.pxd"):
442             path = os.path.join(dir_path, filename)
443             if Utils.path_exists(path):
444                 return 1
445
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() ]
453             f.close()
454             return chunks
455         else:
456             return ()
457
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)
461
462     def find_submodule(self, name):
463         # Find a top-level module, creating a new one if needed.
464         scope = self.lookup_submodule(name)
465         if not scope:
466             scope = ModuleScope(name, 
467                 parent_module = None, context = self)
468             self.modules[name] = scope
469         return scope
470
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
475         scope.cpp = self.cpp
476         # Parse the given source file and return a parse tree.
477         try:
478             f = Utils.open_source_file(source_filename, "rU")
479             try:
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)
483             finally:
484                 f.close()
485         except UnicodeDecodeError, msg:
486             #import traceback
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:
490             raise CompileError
491         return tree
492
493     def extract_module_name(self, path, options):
494         # Find fully_qualified module name from the full pathname
495         # of a source file.
496         dir, filename = os.path.split(path)
497         module_name, _ = os.path.splitext(filename)
498         if "." in module_name:
499             return 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)
505             if parent == dir:
506                 break
507             names.append(package_name)
508             dir = parent
509         names.reverse()
510         return ".".join(names)
511
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
517         else:
518             path = None
519         Errors.open_listing_file(path=path,
520                                  echo_to_stderr=options.errors_to_stderr)
521
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:
529             err = True
530         if err and result.c_file:
531             try:
532                 Utils.castrate_file(result.c_file, os.stat(source_desc.filename))
533             except EnvironmentError:
534                 pass
535             result.c_file = None
536
537 def create_parse(context):
538     def parse(compsrc):
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
545         tree.scope = scope
546         tree.is_pxd = False
547         return tree
548     return parse
549
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)
557     else:
558         if options.cplus:
559             c_suffix = ".cpp"
560         else:
561             c_suffix = ".c"
562         result.c_file = Utils.replace_suffix(source_desc.filename, c_suffix)
563     return result
564
565 def run_pipeline(source, options, full_module_name = None):
566     # Set up context
567     context = Context(options.include_path, options.compiler_directives,
568                       options.cplus, options.language_level)
569
570     # Set up source object
571     cwd = os.getcwd()
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)
575
576     # Set up result object
577     result = create_default_resultobj(source, options)
578     
579     # Get pipeline
580     if source_desc.filename.endswith(".py"):
581         pipeline = context.create_py_pipeline(options, result)
582     else:
583         pipeline = context.create_pyx_pipeline(options, result)
584
585     context.setup_errors(options, result)
586     err, enddata = context.run_pipeline(pipeline, source)
587     context.teardown_errors(err, options, result)
588     return result
589     
590
591 #------------------------------------------------------------------------
592 #
593 #  Main Python entry points
594 #
595 #------------------------------------------------------------------------
596
597 class CompilationSource(object):
598     """
599     Contains the data necesarry to start up a compilation pipeline for
600     a single compilation unit.
601     """
602     def __init__(self, source_desc, full_module_name, cwd):
603         self.source_desc = source_desc
604         self.full_module_name = full_module_name
605         self.cwd = cwd
606
607 class CompilationOptions(object):
608     """
609     Options to the Cython compiler:
610     
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
625     
626     cplus             boolean   Compile as c++ code
627     """
628     
629     def __init__(self, defaults = None, **kw):
630         self.include_path = []
631         if defaults:
632             if isinstance(defaults, CompilationOptions):
633                 defaults = defaults.__dict__
634         else:
635             defaults = default_options
636         self.__dict__.update(defaults)
637         self.__dict__.update(kw)
638
639
640 class CompilationResult(object):
641     """
642     Results from the Cython compiler:
643     
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
653     """
654     
655     def __init__(self):
656         self.c_file = None
657         self.h_file = None
658         self.i_file = None
659         self.api_file = None
660         self.listing_file = None
661         self.object_file = None
662         self.extension_file = None
663         self.main_source_file = None
664
665
666 class CompilationResultSet(dict):
667     """
668     Results from compiling multiple Pyrex source files. A mapping
669     from source file paths to CompilationResult instances. Also
670     has the following attributes:
671     
672     num_errors   integer   Total number of compilation errors
673     """
674     
675     num_errors = 0
676
677     def add(self, source, result):
678         self[source] = result
679         self.num_errors += result.num_errors
680
681
682 def compile_single(source, options, full_module_name = None):
683     """
684     compile_single(source, options, full_module_name)
685     
686     Compile the given Pyrex implementation file and return a CompilationResult.
687     Always compiles a single file; does not perform timestamp checking or
688     recursion.
689     """
690     return run_pipeline(source, options, full_module_name)
691
692
693 def compile_multiple(sources, options):
694     """
695     compile_multiple(sources, options)
696     
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.
700     """
701     sources = [os.path.abspath(source) for source in sources]
702     processed = set()
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
712             # work properly yet.
713             if not timestamps or context.c_file_out_of_date(source):
714                 if verbose:
715                     sys.stderr.write("Compiling %s\n" % source)
716
717                 result = run_pipeline(source, options)
718                 results.add(source, result)
719             processed.add(source)
720             if recursive:
721                 for module_name in context.find_cimported_module_names(source):
722                     path = context.find_pyx_file(module_name, [source])
723                     if path:
724                         sources.append(path)
725                     else:
726                         sys.stderr.write(
727                             "Cannot find .pyx file for cimported module '%s'\n" % module_name)
728     return results
729
730 def compile(source, options = None, full_module_name = None, **kwds):
731     """
732     compile(source [, options], [, <option> = <value>]...)
733     
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.
739     """
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)
744     else:
745         return compile_multiple(source, options)
746
747 #------------------------------------------------------------------------
748 #
749 #  Main command-line entry point
750 #
751 #------------------------------------------------------------------------
752 def setuptools_main():
753     return main(command_line = 1)
754
755 def main(command_line = 0):
756     args = sys.argv[1:]
757     any_failures = 0
758     if command_line:
759         from CmdLine import parse_command_line
760         options, sources = parse_command_line(args)
761     else:
762         options = CompilationOptions(default_options)
763         sources = args
764
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)
769     try:
770         result = compile(sources, options)
771         if result.num_errors > 0:
772             any_failures = 1
773     except (EnvironmentError, PyrexError), e:
774         sys.stderr.write(str(e) + '\n')
775         any_failures = 1
776     if any_failures:
777         sys.exit(1)
778
779
780
781 #------------------------------------------------------------------------
782 #
783 #  Set the default options depending on the platform
784 #
785 #------------------------------------------------------------------------
786
787 default_options = dict(
788     show_version = 0,
789     use_listing_file = 0,
790     errors_to_stderr = 1,
791     cplus = 0,
792     output_file = None,
793     annotate = False,
794     generate_pxi = 0,
795     working_path = "",
796     recursive = 0,
797     timestamps = None,
798     verbose = 0,
799     quiet = 0,
800     compiler_directives = {},
801     evaluate_tree_assertions = False,
802     emit_linenums = False,
803     language_level = 2,
804 )