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