[jhbuild] Rewrite of get_module_list (GNOME bug 669554)



commit 53cb43149b385e89c4058d279398a61a3331003a
Author: Craig Keogh <cskeogh adam com au>
Date:   Fri Jan 6 15:28:45 2012 +1030

    Rewrite of get_module_list (GNOME bug 669554)

 jhbuild/commands/base.py     |   17 ++--
 jhbuild/commands/bot.py      |    2 +-
 jhbuild/commands/rdepends.py |    4 +-
 jhbuild/commands/sysdeps.py  |    2 +-
 jhbuild/frontends/gtkui.py   |    9 +-
 jhbuild/moduleset.py         |  281 ++++++++++++++++++------------------------
 6 files changed, 139 insertions(+), 176 deletions(-)
---
diff --git a/jhbuild/commands/base.py b/jhbuild/commands/base.py
index 562da41..dd24e51 100644
--- a/jhbuild/commands/base.py
+++ b/jhbuild/commands/base.py
@@ -63,7 +63,7 @@ class cmd_update(Command):
         module_set = jhbuild.moduleset.load(config)
         module_list = module_set.get_module_list(args or config.modules,
                 config.skip, tags=config.tags,
-                ignore_suggests=config.ignore_suggests)
+                include_suggests=not config.ignore_suggests)
         # remove modules up to startat
         if options.startat:
             while module_list and module_list[0].name != options.startat:
@@ -221,9 +221,9 @@ class cmd_build(Command):
         module_set = jhbuild.moduleset.load(config)
         modules = args or config.modules
         module_list = module_set.get_module_list(modules,
-                config.skip, tags = config.tags,
-                include_optional_modules=options.build_optional_modules,
-                ignore_suggests=config.ignore_suggests)
+                config.skip, tags=config.tags,
+                include_suggests=not config.ignore_suggests,
+                include_afters=options.build_optional_modules)
         # remove modules up to startat
         if options.startat:
             while module_list and module_list[0].name != options.startat:
@@ -445,10 +445,11 @@ class cmd_list(Command):
         if options.list_all_modules:
             module_list = module_set.modules.values()
         else:
-            module_list = module_set.get_module_list(args or config.modules,
-                                config.skip, tags = config.tags,
-                                include_optional_modules = options.list_optional_modules,
-                                ignore_suggests=config.ignore_suggests)
+            module_list = module_set.get_module_list \
+                              (args or config.modules, config.skip,
+                               tags=config.tags,
+                               include_suggests= not config.ignore_suggests,
+                               include_afters=options.list_optional_modules)
 
         # remove modules up to startat
         if options.startat:
diff --git a/jhbuild/commands/bot.py b/jhbuild/commands/bot.py
index 7838794..14d5a4e 100644
--- a/jhbuild/commands/bot.py
+++ b/jhbuild/commands/bot.py
@@ -399,7 +399,7 @@ class cmd_bot(Command):
                 module_list = module_set.get_module_list(
                         self.jhbuild_config.modules,
                         self.jhbuild_config.skip,
-                        include_optional_modules=True)
+                        include_afters=True)
                 config['projects'] = [x.name for x in module_list \
                                       if not x.name.startswith('meta-')]
 
diff --git a/jhbuild/commands/rdepends.py b/jhbuild/commands/rdepends.py
index 1deaaf4..760bf66 100644
--- a/jhbuild/commands/rdepends.py
+++ b/jhbuild/commands/rdepends.py
@@ -67,9 +67,7 @@ class cmd_rdepends(Command):
                 if modname in module.dependencies:
                     uprint(module.name)
             else:
-                module_list = module_set.get_module_list([module.name],
-                                                         ignore_cycles=True,
-                                                         ignore_missing=True)
+                module_list = module_set.get_module_list([module.name])
                 if modname in [x.name for x in module_list]:
                     seen_modules.append(module.name)
                     deps = ''
diff --git a/jhbuild/commands/sysdeps.py b/jhbuild/commands/sysdeps.py
index bdc9530..1ac0b38 100644
--- a/jhbuild/commands/sysdeps.py
+++ b/jhbuild/commands/sysdeps.py
@@ -52,7 +52,7 @@ class cmd_sysdeps(cmd_build):
 
         module_set = jhbuild.moduleset.load(config)
         modules = args or config.modules
-        module_list = module_set.get_module_list(modules, process_sysdeps=False)
+        module_list = module_set.get_full_module_list(modules)
         module_state = module_set.get_system_modules(module_list)
 
         have_new_enough = False
diff --git a/jhbuild/frontends/gtkui.py b/jhbuild/frontends/gtkui.py
index 61bfb62..a8ea92f 100644
--- a/jhbuild/frontends/gtkui.py
+++ b/jhbuild/frontends/gtkui.py
@@ -282,9 +282,10 @@ class AppWindow(gtk.Window, buildscript.BuildScript):
             modules = [self.modules_list_model.get(
                     self.module_combo.get_active_iter(), 0)[0]]
 
-            self.modulelist = self.module_set.get_module_list(modules,
-                    self.config.skip, tags = self.config.tags,
-                    ignore_suggests=self.config.ignore_suggests)
+            self.modulelist = self.module_set.get_module_list \
+                                  (modules, self.config.skip,
+                                   tags = self.config.tags,
+                                   include_suggests=not self.config.ignore_suggests)
         else:
             self.orig_modulelist = None
 
@@ -679,7 +680,7 @@ class SelectModulesDialog(gtk.Dialog):
 
         self.startat_model.clear()
         modulelist = self.app.module_set.get_module_list([self.selected_module],
-                ignore_suggests=self.app.config.ignore_suggests)
+                include_suggests=not self.app.config.ignore_suggests)
         for module in modulelist:
             iter = self.startat_model.append((module.name,))
             if module.name == old_start_at:
diff --git a/jhbuild/moduleset.py b/jhbuild/moduleset.py
index 4562d46..e63fb14 100644
--- a/jhbuild/moduleset.py
+++ b/jhbuild/moduleset.py
@@ -53,6 +53,7 @@ class ModuleSet:
     def __init__(self, config = None, db=None):
         self.config = config
         self.modules = {}
+        self.raise_exception_on_warning=False
 
         if db is None:
             legacy_pkgdb_path = os.path.join(self.config.prefix, 'share', 'jhbuild', 'packagedb.xml')
@@ -79,169 +80,91 @@ class ModuleSet:
                 return self.modules[module]
         raise KeyError(module_name)
 
-    def get_module_list(self, seed, skip=[], tags=[], ignore_cycles=False,
-                ignore_suggests=False, include_optional_modules=False,
-                ignore_missing=False, process_sysdeps=True):
-        '''gets a list of module objects (in correct dependency order)
-        needed to build the modules in the seed list'''
-
-        if seed == 'all': seed = self.modules.keys()
+    def get_module_list(self, module_names, skip=[], tags=[],
+                        include_suggests=True, include_afters=False):
+        module_list = self.get_full_module_list(module_names, skip,
+                                                include_suggests,
+                                                include_afters)
+        module_list = self.remove_system_modules(module_list)
+        module_list = self.remove_tag_modules(module_list, tags)
+        return module_list
+
+    def get_full_module_list(self, module_names, skip=[],
+                                include_suggests=True, include_afters=False):
+
+        def dep_resolve(node, resolved, seen, after):
+            ''' Recursive depth-first search of the dependency tree. Creates
+            the build order into the list 'resolved'. <after/> modules are
+            added to the dependency tree but flagged. When search finished
+            <after/> modules not a real dependency are removed.
+            '''
+            circular = False
+            seen.append(node)
+            if include_suggests:
+                edges = node.dependencies + node.suggests + node.after
+            else:
+                edges = node.dependencies + node.after
+            # do not include <after> modules because a previous visited <after>
+            # module may later be a hard dependency
+            resolved_deps = [module for module, after_module in resolved \
+                             if not after_module]
+            for edge_name in edges:
+                edge = self.modules.get(edge_name)
+                if edge == None:
+                    if node not in [i[0] for i in resolved]:
+                        self._warn(_('%(module)s has a dependency on unknown'
+                                     ' "%(invalid)s" module') % \
+                                         {'module'  : node.name,
+                                          'invalid' : edge_name})
+                elif edge_name not in skip and edge not in resolved_deps:
+                    if edge in seen:
+                        self._warn(_('Circular dependencies detected: %s') % \
+                                   ' -> '.join([i.name for i in seen] + \
+                                               [edge.name]))
+                        circular = True
+                        break
+                    else:
+                        if edge_name in node.after:
+                            dep_resolve(edge, resolved, seen, True)
+                        elif edge_name in node.suggests:
+                            dep_resolve(edge, resolved, seen, after)
+                        elif edge_name in node.dependencies:
+                            dep_resolve(edge, resolved, seen, after)
+                            # hard dependency may be missed if a cyclic
+                            # dependency. Add it:
+                            if edge not in [i[0] for i in resolved]:
+                                resolved.append((edge, after))
+
+            seen.remove(node)
+
+            if not circular:
+                if node not in [i[0] for i in resolved]:
+                    resolved.append((node, after))
+                elif not after:
+                    # a dependency exists for an after, flag to keep
+                    for index, item in enumerate(resolved):
+                        if item[1] == True and item[0] == node:
+                            resolved[index] = (node, False)
+
+        if module_names == 'all':
+            module_names = self.modules.keys()
         try:
-            all_modules = [self.get_module(mod, ignore_case = True) for mod in seed if mod not in skip]
+            # remove skip modules from module_name list
+            modules = [self.get_module(module, ignore_case = True) \
+                       for module in module_names if module not in skip]
         except KeyError, e:
             raise UsageError(_('module "%s" not found') % e)
 
-        asked_modules = all_modules[:]
-
-        # 1st: get all modules that will be needed
-        # note this is only needed to skip "after" modules that would not
-        # otherwise be built
-        i = 0
-        while i < len(all_modules):
-            dep_missing = False
-            for modname in all_modules[i].dependencies:
-                depmod = self.modules.get(modname)
-                if not depmod:
-                    if not ignore_missing:
-                        raise UsageError(_(
-                                '%(module)s has a dependency on unknown "%(invalid)s" module') % {
-                                    'module': all_modules[i].name,
-                                    'invalid': modname})
-                    logging.info(_(
-                                '%(module)s has a dependency on unknown "%(invalid)s" module') % {
-                                    'module': all_modules[i].name,
-                                    'invalid': modname})
-                    dep_missing = True
-                    continue
-
-                if not depmod in all_modules:
-                    all_modules.append(depmod)
-
-            if not ignore_suggests:
-                # suggests can be ignored if not in moduleset
-                for modname in all_modules[i].suggests:
-                    depmod = self.modules.get(modname)
-                    if not depmod:
-                        continue
-                    if not depmod in all_modules:
-                        all_modules.append(depmod)
-
-            if dep_missing:
-                del all_modules[i]
-
-            i += 1
-
-        # 2nd: order them, raise an exception on hard dependency cycle, ignore
-        # them for soft dependencies
-        self._ordered = []
-        self._state = {}
-
-        for modname in skip:
-            # mark skipped modules as already processed
-            self._state[self.modules.get(modname)] = 'processed'
-
-        # process_sysdeps lets us avoid repeatedly checking system module state when
-        # handling recursive dependencies.
-        if self.config.partial_build and process_sysdeps:
-            system_module_state = self.get_system_modules(all_modules)
-            for pkg_config,(module, req_version, installed_version, new_enough) in system_module_state.iteritems():
-                # Only mark a module as processed if new enough *and* we haven't built it before
-                if new_enough and not self.packagedb.check(module.name):
-                    self._state[module] = 'processed'
+        resolved = []
+        for module in modules:
+            dep_resolve(module, resolved, [], False)
 
-        if tags:
-            for modname in self.modules:
-                for tag in tags:
-                    if tag in self.modules[modname].tags:
-                        break
-                else:
-                    # no tag matched, mark module as processed
-                    self._state[self.modules[modname]] = 'processed'
-
-        def order(modules, module, mode = 'dependencies'):
-            if self._state.get(module, 'clean') == 'processed':
-                # already seen
-                return
-            if self._state.get(module, 'clean') == 'in-progress':
-                # dependency circle, abort when processing hard dependencies
-                if not ignore_cycles:
-                    raise DependencyCycleError()
-                else:
-                    self._state[module] = 'in-progress'
-                    return
-            self._state[module] = 'in-progress'
-            for modname in module.dependencies:
-                try:
-                    depmod = self.modules[modname]
-                    order([self.modules[x] for x in depmod.dependencies], depmod, 'dependencies')
-                except KeyError:
-                    pass # user already notified via logging.info above
-            if not ignore_suggests:
-                for modname in module.suggests:
-                    depmod = self.modules.get(modname)
-                    if not depmod:
-                        continue
-                    save_state, save_ordered = self._state.copy(), self._ordered[:]
-                    try:
-                        order([self.modules[x] for x in depmod.dependencies], depmod, 'suggests')
-                    except DependencyCycleError:
-                        self._state, self._ordered = save_state, save_ordered
-                    except KeyError:
-                        pass # user already notified via logging.info above
-
-            extra_afters = []
-            for modname in module.after:
-                depmod = self.modules.get(modname)
-                if not depmod:
-                    # this module doesn't exist, skip.
-                    continue
-                if not depmod in all_modules and not include_optional_modules:
-                    # skip modules that would not be built otherwise
-                    # (build_optional_modules being the argument to force them
-                    # to be included nevertheless)
-
-                    if not depmod.dependencies:
-                        # depmod itself has no dependencies, skip.
-                        continue
-
-                    # more expensive, if depmod has dependencies, compute its
-                    # full list of hard dependencies, getting it into
-                    # extra_afters, so they are also evaluated.
-                    # <http://bugzilla.gnome.org/show_bug.cgi?id=546640>
-                    t_ms = ModuleSet(self.config)
-                    t_ms.modules = self.modules.copy()
-                    dep_modules = t_ms.get_module_list(seed=[depmod.name], process_sysdeps=False)
-                    for m in dep_modules[:-1]:
-                        if m in all_modules:
-                            extra_afters.append(m)
-                    continue
-                save_state, save_ordered = self._state.copy(), self._ordered[:]
-                try:
-                    order([self.modules[x] for x in depmod.dependencies], depmod, 'after')
-                except DependencyCycleError:
-                    self._state, self._ordered = save_state, save_ordered
-            for depmod in extra_afters:
-                save_state, save_ordered = self._state.copy(), self._ordered[:]
-                try:
-                    order([self.modules[x] for x in depmod.dependencies], depmod, 'after')
-                except DependencyCycleError:
-                    self._state, self._ordered = save_state, save_ordered
-            self._state[module] = 'processed'
-            self._ordered.append(module)
-
-        for i, module in enumerate(all_modules):
-            order([], module)
-            if i+1 == len(asked_modules): 
-                break
-
-        ordered = self._ordered[:]
-        del self._ordered
-        del self._state
-        return ordered
-    
-    def get_full_module_list(self, skip=[], ignore_cycles=False):
-        return self.get_module_list(self.modules.keys(), skip=skip,
-                ignore_cycles=ignore_cycles, ignore_missing=True)
+        if include_afters:
+            module_list = [module[0] for module in resolved]
+        else:
+            module_list = [module for module, after_module in resolved \
+                           if not after_module]
+        return module_list
 
     def get_test_module_list (self, seed, skip=[]):
         test_modules = []
@@ -275,7 +198,40 @@ class ModuleSet:
                 new_enough = compare_version(installed_version, required_version)
                 module_state[module_pkg] = (module, required_version, installed_version, new_enough)
         return module_state
-    
+
+    def remove_system_modules(self, modules):
+        if not self.config.partial_build:
+            return modules
+
+        installed_pkgconfig = systeminstall.get_installed_pkgconfigs \
+                                  (self.config)
+        return_list = []
+
+        for module in modules:
+            skip = False
+            if module.pkg_config is not None and \
+            isinstance(module.branch, TarballBranch):
+                # Strip off the .pc
+                module_pkg = module.pkg_config[:-3]
+                required_version = module.branch.version
+                if module_pkg in installed_pkgconfig:
+                    installed_version = installed_pkgconfig[module_pkg]
+                    skip = compare_version(installed_version, required_version)
+            if not skip:
+                return_list.append(module)
+        return return_list
+
+    def remove_tag_modules(self, modules, tags):
+        if tags:
+            return_list = []
+            for module in modules:
+                for tag in tags:
+                    if tag in self.modules[module.name].tags:
+                        return_list.append(module)
+            return return_list
+        else:
+            return modules
+
     def write_dot(self, modules=None, fp=sys.stdout, suggests=False, clusters=False):
         from jhbuild.modtypes import MetaModule
         from jhbuild.modtypes.autotools import AutogenModule
@@ -341,6 +297,13 @@ class ModuleSet:
 
         fp.write('}\n')
 
+    def _warn(self, msg):
+        if self.raise_exception_on_warning:
+            raise UsageError(msg)
+        else:
+            logging.warn(msg)
+
+
 def load(config, uri=None):
     if uri is not None:
         modulesets = [ uri ]



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]