From 28d04b08d78a9cdb3d35b49934481e24244707fc Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 30 Sep 2024 18:45:23 +0200 Subject: Merging debian version 20240709-2. Signed-off-by: Daniel Baumann --- debian/bin/check_upstream.py | 57 ++++--- debian/bin/gencontrol.py | 378 +++++++++++++++++++++---------------------- debian/bin/release-update | 78 ++++++++- debian/bin/update-modinfo | 46 ++++-- 4 files changed, 320 insertions(+), 239 deletions(-) (limited to 'debian/bin') diff --git a/debian/bin/check_upstream.py b/debian/bin/check_upstream.py index e2931c4..d69ce75 100755 --- a/debian/bin/check_upstream.py +++ b/debian/bin/check_upstream.py @@ -11,7 +11,7 @@ rules_defs = dict((match.group(1), match.group(2)) sys.path.append('/usr/share/linux-support-%s/lib/python' % rules_defs['KERNELVERSION']) from debian_linux.firmware import FirmwareWhence -from config import Config +from config import Config, pattern_to_re class DistState(Enum): undistributable = 1 @@ -20,23 +20,30 @@ class DistState(Enum): def is_source_available(section): for file_info in section.files.values(): - if not (file_info.source or file_info.binary.endswith('.cis')): + if not (file_info.source + or file_info.binary.endswith('.txt') + or file_info.binary.endswith('.cis')): return False return True def check_section(section): - if re.search(r'^BSD\b' - r'|^GPLv2 or OpenIB\.org BSD\b' - r'|\bPermission\s+is\s+hereby\s+granted\s+for\s+the\s+' - r'distribution\s+of\s+this\s+firmware\s+(?:data|image)\b' - r'(?!\s+as\s+part\s+of)' - r'|\bRedistribution\s+and\s+use\s+in(?:\s+source\s+and)?' - r'\s+binary\s+forms\b' - r'|\bPermission\s+is\s+hereby\s+granted\b[^.]+\sto' - r'\s+deal\s+in\s+the\s+Software\s+without' - r'\s+restriction\b' - r'|\bredistributable\s+in\s+binary\s+form\b', - section.licence): + if section.licence is None: + # Maybe undistributable + return DistState.undistributable + elif re.search(r'^BSD\b' + r'|^GPLv2 or OpenIB\.org BSD\b' + r'|\bPermission\s+is\s+hereby\s+granted\s+for\s+the\s+' + r'distribution\s+of\s+this\s+firmware\s+(?:data|image)\b' + r'(?!\s+as\s+part\s+of)' + r'|\bRedistribution\s+and\s+use\s+in(?:\s+source\s+and)?' + r'\s+binary\s+forms\b' + r'|\bPermission\s+is\s+hereby\s+granted\b[^.]+\sto' + r'\s+deal\s+in\s+the\s+Software\s+without' + r'\s+restriction\b' + r'|\bredistributable\s+in\s+binary\s+form\b' + r'|\bgrants\s+permission\s+to\s+use\s+and\s+redistribute' + r'\s+these\s+firmware\s+files\b', + section.licence): return (DistState.free if is_source_available(section) else DistState.non_free) elif re.match(r'^(?:D|Red)istributable\b', section.licence): @@ -54,10 +61,16 @@ def main(source_dir='.'): package in config['base',]['packages']] with open("debian/copyright") as f: exclusions = deb822.Deb822(f).get("Files-Excluded", '').strip().split() - packaged_files = {} + + package_file_res = [] for package in config['base',]['packages']: - for filename in config['base', package]['files']: - packaged_files[filename] = package + config_entry = config['base', package] + package_file_res.append( + ([pattern_to_re(pattern) + for pattern in config_entry['files']], + [pattern_to_re(pattern) + for pattern in config_entry.get('files-exclude', [])]) + ) for section in FirmwareWhence(open(os.path.join(source_dir, 'WHENCE'))): dist_state = check_section(section) @@ -65,9 +78,15 @@ def main(source_dir='.'): if dist_state == DistState.non_free: if not any(fnmatch.fnmatch(file_info.binary, exclusion) for exclusion in exclusions): - if file_info.binary in packaged_files: + if any( + (any(inc_re.fullmatch(file_info.binary) + for inc_re in inc_res) + and not any(exc_re.fullmatch(file_info.binary) + for exc_re in exc_res)) + for inc_res, exc_res in package_file_res + ): update_file(source_dir, over_dirs, file_info.binary) - elif os.path.isfile(filename): + elif os.path.isfile(file_info.binary): print('I: %s is not included in any binary package' % file_info.binary) else: diff --git a/debian/bin/gencontrol.py b/debian/bin/gencontrol.py index 293432b..41a9d45 100755 --- a/debian/bin/gencontrol.py +++ b/debian/bin/gencontrol.py @@ -1,79 +1,66 @@ #!/usr/bin/env python3 +import dataclasses import io +import itertools import json import locale import os +import pathlib import re import sys +from typing import Iterable, Optional sys.path.insert(0, "debian/lib/python") sys.path.append(sys.argv[1] + "/lib/python") locale.setlocale(locale.LC_CTYPE, "C.UTF-8") -from config import Config -from debian_linux.debian import BinaryPackage, PackageRelation, _ControlFileDict -from debian_linux.debian import PackageDescription as PackageDescriptionBase +from config import Config, pattern_to_re +from debian_linux.dataclasses_deb822 import field_deb822, read_deb822, write_deb822 +from debian_linux.debian import BinaryPackage as BinaryPackageBase, PackageDescription, PackageRelation import debian_linux.gencontrol -from debian_linux.gencontrol import Makefile, MakeFlags, PackagesList -from debian_linux.utils import TextWrapper +from debian_linux.gencontrol import MakeFlags from debian_linux.utils import Templates as TemplatesBase -from collections import OrderedDict - -class PackageDescription(PackageDescriptionBase): - __slots__ = () - - def __init__(self, value = None): - self.short = [] - self.long = [] - if value is not None: - value = value.split("\n", 1) - self.append_short(value[0]) - if len(value) > 1: - self.append(value[1]) - - def __str__(self): - wrap = TextWrapper(width = 74, fix_sentence_endings = True).wrap - short = ', '.join(self.short) - long_pars = [] - for t in self.long: - if isinstance(t, str): - t = wrap(t) - long_pars.append('\n '.join(t)) - long = '\n .\n '.join(long_pars) - return short + '\n ' + long - - def append_pre(self, l): - self.long.append(l) - - def extend(self, desc): - if isinstance(desc, PackageDescription): - self.short.extend(desc.short) - self.long.extend(desc.long) - elif isinstance(desc, (list, tuple)): - for i in desc: - self.append(i) - -BinaryPackage._fields['Description'] = PackageDescription - -class Template(_ControlFileDict): - _fields = OrderedDict(( - ('Template', str), - ('Type', str), - ('Default', str), - ('Description', PackageDescriptionBase), - )) + + +# XXX Delete after this field is added in linux-support +@dataclasses.dataclass +class BinaryPackage(BinaryPackageBase): + homepage: 'Optional[str]' = field_deb822( + 'Homepage', + default=None, + ) + + +@dataclasses.dataclass +class Template: + template: 'str' = field_deb822('Template') + type: 'str' = field_deb822('Type') + default: 'Optional[str]' = field_deb822( + 'Default', + default=None, + ) + description: PackageDescription = field_deb822( + 'Description', + default_factory=PackageDescription, + ) class Templates(TemplatesBase): - def get_templates_control(self, key: str, context: dict[str, str] = {}) -> Template: - return Template.read_rfc822(io.StringIO(self.get(key, context))) + def get_control( + self, key: str, context: dict[str, str] = {}, + ) -> Iterable[BinaryPackage]: + return read_deb822(BinaryPackage, io.StringIO(self.get(key, context))) + + def get_templates_control( + self, key: str, context: dict[str, str] = {} + ) -> Iterable[Template]: + return read_deb822(Template, io.StringIO(self.get(key, context))) class GenControl(debian_linux.gencontrol.Gencontrol): def __init__(self): - self.config = Config() - self.templates = Templates() + super().__init__(Config(), Templates()) with open('debian/modinfo.json', 'r') as f: self.modinfo = json.load(f) @@ -85,49 +72,30 @@ class GenControl(debian_linux.gencontrol.Gencontrol): self.firmware_modules.setdefault(firmware_filename, []) \ .append(name) - def __call__(self): - packages = PackagesList() - makefile = Makefile() - - self.do_source(packages) - self.do_extra(packages, makefile) - self.do_main(packages, makefile) - - self.write(packages, makefile) - - def do_source(self, packages): - packages['source'] = self.templates.get_source_control("control.source", {})[0] - - def do_extra(self, packages, makefile): - config_entry = self.config['base',] - vars = {} - vars.update(config_entry) - - for package_binary in self.templates.get_control("control.extra", {}): - assert package_binary['Package'].startswith('firmware-') - package = package_binary['Package'].replace('firmware-', '') - - makeflags = MakeFlags() - makeflags['FILES'] = '' - makeflags['PACKAGE'] = package - makefile.add_cmds('binary-indep', ["$(MAKE) -f debian/rules.real binary-indep %s" % makeflags]) - - packages.append(package_binary) - - def do_main(self, packages, makefile): + def do_main(self): config_entry = self.config['base',] vars = {} vars.update(config_entry) makeflags = MakeFlags() - for i in ('build', 'binary-arch', 'setup'): - makefile.add_cmds("%s_%%" % i, ["@true"]) + self.file_errors = False + self.file_packages = {} for package in config_entry['packages']: - self.do_package(packages, makefile, package, vars.copy(), makeflags.copy()) - - def do_package(self, packages, makefile, package, vars, makeflags): + self.do_package(package, vars.copy(), makeflags.copy()) + + for canon_path, package_suffixes in self.file_packages.items(): + if len(package_suffixes) > 1: + print(f'E: {canon_path!s} is included in multiple packages:', + ', '.join(f'firmware-{suffix}' + for suffix in package_suffixes), + file=sys.stderr) + self.file_errors = True + if self.file_errors: + raise Exception('error(s) found in file lists') + + def do_package(self, package, vars, makeflags): config_entry = self.config['base', package] vars.update(config_entry) vars['package'] = package @@ -141,7 +109,9 @@ class GenControl(debian_linux.gencontrol.Gencontrol): if optional not in vars: vars[optional] = '' - package_dir = "debian/config/%s" % package + cur_dir = pathlib.Path.cwd() + install_dir = pathlib.Path('debian/build/install') + package_dir = pathlib.Path('debian/config') / package try: os.unlink('debian/firmware-%s.bug-presubj' % package) @@ -149,98 +119,107 @@ class GenControl(debian_linux.gencontrol.Gencontrol): pass os.symlink('bug-presubj', 'debian/firmware-%s.bug-presubj' % package) - files_orig = config_entry['files'] + files_include = [(pattern, pattern_to_re(pattern)) + for pattern in config_entry['files']] + files_exclude = [pattern_to_re(pattern) + for pattern in config_entry.get('files-excluded', [])] + files_added = set() + files_unused = set() files_real = {} - files_unused = [] links = {} links_rev = {} - # Look for additional and replacement files in binary package config - for root, dirs, files in os.walk(package_dir): - try: - dirs.remove('.svn') - except ValueError: - pass - for f in files: - cur_path = root + '/' + f - if root != package_dir: - f = root[len(package_dir) + 1 : ] + '/' + f - if os.path.islink(cur_path): - if f in files_orig: - links[f] = os.readlink(cur_path) - continue - f1 = f.rsplit('-', 1) - if f in files_orig: - files_real[f] = f, cur_path, None - continue - if len(f1) > 1: - f_base, f_version = f1 - if f_base in files_orig: - if f_base in files_real: - raise RuntimeError("Multiple files for %s" % f_base) - files_real[f_base] = f_base, package_dir + '/' + f, \ - f_version + # List all additional and replacement files in binary package + # config so we can: + # - match dangling symlinks which pathlib.Path.glob() would ignore + # - warn if any are unused + for root, dir_names, file_names in os.walk(package_dir): + root = pathlib.Path(root) + for name in file_names: + if not (root == package_dir \ + and name in ['defines', 'LICENSE.install', + 'update.py', 'update.sh']): + canon_path = root.relative_to(package_dir) / name + files_added.add(canon_path) + files_unused.add(canon_path) + + for pattern, pattern_re in files_include: + matched = False + matched_more = False + + for paths, is_added in [ + (((canon_path, package_dir / canon_path) + for canon_path in files_added + if pattern_re.fullmatch(str(canon_path))), + True), + (((cur_path.relative_to(install_dir), cur_path) + for cur_path in install_dir.glob(pattern)), + False) + ]: + for canon_path, cur_path in paths: + canon_name = str(canon_path) + if any(exc_pattern_re.fullmatch(canon_name) + for exc_pattern_re in files_exclude): + continue + + matched = True + + # Skip if already matched by earlier pattern or in + # other directory + if canon_path in files_real or canon_path in links: continue - # Whitelist files not expected to be installed as firmware - if f in ['defines', 'LICENSE.install', - 'update.py', 'update.sh']: - continue - files_unused.append(f) - - # Take all the other files from upstream - for f in files_orig: - if f not in files_real and f not in links: - f_upstream = os.path.join('debian/build/install', f) - if os.path.islink(f_upstream): - links[f] = os.readlink(f_upstream) - elif os.path.isfile(f_upstream): - files_real[f] = f, f_upstream, None - - for f in links: - link_target = os.path.normpath(os.path.join(f, '..', links[f])) - links_rev.setdefault(link_target, []).append(f) + + matched_more = True + if is_added: + files_unused.remove(canon_path) + if cur_path.is_symlink(): + links[canon_path] = cur_path.readlink() + elif cur_path.is_file(): + files_real[canon_path] = cur_path + + self.file_packages.setdefault(canon_path, []) \ + .append(package) + + # Non-matching pattern is an error + if not matched: + print(f'E: {package}: {pattern} did not match anything', + file=sys.stderr) + self.file_errors = True + # Redundant pattern deserves a warning + elif not matched_more: + print(f'W: {package}: pattern {pattern} is redundant with earlier patterns', + file=sys.stderr) + + for canon_path in links: + link_target = ((canon_path.parent / links[canon_path]) + .resolve(strict=False) + .relative_to(cur_dir)) + links_rev.setdefault(link_target, []).append(canon_path) if files_unused: - print('W: %s: unused files:' % package, ' '.join(files_unused), + print(f'W: {package}: unused files:', + ', '.join(str(path) for path in files_unused), file=sys.stderr) - makeflags['FILES'] = ' '.join(["%s:%s" % (i[1], i[0]) for i in sorted(files_real.values())]) - vars['files_real'] = ' '.join(["/lib/firmware/%s" % i for i in config_entry['files']]) + makeflags['FILES'] = \ + ' '.join([f'"{source}":"{dest}"' + for dest, source in sorted(files_real.items())]) \ + .replace(',', '[comma]') + makeflags['LINKS'] = \ + ' '.join([f'"{link}":"{target}"' + for link, target in sorted(links.items())]) \ + .replace(',', '[comma]') - makeflags['LINKS'] = ' '.join(["%s:%s" % (link, target) - for link, target in sorted(links.items())]) - - files_desc = ["Contents:"] firmware_meta_temp = self.templates.get("metainfo.xml.firmware") firmware_meta_list = [] module_names = set() - wrap = TextWrapper(width = 71, fix_sentence_endings = True, - initial_indent = ' * ', - subsequent_indent = ' ').wrap - for f in config_entry['files']: + for canon_path in sorted(itertools.chain(files_real, links)): + canon_name = str(canon_path) firmware_meta_list.append(self.substitute(firmware_meta_temp, - {'filename': f})) - for module_name in self.firmware_modules.get(f, []): + {'filename': canon_name})) + for module_name in self.firmware_modules.get(canon_name, []): module_names.add(module_name) - if f in links: - continue - f, f_real, version = files_real[f] - c = self.config.get(('base', package, f), {}) - desc = c.get('desc') - if version is None: - version = c.get('version') - try: - f = f + ', ' + ', '.join(sorted(links_rev[f])) - except KeyError: - pass - if desc and version: - desc = "%s, version %s (%s)" % (desc, version, f) - elif desc: - desc = "%s (%s)" % (desc, f) - else: - desc = "%s" % f - files_desc.extend(wrap(desc)) modaliases = set() for module_name in module_names: @@ -252,35 +231,45 @@ class GenControl(debian_linux.gencontrol.Gencontrol): for alias in sorted(list(modaliases)) ] - packages_binary = self.templates.get_control("control.binary", vars) + packages_binary = list(self.templates.get_control("binary.control", vars)) - packages_binary[0]['Description'].append_pre(files_desc) + scripts = {} if 'initramfs-tools' in config_entry.get('support', []): postinst = self.templates.get('postinst.initramfs-tools') - open("debian/firmware-%s.postinst" % package, 'w').write(self.substitute(postinst, vars)) + scripts.setdefault("postinst", []).append(self.substitute(postinst, vars)) if 'license-accept' in config_entry: license = open("%s/LICENSE.install" % package_dir, 'r').read() preinst = self.templates.get('preinst.license') - preinst_filename = "debian/firmware-%s.preinst" % package - open(preinst_filename, 'w').write(self.substitute(preinst, vars)) + scripts.setdefault("preinst", []).append(self.substitute(preinst, vars)) - templates = self.templates.get_templates_control('templates.license', vars) - templates[0]['Description'].append(re.sub('\n\n', '\n.\n', license)) + templates = list(self.templates.get_templates_control('templates.license', vars)) + templates[0].description.append(re.sub('\n\n', '\n.\n', license)) templates_filename = "debian/firmware-%s.templates" % package - self.write_rfc822(open(templates_filename, 'w'), templates) + write_deb822(templates, open(templates_filename, 'w')) - desc = packages_binary[0]['Description'] + desc = packages_binary[0].description desc.append( """This firmware is covered by the %s. You must agree to the terms of this license before it is installed.""" % vars['license-title']) - packages_binary[0]['Pre-Depends'] = PackageRelation('debconf | debconf-2.0') + packages_binary[0].pre_depends = PackageRelation('debconf | debconf-2.0') + + if config_entry.get('usrmovemitigation', []): + vars['files'] = ' '.join(config_entry['usrmovemitigation']) + for script in ("preinst", "postinst"): + script_template = self.templates.get(script + '.usrmovemitigation') + script_content = self.substitute(script_template, vars) + scripts.setdefault(script, []).append(script_content) + del vars['files'] - packages.extend(packages_binary) + for script, script_contents in scripts.items(): + script_contents.insert(0, "#!/bin/sh\n\nset -e\n") + script_contents.append("#DEBHELPER#\n\nexit 0\n") + open("debian/firmware-%s.%s" % (package, script), "w").write("\n".join(script_contents)) - makefile.add_cmds('binary-indep', ["$(MAKE) -f debian/rules.real binary-indep %s" % makeflags]) + self.bundle.add_packages(packages_binary, (package,), makeflags) vars['firmware-list'] = ''.join(firmware_meta_list) vars['modalias-list'] = ''.join(modalias_meta_list) @@ -292,6 +281,21 @@ You must agree to the terms of this license before it is installed.""" # XXX Might need to escape some characters open("debian/firmware-%s.metainfo.xml" % package, 'w').write(self.substitute(package_meta_temp, vars)) + # XXX Delete after updating to linux-support-6.11 + def do_extra(self) -> None: + try: + packages_extra = self.templates.get_control("extra.control", self.vars) + except KeyError: + return + + for package in packages_extra: + package.meta_rules_target = 'meta' + if not package.architecture: + raise RuntimeError('Require Architecture in debian/templates/extra.control') + for arch in package.architecture: + self.bundle.add_packages([package], (arch, ), + MakeFlags(), arch=arch, check_packages=False) + def process_template(self, in_entry, vars): e = Template() for key, value in in_entry.items(): @@ -319,23 +323,5 @@ You must agree to the terms of this license before it is installed.""" return vars[match.group(2)] return re.sub(r'@(\??)([-_a-z]+)@', subst, str(s)) - def write(self, packages, makefile): - self.write_control(packages.values()) - self.write_makefile(makefile) - - def write_control(self, list): - self.write_rfc822(open("debian/control", 'w'), list) - - def write_makefile(self, makefile): - f = open("debian/rules.gen", 'w') - makefile.write(f) - f.close() - - def write_rfc822(self, f, list): - for entry in list: - for key, value in entry.items(): - f.write("%s: %s\n" % (key, value)) - f.write('\n') - if __name__ == '__main__': GenControl()() diff --git a/debian/bin/release-update b/debian/bin/release-update index 5eb75cd..ee564c4 100755 --- a/debian/bin/release-update +++ b/debian/bin/release-update @@ -8,12 +8,62 @@ import locale from debian_linux.debian import Changelog, Version -def print_stable_log(log, cur_ver, new_ver): - log.flush() # serialise our output with git's - subprocess.check_call(['git', 'log', '--reverse', '--no-merges', - '--pretty= - %s', - '{}..{}'.format(cur_ver, new_ver)], - stdout=log) +from config import Config, pattern_to_re + + +# Convert Python glob pattern to Git patterns. Notable difference is +# that in Python globbing '**/' matches a directory and its +# descendants, but in Git it only matches descendants. Assume there +# is at most one '**' in a pattern. +def pattern_to_git_patterns(pattern): + yield f':{pattern}' + if '**/' in pattern: + yield f':{ pattern.replace("**/", "") }' + + +# Convert Python glob pattern to Git reegexp. This assumes +# pattern_to_re() produces compatible regular expressions except for +# the use of '(?:...)' which we fix up. +def pattern_to_git_re(pattern): + return pattern_to_re(pattern).pattern.replace('(?:', '(') + + +def print_stable_log(log, cur_ver, new_ver, files_include): + inc_git_patterns = [] + inc_git_res = [] + for pattern in files_include: + inc_git_patterns.extend(pattern_to_git_patterns(pattern)) + inc_git_res.append(pattern_to_git_re(pattern)) + + git_rev_range = f'{cur_ver}..{new_ver}' + + # List commits changing files that we include + with subprocess.Popen(['git', 'log', '--no-merges', '--pretty=%s', + git_rev_range, '--'] + inc_git_patterns, + stdout=subprocess.PIPE, text=True) \ + as proc: + lines = proc.stdout.readlines() + + # List commits changing links that we include + with subprocess.Popen(['git', 'log', '--no-merges', '--pretty=%s', + '-G', f'^Link: *({ "|".join(inc_git_res) }) ->', + git_rev_range, '--', 'WHENCE'], + stdout=subprocess.PIPE, text=True) \ + as proc: + lines.extend(proc.stdout.readlines()) + + # Strip useless subject prefix + strip_re = re.compile(r'^linux-firmware: *') + lines = [strip_re.sub('', line) for line in lines] + + # Sort and de-dupe lines + lines.sort(key=str.casefold) + last_line = None + for line in lines: + if line != last_line: + log.write(f' - {line}') + last_line = line + def main(repo, new_ver): locale.setlocale(locale.LC_CTYPE, "C.UTF-8") @@ -26,6 +76,16 @@ def main(repo, new_ver): if cur_ver == new_ver: sys.exit(0) + # Get list of file patterns that we include. Don't get exclusions + # because in some cases files are excluded from one binary package + # so they can be included in another, and I don't think we can + # construct a single pattern list that exactly matches our include/ + # exclude behaviour. + config = Config() + files_include = sum((config['base', package]['files'] + for package in config['base',]['packages']), + []) + new_pkg_ver = new_ver + '-1' # Three possible cases: @@ -68,12 +128,14 @@ def main(repo, new_ver): # Case 2(a) if line_no == 3 and line != intro_line: new_log.write(intro_line) - print_stable_log(new_log, cur_ver, new_ver) + print_stable_log(new_log, cur_ver, new_ver, + files_include) new_log.write('\n') inserted = True # Case 1 or 2(b) elif line_no > 3 and line == '\n': - print_stable_log(new_log, cur_ver, new_ver) + print_stable_log(new_log, cur_ver, new_ver, + files_include) inserted = True # Check that we inserted before hitting the end of the diff --git a/debian/bin/update-modinfo b/debian/bin/update-modinfo index ea37486..1470dd9 100755 --- a/debian/bin/update-modinfo +++ b/debian/bin/update-modinfo @@ -2,6 +2,7 @@ # Update the module information used to generate related device IDs +import itertools import json import os.path import subprocess @@ -28,34 +29,47 @@ def get_module_info(filename, attr_name): return output.rstrip('\n').split('\n') -def main(kernel_id=None): - if kernel_id is None: - kernel_dir = '/lib/modules/' + os.uname().release - elif '/' not in kernel_id: - kernel_dir = '/lib/modules/' + kernel_id +class JSONEncoderWithSet(json.JSONEncoder): + def default(self, o): + if isinstance(o, set): + return sorted(o) + return super().default(o) + + +def main(*kernel_ids): + if not kernel_ids: + kernel_dirs = '/lib/modules/' + os.uname().release else: - kernel_dir = kernel_id + kernel_dirs = [('/lib/modules/' + kernel_id + if '/' not in kernel_id + else kernel_id) + for kernel_id in kernel_ids] modinfo = {} - for name, filename in iter_modules(kernel_dir): + for name, filename in itertools.chain.from_iterable( + iter_modules(kernel_dir) for kernel_dir in kernel_dirs): # We only care about modules that might request firmware - firmware = get_module_info(filename, 'firmware') + firmware = set(get_module_info(filename, 'firmware')) if not firmware: continue # We only care about aliases generated from device IDs, which # start with ":" - aliases = [alias - for alias in get_module_info(filename, 'alias') - if ':' in alias] + aliases = set(alias + for alias in get_module_info(filename, 'alias') + if ':' in alias) - modinfo[name] = { - 'alias': aliases, - 'firmware': firmware, - } + if name not in modinfo: + modinfo[name] = { + 'alias': aliases, + 'firmware': firmware, + } + else: + modinfo[name]['alias'] |= aliases + modinfo[name]['firmware'] |= firmware with open('debian/modinfo.json', 'w') as f: - json.dump(modinfo, f, indent=2, sort_keys=True) + json.dump(modinfo, f, indent=2, sort_keys=True, cls=JSONEncoderWithSet) if __name__ == '__main__': -- cgit v1.2.3