2e283d8d92a0d0ac36bcdeecd2eed1c6eabf2919
[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         if options.use_listing_file:
514             result.listing_file = Utils.replace_suffix(source, ".lis")
515             path = result.listing_file
516         else:
517             path = None
518         Errors.open_listing_file(path=path,
519                                  echo_to_stderr=options.errors_to_stderr)
520
521     def teardown_errors(self, err, options, result):
522         source_desc = result.compilation_source.source_desc
523         if not isinstance(source_desc, FileSourceDescriptor):
524             raise RuntimeError("Only file sources for code supported")
525         Errors.close_listing_file()
526         result.num_errors = Errors.num_errors
527         if result.num_errors > 0:
528             err = True
529         if err and result.c_file:
530             try:
531                 Utils.castrate_file(result.c_file, os.stat(source_desc.filename))
532             except EnvironmentError:
533                 pass
534             result.c_file = None
535
536 def create_parse(context):
537     def parse(compsrc):
538         source_desc = compsrc.source_desc
539         full_module_name = compsrc.full_module_name
540         initial_pos = (source_desc, 1, 0)
541         scope = context.find_module(full_module_name, pos = initial_pos, need_pxd = 0)
542         tree = context.parse(source_desc, scope, pxd = 0, full_module_name = full_module_name)
543         tree.compilation_source = compsrc
544         tree.scope = scope
545         tree.is_pxd = False
546         return tree
547     return parse
548
549 def create_default_resultobj(compilation_source, options):
550     result = CompilationResult()
551     result.main_source_file = compilation_source.source_desc.filename
552     result.compilation_source = compilation_source
553     source_desc = compilation_source.source_desc
554     if options.output_file:
555         result.c_file = os.path.join(compilation_source.cwd, options.output_file)
556     else:
557         if options.cplus:
558             c_suffix = ".cpp"
559         else:
560             c_suffix = ".c"
561         result.c_file = Utils.replace_suffix(source_desc.filename, c_suffix)
562     return result
563
564 def run_pipeline(source, options, full_module_name = None):
565     # Set up context
566     context = Context(options.include_path, options.compiler_directives,
567                       options.cplus, options.language_level)
568
569     # Set up source object
570     cwd = os.getcwd()
571     source_desc = FileSourceDescriptor(os.path.join(cwd, source))
572     full_module_name = full_module_name or context.extract_module_name(source, options)
573     source = CompilationSource(source_desc, full_module_name, cwd)
574
575     # Set up result object
576     result = create_default_resultobj(source, options)
577     
578     # Get pipeline
579     if source_desc.filename.endswith(".py"):
580         pipeline = context.create_py_pipeline(options, result)
581     else:
582         pipeline = context.create_pyx_pipeline(options, result)
583
584     context.setup_errors(options, result)
585     err, enddata = context.run_pipeline(pipeline, source)
586     context.teardown_errors(err, options, result)
587     return result
588     
589
590 #------------------------------------------------------------------------
591 #
592 #  Main Python entry points
593 #
594 #------------------------------------------------------------------------
595
596 class CompilationSource(object):
597     """
598     Contains the data necesarry to start up a compilation pipeline for
599     a single compilation unit.
600     """
601     def __init__(self, source_desc, full_module_name, cwd):
602         self.source_desc = source_desc
603         self.full_module_name = full_module_name
604         self.cwd = cwd
605
606 class CompilationOptions(object):
607     """
608     Options to the Cython compiler:
609     
610     show_version      boolean   Display version number
611     use_listing_file  boolean   Generate a .lis file
612     errors_to_stderr  boolean   Echo errors to stderr when using .lis
613     include_path      [string]  Directories to search for include files
614     output_file       string    Name of generated .c file
615     generate_pxi      boolean   Generate .pxi file for public declarations
616     recursive         boolean   Recursively find and compile dependencies
617     timestamps        boolean   Only compile changed source files. If None,
618                                 defaults to true when recursive is true.
619     verbose           boolean   Always print source names being compiled
620     quiet             boolean   Don't print source names in recursive mode
621     compiler_directives  dict      Overrides for pragma options (see Options.py)
622     evaluate_tree_assertions boolean  Test support: evaluate parse tree assertions
623     language_level    integer   The Python language level: 2 or 3
624     
625     cplus             boolean   Compile as c++ code
626     """
627     
628     def __init__(self, defaults = None, **kw):
629         self.include_path = []
630         if defaults:
631             if isinstance(defaults, CompilationOptions):
632                 defaults = defaults.__dict__
633         else:
634             defaults = default_options
635         self.__dict__.update(defaults)
636         self.__dict__.update(kw)
637
638
639 class CompilationResult(object):
640     """
641     Results from the Cython compiler:
642     
643     c_file           string or None   The generated C source file
644     h_file           string or None   The generated C header file
645     i_file           string or None   The generated .pxi file
646     api_file         string or None   The generated C API .h file
647     listing_file     string or None   File of error messages
648     object_file      string or None   Result of compiling the C file
649     extension_file   string or None   Result of linking the object file
650     num_errors       integer          Number of compilation errors
651     compilation_source CompilationSource
652     """
653     
654     def __init__(self):
655         self.c_file = None
656         self.h_file = None
657         self.i_file = None
658         self.api_file = None
659         self.listing_file = None
660         self.object_file = None
661         self.extension_file = None
662         self.main_source_file = None
663
664
665 class CompilationResultSet(dict):
666     """
667     Results from compiling multiple Pyrex source files. A mapping
668     from source file paths to CompilationResult instances. Also
669     has the following attributes:
670     
671     num_errors   integer   Total number of compilation errors
672     """
673     
674     num_errors = 0
675
676     def add(self, source, result):
677         self[source] = result
678         self.num_errors += result.num_errors
679
680
681 def compile_single(source, options, full_module_name = None):
682     """
683     compile_single(source, options, full_module_name)
684     
685     Compile the given Pyrex implementation file and return a CompilationResult.
686     Always compiles a single file; does not perform timestamp checking or
687     recursion.
688     """
689     return run_pipeline(source, options, full_module_name)
690
691
692 def compile_multiple(sources, options):
693     """
694     compile_multiple(sources, options)
695     
696     Compiles the given sequence of Pyrex implementation files and returns
697     a CompilationResultSet. Performs timestamp checking and/or recursion
698     if these are specified in the options.
699     """
700     sources = [os.path.abspath(source) for source in sources]
701     processed = set()
702     results = CompilationResultSet()
703     recursive = options.recursive
704     timestamps = options.timestamps
705     if timestamps is None:
706         timestamps = recursive
707     verbose = options.verbose or ((recursive or timestamps) and not options.quiet)
708     for source in sources:
709         if source not in processed:
710             # Compiling multiple sources in one context doesn't quite
711             # work properly yet.
712             if not timestamps or context.c_file_out_of_date(source):
713                 if verbose:
714                     sys.stderr.write("Compiling %s\n" % source)
715
716                 result = run_pipeline(source, options)
717                 results.add(source, result)
718             processed.add(source)
719             if recursive:
720                 for module_name in context.find_cimported_module_names(source):
721                     path = context.find_pyx_file(module_name, [source])
722                     if path:
723                         sources.append(path)
724                     else:
725                         sys.stderr.write(
726                             "Cannot find .pyx file for cimported module '%s'\n" % module_name)
727     return results
728
729 def compile(source, options = None, full_module_name = None, **kwds):
730     """
731     compile(source [, options], [, <option> = <value>]...)
732     
733     Compile one or more Pyrex implementation files, with optional timestamp
734     checking and recursing on dependecies. The source argument may be a string
735     or a sequence of strings If it is a string and no recursion or timestamp
736     checking is requested, a CompilationResult is returned, otherwise a
737     CompilationResultSet is returned.
738     """
739     options = CompilationOptions(defaults = options, **kwds)
740     if isinstance(source, basestring) and not options.timestamps \
741             and not options.recursive:
742         return compile_single(source, options, full_module_name)
743     else:
744         return compile_multiple(source, options)
745
746 #------------------------------------------------------------------------
747 #
748 #  Main command-line entry point
749 #
750 #------------------------------------------------------------------------
751 def setuptools_main():
752     return main(command_line = 1)
753
754 def main(command_line = 0):
755     args = sys.argv[1:]
756     any_failures = 0
757     if command_line:
758         from CmdLine import parse_command_line
759         options, sources = parse_command_line(args)
760     else:
761         options = CompilationOptions(default_options)
762         sources = args
763
764     if options.show_version:
765         sys.stderr.write("Cython version %s\n" % Version.version)
766     if options.working_path!="":
767         os.chdir(options.working_path)
768     try:
769         result = compile(sources, options)
770         if result.num_errors > 0:
771             any_failures = 1
772     except (EnvironmentError, PyrexError), e:
773         sys.stderr.write(str(e) + '\n')
774         any_failures = 1
775     if any_failures:
776         sys.exit(1)
777
778
779
780 #------------------------------------------------------------------------
781 #
782 #  Set the default options depending on the platform
783 #
784 #------------------------------------------------------------------------
785
786 default_options = dict(
787     show_version = 0,
788     use_listing_file = 0,
789     errors_to_stderr = 1,
790     cplus = 0,
791     output_file = None,
792     annotate = False,
793     generate_pxi = 0,
794     working_path = "",
795     recursive = 0,
796     timestamps = None,
797     verbose = 0,
798     quiet = 0,
799     compiler_directives = {},
800     evaluate_tree_assertions = False,
801     emit_linenums = False,
802     language_level = 2,
803 )