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

5
import os, sys, re, codecs
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

28 29
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
30 31
verbose = 0

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

37
class CompilationData(object):
38 39 40 41 42 43 44 45 46 47 48 49 50
    #  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

51
class Context(object):
William Stein's avatar
William Stein committed
52
    #  This class encapsulates the context needed for compiling
53
    #  one or more Cython implementation files along with their
William Stein's avatar
William Stein committed
54 55 56 57 58 59
    #  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
60
    #  future_directives     [object]
William Stein's avatar
William Stein committed
61
    
62
    def __init__(self, include_directories, pragma_overrides):
63
        #self.modules = {"__builtin__" : BuiltinScope()}
64
        import Builtin, CythonScope
65
        self.modules = {"__builtin__" : Builtin.builtin_scope}
66
        self.modules["cython"] = CythonScope.create_cython_scope(self)
William Stein's avatar
William Stein committed
67
        self.include_directories = include_directories
Stefan Behnel's avatar
Stefan Behnel committed
68
        self.future_directives = set()
69
        self.pragma_overrides = pragma_overrides
70

71 72
        self.pxds = {} # full name -> node tree

73 74 75 76
        standard_include_path = os.path.abspath(
            os.path.join(os.path.dirname(__file__), '..', 'Includes'))
        self.include_directories = include_directories + [standard_include_path]

77
    def create_pipeline(self, pxd, py=False):
78 79 80 81
        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
82
        from ParseTreeTransforms import InterpretCompilerDirectives, TransformBuiltinMethods
83
        from ParseTreeTransforms import AlignFunctionDefinitions
84
        from AutoDocTransforms import EmbedSignature
85
        from Optimize import FlattenInListTransform, SwitchTransform, IterationTransform
86
        from Optimize import FlattenBuiltinTypeCreation, ConstantFolding, FinalOptimizePhase
87
        from Buffer import IntroduceBufferAuxiliaryVars
88
        from ModuleNode import check_c_declarations, check_c_declarations_pxd
89

90 91 92 93 94 95 96 97 98
        # 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

99
        if pxd:
100
            _check_c_declarations = check_c_declarations_pxd
101 102
            _specific_post_parse = PxdPostParse(self)
        else:
103
            _check_c_declarations = check_c_declarations
104
            _specific_post_parse = None
105 106 107 108 109
            
        if py and not pxd:
            _align_function_definitions = AlignFunctionDefinitions(self)
        else:
            _align_function_definitions = None
110 111 112 113 114
 
        return [
            NormalizeTree(self),
            PostParse(self),
            _specific_post_parse,
115
            InterpretCompilerDirectives(self, self.pragma_overrides),
116
            _align_function_definitions,
117
            FlattenInListTransform(),
118 119 120
            WithTransform(self),
            DecoratorTransform(self),
            AnalyseDeclarationsTransform(self),
121
            EmbedSignature(self),
Robert Bradshaw's avatar
Robert Bradshaw committed
122
            TransformBuiltinMethods(self),
123
            IntroduceBufferAuxiliaryVars(self),
124
            _check_c_declarations,
125
            AnalyseExpressionsTransform(self),
126
            FlattenBuiltinTypeCreation(),
127
            ConstantFolding(),
128
            IterationTransform(),
129
            SwitchTransform(),
130
            FinalOptimizePhase(self),
131
#            ClearResultCodes(self),
Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
132
#            SpecialFunctions(self),
133 134 135
            #        CreateClosureClasses(context),
            ]

136
    def create_pyx_pipeline(self, options, result, py=False):
137 138 139 140 141 142 143 144 145
        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():
146 147 148 149
                # 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.
150
                 stats.append(statlistnode)
151 152 153
                 # 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)
154 155 156
            return module_node

        return ([
157
                create_parse(self),
158
            ] + self.create_pipeline(pxd=False, py=py) + [
159 160 161
                inject_pxd_code,
                generate_pyx_code,
            ])
162 163 164 165 166 167 168 169

    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
170 171 172 173 174 175

        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) + [
176
            ExtractPxdCode(self),
177
            ]
178 179 180 181
            
    def create_py_pipeline(self, options, result):
        return self.create_pyx_pipeline(options, result, py=True)

182 183 184

    def process_pxd(self, source_desc, scope, module_name):
        pipeline = self.create_pxd_pipeline(scope, module_name)
185 186 187
        result = self.run_pipeline(pipeline, source_desc)
        return result
    
188 189 190 191
    def nonfatal_error(self, exc):
        return Errors.report_error(exc)

    def run_pipeline(self, pipeline, source):
192
        err = None
193 194 195 196 197 198
        data = source
        try:
            for phase in pipeline:
                if phase is not None:
                    data = phase(data)
        except CompileError, err:
199
            # err is set
200
            Errors.report_error(err)
201 202 203 204
        except InternalError, err:
            # Only raise if there was not an earlier error
            if Errors.num_errors == 0:
                raise
205
        return (err, data)
206

William Stein's avatar
William Stein committed
207 208 209 210 211 212 213 214 215 216 217
    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
218 219
            print("Context.find_module: module_name = %s, relative_to = %s, pos = %s, need_pxd = %s" % (
                    module_name, relative_to, pos, need_pxd))
220

William Stein's avatar
William Stein committed
221 222
        scope = None
        pxd_pathname = None
223
        if not module_name_pattern.match(module_name):
Stefan Behnel's avatar
Stefan Behnel committed
224 225 226
            if pos is None:
                pos = (module_name, 0, 0)
            raise CompileError(pos,
227
                "'%s' is not a valid module name" % module_name)
William Stein's avatar
William Stein committed
228 229
        if "." not in module_name and relative_to:
            if debug_find_module:
Stefan Behnel's avatar
Stefan Behnel committed
230
                print("...trying relative import")
William Stein's avatar
William Stein committed
231 232 233 234 235 236 237 238
            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
239
                print("...trying absolute import")
William Stein's avatar
William Stein committed
240 241 242 243
            scope = self
            for name in module_name.split("."):
                scope = scope.find_submodule(name)
        if debug_find_module:
Stefan Behnel's avatar
Stefan Behnel committed
244
            print("...scope =", scope)
William Stein's avatar
William Stein committed
245 246
        if not scope.pxd_file_loaded:
            if debug_find_module:
Stefan Behnel's avatar
Stefan Behnel committed
247
                print("...pxd not loaded")
William Stein's avatar
William Stein committed
248 249 250
            scope.pxd_file_loaded = 1
            if not pxd_pathname:
                if debug_find_module:
Stefan Behnel's avatar
Stefan Behnel committed
251
                    print("...looking for pxd file")
William Stein's avatar
William Stein committed
252 253
                pxd_pathname = self.find_pxd_file(module_name, pos)
                if debug_find_module:
Stefan Behnel's avatar
Stefan Behnel committed
254
                    print("......found ", pxd_pathname)
William Stein's avatar
William Stein committed
255
                if not pxd_pathname and need_pxd:
256 257 258 259 260
                    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
261 262 263
            if pxd_pathname:
                try:
                    if debug_find_module:
Stefan Behnel's avatar
Stefan Behnel committed
264
                        print("Context.find_module: Parsing %s" % pxd_pathname)
265
                    source_desc = FileSourceDescriptor(pxd_pathname)
266 267 268 269
                    err, result = self.process_pxd(source_desc, scope, module_name)
                    if err:
                        raise err
                    (pxd_codenodes, pxd_scope) = result
270
                    self.pxds[module_name] = (pxd_codenodes, pxd_scope)
William Stein's avatar
William Stein committed
271 272 273 274
                except CompileError:
                    pass
        return scope
    
275 276 277
    def find_pxd_file(self, qualified_name, pos):
        # Search include path for the .pxd file corresponding to the
        # given fully-qualified module name.
278 279 280 281 282
        # 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.
283 284 285 286 287 288
        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
289 290 291 292
    
    def find_include_file(self, filename, pos):
        # Search list of include directories for filename.
        # Reports an error and returns None if not found.
293
        path = self.search_include_directories(filename, "", pos,
294
                                               include=True)
William Stein's avatar
William Stein committed
295 296 297 298
        if not path:
            error(pos, "'%s' not found" % filename)
        return path
    
299
    def search_include_directories(self, qualified_name, suffix, pos,
300
                                   include=False):
William Stein's avatar
William Stein committed
301 302 303 304
        # 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.
305
        # The 'include' option will disable package dereferencing.
William Stein's avatar
William Stein committed
306 307
        dirs = self.include_directories
        if pos:
308 309 310
            file_desc = pos[0]
            if not isinstance(file_desc, FileSourceDescriptor):
                raise RuntimeError("Only file sources for code supported")
311 312 313 314
            if include:
                dirs = [os.path.dirname(file_desc.filename)] + dirs
            else:
                dirs = [self.find_root_package_dir(file_desc.filename)] + dirs
315 316

        dotted_filename = qualified_name + suffix
317
        if not include:
318 319 320 321 322 323
            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
324
        for dir in dirs:
325
            path = os.path.join(dir, dotted_filename)
William Stein's avatar
William Stein committed
326 327
            if os.path.exists(path):
                return path
328
            if not include:
329 330 331 332 333 334 335 336 337
                package_dir = self.check_package_dir(dir, package_names)
                if package_dir is not None:
                    path = os.path.join(package_dir, module_filename)
                    if os.path.exists(path):
                        return path
                    path = os.path.join(dir, package_dir, module_name,
                                        package_filename)
                    if os.path.exists(path):
                        return path
William Stein's avatar
William Stein committed
338 339
        return None

340 341 342 343 344 345 346 347 348 349 350 351 352 353
    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

    def is_package_dir(self, dir):
        package_init = os.path.join(dir, "__init__.py")
        return os.path.exists(package_init) or \
            os.path.exists(package_init + "x") # same with .pyx

354 355 356 357 358 359
    def check_package_dir(self, dir, package_names):
        package_dir = os.path.join(dir, *package_names)
        if not os.path.exists(package_dir):
            return None
        for dirname in package_names:
            dir = os.path.join(dir, dirname)
360
            if not self.is_package_dir(dir):
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
                return None
        return package_dir

    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" ]
389 390 391 392 393 394 395 396

    def is_package_dir(self, dir_path):
        #  Return true if the given directory is a package directory.
        for filename in ("__init__.py", "__init__.pyx"):
            path = os.path.join(dir_path, filename)
            if os.path.exists(path):
                return 1

397
    def read_dependency_file(self, source_path):
398
        dep_path = Utils.replace_suffix(source_path, ".dep")
399 400 401 402 403 404 405 406 407 408
        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
409 410 411 412 413 414 415 416 417 418 419 420 421
    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

422
    def parse(self, source_desc, scope, pxd, full_module_name):
423 424 425
        if not isinstance(source_desc, FileSourceDescriptor):
            raise RuntimeError("Only file sources for code supported")
        source_filename = Utils.encode_filename(source_desc.filename)
William Stein's avatar
William Stein committed
426 427
        # Parse the given source file and return a parse tree.
        try:
428
            f = Utils.open_source_file(source_filename, "rU")
429
            try:
430
                s = PyrexScanner(f, source_desc, source_encoding = f.encoding,
431
                                 scope = scope, context = self)
Stefan Behnel's avatar
Stefan Behnel committed
432
                tree = Parsing.p_module(s, pxd, full_module_name)
433 434 435
            finally:
                f.close()
        except UnicodeDecodeError, msg:
Stefan Behnel's avatar
Stefan Behnel committed
436 437
            #import traceback
            #traceback.print_exc()
438
            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
439 440 441 442
        if Errors.num_errors > 0:
            raise CompileError
        return tree

443
    def extract_module_name(self, path, options):
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
        # 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
461

462 463 464 465 466 467 468 469
    def setup_errors(self, options):
        if options.use_listing_file:
            result.listing_file = Utils.replace_suffix(source, ".lis")
            Errors.open_listing_file(result.listing_file,
                echo_to_stderr = options.errors_to_stderr)
        else:
            Errors.open_listing_file(None)

470
    def teardown_errors(self, err, options, result):
471 472 473
        source_desc = result.compilation_source.source_desc
        if not isinstance(source_desc, FileSourceDescriptor):
            raise RuntimeError("Only file sources for code supported")
474 475 476
        Errors.close_listing_file()
        result.num_errors = Errors.num_errors
        if result.num_errors > 0:
477 478
            err = True
        if err and result.c_file:
479 480 481 482 483 484 485 486 487 488 489 490 491 492 493
            try:
                Utils.castrate_file(result.c_file, os.stat(source_desc.filename))
            except EnvironmentError:
                pass
            result.c_file = None
        if result.c_file and not options.c_only and c_compile:
            result.object_file = c_compile(result.c_file,
                verbose_flag = options.show_version,
                cplus = options.cplus)
            if not options.obj_only and c_link:
                result.extension_file = c_link(result.object_file,
                    extra_objects = options.objects,
                    verbose_flag = options.show_version,
                    cplus = options.cplus)

494
def create_parse(context):
495 496 497
    def parse(compsrc):
        source_desc = compsrc.source_desc
        full_module_name = compsrc.full_module_name
498 499
        initial_pos = (source_desc, 1, 0)
        scope = context.find_module(full_module_name, pos = initial_pos, need_pxd = 0)
500
        tree = context.parse(source_desc, scope, pxd = 0, full_module_name = full_module_name)
501
        tree.compilation_source = compsrc
502
        tree.scope = scope
503
        tree.is_pxd = False
504 505 506
        return tree
    return parse

507
def create_default_resultobj(compilation_source, options):
508
    result = CompilationResult()
509
    result.main_source_file = compilation_source.source_desc.filename
510
    result.compilation_source = compilation_source
511
    source_desc = compilation_source.source_desc
512
    if options.output_file:
513
        result.c_file = os.path.join(compilation_source.cwd, options.output_file)
514 515 516 517 518 519 520 521
    else:
        if options.cplus:
            c_suffix = ".cpp"
        else:
            c_suffix = ".c"
        result.c_file = Utils.replace_suffix(source_desc.filename, c_suffix)
    return result

522
def run_pipeline(source, options, full_module_name = None):
523
    # Set up context
524
    context = Context(options.include_path, options.pragma_overrides)
525 526 527 528 529

    # 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)
530
    source = CompilationSource(source_desc, full_module_name, cwd)
531

532 533 534
    # Set up result object
    result = create_default_resultobj(source, options)
    
535
    # Get pipeline
536 537 538 539
    if source_desc.filename.endswith(".py"):
        pipeline = context.create_py_pipeline(options, result)
    else:
        pipeline = context.create_pyx_pipeline(options, result)
540

541
    context.setup_errors(options)
542 543
    err, enddata = context.run_pipeline(pipeline, source)
    context.teardown_errors(err, options, result)
544 545
    return result

William Stein's avatar
William Stein committed
546 547
#------------------------------------------------------------------------
#
548
#  Main Python entry points
William Stein's avatar
William Stein committed
549 550 551
#
#------------------------------------------------------------------------

552 553 554 555 556 557 558 559 560 561
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

562
class CompilationOptions(object):
William Stein's avatar
William Stein committed
563
    """
564
    Options to the Cython compiler:
William Stein's avatar
William Stein committed
565 566 567 568 569 570
    
    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
571
    generate_pxi      boolean   Generate .pxi file for public declarations
572 573 574
    recursive         boolean   Recursively find and compile dependencies
    timestamps        boolean   Only compile changed source files. If None,
                                defaults to true when recursive is true.
575
    verbose           boolean   Always print source names being compiled
576
    quiet             boolean   Don't print source names in recursive mode
577
    pragma_overrides  dict      Overrides for pragma options (see Options.py)
William Stein's avatar
William Stein committed
578 579 580 581 582 583 584 585 586
    
    Following options are experimental and only used on MacOSX:
    
    c_only            boolean   Stop after generating C file (default)
    obj_only          boolean   Stop after compiling to .o file
    objects           [string]  Extra .o files to link with
    cplus             boolean   Compile as c++ code
    """
    
587
    def __init__(self, defaults = None, c_compile = 0, c_link = 0, **kw):
William Stein's avatar
William Stein committed
588 589 590
        self.include_path = []
        self.objects = []
        if defaults:
591 592 593 594 595
            if isinstance(defaults, CompilationOptions):
                defaults = defaults.__dict__
        else:
            defaults = default_options
        self.__dict__.update(defaults)
William Stein's avatar
William Stein committed
596
        self.__dict__.update(kw)
597 598 599 600
        if c_compile:
            self.c_only = 0
        if c_link:
            self.obj_only = 0
William Stein's avatar
William Stein committed
601 602


603
class CompilationResult(object):
William Stein's avatar
William Stein committed
604
    """
605
    Results from the Cython compiler:
William Stein's avatar
William Stein committed
606 607 608 609
    
    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
610
    api_file         string or None   The generated C API .h file
William Stein's avatar
William Stein committed
611 612 613 614
    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
615
    compilation_source CompilationSource
William Stein's avatar
William Stein committed
616 617 618 619 620 621
    """
    
    def __init__(self):
        self.c_file = None
        self.h_file = None
        self.i_file = None
622
        self.api_file = None
William Stein's avatar
William Stein committed
623 624 625
        self.listing_file = None
        self.object_file = None
        self.extension_file = None
626
        self.main_source_file = None
William Stein's avatar
William Stein committed
627 628


629
class CompilationResultSet(dict):
William Stein's avatar
William Stein committed
630
    """
631 632 633
    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
634
    
635 636 637 638 639 640 641 642 643 644 645
    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
646
    """
647
    compile_single(source, options, full_module_name)
William Stein's avatar
William Stein committed
648
    
649 650 651
    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
652
    """
653 654
    return run_pipeline(source, options, full_module_name)

William Stein's avatar
William Stein committed
655

656 657 658 659 660 661 662 663 664 665 666 667 668 669 670
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
671
    verbose = options.verbose or ((recursive or timestamps) and not options.quiet)
672 673
    for source in sources:
        if source not in processed:
674 675
            # Compiling multiple sources in one context doesn't quite
            # work properly yet.
676 677 678
            if not timestamps or context.c_file_out_of_date(source):
                if verbose:
                    sys.stderr.write("Compiling %s\n" % source)
679

680
                result = run_pipeline(source, options)
681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709
                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

def compile(source, options = None, c_compile = 0, c_link = 0,
            full_module_name = None, **kwds):
    """
    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.
    """
    options = CompilationOptions(defaults = options, c_compile = c_compile,
        c_link = c_link, **kwds)
    if isinstance(source, basestring) and not options.timestamps \
            and not options.recursive:
        return compile_single(source, options, full_module_name)
    else:
710
        return compile_multiple(source, options)
711

William Stein's avatar
William Stein committed
712 713 714 715 716 717 718 719 720 721 722 723 724
#------------------------------------------------------------------------
#
#  Main command-line entry point
#
#------------------------------------------------------------------------

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:
725
        options = CompilationOptions(default_options)
William Stein's avatar
William Stein committed
726
        sources = args
727

William Stein's avatar
William Stein committed
728
    if options.show_version:
Stefan Behnel's avatar
Stefan Behnel committed
729
        sys.stderr.write("Cython version %s\n" % Version.version)
Gary Furnish's avatar
-w  
Gary Furnish committed
730 731
    if options.working_path!="":
        os.chdir(options.working_path)
732 733 734
    try:
        result = compile(sources, options)
        if result.num_errors > 0:
William Stein's avatar
William Stein committed
735
            any_failures = 1
736 737 738
    except (EnvironmentError, PyrexError), e:
        sys.stderr.write(str(e) + '\n')
        any_failures = 1
William Stein's avatar
William Stein committed
739 740 741
    if any_failures:
        sys.exit(1)

742 743


William Stein's avatar
William Stein committed
744 745 746 747 748 749
#------------------------------------------------------------------------
#
#  Set the default options depending on the platform
#
#------------------------------------------------------------------------

750
default_options = dict(
William Stein's avatar
William Stein committed
751 752 753 754 755 756
    show_version = 0,
    use_listing_file = 0,
    errors_to_stderr = 1,
    c_only = 1,
    obj_only = 1,
    cplus = 0,
757
    output_file = None,
758
    annotate = False,
759
    generate_pxi = 0,
Robert Bradshaw's avatar
Robert Bradshaw committed
760
    working_path = "",
761 762
    recursive = 0,
    timestamps = None,
763
    verbose = 0,
764
    quiet = 0,
765 766
    pragma_overrides = {},
    emit_linenums = False,
767
)
William Stein's avatar
William Stein committed
768
if sys.platform == "mac":
William Stein's avatar
William Stein committed
769
    from Cython.Mac.MacSystem import c_compile, c_link, CCompilerError
770
    default_options['use_listing_file'] = 1
William Stein's avatar
William Stein committed
771
elif sys.platform == "darwin":
William Stein's avatar
William Stein committed
772
    from Cython.Mac.DarwinSystem import c_compile, c_link, CCompilerError
William Stein's avatar
William Stein committed
773 774 775 776 777
else:
    c_compile = None
    c_link = None