Lichen

Annotated importer.py

1027:dd0745ab8b8a
5 months ago Paul Boddie Reordered GCC arguments to prevent linking failures. Someone decided to change the GCC invocation or linking semantics at some point, meaning that libraries specified "too early" in the argument list no longer provide the symbols required by the program objects, whereas specifying them at the end of the argument list allows those symbols to be found and obtained.
paul@0 1
#!/usr/bin/env python
paul@0 2
paul@0 3
"""
paul@0 4
Import logic.
paul@0 5
paul@0 6
Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013,
paul@819 7
              2014, 2015, 2016, 2017, 2018 Paul Boddie <paul@boddie.org.uk>
paul@0 8
paul@0 9
This program is free software; you can redistribute it and/or modify it under
paul@0 10
the terms of the GNU General Public License as published by the Free Software
paul@0 11
Foundation; either version 3 of the License, or (at your option) any later
paul@0 12
version.
paul@0 13
paul@0 14
This program is distributed in the hope that it will be useful, but WITHOUT
paul@0 15
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
paul@0 16
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
paul@0 17
details.
paul@0 18
paul@0 19
You should have received a copy of the GNU General Public License along with
paul@0 20
this program.  If not, see <http://www.gnu.org/licenses/>.
paul@0 21
"""
paul@0 22
paul@0 23
from errors import ProgramError
paul@0 24
from os.path import exists, extsep, getmtime, join
paul@0 25
from os import listdir, makedirs, remove
paul@724 26
from common import init_item, order_dependencies, readfile, writefile
paul@13 27
from modules import CachedModule
paul@0 28
from referencing import Reference
paul@0 29
import inspector
paul@0 30
import sys
paul@0 31
paul@0 32
class Importer:
paul@0 33
paul@0 34
    "An import machine, searching for and loading modules."
paul@0 35
paul@526 36
    special_attributes = ("__args__", "__file__", "__fn__", "__name__", "__parent__")
paul@526 37
paul@558 38
    def __init__(self, path, cache=None, verbose=False, warnings=None):
paul@0 39
paul@0 40
        """
paul@0 41
        Initialise the importer with the given search 'path' - a list of
paul@0 42
        directories to search for Python modules.
paul@0 43
paul@0 44
        The optional 'cache' should be the name of a directory used to store
paul@0 45
        cached module information.
paul@0 46
paul@0 47
        The optional 'verbose' parameter causes output concerning the activities
paul@0 48
        of the object to be produced if set to a true value (not the default).
paul@558 49
paul@558 50
        The optional 'warnings' parameter may indicate classes of warnings to be
paul@558 51
        produced.
paul@0 52
        """
paul@0 53
paul@0 54
        self.path = path
paul@0 55
        self.cache = cache
paul@0 56
        self.verbose = verbose
paul@558 57
        self.warnings = warnings
paul@0 58
paul@41 59
        # Module importing queue, required modules, removed modules and active
paul@41 60
        # modules in the final program.
paul@41 61
paul@12 62
        self.to_import = set()
paul@16 63
        self.required = set(["__main__"])
paul@24 64
        self.removed = {}
paul@41 65
        self.modules = {}
paul@12 66
paul@41 67
        # Module relationships and invalidated cached modules.
paul@41 68
paul@12 69
        self.accessing_modules = {}
paul@0 70
        self.invalidated = set()
paul@0 71
paul@425 72
        # Object relationships and dependencies.
paul@425 73
paul@425 74
        self.depends = {}
paul@427 75
        self.module_depends = {}
paul@425 76
paul@41 77
        # Basic program information.
paul@41 78
paul@0 79
        self.objects = {}
paul@0 80
        self.classes = {}
paul@0 81
        self.function_parameters = {}
paul@819 82
        self.function_attr_initialisers = {}
paul@0 83
        self.function_defaults = {}
paul@109 84
        self.function_locals = {}
paul@0 85
paul@41 86
        # Unresolved names.
paul@41 87
paul@41 88
        self.missing = set()
paul@41 89
paul@0 90
        # Derived information.
paul@0 91
paul@0 92
        self.subclasses = {}
paul@0 93
paul@0 94
        # Attributes of different object types.
paul@0 95
paul@0 96
        self.all_class_attrs = {}
paul@0 97
        self.all_instance_attrs = {}
paul@0 98
        self.all_instance_attr_constants = {}
paul@0 99
        self.all_combined_attrs = {}
paul@0 100
        self.all_module_attrs = {}
paul@0 101
        self.all_shadowed_attrs = {}
paul@0 102
paul@0 103
        # References to external names and aliases within program units.
paul@0 104
paul@0 105
        self.all_name_references = {}
paul@0 106
        self.all_initialised_names = {}
paul@0 107
        self.all_aliased_names = {}
paul@0 108
paul@0 109
        # General attribute accesses.
paul@0 110
paul@0 111
        self.all_attr_accesses = {}
paul@0 112
        self.all_const_accesses = {}
paul@0 113
        self.all_attr_access_modifiers = {}
paul@0 114
paul@0 115
        # Constant literals and values.
paul@0 116
paul@0 117
        self.all_constants = {}
paul@0 118
        self.all_constant_values = {}
paul@0 119
paul@742 120
        # Common function value collection used during inspection.
paul@742 121
paul@742 122
        self.all_return_values = {}
paul@742 123
paul@0 124
        self.make_cache()
paul@0 125
paul@558 126
    def give_warning(self, name):
paul@558 127
paul@558 128
        "Return whether the indicated warning 'name' should be given."
paul@558 129
paul@558 130
        return self.warnings and (name in self.warnings or "all" in self.warnings)
paul@558 131
paul@0 132
    def make_cache(self):
paul@441 133
paul@441 134
        "Make a cache directory if it does not already exist."
paul@441 135
paul@0 136
        if self.cache and not exists(self.cache):
paul@0 137
            makedirs(self.cache)
paul@0 138
paul@0 139
    def check_cache(self, details):
paul@0 140
paul@0 141
        """
paul@0 142
        Check whether the cache applies for the given 'details', invalidating it
paul@0 143
        if it does not.
paul@0 144
        """
paul@0 145
paul@0 146
        recorded_details = self.get_cache_details()
paul@0 147
paul@0 148
        if recorded_details != details:
paul@0 149
            self.remove_cache()
paul@0 150
paul@0 151
        writefile(self.get_cache_details_filename(), details)
paul@0 152
paul@0 153
    def get_cache_details_filename(self):
paul@0 154
paul@0 155
        "Return the filename for the cache details."
paul@0 156
paul@0 157
        return join(self.cache, "$details")
paul@0 158
paul@0 159
    def get_cache_details(self):
paul@0 160
paul@0 161
        "Return details of the cache."
paul@0 162
paul@0 163
        details_filename = self.get_cache_details_filename()
paul@0 164
paul@0 165
        if not exists(details_filename):
paul@0 166
            return None
paul@0 167
        else:
paul@0 168
            return readfile(details_filename)
paul@0 169
paul@0 170
    def remove_cache(self):
paul@0 171
paul@0 172
        "Remove the contents of the cache."
paul@0 173
paul@0 174
        for filename in listdir(self.cache):
paul@0 175
            remove(join(self.cache, filename))
paul@0 176
paul@0 177
    def to_cache(self):
paul@0 178
paul@0 179
        "Write modules to the cache."
paul@0 180
paul@0 181
        if self.cache:
paul@0 182
            for module_name, module in self.modules.items():
paul@0 183
                module.to_cache(join(self.cache, module_name))
paul@0 184
paul@0 185
    # Object retrieval and storage.
paul@0 186
paul@0 187
    def get_object(self, name):
paul@0 188
paul@0 189
        """
paul@0 190
        Return a reference for the given 'name' or None if no such object
paul@0 191
        exists.
paul@0 192
        """
paul@0 193
paul@0 194
        return self.objects.get(name)
paul@0 195
paul@0 196
    def set_object(self, name, value=None):
paul@0 197
paul@0 198
        "Set the object with the given 'name' and the given 'value'."
paul@0 199
paul@0 200
        if isinstance(value, Reference):
paul@0 201
            ref = value.alias(name)
paul@0 202
        else:
paul@0 203
            ref = Reference(value, name)
paul@0 204
paul@0 205
        self.objects[name] = ref
paul@0 206
paul@27 207
    # Identification of both stored object names and name references.
paul@27 208
paul@27 209
    def identify(self, name):
paul@27 210
paul@27 211
        "Identify 'name' using stored object and external name records."
paul@27 212
paul@27 213
        return self.objects.get(name) or self.all_name_references.get(name)
paul@27 214
paul@0 215
    # Indirect object retrieval.
paul@0 216
paul@0 217
    def get_attributes(self, ref, attrname):
paul@0 218
paul@0 219
        """
paul@0 220
        Return attributes provided by 'ref' for 'attrname'. Class attributes
paul@0 221
        may be provided by instances.
paul@0 222
        """
paul@0 223
paul@0 224
        kind = ref.get_kind()
paul@0 225
        if kind == "<class>":
paul@0 226
            ref = self.get_class_attribute(ref.get_origin(), attrname)
paul@0 227
            return ref and set([ref]) or set()
paul@0 228
        elif kind == "<instance>":
paul@0 229
            return self.get_combined_attributes(ref.get_origin(), attrname)
paul@0 230
        elif kind == "<module>":
paul@0 231
            ref = self.get_module_attribute(ref.get_origin(), attrname)
paul@0 232
            return ref and set([ref]) or set()
paul@0 233
        else:
paul@0 234
            return set()
paul@0 235
paul@0 236
    def get_class_attribute(self, object_type, attrname):
paul@0 237
paul@0 238
        "Return from 'object_type' the details of class attribute 'attrname'."
paul@0 239
paul@324 240
        attrs = self.all_class_attrs.get(object_type)
paul@324 241
        attr = attrs and attrs.get(attrname)
paul@0 242
        return attr and self.get_object(attr)
paul@0 243
paul@0 244
    def get_instance_attributes(self, object_type, attrname):
paul@0 245
paul@0 246
        """
paul@0 247
        Return from 'object_type' the details of instance attribute 'attrname'.
paul@0 248
        """
paul@0 249
paul@0 250
        consts = self.all_instance_attr_constants.get(object_type)
paul@0 251
        attrs = set()
paul@0 252
        for attr in self.all_instance_attrs[object_type].get(attrname, []):
paul@0 253
            attrs.add(consts and consts.get(attrname) or Reference("<var>", attr))
paul@0 254
        return attrs
paul@0 255
paul@0 256
    def get_combined_attributes(self, object_type, attrname):
paul@0 257
paul@0 258
        """
paul@0 259
        Return from 'object_type' the details of class or instance attribute
paul@0 260
        'attrname'.
paul@0 261
        """
paul@0 262
paul@0 263
        ref = self.get_class_attribute(object_type, attrname)
paul@0 264
        refs = ref and set([ref]) or set()
paul@0 265
        refs.update(self.get_instance_attributes(object_type, attrname))
paul@0 266
        return refs
paul@0 267
paul@0 268
    def get_module_attribute(self, object_type, attrname):
paul@0 269
paul@0 270
        "Return from 'object_type' the details of module attribute 'attrname'."
paul@0 271
paul@0 272
        if attrname in self.all_module_attrs[object_type]:
paul@0 273
            return self.get_object("%s.%s" % (object_type, attrname))
paul@0 274
        else:
paul@0 275
            return None
paul@0 276
paul@96 277
    # Convenience methods for deducing which kind of object provided an
paul@96 278
    # attribute.
paul@96 279
paul@96 280
    def get_attribute_provider(self, ref, attrname):
paul@96 281
paul@96 282
        """
paul@96 283
        Return the kind of provider of the attribute accessed via 'ref' using
paul@96 284
        'attrname'.
paul@96 285
        """
paul@96 286
paul@96 287
        kind = ref.get_kind()
paul@96 288
paul@96 289
        if kind in ["<class>", "<module>"]:
paul@96 290
            return kind
paul@96 291
        else:
paul@96 292
            return self.get_instance_attribute_provider(ref.get_origin(), attrname)
paul@96 293
paul@96 294
    def get_instance_attribute_provider(self, object_type, attrname):
paul@96 295
paul@96 296
        """
paul@96 297
        Return the kind of provider of the attribute accessed via an instance of
paul@96 298
        'object_type' using 'attrname'.
paul@96 299
        """
paul@96 300
paul@96 301
        if self.get_class_attribute(object_type, attrname):
paul@96 302
            return "<class>"
paul@96 303
        else:
paul@96 304
            return "<instance>"
paul@96 305
paul@0 306
    # Module management.
paul@0 307
paul@16 308
    def queue_module(self, name, accessor, required=False):
paul@12 309
paul@12 310
        """
paul@12 311
        Queue the module with the given 'name' for import from the given
paul@16 312
        'accessor' module. If 'required' is true (it is false by default), the
paul@16 313
        module will be required in the final program.
paul@12 314
        """
paul@12 315
paul@12 316
        if not self.modules.has_key(name):
paul@12 317
            self.to_import.add(name)
paul@12 318
paul@16 319
        if required:
paul@16 320
            self.required.add(name)
paul@16 321
paul@12 322
        init_item(self.accessing_modules, name, set)
paul@16 323
        self.accessing_modules[name].add(accessor.name)
paul@12 324
paul@0 325
    def get_modules(self):
paul@0 326
paul@0 327
        "Return all modules known to the importer."
paul@0 328
paul@0 329
        return self.modules.values()
paul@0 330
paul@12 331
    def get_module(self, name):
paul@0 332
paul@0 333
        "Return the module with the given 'name'."
paul@0 334
paul@0 335
        if not self.modules.has_key(name):
paul@0 336
            return None
paul@0 337
paul@12 338
        return self.modules[name]
paul@0 339
paul@0 340
    # Program operations.
paul@0 341
paul@0 342
    def initialise(self, filename, reset=False):
paul@0 343
paul@0 344
        """
paul@0 345
        Initialise a program whose main module is 'filename', resetting the
paul@0 346
        cache if 'reset' is true. Return the main module.
paul@0 347
        """
paul@0 348
paul@0 349
        if reset:
paul@0 350
            self.remove_cache()
paul@0 351
        self.check_cache(filename)
paul@0 352
paul@0 353
        # Load the program itself.
paul@0 354
paul@0 355
        m = self.load_from_file(filename)
paul@0 356
paul@12 357
        # Load any queued modules.
paul@12 358
paul@12 359
        while self.to_import:
paul@12 360
            for name in list(self.to_import): # avoid mutation issue
paul@12 361
                self.load(name)
paul@12 362
paul@12 363
        # Resolve dependencies between modules.
paul@12 364
paul@12 365
        self.resolve()
paul@12 366
paul@16 367
        # Record the type of all classes.
paul@16 368
paul@16 369
        self.type_ref = self.get_object("__builtins__.type")
paul@16 370
paul@0 371
        # Resolve dependencies within the program.
paul@0 372
paul@12 373
        for module in self.modules.values():
paul@12 374
            module.complete()
paul@0 375
paul@16 376
        # Remove unneeded modules.
paul@16 377
paul@16 378
        all_modules = self.modules.items()
paul@16 379
paul@16 380
        for name, module in all_modules:
paul@16 381
            if name not in self.required:
paul@16 382
                module.unpropagate()
paul@16 383
                del self.modules[name]
paul@24 384
                self.removed[name] = module
paul@16 385
paul@68 386
        # Collect redundant objects.
paul@68 387
paul@68 388
        for module in self.removed.values():
paul@68 389
            module.collect()
paul@68 390
paul@68 391
        # Assert module objects where aliases have been removed.
paul@68 392
paul@68 393
        for name in self.required:
paul@68 394
            if not self.objects.has_key(name):
paul@68 395
                self.objects[name] = Reference("<module>", name)
paul@68 396
paul@0 397
        return m
paul@0 398
paul@0 399
    def finalise(self):
paul@0 400
paul@41 401
        """
paul@41 402
        Finalise the inspected program, returning whether the program could be
paul@41 403
        finalised.
paul@41 404
        """
paul@41 405
paul@358 406
        self.finalise_classes()
paul@423 407
        self.add_init_dependencies()
paul@358 408
        self.to_cache()
paul@358 409
paul@41 410
        if self.missing:
paul@41 411
            return False
paul@0 412
paul@0 413
        self.set_class_types()
paul@0 414
        self.define_instantiators()
paul@0 415
        self.collect_constants()
paul@0 416
paul@41 417
        return True
paul@41 418
paul@12 419
    # Supporting operations.
paul@12 420
paul@12 421
    def resolve(self):
paul@12 422
paul@12 423
        "Resolve dependencies between modules."
paul@12 424
paul@35 425
        self.waiting = {}
paul@35 426
paul@35 427
        for module in self.modules.values():
paul@35 428
paul@35 429
            # Resolve all deferred references in each module.
paul@12 430
paul@391 431
            original_deferred = []
paul@391 432
paul@35 433
            for ref in module.deferred:
paul@391 434
paul@391 435
                # Retain original references for caching.
paul@391 436
paul@391 437
                original_deferred.append(ref.copy())
paul@391 438
paul@391 439
                # Update references throughout the program.
paul@391 440
paul@35 441
                found = self.find_dependency(ref)
paul@35 442
                if not found:
paul@41 443
                    self.missing.add((module.name, ref.get_origin()))
paul@35 444
paul@35 445
                # Record the resolved names and identify required modules.
paul@12 446
paul@35 447
                else:
paul@170 448
                    # Find the providing module of this reference.
paul@170 449
                    # Where definitive details of the origin cannot be found,
paul@170 450
                    # identify the provider using the deferred reference.
paul@170 451
                    # NOTE: This may need to test for static origins.
paul@170 452
paul@170 453
                    provider = self.get_module_provider(found.unresolved() and ref or found)
paul@35 454
                    ref.mutate(found)
paul@35 455
paul@186 456
                    # Record any external dependency.
paul@186 457
paul@186 458
                    if provider and provider != module.name:
paul@186 459
paul@543 460
                        # Handle built-in modules accidentally referenced by
paul@543 461
                        # names.
paul@543 462
paul@543 463
                        if provider == "__builtins__" and found.has_kind("<module>"):
paul@543 464
                            raise ProgramError("Name %s, used by %s, refers to module %s." %
paul@543 465
                                               (found.leaf(), module.name, found.get_origin()))
paul@543 466
paul@186 467
                        # Record the provider dependency.
paul@16 468
paul@35 469
                        module.required.add(provider)
paul@35 470
                        self.accessing_modules[provider].add(module.name)
paul@35 471
paul@35 472
                        # Postpone any inclusion of the provider until this
paul@35 473
                        # module becomes required.
paul@12 474
paul@35 475
                        if module.name not in self.required:
paul@35 476
                            init_item(self.waiting, module.name, set)
paul@35 477
                            self.waiting[module.name].add(provider)
paul@418 478
                            if self.verbose:
paul@418 479
                                print >>sys.stderr, "Noting", provider, "for", ref, "from", module.name
paul@35 480
paul@35 481
                        # Make this module required in the accessing module.
paul@32 482
paul@53 483
                        elif provider not in self.required:
paul@35 484
                            self.required.add(provider)
paul@53 485
                            if self.verbose:
paul@418 486
                                print >>sys.stderr, "Requiring", provider, "for", ref, "from", module.name
paul@35 487
paul@425 488
                        # Record a module ordering dependency.
paul@425 489
paul@429 490
                        if not found.static() or self.is_dynamic_class(found) or self.is_dynamic_callable(found):
paul@427 491
                            self.add_module_dependency(module.name, provider)
paul@425 492
paul@425 493
            # Restore the original references so that they may be read back in
paul@425 494
            # and produce the same results.
paul@425 495
paul@391 496
            module.deferred = original_deferred
paul@391 497
paul@38 498
        # Check modules again to see if they are now required and should now
paul@38 499
        # cause the inclusion of other modules providing objects to the program.
paul@38 500
paul@35 501
        for module_name in self.waiting.keys():
paul@35 502
            self.require_providers(module_name)
paul@16 503
paul@423 504
        self.add_special_dependencies()
paul@427 505
        self.add_module_dependencies()
paul@419 506
paul@35 507
    def require_providers(self, module_name):
paul@38 508
paul@38 509
        """
paul@38 510
        Test if 'module_name' is itself required and, if so, require modules
paul@38 511
        containing objects provided to the module.
paul@38 512
        """
paul@38 513
paul@35 514
        if module_name in self.required and self.waiting.has_key(module_name):
paul@35 515
            for provider in self.waiting[module_name]:
paul@35 516
                if provider not in self.required:
paul@35 517
                    self.required.add(provider)
paul@53 518
                    if self.verbose:
paul@53 519
                        print >>sys.stderr, "Requiring", provider
paul@35 520
                    self.require_providers(provider)
paul@32 521
paul@423 522
    def add_special_dependencies(self):
paul@423 523
paul@423 524
        "Add dependencies due to the use of special names in namespaces."
paul@423 525
paul@423 526
        for module in self.modules.values():
paul@423 527
            for ref, paths in module.special.values():
paul@423 528
                for path in paths:
paul@423 529
                    self.add_dependency(path, ref.get_origin())
paul@423 530
paul@423 531
    def add_init_dependencies(self):
paul@423 532
paul@423 533
        "Add dependencies related to object initialisation."
paul@418 534
paul@423 535
        for name in self.classes.keys():
paul@423 536
            if self.is_dynamic_class(name):
paul@423 537
paul@423 538
                # Make subclasses depend on any class with non-static
paul@423 539
                # attributes, plus its module for the initialisation.
paul@418 540
paul@423 541
                for subclass in self.subclasses[name]:
paul@423 542
                    ref = Reference("<class>", subclass)
paul@423 543
                    self.add_dependency(subclass, name)
paul@423 544
                    self.add_dependency(subclass, self.get_module_provider(ref))
paul@423 545
paul@423 546
                # Also make the class dependent on its module for
paul@423 547
                # initialisation.
paul@423 548
paul@423 549
                ref = Reference("<class>", name)
paul@423 550
                self.add_dependency(name, self.get_module_provider(ref))
paul@418 551
paul@423 552
        for name in self.function_defaults.keys():
paul@423 553
            if self.is_dynamic_callable(name):
paul@423 554
paul@423 555
                # Make functions with defaults requiring initialisation depend
paul@428 556
                # on the parent scope, if a function, or the module scope.
paul@423 557
paul@423 558
                ref = Reference("<function>", name)
paul@428 559
                parent_ref = self.get_object(ref.parent())
paul@428 560
paul@428 561
                # Function no longer present in the program.
paul@428 562
paul@428 563
                if not parent_ref:
paul@428 564
                    continue
paul@428 565
paul@428 566
                if parent_ref.has_kind("<class>"):
paul@428 567
                    parent = self.get_module_provider(parent_ref)
paul@428 568
                else:
paul@428 569
                    parent = parent_ref.get_origin()
paul@428 570
paul@428 571
                self.add_dependency(name, parent)
paul@423 572
paul@427 573
    def add_module_dependencies(self):
paul@427 574
paul@427 575
        "Record module-based dependencies."
paul@427 576
paul@427 577
        for module_name, providers in self.module_depends.items():
paul@427 578
            if self.modules.has_key(module_name):
paul@427 579
                for provider in providers:
paul@427 580
                    if self.modules.has_key(provider):
paul@427 581
                        self.add_dependency(module_name, provider)
paul@427 582
paul@423 583
    def add_dependency(self, path, origin):
paul@423 584
paul@423 585
        "Add dependency details for 'path' involving 'origin'."
paul@423 586
paul@427 587
        if origin and not origin.startswith("%s." % path):
paul@423 588
            init_item(self.depends, path, set)
paul@423 589
            self.depends[path].add(origin)
paul@423 590
paul@427 591
    def add_module_dependency(self, module_name, provider):
paul@427 592
paul@427 593
        "Add dependency details for 'module_name' involving 'provider'."
paul@427 594
paul@427 595
        if provider:
paul@427 596
            init_item(self.module_depends, module_name, set)
paul@427 597
            self.module_depends[module_name].add(provider)
paul@427 598
paul@427 599
    def condense_dependencies(self):
paul@427 600
paul@427 601
        """
paul@427 602
        Condense the dependencies by removing all functions that do not need
paul@427 603
        initialisation.
paul@427 604
        """
paul@427 605
paul@427 606
        d = {}
paul@427 607
        for path, depends in self.depends.items():
paul@427 608
            d[path] = {}
paul@427 609
            d[path] = self.condense_dependency_entry(depends, d)
paul@427 610
paul@427 611
        self.depends = {}
paul@427 612
paul@427 613
        for path, depends in d.items():
paul@427 614
            if depends:
paul@427 615
                self.depends[path] = depends
paul@427 616
paul@427 617
    def condense_dependency_entry(self, depends, d):
paul@427 618
        l = set()
paul@427 619
        for depend in depends:
paul@427 620
            if self.modules.has_key(depend) or self.classes.has_key(depend) or \
paul@427 621
               self.is_dynamic_callable(depend):
paul@427 622
paul@427 623
                l.add(depend)
paul@427 624
            else:
paul@427 625
                deps = d.get(depend)
paul@427 626
                if deps:
paul@427 627
                    l.update(self.condense_dependency_entry(deps, d))
paul@427 628
        return l
paul@427 629
paul@428 630
    def is_dynamic(self, ref):
paul@428 631
        return not ref or not ref.static() and not ref.is_constant_alias() and not ref.is_predefined_value()
paul@428 632
paul@423 633
    def is_dynamic_class(self, name):
paul@423 634
paul@423 635
        """
paul@423 636
        Return whether 'name' refers to a class with members that must be
paul@423 637
        initialised dynamically.
paul@423 638
        """
paul@423 639
paul@423 640
        attrs = self.all_class_attrs.get(name)
paul@423 641
paul@423 642
        if not attrs:
paul@418 643
            return False
paul@418 644
paul@423 645
        for attrname, attr in attrs.items():
paul@423 646
            if attrname in self.special_attributes:
paul@423 647
                continue
paul@428 648
            ref = attr and self.get_object(attr)
paul@428 649
            if self.is_dynamic(ref):
paul@423 650
                return True
paul@423 651
paul@423 652
        return False
paul@423 653
paul@423 654
    def is_dynamic_callable(self, name):
paul@334 655
paul@334 656
        """
paul@423 657
        Return whether 'name' refers to a callable employing defaults that may
paul@334 658
        need initialising before the callable can be used.
paul@334 659
        """
paul@334 660
paul@338 661
        # Find any defaults for the function or method.
paul@338 662
paul@423 663
        defaults = self.function_defaults.get(name)
paul@338 664
        if not defaults:
paul@338 665
            return False
paul@338 666
paul@338 667
        # Identify non-constant defaults.
paul@338 668
paul@338 669
        for name, ref in defaults:
paul@428 670
            if self.is_dynamic(ref):
paul@338 671
                return True
paul@338 672
paul@338 673
        return False
paul@334 674
paul@423 675
    def order_objects(self):
paul@186 676
paul@423 677
        "Produce an object initialisation ordering."
paul@186 678
paul@427 679
        self.condense_dependencies()
paul@427 680
paul@724 681
        try:
paul@724 682
            ordered = order_dependencies(self.depends)
paul@724 683
        except ValueError, exc:
paul@724 684
            raise ProgramError("Modules with unresolvable dependencies exist: %s" % ", ".join(exc.args[0].keys()))
paul@313 685
paul@463 686
        if "__main__" in ordered:
paul@463 687
            ordered.remove("__main__")
paul@463 688
paul@313 689
        ordered.append("__main__")
paul@186 690
        return ordered
paul@186 691
paul@423 692
    def order_modules(self):
paul@418 693
paul@423 694
        "Produce a module initialisation ordering."
paul@418 695
paul@423 696
        ordered = self.order_objects()
paul@423 697
        filtered = []
paul@418 698
paul@423 699
        for module_name in self.modules.keys():
paul@423 700
            if module_name not in ordered:
paul@423 701
                filtered.append(module_name)
paul@418 702
paul@423 703
        for path in ordered:
paul@423 704
            if self.modules.has_key(path):
paul@423 705
                filtered.append(path)
paul@418 706
paul@423 707
        return filtered
paul@186 708
paul@12 709
    def find_dependency(self, ref):
paul@12 710
paul@12 711
        "Find the ultimate dependency for 'ref'."
paul@12 712
paul@12 713
        found = set()
paul@12 714
        while ref and ref.has_kind("<depends>") and not ref in found:
paul@12 715
            found.add(ref)
paul@35 716
            ref = self.identify(ref.get_origin())
paul@12 717
        return ref
paul@12 718
paul@16 719
    def get_module_provider(self, ref):
paul@16 720
paul@16 721
        "Identify the provider of the given 'ref'."
paul@16 722
paul@16 723
        for ancestor in ref.ancestors():
paul@16 724
            if self.modules.has_key(ancestor):
paul@16 725
                return ancestor
paul@16 726
        return None
paul@16 727
paul@0 728
    def finalise_classes(self):
paul@0 729
paul@0 730
        "Finalise the class relationships and attributes."
paul@0 731
paul@0 732
        self.derive_inherited_attrs()
paul@0 733
        self.derive_subclasses()
paul@0 734
        self.derive_shadowed_attrs()
paul@0 735
paul@0 736
    def derive_inherited_attrs(self):
paul@0 737
paul@0 738
        "Derive inherited attributes for classes throughout the program."
paul@0 739
paul@0 740
        for name in self.classes.keys():
paul@0 741
            self.propagate_attrs_for_class(name)
paul@0 742
paul@0 743
    def propagate_attrs_for_class(self, name, visited=None):
paul@0 744
paul@0 745
        "Propagate inherited attributes for class 'name'."
paul@0 746
paul@0 747
        # Visit classes only once.
paul@0 748
paul@0 749
        if self.all_combined_attrs.has_key(name):
paul@0 750
            return
paul@0 751
paul@0 752
        visited = visited or []
paul@0 753
paul@0 754
        if name in visited:
paul@0 755
            raise ProgramError, "Class %s may not inherit from itself: %s -> %s." % (name, " -> ".join(visited), name)
paul@0 756
paul@0 757
        visited.append(name)
paul@0 758
paul@0 759
        class_attrs = {}
paul@0 760
        instance_attrs = {}
paul@0 761
paul@0 762
        # Aggregate the attributes from base classes, recording the origins of
paul@0 763
        # applicable attributes.
paul@0 764
paul@0 765
        for base in self.classes[name][::-1]:
paul@0 766
paul@0 767
            # Get the identity of the class from the reference.
paul@0 768
paul@0 769
            base = base.get_origin()
paul@0 770
paul@0 771
            # Define the base class completely before continuing with this
paul@0 772
            # class.
paul@0 773
paul@0 774
            self.propagate_attrs_for_class(base, visited)
paul@0 775
            class_attrs.update(self.all_class_attrs[base])
paul@0 776
paul@0 777
            # Instance attribute origins are combined if different.
paul@0 778
paul@0 779
            for key, values in self.all_instance_attrs[base].items():
paul@0 780
                init_item(instance_attrs, key, set)
paul@0 781
                instance_attrs[key].update(values)
paul@0 782
paul@0 783
        # Class attributes override those defined earlier in the hierarchy.
paul@0 784
paul@0 785
        class_attrs.update(self.all_class_attrs.get(name, {}))
paul@0 786
paul@0 787
        # Instance attributes are merely added if not already defined.
paul@0 788
paul@0 789
        for key in self.all_instance_attrs.get(name, []):
paul@0 790
            if not instance_attrs.has_key(key):
paul@0 791
                instance_attrs[key] = set(["%s.%s" % (name, key)])
paul@0 792
paul@0 793
        self.all_class_attrs[name] = class_attrs
paul@0 794
        self.all_instance_attrs[name] = instance_attrs
paul@0 795
        self.all_combined_attrs[name] = set(class_attrs.keys()).union(instance_attrs.keys())
paul@0 796
paul@0 797
    def derive_subclasses(self):
paul@0 798
paul@0 799
        "Derive subclass details for classes."
paul@0 800
paul@0 801
        for name, bases in self.classes.items():
paul@0 802
            for base in bases:
paul@0 803
paul@0 804
                # Get the identity of the class from the reference.
paul@0 805
paul@0 806
                base = base.get_origin()
paul@0 807
                self.subclasses[base].add(name)
paul@0 808
paul@0 809
    def derive_shadowed_attrs(self):
paul@0 810
paul@0 811
        "Derive shadowed attributes for classes."
paul@0 812
paul@0 813
        for name, attrs in self.all_instance_attrs.items():
paul@0 814
            attrs = set(attrs.keys()).intersection(self.all_class_attrs[name].keys())
paul@0 815
            if attrs:
paul@0 816
                self.all_shadowed_attrs[name] = attrs
paul@0 817
paul@0 818
    def set_class_types(self):
paul@0 819
paul@0 820
        "Set the type of each class."
paul@0 821
paul@0 822
        for attrs in self.all_class_attrs.values():
paul@16 823
            attrs["__class__"] = self.type_ref.get_origin()
paul@0 824
paul@0 825
    def define_instantiators(self):
paul@0 826
paul@0 827
        """
paul@0 828
        Consolidate parameter and default details, incorporating initialiser
paul@0 829
        details to define instantiator signatures.
paul@0 830
        """
paul@0 831
paul@0 832
        for cls, attrs in self.all_class_attrs.items():
paul@0 833
            initialiser = attrs["__init__"]
paul@119 834
            self.function_parameters[cls] = self.function_parameters[initialiser]
paul@0 835
            self.function_defaults[cls] = self.function_defaults[initialiser]
paul@0 836
paul@0 837
    def collect_constants(self):
paul@0 838
paul@0 839
        "Get constants from all active modules."
paul@0 840
paul@0 841
        for module in self.modules.values():
paul@0 842
            self.all_constants.update(module.constants)
paul@0 843
paul@0 844
    # Import methods.
paul@0 845
paul@0 846
    def find_in_path(self, name):
paul@0 847
paul@0 848
        """
paul@0 849
        Find the given module 'name' in the search path, returning None where no
paul@0 850
        such module could be found, or a 2-tuple from the 'find' method
paul@0 851
        otherwise.
paul@0 852
        """
paul@0 853
paul@0 854
        for d in self.path:
paul@0 855
            m = self.find(d, name)
paul@0 856
            if m: return m
paul@0 857
        return None
paul@0 858
paul@0 859
    def find(self, d, name):
paul@0 860
paul@0 861
        """
paul@0 862
        In the directory 'd', find the given module 'name', where 'name' can
paul@0 863
        either refer to a single file module or to a package. Return None if the
paul@0 864
        'name' cannot be associated with either a file or a package directory,
paul@0 865
        or a 2-tuple from '_find_package' or '_find_module' otherwise.
paul@0 866
        """
paul@0 867
paul@0 868
        m = self._find_package(d, name)
paul@0 869
        if m: return m
paul@0 870
        m = self._find_module(d, name)
paul@0 871
        if m: return m
paul@0 872
        return None
paul@0 873
paul@0 874
    def _find_module(self, d, name):
paul@0 875
paul@0 876
        """
paul@0 877
        In the directory 'd', find the given module 'name', returning None where
paul@0 878
        no suitable file exists in the directory, or a 2-tuple consisting of
paul@0 879
        None (indicating that no package directory is involved) and a filename
paul@0 880
        indicating the location of the module.
paul@0 881
        """
paul@0 882
paul@0 883
        name_py = name + extsep + "py"
paul@0 884
        filename = self._find_file(d, name_py)
paul@0 885
        if filename:
paul@0 886
            return None, filename
paul@0 887
        return None
paul@0 888
paul@0 889
    def _find_package(self, d, name):
paul@0 890
paul@0 891
        """
paul@0 892
        In the directory 'd', find the given package 'name', returning None
paul@0 893
        where no suitable package directory exists, or a 2-tuple consisting of
paul@0 894
        a directory (indicating the location of the package directory itself)
paul@0 895
        and a filename indicating the location of the __init__.py module which
paul@0 896
        declares the package's top-level contents.
paul@0 897
        """
paul@0 898
paul@0 899
        filename = self._find_file(d, name)
paul@0 900
        if filename:
paul@0 901
            init_py = "__init__" + extsep + "py"
paul@0 902
            init_py_filename = self._find_file(filename, init_py)
paul@0 903
            if init_py_filename:
paul@0 904
                return filename, init_py_filename
paul@0 905
        return None
paul@0 906
paul@0 907
    def _find_file(self, d, filename):
paul@0 908
paul@0 909
        """
paul@0 910
        Return the filename obtained when searching the directory 'd' for the
paul@0 911
        given 'filename', or None if no actual file exists for the filename.
paul@0 912
        """
paul@0 913
paul@0 914
        filename = join(d, filename)
paul@0 915
        if exists(filename):
paul@0 916
            return filename
paul@0 917
        else:
paul@0 918
            return None
paul@0 919
paul@12 920
    def load(self, name):
paul@0 921
paul@0 922
        """
paul@0 923
        Load the module or package with the given 'name'. Return an object
paul@0 924
        referencing the loaded module or package, or None if no such module or
paul@0 925
        package exists.
paul@0 926
        """
paul@0 927
paul@0 928
        # Loaded modules are returned immediately.
paul@0 929
        # Modules may be known but not yet loading (having been registered as
paul@0 930
        # submodules), loading, loaded, or completely unknown.
paul@0 931
paul@12 932
        module = self.get_module(name)
paul@0 933
paul@0 934
        if module:
paul@12 935
            return self.modules[name]
paul@0 936
paul@0 937
        # Otherwise, modules are loaded.
paul@0 938
paul@0 939
        # Split the name into path components, and try to find the uppermost in
paul@0 940
        # the search path.
paul@0 941
paul@0 942
        path = name.split(".")
paul@0 943
        path_so_far = []
paul@12 944
        module = None
paul@0 945
paul@0 946
        for p in path:
paul@0 947
paul@0 948
            # Get the module's filesystem details.
paul@0 949
paul@0 950
            if not path_so_far:
paul@0 951
                m = self.find_in_path(p)
paul@0 952
            elif d:
paul@0 953
                m = self.find(d, p)
paul@0 954
            else:
paul@0 955
                m = None
paul@0 956
paul@0 957
            path_so_far.append(p)
paul@0 958
            module_name = ".".join(path_so_far)
paul@0 959
paul@469 960
            # Raise an exception if the module could not be located.
paul@329 961
paul@0 962
            if not m:
paul@469 963
                raise ProgramError("Module not found: %s" % name)
paul@0 964
paul@329 965
            # Get the directory and module filename.
paul@0 966
paul@0 967
            d, filename = m
paul@0 968
paul@329 969
        # Get the module itself.
paul@329 970
paul@329 971
        return self.load_from_file(filename, module_name)
paul@0 972
paul@12 973
    def load_from_file(self, filename, module_name=None):
paul@0 974
paul@0 975
        "Load the module from the given 'filename'."
paul@0 976
paul@0 977
        if module_name is None:
paul@0 978
            module_name = "__main__"
paul@0 979
paul@0 980
        module = self.modules.get(module_name)
paul@0 981
paul@0 982
        if not module:
paul@0 983
paul@0 984
            # Try to load from cache.
paul@0 985
paul@12 986
            module = self.load_from_cache(filename, module_name)
paul@0 987
            if module:
paul@0 988
                return module
paul@0 989
paul@0 990
            # If no cache entry exists, load from file.
paul@0 991
paul@0 992
            module = inspector.InspectedModule(module_name, self)
paul@0 993
            self.add_module(module_name, module)
paul@0 994
            self.update_cache_validity(module)
paul@0 995
paul@12 996
            self._load(module, module_name, lambda m: m.parse, filename)
paul@0 997
paul@0 998
        return module
paul@0 999
paul@0 1000
    def update_cache_validity(self, module):
paul@0 1001
paul@0 1002
        "Make 'module' valid in the cache, but invalidate accessing modules."
paul@0 1003
paul@12 1004
        accessing = self.accessing_modules.get(module.name)
paul@12 1005
        if accessing:
paul@12 1006
            self.invalidated.update(accessing)
paul@0 1007
        if module.name in self.invalidated:
paul@0 1008
            self.invalidated.remove(module.name)
paul@0 1009
paul@0 1010
    def source_is_new(self, filename, module_name):
paul@0 1011
paul@0 1012
        "Return whether 'filename' is newer than the cached 'module_name'."
paul@0 1013
paul@0 1014
        if self.cache:
paul@0 1015
            cache_filename = join(self.cache, module_name)
paul@0 1016
            return not exists(cache_filename) or \
paul@0 1017
                getmtime(filename) > getmtime(cache_filename) or \
paul@0 1018
                module_name in self.invalidated
paul@0 1019
        else:
paul@0 1020
            return True
paul@0 1021
paul@12 1022
    def load_from_cache(self, filename, module_name):
paul@0 1023
paul@0 1024
        "Return a module residing in the cache."
paul@0 1025
paul@0 1026
        module = self.modules.get(module_name)
paul@0 1027
paul@12 1028
        if not module and not self.source_is_new(filename, module_name):
paul@13 1029
            module = CachedModule(module_name, self)
paul@12 1030
            self.add_module(module_name, module)
paul@0 1031
paul@12 1032
            filename = join(self.cache, module_name)
paul@12 1033
            self._load(module, module_name, lambda m: m.from_cache, filename)
paul@0 1034
paul@0 1035
        return module
paul@0 1036
paul@12 1037
    def _load(self, module, module_name, fn, filename):
paul@0 1038
paul@0 1039
        """
paul@12 1040
        Load 'module' for the given 'module_name', and with 'fn' performing an
paul@12 1041
        invocation on the module with the given 'filename'.
paul@0 1042
        """
paul@0 1043
paul@12 1044
        # Load the module.
paul@0 1045
paul@0 1046
        if self.verbose:
paul@53 1047
            print >>sys.stderr, module_name in self.required and "Required" or "Loading", module_name, "from", filename
paul@0 1048
        fn(module)(filename)
paul@0 1049
paul@54 1050
        # Add the module object if not already defined.
paul@54 1051
paul@54 1052
        if not self.objects.has_key(module_name):
paul@54 1053
            self.objects[module_name] = Reference("<module>", module_name)
paul@54 1054
paul@0 1055
    def add_module(self, module_name, module):
paul@0 1056
paul@0 1057
        """
paul@0 1058
        Return the module with the given 'module_name', adding a new module
paul@0 1059
        object if one does not already exist.
paul@0 1060
        """
paul@0 1061
paul@0 1062
        self.modules[module_name] = module
paul@12 1063
        if module_name in self.to_import:
paul@12 1064
            self.to_import.remove(module_name)
paul@0 1065
paul@0 1066
# vim: tabstop=4 expandtab shiftwidth=4