summaryrefslogtreecommitdiffstats
path: root/debian/bin/gencontrol.py
diff options
context:
space:
mode:
Diffstat (limited to 'debian/bin/gencontrol.py')
-rwxr-xr-xdebian/bin/gencontrol.py378
1 files changed, 182 insertions, 196 deletions
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()()