8bd6ae
#!/usr/bin/env python3
8bd6ae
"""
8bd6ae
filter kmods into groups for packaging, see filtermods.adoc
8bd6ae
"""
8bd6ae
8bd6ae
import argparse
8bd6ae
import os
8bd6ae
import re
8bd6ae
import subprocess
8bd6ae
import sys
8bd6ae
import yaml
8bd6ae
import unittest
8bd6ae
8bd6ae
from logging import getLogger, DEBUG, INFO, WARN, ERROR, CRITICAL, NOTSET, FileHandler, StreamHandler, Formatter, Logger
8bd6ae
from typing import Optional
8bd6ae
8bd6ae
log = getLogger('filtermods')
8bd6ae
8bd6ae
8bd6ae
def get_td(filename):
8bd6ae
    script_dir = os.path.dirname(os.path.realpath(__file__))
8bd6ae
    return os.path.join(script_dir, 'filtermods-testdata', filename)
8bd6ae
8bd6ae
8bd6ae
def run_command(cmd, cwddir=None):
8bd6ae
    p = subprocess.Popen(cmd, cwd=cwddir, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
8bd6ae
    out, err = p.communicate()
8bd6ae
    out_str = out.decode('utf-8')
8bd6ae
    err_str = err.decode('utf-8')
8bd6ae
    return p.returncode, out_str, err_str
8bd6ae
8bd6ae
8bd6ae
def safe_run_command(cmd, cwddir=None):
8bd6ae
    log.info('%s', cmd)
8bd6ae
    retcode, out, err = run_command(cmd, cwddir)
8bd6ae
    if retcode != 0:
8bd6ae
        log.warning('Command failed: %s, ret_code: %d', cmd, retcode)
8bd6ae
        log.warning(out)
8bd6ae
        log.warning(err)
8bd6ae
        raise Exception(err)
8bd6ae
    log.info('  ^^[OK]')
8bd6ae
    return retcode, out, err
8bd6ae
8bd6ae
8bd6ae
def setup_logging(log_filename, stdout_log_level):
8bd6ae
    log_format = '%(asctime)s %(levelname)7.7s %(funcName)20.20s:%(lineno)4s %(message)s'
8bd6ae
    log = getLogger('filtermods')
8bd6ae
    log.setLevel(DEBUG)
8bd6ae
8bd6ae
    handler = StreamHandler(sys.stdout)
8bd6ae
    formatter = Formatter(log_format, '%H:%M:%S')
8bd6ae
    handler.setFormatter(formatter)
8bd6ae
    handler.setLevel(stdout_log_level)
8bd6ae
    log.addHandler(handler)
8bd6ae
    log.debug('stdout logging on')
8bd6ae
8bd6ae
    if log_filename:
8bd6ae
        file_handler = FileHandler(log_filename, 'w')
8bd6ae
        file_handler.setFormatter(formatter)
8bd6ae
        file_handler.setLevel(DEBUG)
8bd6ae
        log.addHandler(file_handler)
8bd6ae
        log.info('file logging on: %s', log_filename)
8bd6ae
8bd6ae
    return log
8bd6ae
8bd6ae
8bd6ae
def canon_modname(kmod_pathname: str) -> str:
8bd6ae
    name = os.path.basename(kmod_pathname)
8bd6ae
    if name.endswith('.xz'):
8bd6ae
        name = name[:-3]
8bd6ae
    return name
8bd6ae
8bd6ae
8bd6ae
class HierarchyObject:
8bd6ae
    def __init__(self):
8bd6ae
        self.depends_on = set()
8bd6ae
8bd6ae
8bd6ae
def get_topo_order(obj_list: list[HierarchyObject], func_get_linked_objs=lambda x: x.depends_on) -> list[HierarchyObject]:
8bd6ae
    topo_order = []
8bd6ae
    objs_to_sort = set(obj_list)
8bd6ae
    objs_sorted = set()
8bd6ae
8bd6ae
    while len(objs_to_sort) > 0:
8bd6ae
        no_deps = set()
8bd6ae
        for obj in objs_to_sort:
8bd6ae
            linked = func_get_linked_objs(obj)
8bd6ae
            if not linked:
8bd6ae
                no_deps.add(obj)
8bd6ae
            else:
8bd6ae
                all_deps_sorted = True
8bd6ae
                for dep in linked:
8bd6ae
                    if dep not in objs_sorted:
8bd6ae
                        all_deps_sorted = False
8bd6ae
                        break
8bd6ae
                if all_deps_sorted:
8bd6ae
                    no_deps.add(obj)
8bd6ae
8bd6ae
        for obj in no_deps:
8bd6ae
            topo_order.append(obj)
8bd6ae
            objs_sorted.add(obj)
8bd6ae
            objs_to_sort.remove(obj)
8bd6ae
8bd6ae
    return topo_order
8bd6ae
8bd6ae
8bd6ae
class KMod(HierarchyObject):
8bd6ae
    def __init__(self, kmod_pathname: str) -> None:
8bd6ae
        super(KMod, self).__init__()
8bd6ae
        self.name: str = canon_modname(kmod_pathname)
8bd6ae
        self.kmod_pathname: str = kmod_pathname
8bd6ae
        self.is_dependency_for: set[KMod] = set()
8bd6ae
        self.assigned_to_pkg: Optional[KModPackage] = None
8bd6ae
        self.preferred_pkg: Optional[KModPackage] = None
8bd6ae
        self.rule_specifity: int = 0
8bd6ae
        self.allowed_list: Optional[set[KModPackage]] = None
8bd6ae
        self.err = 0
8bd6ae
8bd6ae
    def __str__(self):
8bd6ae
        depends_on = ''
8bd6ae
        for kmod in self.depends_on:
8bd6ae
            depends_on = depends_on + ' ' + kmod.name
8bd6ae
        return '%s {%s}' % (self.name, depends_on)
8bd6ae
8bd6ae
8bd6ae
class KModList():
8bd6ae
    def __init__(self) -> None:
8bd6ae
        self.name_to_kmod_map: dict[str, KMod] = {}
8bd6ae
        self.topo_order: Optional[list[KMod]] = None
8bd6ae
8bd6ae
    def get(self, kmod_pathname, create_if_missing=False):
8bd6ae
        kmod_name = canon_modname(kmod_pathname)
8bd6ae
        if kmod_name in self.name_to_kmod_map:
8bd6ae
            return self.name_to_kmod_map[kmod_name]
8bd6ae
        if not create_if_missing:
8bd6ae
            return None
8bd6ae
8bd6ae
        kmod = KMod(kmod_pathname)
8bd6ae
        # log.debug('Adding kmod %s (%s) to list', kmod.name, kmod.kmod_pathname)
8bd6ae
        if kmod.kmod_pathname != kmod_pathname:
8bd6ae
            raise Exception('Already have %s, but path changed? %s', kmod_name, kmod_pathname)
8bd6ae
        if not kmod.name:
8bd6ae
            raise Exception('Each kmod needs a name')
8bd6ae
        self.name_to_kmod_map[kmod_name] = kmod
8bd6ae
        return kmod
8bd6ae
8bd6ae
    def process_depmod_line(self, line):
8bd6ae
        tmp = line.split(':')
8bd6ae
        if len(tmp) != 2:
8bd6ae
            raise Exception('Depmod line has unexpected format: %s', line)
8bd6ae
        kmod_pathname = tmp[0].strip()
8bd6ae
        dependencies_pathnames = tmp[1].strip()
8bd6ae
        kmod = self.get(kmod_pathname, create_if_missing=True)
8bd6ae
8bd6ae
        if dependencies_pathnames:
8bd6ae
            for dep_pathname in dependencies_pathnames.split(' '):
8bd6ae
                dep_kmod = self.get(dep_pathname, create_if_missing=True)
8bd6ae
                kmod.depends_on.add(dep_kmod)
8bd6ae
                dep_kmod.is_dependency_for.add(kmod)
8bd6ae
8bd6ae
    def load_depmod_file(self, filepath):
8bd6ae
        with open(filepath) as f:
8bd6ae
            lines = f.readlines()
8bd6ae
            for line in lines:
8bd6ae
                if not line or line.startswith('#'):
8bd6ae
                    continue
8bd6ae
                self.process_depmod_line(line)
8bd6ae
        log.info('depmod %s loaded, number of kmods: %s', filepath, len(self.name_to_kmod_map))
8bd6ae
8bd6ae
    def dump(self):
8bd6ae
        for kmod in self.name_to_kmod_map.values():
8bd6ae
            print(kmod)
8bd6ae
8bd6ae
    def get_topo_order(self):
8bd6ae
        if self.topo_order is None:
8bd6ae
            self.topo_order = get_topo_order(self.name_to_kmod_map.values())
8bd6ae
        # TODO: what if we add something after?
8bd6ae
        return self.topo_order
8bd6ae
8bd6ae
    def get_alphabetical_order(self):
8bd6ae
        kmods = list(self.name_to_kmod_map.values())
8bd6ae
        kmods.sort(key=lambda k: k.kmod_pathname)
8bd6ae
        return kmods
8bd6ae
8bd6ae
    def load_kmods_from_dir(self, topdir):
8bd6ae
        ret = []
8bd6ae
        for root, dirs, files in os.walk(topdir):
8bd6ae
            for filename in files:
8bd6ae
                if filename.endswith('.xz'):
8bd6ae
                    filename = filename[:-3]
8bd6ae
                if filename.endswith('.ko'):
8bd6ae
                    kmod_pathname = os.path.join(root, filename)
8bd6ae
                    ret.append(kmod_pathname)
8bd6ae
8bd6ae
        return ret
8bd6ae
8bd6ae
    def check_depmod_has_all_kmods(self, dirpath):
8bd6ae
        ret = self.load_kmods_from_dir(dirpath)
8bd6ae
        for kmod_pathname in ret:
8bd6ae
            kmod = self.get(kmod_pathname)
8bd6ae
            if not kmod:
8bd6ae
                raise Exception('Could not find kmod %s in depmod', kmod_pathname)
8bd6ae
        log.debug('OK: all (%s) kmods from %s are known', len(ret), dirpath)
8bd6ae
8bd6ae
8bd6ae
class KModPackage(HierarchyObject):
8bd6ae
    def _get_depends_on(pkg):
8bd6ae
        return pkg.depends_on
8bd6ae
8bd6ae
    def _get_deps_for(pkg):
8bd6ae
        return pkg.is_dependency_for
8bd6ae
8bd6ae
    def __init__(self, name: str, depends_on=[]) -> None:
8bd6ae
        self.name: str = name
8bd6ae
        self.depends_on: set[KModPackage] = set(depends_on)
8bd6ae
        self.is_dependency_for: set[KModPackage] = set()
8bd6ae
8bd6ae
        for pkg in self.depends_on:
8bd6ae
            pkg.is_dependency_for.add(self)
8bd6ae
        self.all_depends_on_list: list[KModPackage] = self._get_all_linked(KModPackage._get_depends_on)
8bd6ae
        self.all_depends_on: set[KModPackage] = set(self.all_depends_on_list)
8bd6ae
        self.all_deps_for: Optional[set[KModPackage]] = None
8bd6ae
        self.default = False
8bd6ae
        log.debug('KModPackage created %s, depends_on: %s', name, [pkg.name for pkg in depends_on])
8bd6ae
8bd6ae
    def __repr__(self):
8bd6ae
        return self.name
8bd6ae
8bd6ae
    def get_all_deps_for(self):
8bd6ae
        if self.all_deps_for is None:
8bd6ae
            self.all_deps_for = set(self._get_all_linked(KModPackage._get_deps_for))
8bd6ae
        return self.all_deps_for
8bd6ae
8bd6ae
    def _get_all_linked(self, func_get_links):
8bd6ae
        ret = []
8bd6ae
        explore = func_get_links(self)
8bd6ae
8bd6ae
        while len(explore) > 0:
8bd6ae
            new_explore = set()
8bd6ae
            for pkg in explore:
8bd6ae
                if pkg not in ret:
8bd6ae
                    ret.append(pkg)
8bd6ae
                    for dep in func_get_links(pkg):
8bd6ae
                        new_explore.add(dep)
8bd6ae
            explore = new_explore
8bd6ae
        return ret
8bd6ae
8bd6ae
8bd6ae
class KModPackageList(HierarchyObject):
8bd6ae
    def __init__(self) -> None:
8bd6ae
        self.name_to_obj: dict[str, KModPackage] = {}
8bd6ae
        self.kmod_pkg_list: list[KModPackage] = []
8bd6ae
        self.rules: list[tuple[str, str, str]] = []
8bd6ae
8bd6ae
    def get(self, pkgname):
8bd6ae
        if pkgname in self.name_to_obj:
8bd6ae
            return self.name_to_obj[pkgname]
8bd6ae
        return None
8bd6ae
8bd6ae
    def add_kmod_pkg(self, pkg):
8bd6ae
        self.name_to_obj[pkg.name] = pkg
8bd6ae
        self.kmod_pkg_list.append(pkg)
8bd6ae
8bd6ae
    def __iter__(self):
8bd6ae
        return iter(self.kmod_pkg_list)
8bd6ae
8bd6ae
8bd6ae
def get_kmods_matching_re(kmod_list: KModList, param_re: str) -> list[KMod]:
8bd6ae
    ret = []
8bd6ae
    # first subdir can be anything - this is because during build everything
8bd6ae
    # goes to kernel, but subpackages can move it (e.g. to extra)
8bd6ae
    param_re = '[^/]+/' + param_re
8bd6ae
    pattern = re.compile(param_re)
8bd6ae
8bd6ae
    for kmod in kmod_list.get_topo_order():
8bd6ae
        m = pattern.match(kmod.kmod_pathname)
8bd6ae
        if m:
8bd6ae
            ret.append(kmod)
8bd6ae
    return ret
8bd6ae
8bd6ae
8bd6ae
def walk_kmod_chain(kmod, myfunc):
8bd6ae
    visited = set()
8bd6ae
8bd6ae
    def visit_kmod(kmod, parent_kmod, func_to_call):
8bd6ae
        func_to_call(kmod, parent_kmod)
8bd6ae
        visited.add(kmod)
8bd6ae
        for dep in kmod.depends_on:
8bd6ae
            if dep not in visited:
8bd6ae
                visit_kmod(dep, kmod, func_to_call)
8bd6ae
8bd6ae
    visit_kmod(kmod, None, myfunc)
8bd6ae
    return visited
8bd6ae
8bd6ae
8bd6ae
# is pkg a parent to any pkg from "alist"
8bd6ae
def is_pkg_parent_to_any(pkg: KModPackage, alist: set[KModPackage]) -> bool:
8bd6ae
    if pkg in alist:
8bd6ae
        return True
8bd6ae
8bd6ae
    for some_pkg in alist:
8bd6ae
        if some_pkg in pkg.all_depends_on:
8bd6ae
            return True
8bd6ae
    return False
8bd6ae
8bd6ae
8bd6ae
# is pkg a child to any pkg from "alist"
8bd6ae
def is_pkg_child_to_any(pkg: KModPackage, alist: set[KModPackage]) -> bool:
8bd6ae
    if pkg in alist:
8bd6ae
        return True
8bd6ae
8bd6ae
    for some_pkg in alist:
8bd6ae
        if pkg in some_pkg.all_depends_on:
8bd6ae
            return True
8bd6ae
    return False
8bd6ae
8bd6ae
8bd6ae
def update_allowed(kmod: KMod, visited: set[KMod], update_linked: bool = False) -> int:
8bd6ae
    num_updated = 0
8bd6ae
    init = False
8bd6ae
    to_remove = set()
8bd6ae
8bd6ae
    if kmod in visited:
8bd6ae
        return num_updated
8bd6ae
    visited.add(kmod)
8bd6ae
8bd6ae
    # if we have nothing, try to initialise based on parents and children
8bd6ae
    if kmod.allowed_list is None:
8bd6ae
        init_allowed_list: set[KModPackage] = set()
8bd6ae
8bd6ae
        # init from children
8bd6ae
        for kmod_dep in kmod.depends_on:
8bd6ae
            if kmod_dep.allowed_list:
8bd6ae
                init_allowed_list.update(kmod_dep.allowed_list)
8bd6ae
                init = True
8bd6ae
8bd6ae
        if init:
8bd6ae
            # also add any pkgs that pkgs from list could depend on
8bd6ae
            deps_for = set()
8bd6ae
            for pkg in init_allowed_list:
8bd6ae
                deps_for.update(pkg.get_all_deps_for())
8bd6ae
            init_allowed_list.update(deps_for)
8bd6ae
8bd6ae
        # init from parents
8bd6ae
        if not init:
8bd6ae
            for kmod_par in kmod.is_dependency_for:
8bd6ae
                if kmod_par.allowed_list:
8bd6ae
                    init_allowed_list.update(kmod_par.allowed_list)
8bd6ae
                    # also add any pkgs that depend on pkgs from list
8bd6ae
                    for pkg in kmod_par.allowed_list:
8bd6ae
                        init_allowed_list.update(pkg.all_depends_on)
8bd6ae
                        init = True
8bd6ae
8bd6ae
        if init:
8bd6ae
            kmod.allowed_list = init_allowed_list
8bd6ae
            log.debug('%s: init to %s', kmod.name, [x.name for x in kmod.allowed_list])
8bd6ae
8bd6ae
    kmod_allowed_list = kmod.allowed_list or set()
8bd6ae
    # log.debug('%s: update to %s', kmod.name, [x.name for x in kmod_allowed_list])
8bd6ae
8bd6ae
    # each allowed is parent to at least one child allowed [for _all_ children]
8bd6ae
    for pkg in kmod_allowed_list:
8bd6ae
        for kmod_dep in kmod.depends_on:
8bd6ae
            if kmod_dep.allowed_list is None or kmod_dep.err:
8bd6ae
                continue
8bd6ae
            if not is_pkg_parent_to_any(pkg, kmod_dep.allowed_list):
8bd6ae
                to_remove.add(pkg)
8bd6ae
                log.debug('%s: remove %s from allowed, child: %s [%s]',
8bd6ae
                          kmod.name, [pkg.name], kmod_dep.name, [x.name for x in kmod_dep.allowed_list])
8bd6ae
8bd6ae
    # each allowed is child to at least one parent allowed [for _all_ parents]
8bd6ae
    for pkg in kmod_allowed_list:
8bd6ae
        for kmod_par in kmod.is_dependency_for:
8bd6ae
            if kmod_par.allowed_list is None or kmod_par.err:
8bd6ae
                continue
8bd6ae
8bd6ae
            if not is_pkg_child_to_any(pkg, kmod_par.allowed_list):
8bd6ae
                to_remove.add(pkg)
8bd6ae
                log.debug('%s: remove %s from allowed, parent: %s %s',
8bd6ae
                          kmod.name, [pkg.name], kmod_par.name, [x.name for x in kmod_par.allowed_list])
8bd6ae
8bd6ae
    for pkg in to_remove:
8bd6ae
        kmod_allowed_list.remove(pkg)
8bd6ae
        num_updated = num_updated + 1
8bd6ae
        if len(kmod_allowed_list) == 0:
8bd6ae
            log.error('%s: cleared entire allow list', kmod.name)
8bd6ae
            kmod.err = 1
8bd6ae
8bd6ae
    if init or to_remove or update_linked:
8bd6ae
        if to_remove:
8bd6ae
            log.debug('%s: updated to %s', kmod.name, [x.name for x in kmod_allowed_list])
8bd6ae
8bd6ae
        for kmod_dep in kmod.depends_on:
8bd6ae
            num_updated = num_updated + update_allowed(kmod_dep, visited)
8bd6ae
8bd6ae
        for kmod_dep in kmod.is_dependency_for:
8bd6ae
            num_updated = num_updated + update_allowed(kmod_dep, visited)
8bd6ae
8bd6ae
    return num_updated
8bd6ae
8bd6ae
8bd6ae
def apply_initial_labels(pkg_list: KModPackageList, kmod_list: KModList, treat_default_as_wants=False):
8bd6ae
    log.debug('')
8bd6ae
    for cur_rule in ['needs', 'wants', 'default']:
8bd6ae
        for package_name, rule_type, rule in pkg_list.rules:
8bd6ae
            pkg_obj = pkg_list.get(package_name)
8bd6ae
8bd6ae
            if not pkg_obj:
8bd6ae
                log.error('no package with name %s', package_name)
8bd6ae
8bd6ae
            if cur_rule != rule_type:
8bd6ae
                continue
8bd6ae
8bd6ae
            if rule_type == 'default' and treat_default_as_wants:
8bd6ae
                rule_type = 'wants'
8bd6ae
8bd6ae
            if 'needs' == rule_type:
8bd6ae
                # kmod_matching is already in topo_order
8bd6ae
                kmod_matching = get_kmods_matching_re(kmod_list, rule)
8bd6ae
                for kmod in kmod_matching:
8bd6ae
                    if kmod.assigned_to_pkg and kmod.assigned_to_pkg != pkg_obj:
8bd6ae
                        log.error('%s: can not be required by 2 pkgs %s %s', kmod.name, kmod.assigned_to_pkg, pkg_obj.name)
8bd6ae
                    else:
8bd6ae
                        kmod.assigned_to_pkg = pkg_obj
8bd6ae
                        kmod.allowed_list = set([pkg_obj])
8bd6ae
                        kmod.rule_specifity = len(kmod_matching)
8bd6ae
                        log.debug('%s: needed by %s', kmod.name, [pkg_obj.name])
8bd6ae
8bd6ae
            if 'wants' == rule_type:
8bd6ae
                # kmod_matching is already in topo_order
8bd6ae
                kmod_matching = get_kmods_matching_re(kmod_list, rule)
8bd6ae
                for kmod in kmod_matching:
8bd6ae
                    if kmod.allowed_list is None:
8bd6ae
                        kmod.allowed_list = set(pkg_obj.all_depends_on)
8bd6ae
                        kmod.allowed_list.add(pkg_obj)
8bd6ae
                        kmod.preferred_pkg = pkg_obj
8bd6ae
                        kmod.rule_specifity = len(kmod_matching)
8bd6ae
                        log.debug('%s: wanted by %s, init allowed to %s', kmod.name, [pkg_obj.name], [pkg.name for pkg in kmod.allowed_list])
8bd6ae
                    else:
8bd6ae
                        if kmod.assigned_to_pkg:
8bd6ae
                            log.debug('%s: ignoring wants by %s, already assigned to %s', kmod.name, pkg_obj.name, kmod.assigned_to_pkg.name)
8bd6ae
                        else:
8bd6ae
                            # rule specifity may not be good idea, so just log it
8bd6ae
                            # e.g. .*test.* may not be more specific than arch/x86/.*
8bd6ae
                            log.debug('already have wants for %s %s, new rule: %s', kmod.name, kmod.preferred_pkg, rule)
8bd6ae
8bd6ae
            if 'default' == rule_type:
8bd6ae
                pkg_obj.default = True
8bd6ae
8bd6ae
8bd6ae
def settle(kmod_list: KModList) -> None:
8bd6ae
    kmod_topo_order = list(kmod_list.get_topo_order())
8bd6ae
8bd6ae
    for i in range(0, 25):
8bd6ae
        log.debug('settle start %s', i)
8bd6ae
8bd6ae
        ret = 0
8bd6ae
        for kmod in kmod_topo_order:
8bd6ae
            visited: set[KMod] = set()
8bd6ae
            ret = ret + update_allowed(kmod, visited)
8bd6ae
        log.debug('settle %s updated nodes: %s', i, ret)
8bd6ae
8bd6ae
        if ret == 0:
8bd6ae
            break
8bd6ae
8bd6ae
        kmod_topo_order.reverse()
8bd6ae
8bd6ae
8bd6ae
# phase 1 - propagate initial labels
8bd6ae
def propagate_labels_1(pkg_list: KModPackageList, kmod_list: KModList):
8bd6ae
    log.info('')
8bd6ae
    settle(kmod_list)
8bd6ae
8bd6ae
8bd6ae
def pick_closest_to_preffered(preferred_pkg: KModPackage, allowed_set: set[KModPackage]):
8bd6ae
    for child in preferred_pkg.all_depends_on_list:
8bd6ae
        if child in allowed_set:
8bd6ae
            return child
8bd6ae
    return None
8bd6ae
8bd6ae
8bd6ae
# phase 2 - if some kmods allow more than one pkg, pick wanted package
8bd6ae
def propagate_labels_2(pkg_list: KModPackageList, kmod_list: KModList):
8bd6ae
    log.info('')
8bd6ae
    ret = 0
8bd6ae
    for kmod in kmod_list.get_topo_order():
8bd6ae
        update_linked = False
8bd6ae
8bd6ae
        if kmod.allowed_list is None and kmod.preferred_pkg:
8bd6ae
            log.error('%s: has no allowed list but has preferred_pkg %s', kmod.name, kmod.preferred_pkg.name)
8bd6ae
            kmod.err = 1
8bd6ae
8bd6ae
        if kmod.allowed_list and kmod.preferred_pkg:
8bd6ae
            chosen_pkg = None
8bd6ae
            if kmod.preferred_pkg in kmod.allowed_list:
8bd6ae
                chosen_pkg = kmod.preferred_pkg
8bd6ae
            else:
8bd6ae
                chosen_pkg = pick_closest_to_preffered(kmod.preferred_pkg, kmod.allowed_list)
8bd6ae
8bd6ae
            if chosen_pkg is not None:
8bd6ae
                kmod.allowed_list = set([chosen_pkg])
8bd6ae
                log.debug('%s: making to prefer %s (preffered is %s), allowed: %s', kmod.name, chosen_pkg.name,
8bd6ae
                          kmod.preferred_pkg.name, [pkg.name for pkg in kmod.allowed_list])
8bd6ae
                update_linked = True
8bd6ae
8bd6ae
        visited: set[KMod] = set()
8bd6ae
        ret = ret + update_allowed(kmod, visited, update_linked)
8bd6ae
8bd6ae
    log.debug('updated nodes: %s', ret)
8bd6ae
    settle(kmod_list)
8bd6ae
8bd6ae
8bd6ae
# Is this the best pick? ¯\_(ツ)_/¯
8bd6ae
def pick_topmost_allowed(allowed_set: set[KModPackage]) -> KModPackage:
8bd6ae
    topmost = next(iter(allowed_set))
8bd6ae
    for pkg in allowed_set:
8bd6ae
        if len(pkg.all_depends_on) > len(topmost.all_depends_on):
8bd6ae
            topmost = pkg
8bd6ae
8bd6ae
    return topmost
8bd6ae
8bd6ae
8bd6ae
# phase 3 - assign everything else that remained
8bd6ae
def propagate_labels_3(pkg_list: KModPackageList, kmod_list: KModList):
8bd6ae
    log.info('')
8bd6ae
    ret = 0
8bd6ae
    kmod_topo_order = list(kmod_list.get_topo_order())
8bd6ae
    # do reverse topo order to cover children faster
8bd6ae
    kmod_topo_order.reverse()
8bd6ae
8bd6ae
    default_pkg = None
8bd6ae
    default_name = ''
8bd6ae
    for pkg_obj in pkg_list:
8bd6ae
        if pkg_obj.default:
8bd6ae
            if default_pkg:
8bd6ae
                log.error('Already have default pkg: %s / %s', default_pkg.name, pkg_obj.name)
8bd6ae
            else:
8bd6ae
                default_pkg = pkg_obj
8bd6ae
                default_name = default_pkg.name
8bd6ae
8bd6ae
    for kmod in kmod_topo_order:
8bd6ae
        update_linked = False
8bd6ae
        chosen_pkg = None
8bd6ae
8bd6ae
        if kmod.allowed_list is None:
8bd6ae
            if default_pkg:
8bd6ae
                chosen_pkg = default_pkg
8bd6ae
            else:
8bd6ae
                log.error('%s not assigned and there is no default', kmod.name)
8bd6ae
        elif len(kmod.allowed_list) > 1:
8bd6ae
            if default_pkg:
8bd6ae
                if default_pkg in kmod.allowed_list:
8bd6ae
                    chosen_pkg = default_pkg
8bd6ae
                else:
8bd6ae
                    chosen_pkg = pick_closest_to_preffered(default_pkg, kmod.allowed_list)
8bd6ae
                    if chosen_pkg:
8bd6ae
                        log.debug('closest is %s', chosen_pkg.name)
8bd6ae
            if not chosen_pkg:
8bd6ae
                # multiple pkgs are allowed, but none is preferred or default
8bd6ae
                chosen_pkg = pick_topmost_allowed(kmod.allowed_list)
8bd6ae
                log.debug('topmost is %s', chosen_pkg.name)
8bd6ae
8bd6ae
        if chosen_pkg:
8bd6ae
            kmod.allowed_list = set([chosen_pkg])
8bd6ae
            log.debug('%s: making to prefer %s (default: %s)', kmod.name, [chosen_pkg.name], default_name)
8bd6ae
            update_linked = True
8bd6ae
8bd6ae
        visited: set[KMod] = set()
8bd6ae
        ret = ret + update_allowed(kmod, visited, update_linked)
8bd6ae
8bd6ae
    log.debug('updated nodes: %s', ret)
8bd6ae
    settle(kmod_list)
8bd6ae
8bd6ae
8bd6ae
def load_config(config_pathname: str, kmod_list: KModList, variants=[]):
8bd6ae
    kmod_pkg_list = KModPackageList()
8bd6ae
8bd6ae
    with open(config_pathname, 'r') as file:
8bd6ae
        yobj = yaml.safe_load(file)
8bd6ae
8bd6ae
    for pkg_dict in yobj['packages']:
8bd6ae
        pkg_name = pkg_dict['name']
8bd6ae
        depends_on = pkg_dict.get('depends-on', [])
8bd6ae
        if_variant_in = pkg_dict.get('if_variant_in')
8bd6ae
8bd6ae
        if if_variant_in is not None:
8bd6ae
            if not (set(variants) & set(if_variant_in)):
8bd6ae
                log.debug('Skipping %s for variants %s', pkg_name, variants)
8bd6ae
                continue
8bd6ae
8bd6ae
        pkg_dep_list = []
8bd6ae
        for pkg_dep_name in depends_on:
8bd6ae
            pkg_dep = kmod_pkg_list.get(pkg_dep_name)
8bd6ae
            pkg_dep_list.append(pkg_dep)
8bd6ae
8bd6ae
        pkg_obj = kmod_pkg_list.get(pkg_name)
8bd6ae
        if not pkg_obj:
8bd6ae
            pkg_obj = KModPackage(pkg_name, pkg_dep_list)
8bd6ae
            kmod_pkg_list.add_kmod_pkg(pkg_obj)
8bd6ae
        else:
8bd6ae
            log.error('package %s already exists?', pkg_name)
8bd6ae
8bd6ae
    rules_list = yobj.get('rules', [])
8bd6ae
    for rule_dict in rules_list:
8bd6ae
        if_variant_in = rule_dict.get('if_variant_in')
8bd6ae
        exact_pkg = rule_dict.get('exact_pkg')
8bd6ae
8bd6ae
        for key, value in rule_dict.items():
8bd6ae
            if key in ['if_variant_in', 'exact_pkg']:
8bd6ae
                continue
8bd6ae
8bd6ae
            if if_variant_in is not None:
8bd6ae
                if not (set(variants) & set(if_variant_in)):
8bd6ae
                    continue
8bd6ae
8bd6ae
            rule = key
8bd6ae
            package_name = value
8bd6ae
8bd6ae
            if not kmod_pkg_list.get(package_name):
8bd6ae
                raise Exception('Unknown package ' + package_name)
8bd6ae
8bd6ae
            rule_type = 'wants'
8bd6ae
            if exact_pkg is True:
8bd6ae
                rule_type = 'needs'
8bd6ae
            elif key == 'default':
8bd6ae
                rule_type = 'default'
8bd6ae
                rule = '.*'
8bd6ae
8bd6ae
            log.debug('found rule: %s', (package_name, rule_type, rule))
8bd6ae
            kmod_pkg_list.rules.append((package_name, rule_type, rule))
8bd6ae
8bd6ae
    log.info('loaded config, rules: %s', len(kmod_pkg_list.rules))
8bd6ae
    return kmod_pkg_list
8bd6ae
8bd6ae
8bd6ae
def make_pictures(pkg_list: KModPackageList, kmod_list: KModList, filename: str, print_allowed=True):
8bd6ae
    f = open(filename + '.dot', 'w')
8bd6ae
8bd6ae
    f.write('digraph {\n')
8bd6ae
    f.write('node [style=filled fillcolor="#f8f8f8"]\n')
8bd6ae
    f.write('  subgraph kmods {\n')
8bd6ae
    f.write('  "Legend" [shape=note label="kmod name\\n{desired package}\\nresulting package(s)"]\n')
8bd6ae
8bd6ae
    for kmod in kmod_list.get_topo_order():
8bd6ae
        pkg_name = ''
8bd6ae
        attr = ''
8bd6ae
        if kmod.assigned_to_pkg:
8bd6ae
            attr = 'fillcolor="#eddad5" color="#b22800"'
8bd6ae
            pkg_name = kmod.assigned_to_pkg.name + "!"
8bd6ae
        if kmod.preferred_pkg:
8bd6ae
            attr = 'fillcolor="#ddddf5" color="#b268fe"'
8bd6ae
            pkg_name = kmod.preferred_pkg.name + "?"
8bd6ae
        allowed = ''
8bd6ae
        if kmod.allowed_list and print_allowed:
8bd6ae
            allowed = '=' + ' '.join([pkg.name for pkg in kmod.allowed_list])
8bd6ae
        f.write(' "%s" [label="%s\\n%s\\n%s" shape=box %s] \n' % (kmod.name, kmod.name, pkg_name, allowed, attr))
8bd6ae
8bd6ae
    for kmod in kmod_list.get_topo_order():
8bd6ae
        for kmod_dep in kmod.depends_on:
8bd6ae
            f.write('    "%s" -> "%s";\n' % (kmod.name, kmod_dep.name))
8bd6ae
    f.write('  }\n')
8bd6ae
8bd6ae
    f.write('  subgraph packages {\n')
8bd6ae
    for pkg in pkg_list:
8bd6ae
        desc = ''
8bd6ae
        if pkg.default:
8bd6ae
            desc = '/default'
8bd6ae
        f.write(' "%s" [label="%s\\n%s"] \n' % (pkg.name, pkg.name, desc))
8bd6ae
        for pkg_dep in pkg.depends_on:
8bd6ae
            f.write('    "%s" -> "%s";\n' % (pkg.name, pkg_dep.name))
8bd6ae
    f.write('  }\n')
8bd6ae
    f.write('}\n')
8bd6ae
8bd6ae
    f.close()
8bd6ae
8bd6ae
    # safe_run_command('dot -Tpng -Gdpi=150 %s.dot > %s.png' % (filename, filename))
8bd6ae
    safe_run_command('dot -Tsvg %s.dot > %s.svg' % (filename, filename))
8bd6ae
8bd6ae
8bd6ae
def sort_kmods(depmod_pathname: str, config_str: str, variants=[], do_pictures=''):
8bd6ae
    log.info('%s %s', depmod_pathname, config_str)
8bd6ae
    kmod_list = KModList()
8bd6ae
    kmod_list.load_depmod_file(depmod_pathname)
8bd6ae
8bd6ae
    pkg_list = load_config(config_str, kmod_list, variants)
8bd6ae
8bd6ae
    basename = os.path.splitext(config_str)[0]
8bd6ae
8bd6ae
    apply_initial_labels(pkg_list, kmod_list)
8bd6ae
    if '0' in do_pictures:
8bd6ae
        make_pictures(pkg_list, kmod_list, basename + "_0", print_allowed=False)
8bd6ae
8bd6ae
    try:
8bd6ae
8bd6ae
        propagate_labels_1(pkg_list, kmod_list)
8bd6ae
        if '1' in do_pictures:
8bd6ae
            make_pictures(pkg_list, kmod_list, basename + "_1")
8bd6ae
        propagate_labels_2(pkg_list, kmod_list)
8bd6ae
        propagate_labels_3(pkg_list, kmod_list)
8bd6ae
    finally:
8bd6ae
        if 'f' in do_pictures:
8bd6ae
            make_pictures(pkg_list, kmod_list, basename + "_f")
8bd6ae
8bd6ae
    return pkg_list, kmod_list
8bd6ae
8bd6ae
8bd6ae
def abbrev_list_for_report(alist: list[KMod]) -> str:
8bd6ae
    tmp_str = []
8bd6ae
    for kmod in alist:
8bd6ae
        if kmod.allowed_list:
8bd6ae
            tmp_str.append('%s(%s)' % (kmod.name, ' '.join([x.name for x in kmod.allowed_list])))
8bd6ae
    ret = ', '.join(tmp_str)
8bd6ae
    return ret
8bd6ae
8bd6ae
8bd6ae
def print_report(pkg_list: KModPackageList, kmod_list: KModList):
8bd6ae
    log.info('*'*26 + ' REPORT ' + '*'*26)
8bd6ae
8bd6ae
    kmods_err = 0
8bd6ae
    kmods_moved = 0
8bd6ae
    kmods_good = 0
8bd6ae
    for kmod in kmod_list.get_topo_order():
8bd6ae
        if not kmod.allowed_list:
8bd6ae
            log.error('%s: not assigned to any package! Please check the full log for details', kmod.name)
8bd6ae
            kmods_err = kmods_err + 1
8bd6ae
            continue
8bd6ae
8bd6ae
        if len(kmod.allowed_list) > 1:
8bd6ae
            log.error('%s: assigned to more than one package! Please check the full log for details', kmod.name)
8bd6ae
            kmods_err = kmods_err + 1
8bd6ae
            continue
8bd6ae
8bd6ae
        if not kmod.preferred_pkg:
8bd6ae
            # config doesn't care where it ended up
8bd6ae
            kmods_good = kmods_good + 1
8bd6ae
            continue
8bd6ae
8bd6ae
        if kmod.preferred_pkg in kmod.allowed_list:
8bd6ae
            # it ended up where it needs to be
8bd6ae
            kmods_good = kmods_good + 1
8bd6ae
            continue
8bd6ae
8bd6ae
        bad_parent_list = []
8bd6ae
        for kmod_parent in kmod.is_dependency_for:
8bd6ae
            if not is_pkg_child_to_any(kmod.preferred_pkg, kmod_parent.allowed_list):
8bd6ae
                bad_parent_list.append(kmod_parent)
8bd6ae
8bd6ae
        bad_child_list = []
8bd6ae
        for kmod_child in kmod.depends_on:
8bd6ae
            if not is_pkg_parent_to_any(kmod.preferred_pkg, kmod_child.allowed_list):
8bd6ae
                bad_child_list.append(kmod_parent)
8bd6ae
8bd6ae
        log.info('%s: wanted by %s but ended up in %s', kmod.name, [kmod.preferred_pkg.name], [pkg.name for pkg in kmod.allowed_list])
8bd6ae
        if bad_parent_list:
8bd6ae
            log.info('\thas conflicting parent: %s', abbrev_list_for_report(bad_parent_list))
8bd6ae
        if bad_child_list:
8bd6ae
            log.info('\thas conflicting children: %s', abbrev_list_for_report(bad_child_list))
8bd6ae
8bd6ae
        kmods_moved = kmods_moved + 1
8bd6ae
8bd6ae
    log.info('No. of kmod(s) assigned to preferred package: %s', kmods_good)
8bd6ae
    log.info('No. of kmod(s) moved to a related package: %s', kmods_moved)
8bd6ae
    log.info('No. of kmod(s) which could not be assigned: %s', kmods_err)
8bd6ae
    log.info('*'*60)
8bd6ae
8bd6ae
    return kmods_err
8bd6ae
8bd6ae
8bd6ae
def write_modules_lists(path_prefix: str, pkg_list: KModPackageList, kmod_list: KModList):
8bd6ae
    kmod_list_alphabetical = sorted(kmod_list.get_topo_order(), key=lambda x: x.kmod_pathname)
8bd6ae
    for pkg in pkg_list:
8bd6ae
        output_path = os.path.join(path_prefix, pkg.name + '.list')
8bd6ae
        i = 0
8bd6ae
        with open(output_path, "w") as file:
8bd6ae
            for kmod in kmod_list_alphabetical:
8bd6ae
                if kmod.allowed_list and pkg in kmod.allowed_list:
8bd6ae
                    file.write(kmod.kmod_pathname)
8bd6ae
                    file.write('\n')
8bd6ae
                    i = i + 1
8bd6ae
        log.info('Module list %s created with %s kmods', output_path, i)
8bd6ae
8bd6ae
8bd6ae
class FiltermodTests(unittest.TestCase):
8bd6ae
    do_pictures = ''
8bd6ae
8bd6ae
    def setUp(self):
8bd6ae
        self.pkg_list = None
8bd6ae
        self.kmod_list = None
8bd6ae
8bd6ae
    def _is_kmod_pkg(self, kmodname, pkgnames):
8bd6ae
        self.assertIsNotNone(self.pkg_list)
8bd6ae
        self.assertIsNotNone(self.kmod_list)
8bd6ae
8bd6ae
        if type(pkgnames) is str:
8bd6ae
            pkgnames = [pkgnames]
8bd6ae
8bd6ae
        expected_pkgs = []
8bd6ae
        for pkgname in pkgnames:
8bd6ae
            pkg = self.pkg_list.get(pkgname)
8bd6ae
            self.assertIsNotNone(pkg)
8bd6ae
            expected_pkgs.append(pkg)
8bd6ae
8bd6ae
        kmod = self.kmod_list.get(kmodname)
8bd6ae
        self.assertIsNotNone(kmod)
8bd6ae
8bd6ae
        if expected_pkgs:
8bd6ae
            self.assertTrue(len(kmod.allowed_list) == 1)
8bd6ae
            self.assertIn(next(iter(kmod.allowed_list)), expected_pkgs)
8bd6ae
        else:
8bd6ae
            self.assertEqual(kmod.allowed_list, set())
8bd6ae
8bd6ae
    def test1a(self):
8bd6ae
        self.pkg_list, self.kmod_list = sort_kmods(get_td('test1.dep'), get_td('test1.yaml'),
8bd6ae
                                                   do_pictures=FiltermodTests.do_pictures)
8bd6ae
8bd6ae
        self._is_kmod_pkg('kmod1', 'modules-core')
8bd6ae
        self._is_kmod_pkg('kmod2', 'modules-core')
8bd6ae
        self._is_kmod_pkg('kmod3', 'modules')
8bd6ae
        self._is_kmod_pkg('kmod4', 'modules')
8bd6ae
8bd6ae
    def test1b(self):
8bd6ae
        self.pkg_list, self.kmod_list = sort_kmods(get_td('test1.dep'), get_td('test1.yaml'),
8bd6ae
                                                   do_pictures=FiltermodTests.do_pictures,
8bd6ae
                                                   variants=['rt'])
8bd6ae
8bd6ae
        self.assertIsNotNone(self.pkg_list.get('rt-kvm'))
8bd6ae
        self._is_kmod_pkg('kmod1', 'modules-core')
8bd6ae
        self._is_kmod_pkg('kmod2', 'modules-core')
8bd6ae
        self._is_kmod_pkg('kmod3', 'modules')
8bd6ae
        self._is_kmod_pkg('kmod4', 'rt-kvm')
8bd6ae
8bd6ae
    def test2(self):
8bd6ae
        self.pkg_list, self.kmod_list = sort_kmods(get_td('test2.dep'), get_td('test2.yaml'),
8bd6ae
                                                   do_pictures=FiltermodTests.do_pictures)
8bd6ae
8bd6ae
        self._is_kmod_pkg('kmod1', 'modules-extra')
8bd6ae
        self._is_kmod_pkg('kmod2', 'modules')
8bd6ae
        self._is_kmod_pkg('kmod3', 'modules-core')
8bd6ae
        self._is_kmod_pkg('kmod4', 'modules-core')
8bd6ae
        self._is_kmod_pkg('kmod5', 'modules-core')
8bd6ae
        self._is_kmod_pkg('kmod6', 'modules-extra')
8bd6ae
        self._is_kmod_pkg('kmod8', 'modules')
8bd6ae
8bd6ae
    def test3(self):
8bd6ae
        self.pkg_list, self.kmod_list = sort_kmods(get_td('test3.dep'), get_td('test3.yaml'),
8bd6ae
                                                   do_pictures=FiltermodTests.do_pictures)
8bd6ae
8bd6ae
        self._is_kmod_pkg('kmod2', ['modules-core', 'modules'])
8bd6ae
        self._is_kmod_pkg('kmod4', ['modules-core', 'modules-extra'])
8bd6ae
        self._is_kmod_pkg('kmod5', 'modules-core')
8bd6ae
        self._is_kmod_pkg('kmod6', 'modules-core')
8bd6ae
8bd6ae
    def test4(self):
8bd6ae
        self.pkg_list, self.kmod_list = sort_kmods(get_td('test4.dep'), get_td('test4.yaml'),
8bd6ae
                                                   do_pictures=FiltermodTests.do_pictures)
8bd6ae
8bd6ae
        self._is_kmod_pkg('kmod0', 'modules')
8bd6ae
        self._is_kmod_pkg('kmod1', 'modules')
8bd6ae
        self._is_kmod_pkg('kmod2', 'modules')
8bd6ae
        self._is_kmod_pkg('kmod3', 'modules')
8bd6ae
        self._is_kmod_pkg('kmod4', 'modules')
8bd6ae
        self._is_kmod_pkg('kmod5', 'modules')
8bd6ae
        self._is_kmod_pkg('kmod6', 'modules')
8bd6ae
        self._is_kmod_pkg('kmod7', 'modules-partner2')
8bd6ae
        self._is_kmod_pkg('kmod8', 'modules-partner')
8bd6ae
        self._is_kmod_pkg('kmod9', 'modules-partner')
8bd6ae
8bd6ae
    def _check_preffered_pkg(self, kmodname, pkgname):
8bd6ae
        kmod = self.kmod_list.get(kmodname)
8bd6ae
        self.assertIsNotNone(kmod)
8bd6ae
        self.assertEqual(kmod.preferred_pkg.name, pkgname)
8bd6ae
8bd6ae
    def test5(self):
8bd6ae
        self.pkg_list, self.kmod_list = sort_kmods(get_td('test5.dep'), get_td('test5.yaml'),
8bd6ae
                                                   do_pictures=FiltermodTests.do_pictures)
8bd6ae
8bd6ae
        self._check_preffered_pkg('kmod2', 'modules')
8bd6ae
        self._check_preffered_pkg('kmod3', 'modules-partner')
8bd6ae
        self._check_preffered_pkg('kmod4', 'modules-partner')
8bd6ae
8bd6ae
    def test6(self):
8bd6ae
        self.pkg_list, self.kmod_list = sort_kmods(get_td('test6.dep'), get_td('test6.yaml'),
8bd6ae
                                                   do_pictures=FiltermodTests.do_pictures)
8bd6ae
8bd6ae
        self._is_kmod_pkg('kmod2', 'modules-core')
8bd6ae
        self._is_kmod_pkg('kmod3', 'modules')
8bd6ae
        self._is_kmod_pkg('kmod4', 'modules')
8bd6ae
        self._is_kmod_pkg('kmod1', [])
8bd6ae
8bd6ae
    def test7(self):
8bd6ae
        self.pkg_list, self.kmod_list = sort_kmods(get_td('test7.dep'), get_td('test7.yaml'),
8bd6ae
                                                   do_pictures=FiltermodTests.do_pictures)
8bd6ae
8bd6ae
        self._is_kmod_pkg('kmod1', 'modules-core')
8bd6ae
        self._is_kmod_pkg('kmod2', 'modules-core')
8bd6ae
        self._is_kmod_pkg('kmod3', 'modules-other')
8bd6ae
        self._is_kmod_pkg('kmod4', 'modules')
8bd6ae
8bd6ae
8bd6ae
def do_rpm_mapping_test(config_pathname, kmod_rpms):
8bd6ae
    kmod_dict = {}
8bd6ae
8bd6ae
    def get_kmods_matching_re(pkgname, param_re):
8bd6ae
        matched = []
8bd6ae
        param_re = '^kernel/' + param_re
8bd6ae
        pattern = re.compile(param_re)
8bd6ae
8bd6ae
        for kmod_pathname, kmod_rec in kmod_dict.items():
8bd6ae
            m = pattern.match(kmod_pathname)
8bd6ae
            if m:
8bd6ae
                matched.append(kmod_pathname)
8bd6ae
8bd6ae
        return matched
8bd6ae
8bd6ae
    for kmod_rpm in kmod_rpms.split():
8bd6ae
        filename = os.path.basename(kmod_rpm)
8bd6ae
8bd6ae
        m = re.match(r'.*-modules-([^-]+)', filename)
8bd6ae
        if not m:
8bd6ae
            raise Exception('Unrecognized rpm ' + kmod_rpm + ', expected a kernel-modules* rpm')
8bd6ae
        pkgname = 'modules-' + m.group(1)
8bd6ae
        m = re.match(r'modules-([0-9.]+)', pkgname)
8bd6ae
        if m:
8bd6ae
            pkgname = 'modules'
8bd6ae
8bd6ae
        tmpdir = os.path.join('tmp.filtermods', filename, pkgname)
8bd6ae
        if not os.path.exists(tmpdir):
8bd6ae
            log.info('creating tmp dir %s', tmpdir)
8bd6ae
            os.makedirs(tmpdir)
8bd6ae
            safe_run_command('rpm2cpio %s | cpio -id' % (os.path.abspath(kmod_rpm)), cwddir=tmpdir)
8bd6ae
        else:
8bd6ae
            log.info('using cached content of tmp dir: %s', tmpdir)
8bd6ae
8bd6ae
        for path, subdirs, files in os.walk(tmpdir):
8bd6ae
            for name in files:
8bd6ae
                ret = re.match(r'.*/'+pkgname+'/lib/modules/[^/]+/[^/]+/(.*)', os.path.join(path, name))
8bd6ae
                if not ret:
8bd6ae
                    continue
8bd6ae
8bd6ae
                kmod_pathname = 'kernel/' + ret.group(1)
8bd6ae
                if not kmod_pathname.endswith('.xz') and not kmod_pathname.endswith('.ko'):
8bd6ae
                    continue
8bd6ae
                if kmod_pathname in kmod_dict:
8bd6ae
                    if pkgname not in kmod_dict[kmod_pathname]['target_pkgs']:
8bd6ae
                        kmod_dict[kmod_pathname]['target_pkgs'].append(pkgname)
8bd6ae
                else:
8bd6ae
                    kmod_dict[kmod_pathname] = {}
8bd6ae
                    kmod_dict[kmod_pathname]['target_pkgs'] = [pkgname]
8bd6ae
                    kmod_dict[kmod_pathname]['pkg'] = None
8bd6ae
                    kmod_dict[kmod_pathname]['matched'] = False
8bd6ae
8bd6ae
    kmod_pkg_list = load_config(config_pathname, None)
8bd6ae
8bd6ae
    for package_name, rule_type, rule in kmod_pkg_list.rules:
8bd6ae
        kmod_names = get_kmods_matching_re(package_name, rule)
8bd6ae
8bd6ae
        for kmod_pathname in kmod_names:
8bd6ae
            kmod_rec = kmod_dict[kmod_pathname]
8bd6ae
8bd6ae
            if not kmod_rec['matched']:
8bd6ae
                kmod_rec['matched'] = True
8bd6ae
                kmod_rec['pkg'] = package_name
8bd6ae
    for kmod_pathname, kmod_rec in kmod_dict.items():
8bd6ae
        if kmod_rec['pkg'] not in kmod_rec['target_pkgs']:
8bd6ae
            log.warning('kmod %s wanted by config in %s, in tree it is: %s', kmod_pathname, [kmod_rec['pkg']], kmod_rec['target_pkgs'])
8bd6ae
        elif len(kmod_rec['target_pkgs']) > 1:
8bd6ae
            # if set(kmod_rec['target_pkgs']) != set(['modules', 'modules-core']):
8bd6ae
            log.warning('kmod %s multiple matches in tree: %s/%s', kmod_pathname, [kmod_rec['pkg']], kmod_rec['target_pkgs'])
8bd6ae
8bd6ae
8bd6ae
def cmd_sort(options):
8bd6ae
    do_pictures = ''
8bd6ae
    if options.graphviz:
8bd6ae
        do_pictures = '0f'
8bd6ae
8bd6ae
    pkg_list, kmod_list = sort_kmods(options.depmod, options.config,
8bd6ae
                                     options.variants, do_pictures)
8bd6ae
    ret = print_report(pkg_list, kmod_list)
8bd6ae
    if options.output:
8bd6ae
        write_modules_lists(options.output, pkg_list, kmod_list)
8bd6ae
8bd6ae
    return ret
8bd6ae
8bd6ae
8bd6ae
def cmd_print_rule_map(options):
8bd6ae
    kmod_list = KModList()
8bd6ae
    kmod_list.load_depmod_file(options.depmod)
8bd6ae
    pkg_list = load_config(options.config, kmod_list, options.variants)
8bd6ae
    apply_initial_labels(pkg_list, kmod_list, treat_default_as_wants=True)
8bd6ae
8bd6ae
    for kmod in kmod_list.get_alphabetical_order():
8bd6ae
        print('%-20s %s' % (kmod.preferred_pkg, kmod.kmod_pathname))
8bd6ae
8bd6ae
8bd6ae
def cmd_selftest(options):
8bd6ae
    if options.graphviz:
8bd6ae
        FiltermodTests.do_pictures = '0f'
8bd6ae
8bd6ae
    for arg in ['selftest', '-g', '--graphviz']:
8bd6ae
        if arg in sys.argv:
8bd6ae
            sys.argv.remove(arg)
8bd6ae
8bd6ae
    unittest.main()
8bd6ae
    sys.exit(0)
8bd6ae
8bd6ae
8bd6ae
def cmd_cmp2rpm(options):
8bd6ae
    do_rpm_mapping_test(options.config, options.kmod_rpms)
8bd6ae
8bd6ae
8bd6ae
def main():
8bd6ae
    global log
8bd6ae
8bd6ae
    parser = argparse.ArgumentParser()
8bd6ae
    parser.add_argument('-v', '--verbose', dest='verbose',
8bd6ae
                        help='be more verbose', action='count', default=4)
8bd6ae
    parser.add_argument('-q', '--quiet', dest='quiet',
8bd6ae
                        help='be more quiet', action='count', default=0)
8bd6ae
    parser.add_argument('-l', '--log-filename', dest='log_filename',
8bd6ae
                        help='log filename', default='filtermods.log')
8bd6ae
8bd6ae
    subparsers = parser.add_subparsers(dest='cmd')
8bd6ae
8bd6ae
    def add_graphviz_arg(p):
8bd6ae
        p.add_argument('-g', '--graphviz', dest='graphviz',
8bd6ae
                       help='generate graphviz visualizations',
8bd6ae
                       action='store_true', default=False)
8bd6ae
8bd6ae
    def add_config_arg(p):
8bd6ae
        p.add_argument('-c', '--config', dest='config', required=True,
8bd6ae
                       help='path to yaml config with rules')
8bd6ae
8bd6ae
    def add_depmod_arg(p):
8bd6ae
        p.add_argument('-d', '--depmod', dest='depmod', required=True,
8bd6ae
                       help='path to modules.dep file')
8bd6ae
8bd6ae
    def add_output_arg(p):
8bd6ae
        p.add_argument('-o', '--output', dest='output', default=None,
8bd6ae
                       help='output $module_name.list files to directory specified by this parameter')
8bd6ae
8bd6ae
    def add_variants_arg(p):
8bd6ae
        p.add_argument('-r', '--variants', dest='variants', action='append', default=[],
8bd6ae
                       help='variants to enable in config')
8bd6ae
8bd6ae
    def add_kmod_rpms_arg(p):
8bd6ae
        p.add_argument('-k', '--kmod-rpms', dest='kmod_rpms', required=True,
8bd6ae
                       help='compare content of specified rpm(s) against yaml config rules')
8bd6ae
8bd6ae
    parser_sort = subparsers.add_parser('sort', help='assign kmods specified by modules.dep using rules from yaml config')
8bd6ae
    add_config_arg(parser_sort)
8bd6ae
    add_depmod_arg(parser_sort)
8bd6ae
    add_output_arg(parser_sort)
8bd6ae
    add_variants_arg(parser_sort)
8bd6ae
    add_graphviz_arg(parser_sort)
8bd6ae
8bd6ae
    parser_rule_map = subparsers.add_parser('rulemap', help='print how yaml config maps to kmods')
8bd6ae
    add_config_arg(parser_rule_map)
8bd6ae
    add_depmod_arg(parser_rule_map)
8bd6ae
    add_variants_arg(parser_rule_map)
8bd6ae
8bd6ae
    parser_test = subparsers.add_parser('selftest', help='runs a self-test')
8bd6ae
    add_graphviz_arg(parser_test)
8bd6ae
8bd6ae
    parser_cmp2rpm = subparsers.add_parser('cmp2rpm', help='compare ruleset against RPM(s)')
8bd6ae
    add_config_arg(parser_cmp2rpm)
8bd6ae
    add_kmod_rpms_arg(parser_cmp2rpm)
8bd6ae
8bd6ae
    options = parser.parse_args()
8bd6ae
8bd6ae
    if options.cmd == "selftest":
8bd6ae
        options.verbose = options.verbose - 2
8bd6ae
    options.verbose = max(options.verbose - options.quiet, 0)
8bd6ae
    levels = [NOTSET, CRITICAL, ERROR, WARN, INFO, DEBUG]
8bd6ae
    stdout_log_level = levels[min(options.verbose, len(levels) - 1)]
8bd6ae
8bd6ae
    log = setup_logging(options.log_filename, stdout_log_level)
8bd6ae
8bd6ae
    ret = 0
8bd6ae
    if options.cmd == "sort":
8bd6ae
        ret = cmd_sort(options)
8bd6ae
    elif options.cmd == "rulemap":
8bd6ae
        cmd_print_rule_map(options)
8bd6ae
    elif options.cmd == "selftest":
8bd6ae
        cmd_selftest(options)
8bd6ae
    elif options.cmd == "cmp2rpm":
8bd6ae
        cmd_cmp2rpm(options)
8bd6ae
    else:
8bd6ae
        parser.print_help()
8bd6ae
8bd6ae
    return ret
8bd6ae
8bd6ae
8bd6ae
if __name__ == '__main__':
8bd6ae
    # import profile
8bd6ae
    # profile.run('main()', sort=1)
8bd6ae
    sys.exit(main())