summaryrefslogtreecommitdiffstats
path: root/debian/bin
diff options
context:
space:
mode:
Diffstat (limited to 'debian/bin')
-rwxr-xr-xdebian/bin/check_upstream.py91
-rwxr-xr-xdebian/bin/gencontrol.py341
-rwxr-xr-xdebian/bin/release-update95
-rwxr-xr-xdebian/bin/update-modinfo60
4 files changed, 587 insertions, 0 deletions
diff --git a/debian/bin/check_upstream.py b/debian/bin/check_upstream.py
new file mode 100755
index 0000000..e2931c4
--- /dev/null
+++ b/debian/bin/check_upstream.py
@@ -0,0 +1,91 @@
+#!/usr/bin/env python3
+
+import errno, filecmp, fnmatch, glob, os.path, re, sys
+from debian import deb822
+from enum import Enum
+
+sys.path.insert(0, "debian/lib/python")
+rules_defs = dict((match.group(1), match.group(2))
+ for line in open('debian/rules.defs')
+ for match in [re.match(r'(\w+)\s*:=\s*(.*)\n', line)])
+sys.path.append('/usr/share/linux-support-%s/lib/python' %
+ rules_defs['KERNELVERSION'])
+from debian_linux.firmware import FirmwareWhence
+from config import Config
+
+class DistState(Enum):
+ undistributable = 1
+ non_free = 2
+ free = 3
+
+def is_source_available(section):
+ for file_info in section.files.values():
+ if not (file_info.source 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):
+ return (DistState.free if is_source_available(section)
+ else DistState.non_free)
+ elif re.match(r'^(?:D|Red)istributable\b', section.licence):
+ return DistState.non_free
+ elif re.match(r'^GPL(?:v[23]|\+)?\b|^Dual GPL(?:v[23])?/', section.licence):
+ return (DistState.free if is_source_available(section)
+ else DistState.undistributable)
+ else:
+ # Unrecognised and probably undistributable
+ return DistState.undistributable
+
+def main(source_dir='.'):
+ config = Config()
+ over_dirs = ['debian/config/' + package for
+ package in config['base',]['packages']]
+ with open("debian/copyright") as f:
+ exclusions = deb822.Deb822(f).get("Files-Excluded", '').strip().split()
+ packaged_files = {}
+ for package in config['base',]['packages']:
+ for filename in config['base', package]['files']:
+ packaged_files[filename] = package
+
+ for section in FirmwareWhence(open(os.path.join(source_dir, 'WHENCE'))):
+ dist_state = check_section(section)
+ for file_info in section.files.values():
+ 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:
+ update_file(source_dir, over_dirs, file_info.binary)
+ elif os.path.isfile(filename):
+ print('I: %s is not included in any binary package' %
+ file_info.binary)
+ else:
+ print('I: %s: could be added' % file_info.binary)
+ elif dist_state == DistState.undistributable:
+ if os.path.isfile(file_info.binary):
+ print('W: %s appears to be undistributable' %
+ file_info.binary)
+
+def update_file(source_dir, over_dirs, filename):
+ source_file = os.path.join(source_dir, filename)
+ for over_dir in over_dirs:
+ for over_file in ([os.path.join(over_dir, filename)] +
+ glob.glob(os.path.join(over_dir, filename + '-*'))):
+ if os.path.isfile(over_file):
+ if not filecmp.cmp(source_file, over_file, True):
+ print('I: %s: changed' % filename)
+ return
+
+if __name__ == '__main__':
+ main(*sys.argv[1:])
diff --git a/debian/bin/gencontrol.py b/debian/bin/gencontrol.py
new file mode 100755
index 0000000..293432b
--- /dev/null
+++ b/debian/bin/gencontrol.py
@@ -0,0 +1,341 @@
+#!/usr/bin/env python3
+
+import io
+import json
+import locale
+import os
+import re
+import sys
+
+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
+import debian_linux.gencontrol
+from debian_linux.gencontrol import Makefile, MakeFlags, PackagesList
+from debian_linux.utils import TextWrapper
+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),
+ ))
+
+
+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)))
+
+
+class GenControl(debian_linux.gencontrol.Gencontrol):
+ def __init__(self):
+ self.config = Config()
+ self.templates = Templates()
+
+ with open('debian/modinfo.json', 'r') as f:
+ self.modinfo = json.load(f)
+
+ # Make another dict keyed by firmware names
+ self.firmware_modules = {}
+ for name, info in self.modinfo.items():
+ for firmware_filename in info['firmware']:
+ 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):
+ 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"])
+
+ 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):
+ config_entry = self.config['base', package]
+ vars.update(config_entry)
+ vars['package'] = package
+ vars['package-env-prefix'] = 'FIRMWARE_' + package.upper().replace('-', '_')
+
+ makeflags['PACKAGE'] = package
+
+ # Those might be absent, set them to empty string for replacement to work:
+ empty_list = ['replaces', 'conflicts', 'breaks', 'provides', 'recommends']
+ for optional in ['replaces', 'conflicts', 'breaks', 'provides', 'recommends']:
+ if optional not in vars:
+ vars[optional] = ''
+
+ package_dir = "debian/config/%s" % package
+
+ try:
+ os.unlink('debian/firmware-%s.bug-presubj' % package)
+ except OSError:
+ pass
+ os.symlink('bug-presubj', 'debian/firmware-%s.bug-presubj' % package)
+
+ files_orig = config_entry['files']
+ 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
+ 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)
+
+ if files_unused:
+ print('W: %s: unused files:' % package, ' '.join(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['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']:
+ firmware_meta_list.append(self.substitute(firmware_meta_temp,
+ {'filename': f}))
+ for module_name in self.firmware_modules.get(f, []):
+ 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:
+ for modalias in self.modinfo[module_name]['alias']:
+ modaliases.add(modalias)
+ modalias_meta_list = [
+ self.substitute(self.templates.get("metainfo.xml.modalias"),
+ {'alias': alias})
+ for alias in sorted(list(modaliases))
+ ]
+
+ packages_binary = self.templates.get_control("control.binary", vars)
+
+ packages_binary[0]['Description'].append_pre(files_desc)
+
+ 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))
+
+ 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))
+
+ templates = 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)
+
+ 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.extend(packages_binary)
+
+ makefile.add_cmds('binary-indep', ["$(MAKE) -f debian/rules.real binary-indep %s" % makeflags])
+
+ vars['firmware-list'] = ''.join(firmware_meta_list)
+ vars['modalias-list'] = ''.join(modalias_meta_list)
+ # Underscores are preferred to hyphens
+ vars['package-metainfo'] = package.replace('-', '_')
+ # Summary must not contain line breaks
+ vars['longdesc-metainfo'] = re.sub(r'\s+', ' ', vars['longdesc'])
+ package_meta_temp = self.templates.get("metainfo.xml", {})
+ # XXX Might need to escape some characters
+ open("debian/firmware-%s.metainfo.xml" % package, 'w').write(self.substitute(package_meta_temp, vars))
+
+ def process_template(self, in_entry, vars):
+ e = Template()
+ for key, value in in_entry.items():
+ if isinstance(value, PackageDescription):
+ e[key] = self.process_description(value, vars)
+ elif key[:2] == 'X-':
+ pass
+ else:
+ e[key] = self.substitute(value, vars)
+ return e
+
+ def process_templates(self, in_entries, vars):
+ entries = []
+ for i in in_entries:
+ entries.append(self.process_template(i, vars))
+ return entries
+
+ def substitute(self, s, vars):
+ if isinstance(s, (list, tuple)):
+ return [self.substitute(i, vars) for i in s]
+ def subst(match):
+ if match.group(1):
+ return vars.get(match.group(2), '')
+ else:
+ 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
new file mode 100755
index 0000000..5eb75cd
--- /dev/null
+++ b/debian/bin/release-update
@@ -0,0 +1,95 @@
+#!/usr/bin/python3
+
+import sys
+sys.path.append(sys.path[0] + "/../lib/python")
+
+import os, re, subprocess
+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)
+
+def main(repo, new_ver):
+ locale.setlocale(locale.LC_CTYPE, "C.UTF-8")
+ os.environ['GIT_DIR'] = repo + '/.git'
+
+ changelog = Changelog(version=Version)
+ cur_ver = changelog[0].version.upstream
+
+ # Nothing to update
+ if cur_ver == new_ver:
+ sys.exit(0)
+
+ new_pkg_ver = new_ver + '-1'
+
+ # Three possible cases:
+ # 1. The current version has been released so we need to add a new
+ # version to the changelog.
+ # 2. The current version has not been released so we're changing its
+ # version string.
+ # (a) There are no stable updates included in the current version,
+ # so we need to insert an introductory line, the URL(s) and
+ # git log(s) and a blank line at the top.
+ # (b) One or more stable updates are already included in the current
+ # version, so we need to insert the URL(s) and git log(s) after
+ # them.
+
+ changelog_intro = 'New upstream version:'
+
+ # Case 1
+ if changelog[0].distribution != 'UNRELEASED':
+ subprocess.check_call(['dch', '-v', new_pkg_ver, '-D', 'UNRELEASED',
+ changelog_intro])
+
+ with open('debian/changelog', 'r') as old_log:
+ with open('debian/changelog.new', 'w') as new_log:
+ line_no = 0
+ inserted = False
+ intro_line = ' * {}\n'.format(changelog_intro)
+
+ for line in old_log:
+ line_no += 1
+
+ # Case 2
+ if changelog[0].distribution == 'UNRELEASED' and line_no == 1:
+ print('{} ({}) UNRELEASED; urgency={}'
+ .format(changelog[0].source, new_pkg_ver,
+ changelog[0].urgency),
+ file=new_log)
+ continue
+
+ if not inserted:
+ # 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)
+ 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)
+ inserted = True
+
+ # Check that we inserted before hitting the end of the
+ # first version entry
+ assert not (line.startswith(' -- ') and not inserted)
+
+ new_log.write(line)
+
+ os.rename('debian/changelog.new', 'debian/changelog')
+
+if __name__ == '__main__':
+ if len(sys.argv) != 3:
+ print('''\
+Usage: {} REPO VERSION
+REPO is the git repository to generate a changelog from
+VERSION is the upstream version'''.format(sys.argv[0]),
+ file=sys.stderr)
+ sys.exit(2)
+ main(*sys.argv[1:])
diff --git a/debian/bin/update-modinfo b/debian/bin/update-modinfo
new file mode 100755
index 0000000..64a27cc
--- /dev/null
+++ b/debian/bin/update-modinfo
@@ -0,0 +1,60 @@
+#!/usr/bin/python3
+
+# Update the module information used to generate related device IDs
+
+import json
+import os.path
+import subprocess
+import sys
+
+
+def iter_modules(base_dir):
+ def onerror(e):
+ raise e
+ for root, dirs, files in \
+ os.walk(os.path.join(base_dir, 'kernel'), onerror=onerror):
+ for name in files:
+ if name.endswith('.ko'):
+ yield name[:-3], os.path.join(root, name)
+
+
+def get_module_info(filename, attr_name):
+ output = subprocess.check_output(['modinfo', '-F', attr_name, filename],
+ text=True)
+ if output == '':
+ return []
+ 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
+ else:
+ kernel_dir = kernel_id
+
+ modinfo = {}
+ for name, filename in iter_modules(kernel_dir):
+ # We only care about modules that might request firmware
+ firmware = get_module_info(filename, 'firmware')
+ if not firmware:
+ continue
+
+ # We only care about aliases generated from device IDs, which
+ # start with <type> ":"
+ aliases = [alias
+ for alias in get_module_info(filename, 'alias')
+ if ':' in alias]
+
+ modinfo[name] = {
+ 'alias': aliases,
+ 'firmware': firmware,
+ }
+
+ with open('debian/modinfo.json', 'w') as f:
+ json.dump(modinfo, f, indent=2, sort_keys=True)
+
+
+if __name__ == '__main__':
+ main(*sys.argv[1:])