Main.py 29.8 KB
Newer Older
William Stein's avatar
William Stein committed
1
#
2
#   Cython Top Level
William Stein's avatar
William Stein committed
3 4
#

Lisandro Dalcin's avatar
Lisandro Dalcin committed
5
import os, sys, re
6 7
if sys.version_info[:2] < (2, 3):
    sys.stderr.write("Sorry, Cython requires Python 2.3 or later\n")
William Stein's avatar
William Stein committed
8 9
    sys.exit(1)

Stefan Behnel's avatar
Stefan Behnel committed
10 11 12 13 14 15
try:
    set
except NameError:
    # Python 2.3
    from sets import Set as set

William Stein's avatar
William Stein committed
16
from time import time
17
import Code
William Stein's avatar
William Stein committed
18 19 20
import Errors
import Parsing
import Version
21
from Scanning import PyrexScanner, FileSourceDescriptor
22
from Errors import PyrexError, CompileError, InternalError, error
William Stein's avatar
William Stein committed
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 30
module_name_pattern = re.compile(r"[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$")

William Stein's avatar
William Stein committed
31 32
verbose = 0

33 34 35 36 37
def dumptree(t):
    # For quick debugging in pipelines
    print t.dump()
    return t

38 39 40 41 42 43
def abort_on_errors(node):
    # Stop the pipeline if there are any errors.
    if Errors.num_errors != 0:
        raise InternalError, "abort"
    return node

44
class CompilationData(object):
45 46 47 48 49 50 51 52 53 54 55 56 57
    #  Bundles the information that is passed from transform to transform.
    #  (For now, this is only)

    #  While Context contains every pxd ever loaded, path information etc.,
    #  this only contains the data related to a single compilation pass
    #
    #  pyx                   ModuleNode              Main code tree of this compilation.
    #  pxds                  {string : ModuleNode}   Trees for the pxds used in the pyx.
    #  codewriter            CCodeWriter             Where to output final code.
    #  options               CompilationOptions
    #  result                CompilationResult
    pass

58
class Context(object):
William Stein's avatar
William Stein committed
59
    #  This class encapsulates the context needed for compiling
60
    #  one or more Cython implementation files along with their
William Stein's avatar
William Stein committed
61 62 63 64 65 66
    #  associated and imported declaration files. It includes
    #  the root of the module import namespace and the list
    #  of directories to search for include files.
    #
    #  modules               {string : ModuleScope}
    #  include_directories   [string]
Stefan Behnel's avatar
Stefan Behnel committed
67
    #  future_directives     [object]
William Stein's avatar
William Stein committed
68
    
Robert Bradshaw's avatar
Robert Bradshaw committed
69
    def __init__(self, include_directories, compiler_directives, cpp=False):
70
        #self.modules = {"__builtin__" : BuiltinScope()}
71
        import Builtin, CythonScope
72
        self.modules = {"__builtin__" : Builtin.builtin_scope}
73
        self.modules["cython"] = CythonScope.create_cython_scope(self)
William Stein's avatar
William Stein committed
74
        self.include_directories = include_directories
Stefan Behnel's avatar
Stefan Behnel committed
75
        self.future_directives = set()
76
        self.compiler_directives = compiler_directives
Robert Bradshaw's avatar
Robert Bradshaw committed
77
        self.cpp = cpp
78

79 80
        self.pxds = {} # full name -> node tree

81 82
        standard_include_path = os.path.abspath(os.path.normpath(
            os.path.join(os.path.dirname(__file__), os.path.pardir, 'Includes')))
83 84
        self.include_directories = include_directories + [standard_include_path]

85
    def create_pipeline(self, pxd, py=False):
86 87 88 89
        from Visitor import PrintTree
        from ParseTreeTransforms import WithTransform, NormalizeTree, PostParse, PxdPostParse
        from ParseTreeTransforms import AnalyseDeclarationsTransform, AnalyseExpressionsTransform
        from ParseTreeTransforms import CreateClosureClasses, MarkClosureVisitor, DecoratorTransform
Robert Bradshaw's avatar
Robert Bradshaw committed
90
        from ParseTreeTransforms import InterpretCompilerDirectives, TransformBuiltinMethods
Craig Citro's avatar
Craig Citro committed
91
        from TypeInference import MarkAssignments, MarkOverflowingArithmetic
92
        from ParseTreeTransforms import AlignFunctionDefinitions, GilCheck
93
        from AnalysedTreeTransforms import AutoTestDictTransform
94
        from AutoDocTransforms import EmbedSignature
95
        from Optimize import FlattenInListTransform, SwitchTransform, IterationTransform
96 97
        from Optimize import EarlyReplaceBuiltinCalls, OptimizeBuiltinCalls
        from Optimize import ConstantFolding, FinalOptimizePhase
98
        from Optimize import DropRefcountingTransform
99
        from Buffer import IntroduceBufferAuxiliaryVars
100
        from ModuleNode import check_c_declarations, check_c_declarations_pxd
101

102 103 104 105 106 107 108 109 110
        # Temporary hack that can be used to ensure that all result_code's
        # are generated at code generation time.
        import Visitor
        class ClearResultCodes(Visitor.CythonTransform):
            def visit_ExprNode(self, node):
                self.visitchildren(node)
                node.result_code = "<cleared>"
                return node

111
        if pxd:
112
            _check_c_declarations = check_c_declarations_pxd
113 114
            _specific_post_parse = PxdPostParse(self)
        else:
115
            _check_c_declarations = check_c_declarations
116
            _specific_post_parse = None
117 118 119 120 121
            
        if py and not pxd:
            _align_function_definitions = AlignFunctionDefinitions(self)
        else:
            _align_function_definitions = None
122 123 124 125 126
 
        return [
            NormalizeTree(self),
            PostParse(self),
            _specific_post_parse,
127
            InterpretCompilerDirectives(self, self.compiler_directives),
128
            _align_function_definitions,
Robert Bradshaw's avatar
Robert Bradshaw committed
129
            MarkClosureVisitor(self),
130
            ConstantFolding(),
131
            FlattenInListTransform(),
132 133 134
            WithTransform(self),
            DecoratorTransform(self),
            AnalyseDeclarationsTransform(self),
Robert Bradshaw's avatar
Robert Bradshaw committed
135
            CreateClosureClasses(self),
136
            AutoTestDictTransform(self),
137
            EmbedSignature(self),
138
            EarlyReplaceBuiltinCalls(self),
139
            MarkAssignments(self),
Craig Citro's avatar
Craig Citro committed
140
            MarkOverflowingArithmetic(self),
Robert Bradshaw's avatar
Robert Bradshaw committed
141
            TransformBuiltinMethods(self),
142
            IntroduceBufferAuxiliaryVars(self),
143
            _check_c_declarations,
144
            AnalyseExpressionsTransform(self),
145
            OptimizeBuiltinCalls(self),
146
            IterationTransform(),
147
            SwitchTransform(),
148
            DropRefcountingTransform(),
149
            FinalOptimizePhase(self),
150
            GilCheck(),
Craig Citro's avatar
Craig Citro committed
151 152 153
            #ClearResultCodes(self),
            #SpecialFunctions(self),
            #CreateClosureClasses(context),
154 155
            ]

156
    def create_pyx_pipeline(self, options, result, py=False):
157 158 159 160 161 162 163 164 165
        def generate_pyx_code(module_node):
            module_node.process_implementation(options, result)
            result.compilation_source = module_node.compilation_source
            return result

        def inject_pxd_code(module_node):
            from textwrap import dedent
            stats = module_node.body.stats
            for name, (statlistnode, scope) in self.pxds.iteritems():
166 167 168 169
                # Copy over function nodes to the module
                # (this seems strange -- I believe the right concept is to split
                # ModuleNode into a ModuleNode and a CodeGenerator, and tell that
                # CodeGenerator to generate code both from the pyx and pxd ModuleNodes.
170
                 stats.append(statlistnode)
171 172 173
                 # Until utility code is moved to code generation phase everywhere,
                 # we need to copy it over to the main scope
                 module_node.scope.utility_code_list.extend(scope.utility_code_list)
174 175
            return module_node

176 177 178 179 180
        test_support = []
        if options.evaluate_tree_assertions:
            from Cython.TestUtils import TreeAssertVisitor
            test_support.append(TreeAssertVisitor())

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 187
                generate_pyx_code,
            ])
188 189 190 191 192 193 194 195

    def create_pxd_pipeline(self, scope, module_name):
        def parse_pxd(source_desc):
            tree = self.parse(source_desc, scope, pxd=True,
                              full_module_name=module_name)
            tree.scope = scope
            tree.is_pxd = True
            return tree
196 197 198 199 200 201

        from CodeGeneration import ExtractPxdCode

        # The pxd pipeline ends up with a CCodeWriter containing the
        # code of the pxd, as well as a pxd scope.
        return [parse_pxd] + self.create_pipeline(pxd=True) + [
202
            ExtractPxdCode(self),
203
            ]
204 205 206 207
            
    def create_py_pipeline(self, options, result):
        return self.create_pyx_pipeline(options, result, py=True)

208 209 210

    def process_pxd(self, source_desc, scope, module_name):
        pipeline = self.create_pxd_pipeline(scope, module_name)
211 212 213
        result = self.run_pipeline(pipeline, source_desc)
        return result
    
214 215 216 217
    def nonfatal_error(self, exc):
        return Errors.report_error(exc)

    def run_pipeline(self, pipeline, source):
Stefan Behnel's avatar
Stefan Behnel committed
218
        error = None
219 220 221 222
        data = source
        try:
            for phase in pipeline:
                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 228
                    if DebugFlags.debug_verbose_pipeline:
                        print "    %.3f seconds" % (time() - t)
229
        except CompileError, err:
230
            # err is set
231
            Errors.report_error(err)
Stefan Behnel's avatar
Stefan Behnel committed
232
            error = err
233 234 235 236
        except InternalError, err:
            # Only raise if there was not an earlier error
            if Errors.num_errors == 0:
                raise
Stefan Behnel's avatar
Stefan Behnel committed
237 238
            error = err
        return (error, data)
239

William Stein's avatar
William Stein committed
240 241 242 243 244 245 246 247 248 249 250
    def find_module(self, module_name, 
            relative_to = None, pos = None, need_pxd = 1):
        # Finds and returns the module scope corresponding to
        # the given relative or absolute module name. If this
        # is the first time the module has been requested, finds
        # the corresponding .pxd file and process it.
        # If relative_to is not None, it must be a module scope,
        # and the module will first be searched for relative to
        # that module, provided its name is not a dotted name.
        debug_find_module = 0
        if debug_find_module:
Stefan Behnel's avatar
Stefan Behnel committed
251 252
            print("Context.find_module: module_name = %s, relative_to = %s, pos = %s, need_pxd = %s" % (
                    module_name, relative_to, pos, need_pxd))
253

William Stein's avatar
William Stein committed
254 255
        scope = None
        pxd_pathname = None
256
        if not module_name_pattern.match(module_name):
Stefan Behnel's avatar
Stefan Behnel committed
257 258 259
            if pos is None:
                pos = (module_name, 0, 0)
            raise CompileError(pos,
260
                "'%s' is not a valid module name" % module_name)
William Stein's avatar
William Stein committed
261 262
        if "." not in module_name and relative_to:
            if debug_find_module:
Stefan Behnel's avatar
Stefan Behnel committed
263
                print("...trying relative import")
William Stein's avatar
William Stein committed
264 265 266 267 268 269 270 271
            scope = relative_to.lookup_submodule(module_name)
            if not scope:
                qualified_name = relative_to.qualify_name(module_name)
                pxd_pathname = self.find_pxd_file(qualified_name, pos)
                if pxd_pathname:
                    scope = relative_to.find_submodule(module_name)
        if not scope:
            if debug_find_module:
Stefan Behnel's avatar
Stefan Behnel committed
272
                print("...trying absolute import")
William Stein's avatar
William Stein committed
273 274 275 276
            scope = self
            for name in module_name.split("."):
                scope = scope.find_submodule(name)
        if debug_find_module:
Stefan Behnel's avatar
Stefan Behnel committed
277
            print("...scope =", scope)
William Stein's avatar
William Stein committed
278 279
        if not scope.pxd_file_loaded:
            if debug_find_module:
Stefan Behnel's avatar
Stefan Behnel committed
280
                print("...pxd not loaded")
William Stein's avatar
William Stein committed
281 282 283
            scope.pxd_file_loaded = 1
            if not pxd_pathname:
                if debug_find_module:
Stefan Behnel's avatar
Stefan Behnel committed
284
                    print("...looking for pxd file")
William Stein's avatar
William Stein committed
285 286
                pxd_pathname = self.find_pxd_file(module_name, pos)
                if debug_find_module:
Stefan Behnel's avatar
Stefan Behnel committed
287
                    print("......found ", pxd_pathname)
William Stein's avatar
William Stein committed
288
                if not pxd_pathname and need_pxd:
289 290 291 292 293
                    package_pathname = self.search_include_directories(module_name, ".py", pos)
                    if package_pathname and package_pathname.endswith('__init__.py'):
                        pass
                    else:
                        error(pos, "'%s.pxd' not found" % module_name)
William Stein's avatar
William Stein committed
294 295 296
            if pxd_pathname:
                try:
                    if debug_find_module:
Stefan Behnel's avatar
Stefan Behnel committed
297
                        print("Context.find_module: Parsing %s" % pxd_pathname)
298
                    source_desc = FileSourceDescriptor(pxd_pathname)
299 300 301 302
                    err, result = self.process_pxd(source_desc, scope, module_name)
                    if err:
                        raise err
                    (pxd_codenodes, pxd_scope) = result
303
                    self.pxds[module_name] = (pxd_codenodes, pxd_scope)
William Stein's avatar
William Stein committed
304 305 306 307
                except CompileError:
                    pass
        return scope
    
308 309 310
    def find_pxd_file(self, qualified_name, pos):
        # Search include path for the .pxd file corresponding to the
        # given fully-qualified module name.
311 312 313 314 315
        # Will find either a dotted filename or a file in a
        # package directory. If a source file position is given,
        # the directory containing the source file is searched first
        # for a dotted filename, and its containing package root
        # directory is searched first for a non-dotted filename.
316 317 318 319 320 321
        return self.search_include_directories(qualified_name, ".pxd", pos)

    def find_pyx_file(self, qualified_name, pos):
        # Search include path for the .pyx file corresponding to the
        # given fully-qualified module name, as for find_pxd_file().
        return self.search_include_directories(qualified_name, ".pyx", pos)
William Stein's avatar
William Stein committed
322 323 324 325
    
    def find_include_file(self, filename, pos):
        # Search list of include directories for filename.
        # Reports an error and returns None if not found.
326
        path = self.search_include_directories(filename, "", pos,
327
                                               include=True)
William Stein's avatar
William Stein committed
328 329 330 331
        if not path:
            error(pos, "'%s' not found" % filename)
        return path
    
332
    def search_include_directories(self, qualified_name, suffix, pos,
333
                                   include=False):
William Stein's avatar
William Stein committed
334 335 336 337
        # Search the list of include directories for the given
        # file name. If a source file position is given, first
        # searches the directory containing that file. Returns
        # None if not found, but does not report an error.
338
        # The 'include' option will disable package dereferencing.
William Stein's avatar
William Stein committed
339 340
        dirs = self.include_directories
        if pos:
341 342 343
            file_desc = pos[0]
            if not isinstance(file_desc, FileSourceDescriptor):
                raise RuntimeError("Only file sources for code supported")
344 345 346 347
            if include:
                dirs = [os.path.dirname(file_desc.filename)] + dirs
            else:
                dirs = [self.find_root_package_dir(file_desc.filename)] + dirs
348

Stefan Behnel's avatar
Stefan Behnel committed
349 350 351
        dotted_filename = qualified_name
        if suffix:
            dotted_filename += suffix
352
        if not include:
353 354 355 356 357 358
            names = qualified_name.split('.')
            package_names = names[:-1]
            module_name = names[-1]
            module_filename = module_name + suffix
            package_filename = "__init__" + suffix

William Stein's avatar
William Stein committed
359
        for dir in dirs:
360
            path = os.path.join(dir, dotted_filename)
361
            if Utils.path_exists(path):
William Stein's avatar
William Stein committed
362
                return path
363
            if not include:
364 365 366
                package_dir = self.check_package_dir(dir, package_names)
                if package_dir is not None:
                    path = os.path.join(package_dir, module_filename)
367
                    if Utils.path_exists(path):
368 369 370
                        return path
                    path = os.path.join(dir, package_dir, module_name,
                                        package_filename)
371
                    if Utils.path_exists(path):
372
                        return path
William Stein's avatar
William Stein committed
373 374
        return None

375 376 377 378 379 380 381 382 383
    def find_root_package_dir(self, file_path):
        dir = os.path.dirname(file_path)
        while self.is_package_dir(dir):
            parent = os.path.dirname(dir)
            if parent == dir:
                break
            dir = parent
        return dir

384 385 386
    def check_package_dir(self, dir, package_names):
        for dirname in package_names:
            dir = os.path.join(dir, dirname)
387
            if not self.is_package_dir(dir):
388
                return None
389
        return dir
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415

    def c_file_out_of_date(self, source_path):
        c_path = Utils.replace_suffix(source_path, ".c")
        if not os.path.exists(c_path):
            return 1
        c_time = Utils.modification_time(c_path)
        if Utils.file_newer_than(source_path, c_time):
            return 1
        pos = [source_path]
        pxd_path = Utils.replace_suffix(source_path, ".pxd")
        if os.path.exists(pxd_path) and Utils.file_newer_than(pxd_path, c_time):
            return 1
        for kind, name in self.read_dependency_file(source_path):
            if kind == "cimport":
                dep_path = self.find_pxd_file(name, pos)
            elif kind == "include":
                dep_path = self.search_include_directories(name, pos)
            else:
                continue
            if dep_path and Utils.file_newer_than(dep_path, c_time):
                return 1
        return 0
    
    def find_cimported_module_names(self, source_path):
        return [ name for kind, name in self.read_dependency_file(source_path)
                 if kind == "cimport" ]
416 417 418

    def is_package_dir(self, dir_path):
        #  Return true if the given directory is a package directory.
419 420 421
        for filename in ("__init__.py", 
                         "__init__.pyx", 
                         "__init__.pxd"):
422
            path = os.path.join(dir_path, filename)
423
            if Utils.path_exists(path):
424 425
                return 1

426
    def read_dependency_file(self, source_path):
427
        dep_path = Utils.replace_suffix(source_path, ".dep")
428 429 430 431 432 433 434 435 436 437
        if os.path.exists(dep_path):
            f = open(dep_path, "rU")
            chunks = [ line.strip().split(" ", 1)
                       for line in f.readlines()
                       if " " in line.strip() ]
            f.close()
            return chunks
        else:
            return ()

William Stein's avatar
William Stein committed
438 439 440 441 442 443 444 445 446 447 448 449 450
    def lookup_submodule(self, name):
        # Look up a top-level module. Returns None if not found.
        return self.modules.get(name, None)

    def find_submodule(self, name):
        # Find a top-level module, creating a new one if needed.
        scope = self.lookup_submodule(name)
        if not scope:
            scope = ModuleScope(name, 
                parent_module = None, context = self)
            self.modules[name] = scope
        return scope

451
    def parse(self, source_desc, scope, pxd, full_module_name):
452 453 454
        if not isinstance(source_desc, FileSourceDescriptor):
            raise RuntimeError("Only file sources for code supported")
        source_filename = Utils.encode_filename(source_desc.filename)
Robert Bradshaw's avatar
Robert Bradshaw committed
455
        scope.cpp = self.cpp
William Stein's avatar
William Stein committed
456 457
        # Parse the given source file and return a parse tree.
        try:
458
            f = Utils.open_source_file(source_filename, "rU")
459
            try:
460
                s = PyrexScanner(f, source_desc, source_encoding = f.encoding,
461
                                 scope = scope, context = self)
Stefan Behnel's avatar
Stefan Behnel committed
462
                tree = Parsing.p_module(s, pxd, full_module_name)
463 464 465
            finally:
                f.close()
        except UnicodeDecodeError, msg:
Stefan Behnel's avatar
Stefan Behnel committed
466 467
            #import traceback
            #traceback.print_exc()
468
            error((source_desc, 0, 0), "Decoding error, missing or incorrect coding=<encoding-name> at top of source (%s)" % msg)
William Stein's avatar
William Stein committed
469 470 471 472
        if Errors.num_errors > 0:
            raise CompileError
        return tree

473
    def extract_module_name(self, path, options):
474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490
        # Find fully_qualified module name from the full pathname
        # of a source file.
        dir, filename = os.path.split(path)
        module_name, _ = os.path.splitext(filename)
        if "." in module_name:
            return module_name
        if module_name == "__init__":
            dir, module_name = os.path.split(dir)
        names = [module_name]
        while self.is_package_dir(dir):
            parent, package_name = os.path.split(dir)
            if parent == dir:
                break
            names.append(package_name)
            dir = parent
        names.reverse()
        return ".".join(names)
William Stein's avatar
William Stein committed
491

492
    def setup_errors(self, options, result):
493 494
        if options.use_listing_file:
            result.listing_file = Utils.replace_suffix(source, ".lis")
495
            path = result.listing_file
496
        else:
497 498 499
            path = None
        Errors.open_listing_file(path=path,
                                 echo_to_stderr=options.errors_to_stderr)
500

501
    def teardown_errors(self, err, options, result):
502 503 504
        source_desc = result.compilation_source.source_desc
        if not isinstance(source_desc, FileSourceDescriptor):
            raise RuntimeError("Only file sources for code supported")
505 506 507
        Errors.close_listing_file()
        result.num_errors = Errors.num_errors
        if result.num_errors > 0:
508 509
            err = True
        if err and result.c_file:
510 511 512 513 514 515
            try:
                Utils.castrate_file(result.c_file, os.stat(source_desc.filename))
            except EnvironmentError:
                pass
            result.c_file = None

516
def create_parse(context):
517 518 519
    def parse(compsrc):
        source_desc = compsrc.source_desc
        full_module_name = compsrc.full_module_name
520 521
        initial_pos = (source_desc, 1, 0)
        scope = context.find_module(full_module_name, pos = initial_pos, need_pxd = 0)
522
        tree = context.parse(source_desc, scope, pxd = 0, full_module_name = full_module_name)
523
        tree.compilation_source = compsrc
524
        tree.scope = scope
525
        tree.is_pxd = False
526 527 528
        return tree
    return parse

529
def create_default_resultobj(compilation_source, options):
530
    result = CompilationResult()
531
    result.main_source_file = compilation_source.source_desc.filename
532
    result.compilation_source = compilation_source
533
    source_desc = compilation_source.source_desc
534
    if options.output_file:
535
        result.c_file = os.path.join(compilation_source.cwd, options.output_file)
536 537 538 539 540 541 542 543
    else:
        if options.cplus:
            c_suffix = ".cpp"
        else:
            c_suffix = ".c"
        result.c_file = Utils.replace_suffix(source_desc.filename, c_suffix)
    return result

544
def run_pipeline(source, options, full_module_name = None):
545
    # Set up context
Robert Bradshaw's avatar
Robert Bradshaw committed
546
    context = Context(options.include_path, options.compiler_directives, options.cplus)
547 548 549 550 551

    # Set up source object
    cwd = os.getcwd()
    source_desc = FileSourceDescriptor(os.path.join(cwd, source))
    full_module_name = full_module_name or context.extract_module_name(source, options)
552
    source = CompilationSource(source_desc, full_module_name, cwd)
553

554 555 556
    # Set up result object
    result = create_default_resultobj(source, options)
    
557
    # Get pipeline
558 559 560 561
    if source_desc.filename.endswith(".py"):
        pipeline = context.create_py_pipeline(options, result)
    else:
        pipeline = context.create_pyx_pipeline(options, result)
562

563
    context.setup_errors(options, result)
564 565
    err, enddata = context.run_pipeline(pipeline, source)
    context.teardown_errors(err, options, result)
566 567
    return result

William Stein's avatar
William Stein committed
568 569
#------------------------------------------------------------------------
#
570
#  Main Python entry points
William Stein's avatar
William Stein committed
571 572 573
#
#------------------------------------------------------------------------

574 575 576 577 578 579 580 581 582 583
class CompilationSource(object):
    """
    Contains the data necesarry to start up a compilation pipeline for
    a single compilation unit.
    """
    def __init__(self, source_desc, full_module_name, cwd):
        self.source_desc = source_desc
        self.full_module_name = full_module_name
        self.cwd = cwd

584
class CompilationOptions(object):
William Stein's avatar
William Stein committed
585
    """
586
    Options to the Cython compiler:
William Stein's avatar
William Stein committed
587 588 589 590 591 592
    
    show_version      boolean   Display version number
    use_listing_file  boolean   Generate a .lis file
    errors_to_stderr  boolean   Echo errors to stderr when using .lis
    include_path      [string]  Directories to search for include files
    output_file       string    Name of generated .c file
593
    generate_pxi      boolean   Generate .pxi file for public declarations
594 595 596
    recursive         boolean   Recursively find and compile dependencies
    timestamps        boolean   Only compile changed source files. If None,
                                defaults to true when recursive is true.
597
    verbose           boolean   Always print source names being compiled
598
    quiet             boolean   Don't print source names in recursive mode
599
    compiler_directives  dict      Overrides for pragma options (see Options.py)
600
    evaluate_tree_assertions boolean  Test support: evaluate parse tree assertions
William Stein's avatar
William Stein committed
601 602 603 604
    
    cplus             boolean   Compile as c++ code
    """
    
605
    def __init__(self, defaults = None, **kw):
William Stein's avatar
William Stein committed
606 607
        self.include_path = []
        if defaults:
608 609 610 611 612
            if isinstance(defaults, CompilationOptions):
                defaults = defaults.__dict__
        else:
            defaults = default_options
        self.__dict__.update(defaults)
William Stein's avatar
William Stein committed
613 614 615
        self.__dict__.update(kw)


616
class CompilationResult(object):
William Stein's avatar
William Stein committed
617
    """
618
    Results from the Cython compiler:
William Stein's avatar
William Stein committed
619 620 621 622
    
    c_file           string or None   The generated C source file
    h_file           string or None   The generated C header file
    i_file           string or None   The generated .pxi file
623
    api_file         string or None   The generated C API .h file
William Stein's avatar
William Stein committed
624 625 626 627
    listing_file     string or None   File of error messages
    object_file      string or None   Result of compiling the C file
    extension_file   string or None   Result of linking the object file
    num_errors       integer          Number of compilation errors
628
    compilation_source CompilationSource
William Stein's avatar
William Stein committed
629 630 631 632 633 634
    """
    
    def __init__(self):
        self.c_file = None
        self.h_file = None
        self.i_file = None
635
        self.api_file = None
William Stein's avatar
William Stein committed
636 637 638
        self.listing_file = None
        self.object_file = None
        self.extension_file = None
639
        self.main_source_file = None
William Stein's avatar
William Stein committed
640 641


642
class CompilationResultSet(dict):
William Stein's avatar
William Stein committed
643
    """
644 645 646
    Results from compiling multiple Pyrex source files. A mapping
    from source file paths to CompilationResult instances. Also
    has the following attributes:
William Stein's avatar
William Stein committed
647
    
648 649 650 651 652 653 654 655 656 657 658
    num_errors   integer   Total number of compilation errors
    """
    
    num_errors = 0

    def add(self, source, result):
        self[source] = result
        self.num_errors += result.num_errors


def compile_single(source, options, full_module_name = None):
William Stein's avatar
William Stein committed
659
    """
660
    compile_single(source, options, full_module_name)
William Stein's avatar
William Stein committed
661
    
662 663 664
    Compile the given Pyrex implementation file and return a CompilationResult.
    Always compiles a single file; does not perform timestamp checking or
    recursion.
William Stein's avatar
William Stein committed
665
    """
666 667
    return run_pipeline(source, options, full_module_name)

William Stein's avatar
William Stein committed
668

669 670 671 672 673 674 675 676 677 678 679 680 681 682 683
def compile_multiple(sources, options):
    """
    compile_multiple(sources, options)
    
    Compiles the given sequence of Pyrex implementation files and returns
    a CompilationResultSet. Performs timestamp checking and/or recursion
    if these are specified in the options.
    """
    sources = [os.path.abspath(source) for source in sources]
    processed = set()
    results = CompilationResultSet()
    recursive = options.recursive
    timestamps = options.timestamps
    if timestamps is None:
        timestamps = recursive
684
    verbose = options.verbose or ((recursive or timestamps) and not options.quiet)
685 686
    for source in sources:
        if source not in processed:
687 688
            # Compiling multiple sources in one context doesn't quite
            # work properly yet.
689 690 691
            if not timestamps or context.c_file_out_of_date(source):
                if verbose:
                    sys.stderr.write("Compiling %s\n" % source)
692

693
                result = run_pipeline(source, options)
694 695 696 697 698 699 700 701 702 703 704 705
                results.add(source, result)
            processed.add(source)
            if recursive:
                for module_name in context.find_cimported_module_names(source):
                    path = context.find_pyx_file(module_name, [source])
                    if path:
                        sources.append(path)
                    else:
                        sys.stderr.write(
                            "Cannot find .pyx file for cimported module '%s'\n" % module_name)
    return results

706
def compile(source, options = None, full_module_name = None, **kwds):
707 708 709 710 711 712 713 714 715
    """
    compile(source [, options], [, <option> = <value>]...)
    
    Compile one or more Pyrex implementation files, with optional timestamp
    checking and recursing on dependecies. The source argument may be a string
    or a sequence of strings If it is a string and no recursion or timestamp
    checking is requested, a CompilationResult is returned, otherwise a
    CompilationResultSet is returned.
    """
716
    options = CompilationOptions(defaults = options, **kwds)
717 718 719 720
    if isinstance(source, basestring) and not options.timestamps \
            and not options.recursive:
        return compile_single(source, options, full_module_name)
    else:
721
        return compile_multiple(source, options)
722

William Stein's avatar
William Stein committed
723 724 725 726 727
#------------------------------------------------------------------------
#
#  Main command-line entry point
#
#------------------------------------------------------------------------
728 729
def setuptools_main():
    return main(command_line = 1)
William Stein's avatar
William Stein committed
730 731 732 733 734 735 736 737

def main(command_line = 0):
    args = sys.argv[1:]
    any_failures = 0
    if command_line:
        from CmdLine import parse_command_line
        options, sources = parse_command_line(args)
    else:
738
        options = CompilationOptions(default_options)
William Stein's avatar
William Stein committed
739
        sources = args
740

William Stein's avatar
William Stein committed
741
    if options.show_version:
Stefan Behnel's avatar
Stefan Behnel committed
742
        sys.stderr.write("Cython version %s\n" % Version.version)
Gary Furnish's avatar
-w  
Gary Furnish committed
743 744
    if options.working_path!="":
        os.chdir(options.working_path)
745 746 747
    try:
        result = compile(sources, options)
        if result.num_errors > 0:
William Stein's avatar
William Stein committed
748
            any_failures = 1
749 750 751
    except (EnvironmentError, PyrexError), e:
        sys.stderr.write(str(e) + '\n')
        any_failures = 1
William Stein's avatar
William Stein committed
752 753 754
    if any_failures:
        sys.exit(1)

755 756


William Stein's avatar
William Stein committed
757 758 759 760 761 762
#------------------------------------------------------------------------
#
#  Set the default options depending on the platform
#
#------------------------------------------------------------------------

763
default_options = dict(
William Stein's avatar
William Stein committed
764 765 766 767
    show_version = 0,
    use_listing_file = 0,
    errors_to_stderr = 1,
    cplus = 0,
768
    output_file = None,
769
    annotate = False,
770
    generate_pxi = 0,
Robert Bradshaw's avatar
Robert Bradshaw committed
771
    working_path = "",
772 773
    recursive = 0,
    timestamps = None,
774
    verbose = 0,
775
    quiet = 0,
776
    compiler_directives = {},
777
    evaluate_tree_assertions = False,
778
    emit_linenums = False,
779
)