Main.py 29.6 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 ComprehensionTransform, AlignFunctionDefinitions
84
        from ParseTreeTransforms import GilCheck
85
        from AutoDocTransforms import EmbedSignature
86
        from Optimize import FlattenInListTransform, SwitchTransform, IterationTransform
87
        from Optimize import OptimiseBuiltinCalls, ConstantFolding, FinalOptimizePhase
88
        from Buffer import IntroduceBufferAuxiliaryVars
89
        from ModuleNode import check_c_declarations
90

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

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

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

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

    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
173 174 175 176 177 178

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

185 186 187

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

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

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

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

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

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

353 354 355 356 357 358
    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)
359
            if not self.is_package_dir(dir):
360 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
                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" ]
388 389 390 391 392 393 394 395

    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

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

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

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

461 462 463 464 465 466 467 468
    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)

469
    def teardown_errors(self, err, options, result):
470 471 472
        source_desc = result.compilation_source.source_desc
        if not isinstance(source_desc, FileSourceDescriptor):
            raise RuntimeError("Only file sources for code supported")
473 474 475
        Errors.close_listing_file()
        result.num_errors = Errors.num_errors
        if result.num_errors > 0:
476 477
            err = True
        if err and result.c_file:
478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
            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)

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

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

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

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

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

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

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

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

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


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


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

William Stein's avatar
William Stein committed
654

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

679
                result = run_pipeline(source, options)
680 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
                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:
709
        return compile_multiple(source, options)
710

William Stein's avatar
William Stein committed
711 712 713 714 715 716 717 718 719 720 721 722 723
#------------------------------------------------------------------------
#
#  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:
724
        options = CompilationOptions(default_options)
William Stein's avatar
William Stein committed
725
        sources = args
726

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

741 742


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

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