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