Reimplement dependency order handling to solve a performance issue.
[matrix.git] / matrix / components.py
index e31165f..bd9c970 100644 (file)
@@ -16,11 +16,11 @@ class Component(object):
        __dirty = None
 
        cached = None
-
-       rdepends = None
-       weight = None
        state = None
 
+       rebuild_checked = False
+       needs_rebuild = False
+
        def __init__(self, name, branch=None, flags=[], rank=0):
                self.name = name
 
@@ -58,6 +58,11 @@ class Component(object):
                return self.__dirty
 
        def add_depend(self, c):
+               if self.rank < c.rank:
+                       c.rank = self.rank
+                       if config.debug:
+                               print 'Rank fixup:', c, 'inherits rank from', self
+
                self.depends.add(c)
 
        def remove_depend(self, c):
@@ -67,40 +72,151 @@ class Component(object):
        def get_depends(self):
                return self.depends
 
-       def add_rdepend(self, c):
-               if self.rdepends is None:
-                       self.rdepends = set()
-
-               self.rdepends.add(c)
-
-       def get_rdepends(self):
-               return self.rdepends or ()
-
-       def clear_rdepends(self):
-               if self.rdepends:
-                       del self.rdepends
-
 class PlatformProvidedComponent(Component):
        """A Component that is provided by the platform.
           The sources will not be built during install."""
 
        cached = True
 
-def init():
-       targets = []
-       packages = {}
+by_name = None
+
+def init(targets=[]):
+       global by_name
+       by_name = Resolver().resolve(targets)
+
+class Resolver(object):
+       def __init__(self):
+               self.components = {}
+               self.packages = {}
+
+       def resolve(self, targets):
+               # add all manually specified components before dependencies so
+               # that all custom Component() arguments will come into effect
+               #
+               for c in config.components.itervalues():
+                       self.add_component(c)
+
+               # iterate over a _copy_ of the _current_ package list;
+               # automatic components' dependencies will be initialized
+               # recursively as we go.
+               #
+               for p in self.packages.values():
+                       self.init_package_depends(p)
+
+               for name in targets:
+                       if name not in self.components:
+                               self.add_automatic_component(name)
+
+               self.init_rank_depends()
+               self.check_depends()
 
-       for c in config.components.itervalues():
+               return self.components
+
+       def add_component(self, c):
                if not c.meta.exists():
                        c.meta.clone()
 
-               init_packages(c, targets, packages)
-
-       init_depends(packages)
-       init_rank_depends()
+               # custom packages?
+               #
+               for path in glob(os.path.join(c.meta.path, '*.package')):
+                       name = os.path.basename(path)[:-8]
+                       self.add_package(c, name, path)
+
+                       if config.debug:
+                               print 'Component', c.name, 'provides', name
+
+               # default package?
+               #
+               if not c.packages:
+                       path = os.path.join(c.meta.path, 'info')
+                       if os.path.exists(path):
+                               self.add_package(c, c.name, path)
+                       else:
+                               self.add_package(c, c.name)
+
+               self.components[c.name] = c
+
+       def add_package(self, c, name, path=None):
+               p = parse_package(name, c, path)
+               if p:
+                       c.packages[name] = p
+                       self.packages[name] = p
+
+       def init_package_depends(self, p):
+               for spec in to_seq(p.depends):
+                       depname = Dependency(spec).name
+                       deppkg = self.packages.get(depname)
+
+                       depcomp = None
+                       if deppkg:
+                               if deppkg.component != p.component:
+                                       depcomp = deppkg.component
+                       else:
+                               depcomp = self.add_automatic_component(depname)
+                               if not depcomp:
+                                       log.error('Package %s depends on ' \
+                                                 'non-existent package %s' % \
+                                                 (p.name, depname))
+
+                       if depcomp:
+                               p.component.add_depend(depcomp)
+
+       def add_automatic_component(self, name):
+               if config.debug:
+                       print 'Looking for automatic component:', name
 
-       targets.sort()
-       return targets
+               c = Component(name)
+               if not c.meta.exists():
+                       try:
+                               c.meta.clone()
+                       except Error, e:
+                               log.error(str(e))
+                               return None
+
+               self.add_component(c)
+
+               for p in c.packages.values():
+                       self.init_package_depends(p)
+
+               return c
+
+       def init_rank_depends(self):
+               by_rank = {}
+               for c in self.components.itervalues():
+                       l = by_rank.get(c.rank)
+                       if l is None:
+                               l = []
+                               by_rank[c.rank] = l
+                       l.append(c)
+
+               ranks = by_rank.keys()
+               ranks.sort()
+
+               for i in xrange(1, len(ranks)):
+                       curr_rank = ranks[i]
+                       for curr_comp in by_rank[curr_rank]:
+                               for j in xrange(i):
+                                       prev_rank = ranks[j]
+                                       for prev_comp in by_rank[prev_rank]:
+                                               curr_comp.add_depend(prev_comp)
+
+       def check_depends(self):
+               fail = False
+               for p in self.packages.itervalues():
+                       for spec in to_seq(p.depends):
+                               if not Dependency(spec).check(self.packages):
+                                       fail = True
+                                       log.error('Dependency %s failed for %s' % \
+                                                 (spec, p.name))
+
+                       for spec in to_seq(p.conflicts):
+                               if Dependency(spec).check(self.packages):
+                                       fail = True
+                                       log.error('Package %s conflicts with %s' % \
+                                                 (p.name, spec))
+
+               if fail:
+                       raise Error('Invalid component tree')
 
 class Package(object):
        def __init__(self, name, component):
@@ -118,35 +234,18 @@ def to_seq(value):
                return value.split()
        return value
 
-def init_packages(c, targets, packages):
-       for path in glob(os.path.join(c.meta.path, '*.package')):
-               name = os.path.basename(path)[:-8]
-
-               pkg = parse_package(name, c, path)
-               if not pkg:
-                       continue
+def parse_package(name, component, path=None):
+       p = Package(name, component)
 
-               c.packages[name] = pkg
-               packages[name] = pkg
+       if path:
+               execfile(path, p.__dict__, p.__dict__)
 
-               if config.debug:
-                       print 'Component', c.name, 'provides', name
-
-       if c.packages:
-               targets.append(c.name)
-       elif config.debug:
-               print 'Component', c.name, 'does not provide any packages'
-
-def parse_package(name, component, path):
-       pkg = Package(name, component)
-       execfile(path, pkg.__dict__, pkg.__dict__)
-
-       if pkg.architectures:
+       if p.architectures:
                arch = config.boards[config.board].arch
-               if arch not in pkg.architectures:
+               if arch not in p.architectures:
                        return None
 
-       return pkg
+       return p
 
 class Dependency(object):
        regex = re.compile(r'([@]?)([^\s:]+)[:]?([<>=]*)([^\s:]*)[:]?(.*)')
@@ -164,60 +263,6 @@ class Dependency(object):
                # TODO: check version and flags
                return self.name in packages
 
-def init_depends(packages):
-       for pkg in packages.itervalues():
-               for spec in to_seq(pkg.depends):
-                       depname = Dependency(spec).name
-                       deppkg = packages.get(depname)
-
-                       if not deppkg:
-                               log.error('Package %s depends on ' \
-                                         'non-existent package %s' % \
-                                         (pkg.name, depname))
-                               continue
-
-                       if deppkg.component == pkg.component:
-                               continue
-
-                       pkg.component.add_depend(deppkg.component)
-
-       fail = False
-       for pkg in packages.itervalues():
-               for spec in to_seq(pkg.depends):
-                       if not Dependency(spec).check(packages):
-                               fail = True
-                               log.error('Dependency %s failed for %s' % \
-                                         (spec, pkg.name))
-
-               for spec in to_seq(pkg.conflicts):
-                       if Dependency(spec).check(packages):
-                               fail = True
-                               log.error('Package %s conflicts with %s' % \
-                                         (pkg.name, spec))
-
-       if fail:
-               raise Error('Invalid component tree')
-
-def init_rank_depends():
-       components_by_rank = {}
-       for c in config.components.itervalues():
-               l = components_by_rank.get(c.rank)
-               if l is None:
-                       l = []
-                       components_by_rank[c.rank] = l
-               l.append(c)
-
-       ranks = list(components_by_rank.keys())
-       ranks.sort()
-
-       for i in xrange(1, len(ranks)):
-               curr_rank = ranks[i]
-               for curr_comp in components_by_rank[curr_rank]:
-                       for j in xrange(i):
-                               prev_rank = ranks[j]
-                               for prev_comp in components_by_rank[prev_rank]:
-                                       curr_comp.add_depend(prev_comp)
-
 def fill_in_depends(components):
        depends = True
        while depends:
@@ -229,8 +274,3 @@ def fill_in_depends(components):
                                        depends.add(dep)
 
                components |= depends
-
-def init_rdepends(components):
-       for c in components:
-               for dep in c.get_depends():
-                       dep.add_rdepend(c)