Main.py 27.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
#

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
William Stein's avatar
William Stein committed
22 23
from Errors import PyrexError, CompileError, error
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 38 39 40 41 42 43 44 45 46 47 48 49 50
class CompilationData:
    #  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

William Stein's avatar
William Stein committed
51 52
class Context:
    #  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 78 79 80 81
    def create_pipeline(self, pxd):
        from Visitor import PrintTree
        from ParseTreeTransforms import WithTransform, NormalizeTree, PostParse, PxdPostParse
        from ParseTreeTransforms import AnalyseDeclarationsTransform, AnalyseExpressionsTransform
        from ParseTreeTransforms import CreateClosureClasses, MarkClosureVisitor, DecoratorTransform
Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
82
        from ParseTreeTransforms import ResolveOptions
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
        from Optimize import FlattenInListTransform, SwitchTransform, OptimizeRefcounting
        from Buffer import IntroduceBufferAuxiliaryVars
        from ModuleNode import check_c_classes

        if pxd:
            _check_c_classes = None
            _specific_post_parse = PxdPostParse(self)
        else:
            _check_c_classes = check_c_classes
            _specific_post_parse = None
 
        return [
            NormalizeTree(self),
            PostParse(self),
            _specific_post_parse,
98
            ResolveOptions(self, self.pragma_overrides),
99 100 101 102 103 104 105 106 107
            FlattenInListTransform(),
            WithTransform(self),
            DecoratorTransform(self),
            AnalyseDeclarationsTransform(self),
            IntroduceBufferAuxiliaryVars(self),
            _check_c_classes,
            AnalyseExpressionsTransform(self),
            SwitchTransform(),
            OptimizeRefcounting(self),
Dag Sverre Seljebotn's avatar
Dag Sverre Seljebotn committed
108
#            SpecialFunctions(self),
109 110 111 112
            #        CreateClosureClasses(context),
            ]

    def create_pyx_pipeline(self, options, result):
113 114 115 116 117 118 119 120 121
        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():
122 123 124 125
                # 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.
126 127 128 129
                 stats.append(statlistnode)
                 # 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)
130 131 132
            return module_node

        return ([
133
                create_parse(self),
134 135 136 137
            ] + self.create_pipeline(pxd=False) + [
                inject_pxd_code,
                generate_pyx_code,
            ])
138 139 140 141 142 143 144 145

    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
146 147 148 149 150 151

        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) + [
152
            ExtractPxdCode(self),
153
            ]
154 155 156

    def process_pxd(self, source_desc, scope, module_name):
        pipeline = self.create_pxd_pipeline(scope, module_name)
157 158 159
        result = self.run_pipeline(pipeline, source_desc)
        return result
    
160 161 162 163
    def nonfatal_error(self, exc):
        return Errors.report_error(exc)

    def run_pipeline(self, pipeline, source):
164
        err = None
165 166 167 168 169 170
        data = source
        try:
            for phase in pipeline:
                if phase is not None:
                    data = phase(data)
        except CompileError, err:
171
            # err is set
172
            Errors.report_error(err)
173
        return (err, data)
174

William Stein's avatar
William Stein committed
175 176 177 178 179 180 181 182 183 184 185
    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
186 187
            print("Context.find_module: module_name = %s, relative_to = %s, pos = %s, need_pxd = %s" % (
                    module_name, relative_to, pos, need_pxd))
188

William Stein's avatar
William Stein committed
189 190
        scope = None
        pxd_pathname = None
191
        if not module_name_pattern.match(module_name):
Stefan Behnel's avatar
Stefan Behnel committed
192 193 194
            if pos is None:
                pos = (module_name, 0, 0)
            raise CompileError(pos,
195
                "'%s' is not a valid module name" % module_name)
William Stein's avatar
William Stein committed
196 197
        if "." not in module_name and relative_to:
            if debug_find_module:
Stefan Behnel's avatar
Stefan Behnel committed
198
                print("...trying relative import")
William Stein's avatar
William Stein committed
199 200 201 202 203 204 205 206
            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
207
                print("...trying absolute import")
William Stein's avatar
William Stein committed
208 209 210 211
            scope = self
            for name in module_name.split("."):
                scope = scope.find_submodule(name)
        if debug_find_module:
Stefan Behnel's avatar
Stefan Behnel committed
212
            print("...scope =", scope)
William Stein's avatar
William Stein committed
213 214
        if not scope.pxd_file_loaded:
            if debug_find_module:
Stefan Behnel's avatar
Stefan Behnel committed
215
                print("...pxd not loaded")
William Stein's avatar
William Stein committed
216 217 218
            scope.pxd_file_loaded = 1
            if not pxd_pathname:
                if debug_find_module:
Stefan Behnel's avatar
Stefan Behnel committed
219
                    print("...looking for pxd file")
William Stein's avatar
William Stein committed
220 221
                pxd_pathname = self.find_pxd_file(module_name, pos)
                if debug_find_module:
Stefan Behnel's avatar
Stefan Behnel committed
222
                    print("......found ", pxd_pathname)
William Stein's avatar
William Stein committed
223 224 225 226 227
                if not pxd_pathname and need_pxd:
                    error(pos, "'%s.pxd' not found" % module_name)
            if pxd_pathname:
                try:
                    if debug_find_module:
Stefan Behnel's avatar
Stefan Behnel committed
228
                        print("Context.find_module: Parsing %s" % pxd_pathname)
229
                    source_desc = FileSourceDescriptor(pxd_pathname)
230 231 232 233
                    err, result = self.process_pxd(source_desc, scope, module_name)
                    if err:
                        raise err
                    (pxd_codenodes, pxd_scope) = result
234
                    self.pxds[module_name] = (pxd_codenodes, pxd_scope)
William Stein's avatar
William Stein committed
235 236 237 238
                except CompileError:
                    pass
        return scope
    
239 240 241
    def find_pxd_file(self, qualified_name, pos):
        # Search include path for the .pxd file corresponding to the
        # given fully-qualified module name.
242 243 244 245 246
        # 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.
247 248 249 250 251 252
        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
253 254 255 256
    
    def find_include_file(self, filename, pos):
        # Search list of include directories for filename.
        # Reports an error and returns None if not found.
257
        path = self.search_include_directories(filename, "", pos,
258
                                               include=True)
William Stein's avatar
William Stein committed
259 260 261 262
        if not path:
            error(pos, "'%s' not found" % filename)
        return path
    
263
    def search_include_directories(self, qualified_name, suffix, pos,
264
                                   include=False):
William Stein's avatar
William Stein committed
265 266 267 268
        # 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.
269
        # The 'include' option will disable package dereferencing.
William Stein's avatar
William Stein committed
270 271
        dirs = self.include_directories
        if pos:
272 273 274
            file_desc = pos[0]
            if not isinstance(file_desc, FileSourceDescriptor):
                raise RuntimeError("Only file sources for code supported")
275 276 277 278
            if include:
                dirs = [os.path.dirname(file_desc.filename)] + dirs
            else:
                dirs = [self.find_root_package_dir(file_desc.filename)] + dirs
279 280

        dotted_filename = qualified_name + suffix
281
        if not include:
282 283 284 285 286 287
            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
288
        for dir in dirs:
289
            path = os.path.join(dir, dotted_filename)
William Stein's avatar
William Stein committed
290 291
            if os.path.exists(path):
                return path
292
            if not include:
293 294 295 296 297 298 299 300 301
                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
302 303
        return None

304 305 306 307 308 309 310 311 312 313 314 315 316 317
    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

318 319 320 321 322 323
    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)
324
            if not self.is_package_dir(dir):
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352
                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" ]
353 354 355 356 357 358 359 360

    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

361
    def read_dependency_file(self, source_path):
362
        dep_path = Utils.replace_suffix(source_path, ".dep")
363 364 365 366 367 368 369 370 371 372
        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
373 374 375 376 377 378 379 380 381 382 383 384 385
    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

386
    def parse(self, source_desc, scope, pxd, full_module_name):
387 388 389
        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
390 391
        # Parse the given source file and return a parse tree.
        try:
392
            f = Utils.open_source_file(source_filename, "rU")
393
            try:
394
                s = PyrexScanner(f, source_desc, source_encoding = f.encoding,
395
                                 scope = scope, context = self)
Stefan Behnel's avatar
Stefan Behnel committed
396
                tree = Parsing.p_module(s, pxd, full_module_name)
397 398 399
            finally:
                f.close()
        except UnicodeDecodeError, msg:
400
            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
401
        if Errors.num_errors > 0:
402
            raise CompileError
William Stein's avatar
William Stein committed
403 404
        return tree

405
    def extract_module_name(self, path, options):
406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422
        # 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
423

424 425 426 427 428 429 430 431
    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)

432
    def teardown_errors(self, err, options, result):
433 434 435
        source_desc = result.compilation_source.source_desc
        if not isinstance(source_desc, FileSourceDescriptor):
            raise RuntimeError("Only file sources for code supported")
436 437
        Errors.close_listing_file()
        result.num_errors = Errors.num_errors
438 439 440
        if result.num_errors > 0:
            err = True
        if err and result.c_file:
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455
            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)

456
def create_parse(context):
457 458 459
    def parse(compsrc):
        source_desc = compsrc.source_desc
        full_module_name = compsrc.full_module_name
460 461
        initial_pos = (source_desc, 1, 0)
        scope = context.find_module(full_module_name, pos = initial_pos, need_pxd = 0)
462
        tree = context.parse(source_desc, scope, pxd = 0, full_module_name = full_module_name)
463
        tree.compilation_source = compsrc
464
        tree.scope = scope
465
        tree.is_pxd = False
466 467 468
        return tree
    return parse

469
def create_default_resultobj(compilation_source, options):
470
    result = CompilationResult()
471
    result.main_source_file = compilation_source.source_desc.filename
472
    result.compilation_source = compilation_source
473
    source_desc = compilation_source.source_desc
474
    if options.output_file:
475
        result.c_file = os.path.join(compilation_source.cwd, options.output_file)
476 477 478 479 480 481 482 483
    else:
        if options.cplus:
            c_suffix = ".cpp"
        else:
            c_suffix = ".c"
        result.c_file = Utils.replace_suffix(source_desc.filename, c_suffix)
    return result

484
def run_pipeline(source, options, full_module_name = None):
485
    # Set up context
486
    context = Context(options.include_path, options.pragma_overrides)
487 488 489 490 491

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

494 495 496
    # Set up result object
    result = create_default_resultobj(source, options)
    
497
    # Get pipeline
498
    pipeline = context.create_pyx_pipeline(options, result)
499

500
    context.setup_errors(options)
501 502
    err, enddata = context.run_pipeline(pipeline, source)
    context.teardown_errors(err, options, result)
503 504
    return result

William Stein's avatar
William Stein committed
505 506
#------------------------------------------------------------------------
#
507
#  Main Python entry points
William Stein's avatar
William Stein committed
508 509 510
#
#------------------------------------------------------------------------

511 512 513 514 515 516 517 518 519 520
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

William Stein's avatar
William Stein committed
521 522
class CompilationOptions:
    """
523
    Options to the Cython compiler:
William Stein's avatar
William Stein committed
524 525 526 527 528 529
    
    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
530
    generate_pxi      boolean   Generate .pxi file for public declarations
531 532 533
    recursive         boolean   Recursively find and compile dependencies
    timestamps        boolean   Only compile changed source files. If None,
                                defaults to true when recursive is true.
534
    verbose           boolean   Always print source names being compiled
535
    quiet             boolean   Don't print source names in recursive mode
536
    pragma_overrides  dict      Overrides for pragma options (see Options.py)
William Stein's avatar
William Stein committed
537 538 539 540 541 542 543 544 545
    
    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
    """
    
546
    def __init__(self, defaults = None, c_compile = 0, c_link = 0, **kw):
William Stein's avatar
William Stein committed
547 548 549
        self.include_path = []
        self.objects = []
        if defaults:
550 551 552 553 554
            if isinstance(defaults, CompilationOptions):
                defaults = defaults.__dict__
        else:
            defaults = default_options
        self.__dict__.update(defaults)
William Stein's avatar
William Stein committed
555
        self.__dict__.update(kw)
556 557 558 559
        if c_compile:
            self.c_only = 0
        if c_link:
            self.obj_only = 0
William Stein's avatar
William Stein committed
560 561 562 563


class CompilationResult:
    """
564
    Results from the Cython compiler:
William Stein's avatar
William Stein committed
565 566 567 568
    
    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
569
    api_file         string or None   The generated C API .h file
William Stein's avatar
William Stein committed
570 571 572 573
    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
574
    compilation_source CompilationSource
William Stein's avatar
William Stein committed
575 576 577 578 579 580
    """
    
    def __init__(self):
        self.c_file = None
        self.h_file = None
        self.i_file = None
581
        self.api_file = None
William Stein's avatar
William Stein committed
582 583 584
        self.listing_file = None
        self.object_file = None
        self.extension_file = None
585
        self.main_source_file = None
William Stein's avatar
William Stein committed
586 587


588
class CompilationResultSet(dict):
William Stein's avatar
William Stein committed
589
    """
590 591 592
    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
593
    
594 595 596 597 598 599 600 601 602 603 604
    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
605
    """
606
    compile_single(source, options, full_module_name)
William Stein's avatar
William Stein committed
607
    
608 609 610
    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
611
    """
612 613
    return run_pipeline(source, options, full_module_name)

William Stein's avatar
William Stein committed
614

615 616 617 618 619 620 621 622 623 624 625 626 627 628 629
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
630
    verbose = options.verbose or ((recursive or timestamps) and not options.quiet)
631 632
    for source in sources:
        if source not in processed:
633 634
            # Compiling multiple sources in one context doesn't quite
            # work properly yet.
635 636 637
            if not timestamps or context.c_file_out_of_date(source):
                if verbose:
                    sys.stderr.write("Compiling %s\n" % source)
638

639
                result = run_pipeline(source, options)
640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668
                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:
669
        return compile_multiple(source, options)
670

William Stein's avatar
William Stein committed
671 672 673 674 675 676 677 678 679 680 681 682 683
#------------------------------------------------------------------------
#
#  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:
684
        options = CompilationOptions(default_options)
William Stein's avatar
William Stein committed
685
        sources = args
686

William Stein's avatar
William Stein committed
687
    if options.show_version:
Stefan Behnel's avatar
Stefan Behnel committed
688
        sys.stderr.write("Cython version %s\n" % Version.version)
Gary Furnish's avatar
-w  
Gary Furnish committed
689 690
    if options.working_path!="":
        os.chdir(options.working_path)
691 692 693
    try:
        result = compile(sources, options)
        if result.num_errors > 0:
William Stein's avatar
William Stein committed
694
            any_failures = 1
695 696 697
    except (EnvironmentError, PyrexError), e:
        sys.stderr.write(str(e) + '\n')
        any_failures = 1
William Stein's avatar
William Stein committed
698 699 700
    if any_failures:
        sys.exit(1)

701 702


William Stein's avatar
William Stein committed
703 704 705 706 707 708
#------------------------------------------------------------------------
#
#  Set the default options depending on the platform
#
#------------------------------------------------------------------------

709
default_options = dict(
William Stein's avatar
William Stein committed
710 711 712 713 714 715
    show_version = 0,
    use_listing_file = 0,
    errors_to_stderr = 1,
    c_only = 1,
    obj_only = 1,
    cplus = 0,
716
    output_file = None,
717
    annotate = False,
718
    generate_pxi = 0,
Robert Bradshaw's avatar
Robert Bradshaw committed
719
    working_path = "",
720 721
    recursive = 0,
    timestamps = None,
722
    verbose = 0,
723 724 725
    quiet = 0,
    pragma_overrides = {}
)
William Stein's avatar
William Stein committed
726
if sys.platform == "mac":
William Stein's avatar
William Stein committed
727
    from Cython.Mac.MacSystem import c_compile, c_link, CCompilerError
728
    default_options['use_listing_file'] = 1
William Stein's avatar
William Stein committed
729
elif sys.platform == "darwin":
William Stein's avatar
William Stein committed
730
    from Cython.Mac.DarwinSystem import c_compile, c_link, CCompilerError
William Stein's avatar
William Stein committed
731 732 733 734 735
else:
    c_compile = None
    c_link = None