diff options
Diffstat (limited to 'debian/lib/python/debian_linux')
-rw-r--r-- | debian/lib/python/debian_linux/config_v2.py | 16 | ||||
-rw-r--r-- | debian/lib/python/debian_linux/dataclasses_deb822.py | 234 | ||||
-rw-r--r-- | debian/lib/python/debian_linux/debian.py | 366 | ||||
-rw-r--r-- | debian/lib/python/debian_linux/firmware.py | 34 | ||||
-rw-r--r-- | debian/lib/python/debian_linux/gencontrol.py | 124 | ||||
-rw-r--r-- | debian/lib/python/debian_linux/test_debian.py | 2 | ||||
-rw-r--r-- | debian/lib/python/debian_linux/utils.py | 19 |
7 files changed, 546 insertions, 249 deletions
diff --git a/debian/lib/python/debian_linux/config_v2.py b/debian/lib/python/debian_linux/config_v2.py index 9bc789bfd1..2086e0ec6c 100644 --- a/debian/lib/python/debian_linux/config_v2.py +++ b/debian/lib/python/debian_linux/config_v2.py @@ -308,6 +308,22 @@ class ConfigFeatureset(ConfigBase): for flavour in debianarch.flavour ] + if self.flavour: + # XXX: Remove special case of name + if self.name == 'none': + flavour_default = [i for i in self.flavour if i.defs.is_default] + flavour_quick = [i for i in self.flavour if i.defs.is_quick] + + if not flavour_quick: + flavour_quick = flavour_default or self.flavour[0:1] + flavour_quick[0].defs.is_quick = True + + # Flavours in other featuresets can never be default or quick + else: + for flavour in self.flavour: + flavour.defs.is_default = False + flavour.defs.is_quick = False + self.__post_init_hierarchy__(path) diff --git a/debian/lib/python/debian_linux/dataclasses_deb822.py b/debian/lib/python/debian_linux/dataclasses_deb822.py new file mode 100644 index 0000000000..9858296ec4 --- /dev/null +++ b/debian/lib/python/debian_linux/dataclasses_deb822.py @@ -0,0 +1,234 @@ +from __future__ import annotations + +import dataclasses +import re +from typing import ( + Any, + Callable, + Generic, + IO, + Iterable, + Optional, + overload, + TypeVar, + TYPE_CHECKING, +) + +_T = TypeVar('_T') + +if TYPE_CHECKING: + from dataclasses import _DataclassT +else: + # We can only get to _DataclassT during type checking, use a generic type during runtime + _DataclassT = _T + +__all__ = [ + 'field_deb822', + 'read_deb822', + 'write_deb822', + 'Deb822DecodeError', +] + + +class Deb822Field(Generic[_T]): + key: str + load: Optional[Callable[[str], _T]] + dump: Optional[Callable[[_T], str]] + + def __init__( + self, *, + key: str, + load: Optional[Callable[[str], _T]], + dump: Optional[Callable[[_T], str]], + ) -> None: + self.key = key + self.load = load + self.dump = dump + + +# The return type _T is technically wrong, but it allows checking if during +# runtime we get the correct type. +@overload +def field_deb822( + deb822_key: str, + /, *, + deb822_load: Optional[Callable[[str], _T]] = None, + deb822_dump: Optional[Callable[[_T], str]] = str, + default: _T, +) -> _T: + ... + + +@overload +def field_deb822( + deb822_key: str, + /, *, + deb822_load: Optional[Callable[[str], _T]] = None, + deb822_dump: Optional[Callable[[_T], str]] = str, + default_factory: Callable[[], _T], +) -> _T: + ... + + +@overload +def field_deb822( + deb822_key: str, + /, *, + deb822_load: Optional[Callable[[str], _T]] = None, + deb822_dump: Optional[Callable[[_T], str]] = str, +) -> _T: + ... + + +def field_deb822( + deb822_key: str, + /, *, + deb822_load: Optional[Callable[[str], _T]] = None, + deb822_dump: Optional[Callable[[_T], str]] = str, + default: Any = dataclasses.MISSING, + default_factory: Any = dataclasses.MISSING, +) -> Any: + metadata: dict[str, Any] = { + 'deb822': Deb822Field( + key=deb822_key, + load=deb822_load, + dump=deb822_dump, + ), + } + + if default is not dataclasses.MISSING: + return dataclasses.field( + default=default, + metadata=metadata, + ) + else: + return dataclasses.field( + default_factory=default_factory, + metadata=metadata, + ) + + +class Deb822DecodeError(ValueError): + pass + + +class Deb822DecodeState(Generic[_DataclassT]): + cls: type[_DataclassT] + fields: dict[str, dataclasses.Field] + ignore_unknown: bool + + data: dict[Optional[dataclasses.Field], str] + current: Optional[dataclasses.Field] + + _line_re = re.compile(r''' + ^ + ( + [ \t](?P<cont>.*) + | + (?P<key>[^: \t\n\r\f\v]+)\s*:\s*(?P<value>.*) + ) + $ + ''', re.VERBOSE) + + def __init__( + self, + cls: type[_DataclassT], + ignore_unknown: bool, + ) -> None: + self.reset() + + self.cls = cls + self.fields = {} + self.ignore_unknown = ignore_unknown + + for i in dataclasses.fields(cls): + if i.init and (deb822_field := i.metadata.get('deb822')): + self.fields[deb822_field.key] = i + + def reset(self) -> None: + self.data = {} + self.current = None + + def line(self, linenr: int, line: str) -> None: + m = self._line_re.match(line) + if not m: + raise Deb822DecodeError( + f'Not a header, not a continuation at line {linenr + 1}') + elif cont := m.group('cont'): + self.data[self.current] += '\n' + cont + elif deb822_key := m.group('key'): + field = self.fields.get(deb822_key) + if not field and not self.ignore_unknown: + raise Deb822DecodeError( + f'Unknown field "{deb822_key}" at line {linenr + 1}') + + self.current = field + self.data[field] = m.group('value') + else: + raise NotImplementedError + + def generate(self) -> _DataclassT | None: + if not self.data: + return None + + r: dict[str, Any] = {} + for field, value in self.data.items(): + field_factory: Optional[Callable[[str], Any]] = None + if field is None: + continue + elif (deb822_field := field.metadata.get('deb822')) and (load := deb822_field.load): + field_factory = load + elif isinstance(field.default_factory, type): + field_factory = field.default_factory + elif field.type in ('str', 'Optional[str]'): + field_factory = str + else: + raise RuntimeError(f'Unable to parse type {field.type}') + + if field_factory is not None: + r[field.name] = field_factory(value) + + self.reset() + return self.cls(**r) + + +def read_deb822( + cls: type[_DataclassT], + file: IO[str], + /, + ignore_unknown: bool = False, +) -> Iterable[_DataclassT]: + state = Deb822DecodeState(cls, ignore_unknown) + + for linenr, line in enumerate(file): + line = line.rstrip('\n') + + # Empty line, end of record + if line == '': + if (obj := state.generate()): + yield obj + # Strip comments rather than trying to preserve them + elif line[0] == '#': + continue + else: + state.line(linenr, line) + + if (obj := state.generate()): + yield obj + + +def write_deb822( + objs: Iterable[_DataclassT], + file: IO[str], + /, +) -> None: + for obj in objs: + for field in dataclasses.fields(obj): + if ( + (value := getattr(obj, field.name, None)) + and (deb822_field := field.metadata.get('deb822')) + and (dump := deb822_field.dump) is not None + ): + folded = '\n '.join(dump(value).strip().split('\n')) + file.write(f'{deb822_field.key}: {folded}\n') + file.write('\n') diff --git a/debian/lib/python/debian_linux/debian.py b/debian/lib/python/debian_linux/debian.py index 4a37b62a66..fc90ccac31 100644 --- a/debian/lib/python/debian_linux/debian.py +++ b/debian/lib/python/debian_linux/debian.py @@ -1,20 +1,21 @@ from __future__ import annotations -import collections -import collections.abc import dataclasses import enum import itertools import os.path import re +import shlex import typing -import warnings from typing import ( Iterable, + Optional, Self, TypeAlias, ) +from .dataclasses_deb822 import field_deb822 + class Changelog(list): _top_rules = r""" @@ -273,8 +274,8 @@ class PackageDescription: long_pars = [] for i in self.long: long_pars.append(wrap(i)) - long = '\n .\n '.join('\n '.join(i) for i in long_pars) - return short + '\n ' + long if long else short + long = '\n.\n'.join('\n'.join(i) for i in long_pars) + return short + '\n' + long if long else short def append(self, long: str) -> None: long = long.strip() @@ -540,166 +541,203 @@ class PackageBuildprofile(list[PackageBuildprofileEntry]): return ' '.join(str(i) for i in self) -class _ControlFileDict(collections.abc.MutableMapping): - def __init__(self): - self.__data = {} - self.meta = {} +@dataclasses.dataclass +class _BasePackage: + name: Optional[str] + architecture: PackageArchitecture = field_deb822( + 'Architecture', + default_factory=PackageArchitecture, + ) + section: Optional[str] = field_deb822( + 'Section', + default=None, + ) + priority: Optional[str] = field_deb822( + 'Priority', + default=None, + ) - def __getitem__(self, key): - return self.__data[key] - def __setitem__(self, key, value): - if key.lower().startswith('meta-'): - self.meta[key.lower()[5:]] = value - return +@dataclasses.dataclass +class SourcePackage(_BasePackage): + name: Optional[str] = field_deb822( + 'Source', + default=None, + ) + maintainer: Optional[str] = field_deb822( + 'Maintainer', + default=None, + ) + uploaders: Optional[str] = field_deb822( + 'Uploaders', + default=None, + ) + standards_version: Optional[str] = field_deb822( + 'Standards-Version', + default=None, + ) + build_depends: PackageRelation = field_deb822( + 'Build-Depends', + default_factory=PackageRelation, + ) + build_depends_arch: PackageRelation = field_deb822( + 'Build-Depends-Arch', + default_factory=PackageRelation, + ) + build_depends_indep: PackageRelation = field_deb822( + 'Build-Depends-Indep', + default_factory=PackageRelation, + ) + rules_requires_root: Optional[str] = field_deb822( + 'Rules-Requires-Root', + default=None, + ) + homepage: Optional[str] = field_deb822( + 'Homepage', + default=None, + ) + vcs_browser: Optional[str] = field_deb822( + 'Vcs-Browser', + default=None, + ) + vcs_git: Optional[str] = field_deb822( + 'Vcs-Git', + default=None, + ) + autobuild: Optional[str] = field_deb822( + 'XS-Autobuild', + default=None, + ) - try: - cls = self._fields[key] - if not isinstance(value, cls): - if f := getattr(cls, 'parse', None): - value = f(value) - else: - value = cls(value) - except KeyError: - warnings.warn( - f'setting unknown field {key} in {type(self).__name__}', - stacklevel=2) - self.__data[key] = value - - def __delitem__(self, key): - del self.__data[key] - - def __iter__(self): - keys = set(self.__data.keys()) - for key in self._fields.keys(): - if key in self.__data: - keys.remove(key) - yield key - for key in sorted(keys): - yield key - - def __len__(self): - return len(self.__data) - - def setdefault(self, key): - try: - return self[key] - except KeyError: - try: - ret = self[key] = self._fields[key]() - except KeyError: - warnings.warn( - f'setting unknown field {key} in {type(self).__name__}', - stacklevel=2) - ret = self[key] = '' - return ret - - def copy(self): - ret = self.__class__() - ret.__data = self.__data.copy() - ret.meta = self.meta.copy() - return ret - @classmethod - def read_rfc822(cls, f): - entries = [] - eof = False - - while not eof: - e = cls() - last = None - lines = [] - while True: - line = f.readline() - if not line: - eof = True - break - # Strip comments rather than trying to preserve them - if line[0] == '#': - continue - line = line.strip('\n') - if not line: - break - if line[0] in ' \t': - if not last: - raise ValueError( - 'Continuation line seen before first header') - lines.append(line.lstrip()) - continue - if last: - e[last] = '\n'.join(lines) - i = line.find(':') - if i < 0: - raise ValueError(u"Not a header, not a continuation: ``%s''" % - line) - last = line[:i] - lines = [line[i + 1:].lstrip()] - if last: - e[last] = '\n'.join(lines) - if e: - entries.append(e) - - return entries - - -class SourcePackage(_ControlFileDict): - _fields = collections.OrderedDict(( - ('Source', str), - ('Architecture', PackageArchitecture), - ('Section', str), - ('Priority', str), - ('Maintainer', str), - ('Uploaders', str), - ('Standards-Version', str), - ('Build-Depends', PackageRelation), - ('Build-Depends-Arch', PackageRelation), - ('Build-Depends-Indep', PackageRelation), - ('Rules-Requires-Root', str), - ('Homepage', str), - ('Vcs-Browser', str), - ('Vcs-Git', str), - ('XS-Autobuild', str), - )) - - -class BinaryPackage(_ControlFileDict): - _fields = collections.OrderedDict(( - ('Package', str), - ('Package-Type', str), # for udeb only - ('Architecture', PackageArchitecture), - ('Section', str), - ('Priority', str), - # Build-Depends* fields aren't allowed for binary packages in - # the real control file, but we move them to the source - # package - ('Build-Depends', PackageRelation), - ('Build-Depends-Arch', PackageRelation), - ('Build-Depends-Indep', PackageRelation), - ('Build-Profiles', PackageBuildprofile), - ('Built-Using', PackageRelation), - ('Provides', PackageRelation), - ('Pre-Depends', PackageRelation), - ('Depends', PackageRelation), - ('Recommends', PackageRelation), - ('Suggests', PackageRelation), - ('Replaces', PackageRelation), - ('Breaks', PackageRelation), - ('Conflicts', PackageRelation), - ('Multi-Arch', str), - ('Kernel-Version', str), # for udeb only - ('Description', PackageDescription), - ('Homepage', str), - )) - - -class TestsControl(_ControlFileDict): - _fields = collections.OrderedDict(( - ('Tests', str), - ('Test-Command', str), - ('Architecture', PackageArchitecture), - ('Restrictions', str), - ('Features', str), - ('Depends', PackageRelation), - ('Tests-Directory', str), - ('Classes', str), - )) +@dataclasses.dataclass +class BinaryPackage(_BasePackage): + name: str = field_deb822('Package') + # Build-Depends* fields aren't allowed for binary packages in + # the real control file, but we move them to the source + # package + build_depends: PackageRelation = field_deb822( + 'Build-Depends', + default_factory=PackageRelation, + deb822_dump=None, + ) + package_type: Optional[str] = field_deb822( + 'Package-Type', + default=None, + ) # for udeb only + build_profiles: PackageBuildprofile = field_deb822( + 'Build-Profiles', + deb822_load=PackageBuildprofile.parse, + default_factory=PackageBuildprofile, + ) + built_using: PackageRelation = field_deb822( + 'Built-Using', + default_factory=PackageRelation, + ) + provides: PackageRelation = field_deb822( + 'Provides', + default_factory=PackageRelation, + ) + pre_depends: PackageRelation = field_deb822( + 'Pre-Depends', + default_factory=PackageRelation, + ) + depends: PackageRelation = field_deb822( + 'Depends', + default_factory=PackageRelation, + ) + recommends: PackageRelation = field_deb822( + 'Recommends', + default_factory=PackageRelation, + ) + suggests: PackageRelation = field_deb822( + 'Suggests', + default_factory=PackageRelation, + ) + replaces: PackageRelation = field_deb822( + 'Replaces', + default_factory=PackageRelation, + ) + breaks: PackageRelation = field_deb822( + 'Breaks', + default_factory=PackageRelation, + ) + conflicts: PackageRelation = field_deb822( + 'Conflicts', + default_factory=PackageRelation, + ) + multi_arch: Optional[str] = field_deb822( + 'Multi-Arch', + default=None, + ) + udeb_kernel_version: Optional[str] = field_deb822( + 'Kernel-Version', + default=None, + ) # for udeb only + description: PackageDescription = field_deb822( + 'Description', + default_factory=PackageDescription, + ) + meta_architectures: PackageArchitecture = dataclasses.field( + default_factory=PackageArchitecture, + ) + meta_rules_check_packages: bool = False + meta_rules_makeflags: dict = field_deb822( + 'Meta-Rules-Makeflags', + default_factory=dict, + deb822_load=lambda v: dict(i.split('=', 1) for i in shlex.split(v)), + deb822_dump=None, + ) + meta_rules_ruleids: dict = dataclasses.field(default_factory=dict) + meta_rules_target: Optional[str] = field_deb822( + 'Meta-Rules-Target', + default=None, + deb822_dump=None, + ) + meta_sign_package: Optional[str] = field_deb822( + 'Meta-Sign-Package', + default=None, + deb822_dump=None, + ) + meta_sign_files: list[str] = field_deb822( + 'Meta-Sign-Files', + default_factory=list, + deb822_load=lambda v: v.split(), + deb822_dump=None, + ) + + +@dataclasses.dataclass +class TestsControl: + tests: Optional[str] = field_deb822( + 'Tests', + default=None, + ) + test_command: Optional[str] = field_deb822( + 'Test-Command', + default=None, + ) + architecture: PackageArchitecture = field_deb822( + 'Architecture', + default_factory=PackageArchitecture, + ) + restrictions: Optional[str] = field_deb822( + 'Restrictions', + default=None, + ) + features: Optional[str] = field_deb822( + 'Features', + default=None, + ) + depends: PackageRelation = field_deb822( + 'Depends', + default_factory=PackageRelation, + ) + tests_directory: Optional[str] = field_deb822( + 'Tests-Directory', + default=None, + ) + classes: Optional[str] = field_deb822( + 'Classes', + default=None, + ) diff --git a/debian/lib/python/debian_linux/firmware.py b/debian/lib/python/debian_linux/firmware.py index b1a4a7e85d..4395847815 100644 --- a/debian/lib/python/debian_linux/firmware.py +++ b/debian/lib/python/debian_linux/firmware.py @@ -9,7 +9,7 @@ class FirmwareFile(object): self.version = version -class FirmwareSection(object): +class FirmwareGroup(object): def __init__(self, driver, files, licence) -> None: self.driver = driver self.files = files @@ -20,6 +20,12 @@ class FirmwareWhence(list): def __init__(self, file) -> None: self.read(file) + @staticmethod + def _unquote(name): + if len(name) >= 3 and name[0] == '"' and name[-1] == '"': + name = name[1:-1] + return name + def read(self, file) -> None: in_header = True driver = None @@ -35,9 +41,9 @@ class FirmwareWhence(list): if in_header: in_header = False else: - # Finish old section - if driver: - self.append(FirmwareSection(driver, files, licence)) + # Finish old group + if driver and files: + self.append(FirmwareGroup(driver, files, licence)) driver = None files = {} licence = None @@ -61,21 +67,27 @@ class FirmwareWhence(list): continue match = re.match( - r'(Driver|File|Info|Licen[cs]e|Source|Version' + r'(Driver|(?:Raw)?File|Info|Licen[cs]e|Source|Version' r'|Original licen[cs]e info(?:rmation)?):\s*(.*)\n', line) if match: + # If we've seen a license for the previous group, + # start a new group + if licence: + self.append(FirmwareGroup(driver, files, licence)) + files = {} + licence = None keyword, value = match.group(1, 2) if keyword == 'Driver': driver = value.split(' ')[0].lower() - elif keyword == 'File': - match = re.match(r'(\S+)(?:\s+--\s+(.*))?', value) - binary.append(match.group(1)) + elif keyword in ['File', 'RawFile']: + match = re.match(r'("[^"\n]+"|\S+)(?:\s+--\s+(.*))?', value) + binary.append(self._unquote(match.group(1))) desc = match.group(2) elif keyword in ['Info', 'Version']: version = value elif keyword == 'Source': - source.append(value) + source.append(self._unquote(value)) else: licence = value elif licence is not None: @@ -83,8 +95,8 @@ class FirmwareWhence(list): + re.sub(r'^(?:[/ ]\*| \*/)?\s*(.*?)\s*$', r'\1', line)) - # Finish last section if non-empty + # Finish last group if non-empty for b in binary: files[b] = FirmwareFile(b, desc, source, version) if driver: - self.append(FirmwareSection(driver, files, licence)) + self.append(FirmwareGroup(driver, files, licence)) diff --git a/debian/lib/python/debian_linux/gencontrol.py b/debian/lib/python/debian_linux/gencontrol.py index 49f52e878b..c3305112c0 100644 --- a/debian/lib/python/debian_linux/gencontrol.py +++ b/debian/lib/python/debian_linux/gencontrol.py @@ -4,7 +4,6 @@ import contextlib import itertools import pathlib import re -from collections import OrderedDict from collections.abc import ( Generator, ) @@ -21,23 +20,12 @@ from .config_v2 import ( ConfigMergedFeatureset, ConfigMergedFlavour, ) +from .dataclasses_deb822 import write_deb822 from .debian import Changelog, PackageArchitecture, \ - Version, _ControlFileDict + Version, SourcePackage, BinaryPackage from .utils import Templates -class PackagesList(OrderedDict): - def append(self, package) -> None: - self[package['Package']] = package - - def extend(self, packages) -> None: - for package in packages: - self[package['Package']] = package - - def setdefault(self, package) -> Any: - return super().setdefault(package['Package'], package) - - class Makefile: rules: dict[str, MakefileRule] @@ -149,23 +137,35 @@ class MakeFlags(dict): class PackagesBundle: + class BinaryPackages(dict[str, BinaryPackage]): + def add(self, package: BinaryPackage) -> BinaryPackage: + return super().setdefault(package.name, package) + name: str | None templates: Templates base: pathlib.Path makefile: Makefile - packages: PackagesList + source: SourcePackage + packages: BinaryPackages def __init__( self, name: str | None, + source_template: str, + replace: dict[str, str], templates: Templates, base: pathlib.Path = pathlib.Path('debian'), + override_name: str | None = None, ) -> None: self.name = name self.templates = templates self.base = base self.makefile = Makefile() - self.packages = PackagesList() + self.source = list(self.templates.get_source_control(source_template, replace))[0] + self.packages = self.BinaryPackages() + + if not self.source.name: + self.source.name = override_name def add( self, @@ -179,14 +179,14 @@ class PackagesBundle: ) -> list[Any]: ret = [] for raw_package in self.templates.get_control(f'{pkgid}.control', replace): - package = self.packages.setdefault(raw_package) - package_name = package['Package'] + package = self.packages.add(raw_package) + package_name = package.name ret.append(package) - package.meta.setdefault('rules-ruleids', {})[ruleid] = makeflags + package.meta_rules_ruleids[ruleid] = makeflags if arch: - package.meta.setdefault('architectures', PackageArchitecture()).add(arch) - package.meta['rules-check-packages'] = check_packages + package.meta_architectures.add(arch) + package.meta_rules_check_packages = check_packages for name in ( 'NEWS', @@ -211,7 +211,7 @@ class PackagesBundle: def add_packages( self, - packages: Iterable[_ControlFileDict], + packages: Iterable[BinaryPackage], ruleid: Iterable[str], makeflags: MakeFlags, *, @@ -219,11 +219,11 @@ class PackagesBundle: check_packages: bool = True, ) -> None: for package in packages: - package = self.packages.setdefault(package) - package.meta.setdefault('rules-ruleids', {})[ruleid] = makeflags + package = self.packages.add(package) + package.meta_rules_ruleids[ruleid] = makeflags if arch: - package.meta.setdefault('architectures', PackageArchitecture()).add(arch) - package.meta['rules-check-packages'] = check_packages + package.meta_architectures.add(arch) + package.meta_rules_check_packages = check_packages def path(self, name) -> pathlib.Path: if self.name: @@ -262,20 +262,19 @@ class PackagesBundle: targets: dict[frozenset[str], dict] = {} for package_name, package in self.packages.items(): - target_name = package.meta.get('rules-target') - ruleids = package.meta.get('rules-ruleids') - makeflags = MakeFlags({ - # Requires Python 3.9+ - k.removeprefix('rules-makeflags-').upper(): v - for (k, v) in package.meta.items() if k.startswith('rules-makeflags-') - }) + if not isinstance(package, BinaryPackage): + continue + + target_name = package.meta_rules_target + ruleids = package.meta_rules_ruleids + makeflags = MakeFlags(package.meta_rules_makeflags) if ruleids: - arches = package.meta.get('architectures') + arches = package.meta_architectures if arches: - package['Architecture'] = arches + package.architecture = arches else: - arches = package.get('Architecture') + arches = package.architecture if target_name: for ruleid, makeflags_package in ruleids.items(): @@ -291,7 +290,7 @@ class PackagesBundle: }, ) - if package.meta['rules-check-packages']: + if package.meta_rules_check_packages: target.setdefault('packages', set()).add(package_name) else: target.setdefault('packages_extra', set()).add(package_name) @@ -331,46 +330,40 @@ class PackagesBundle: def merge_build_depends(self) -> None: # Merge Build-Depends pseudo-fields from binary packages into the # source package - source = self.packages["source"] arch_all = PackageArchitecture("all") for name, package in self.packages.items(): - if name == "source": - continue - dep = package.get("Build-Depends") + dep = package.build_depends if not dep: continue - del package["Build-Depends"] - if package["Architecture"] == arch_all: - dep_type = "Build-Depends-Indep" + if package.architecture == arch_all: + build_dep = self.source.build_depends_indep else: - dep_type = "Build-Depends-Arch" + build_dep = self.source.build_depends_arch for group in dep: for item in group: - if package["Architecture"] != arch_all and not item.arches: - item.arches = package["Architecture"] - if package.get("Build-Profiles") and not item.restrictions: - item.restrictions = package["Build-Profiles"] - source.setdefault(dep_type).merge(group) + if package.architecture != arch_all and not item.arches: + item.arches = package.architecture + if package.build_profiles and not item.restrictions: + item.restrictions = package.build_profiles + build_dep.merge(group) def write(self) -> None: self.write_control() self.write_makefile() def write_control(self) -> None: + p = [self.source] + sorted( + self.packages.values(), + # Sort deb before udeb and then according to name + key=lambda i: (i.package_type or '', i.name), + ) with self.open('control') as f: - self.write_rfc822(f, self.packages.values()) + write_deb822(p, f) def write_makefile(self) -> None: with self.open('rules.gen') as f: self.makefile.write(f) - def write_rfc822(self, f: IO, entries: Iterable) -> None: - for entry in entries: - for key, value in entry.items(): - if value: - f.write(u"%s: %s\n" % (key, value)) - f.write('\n') - class Gencontrol(object): config: ConfigMerged @@ -381,7 +374,7 @@ class Gencontrol(object): self.config, self.templates = config, templates self.changelog = Changelog(version=version) self.vars = {} - self.bundles = {'': PackagesBundle(None, templates)} + self.bundles = {} @property def bundle(self) -> PackagesBundle: @@ -395,10 +388,10 @@ class Gencontrol(object): self.write() def do_source(self) -> None: - source = self.templates.get_source_control("source.control", self.vars)[0] - if not source.get('Source'): - source['Source'] = self.changelog[0].source - self.bundle.packages['source'] = source + self.bundles[''] = PackagesBundle( + None, 'source.control', self.vars, self.templates, + override_name=self.changelog[0].source, + ) def do_main(self) -> None: vars = self.vars.copy() @@ -645,10 +638,7 @@ class Gencontrol(object): ) -> None: pass - def substitute(self, s: str | list | tuple, vars) -> str | list: - if isinstance(s, (list, tuple)): - return [self.substitute(i, vars) for i in s] - + def substitute(self, s: str, vars) -> str: def subst(match) -> str: return vars[match.group(1)] diff --git a/debian/lib/python/debian_linux/test_debian.py b/debian/lib/python/debian_linux/test_debian.py index 06133dc46e..73a18dfa89 100644 --- a/debian/lib/python/debian_linux/test_debian.py +++ b/debian/lib/python/debian_linux/test_debian.py @@ -237,7 +237,7 @@ class TestPackageDescription: def test_str(self) -> None: a = PackageDescription('Short\nLong1\n.\nLong2') - assert str(a) == 'Short\n Long1\n .\n Long2' + assert str(a) == 'Short\nLong1\n.\nLong2' class TestPackageRelationEntry: diff --git a/debian/lib/python/debian_linux/utils.py b/debian/lib/python/debian_linux/utils.py index 0c6569b5a0..661a730476 100644 --- a/debian/lib/python/debian_linux/utils.py +++ b/debian/lib/python/debian_linux/utils.py @@ -6,6 +6,7 @@ import typing import jinja2 +from .dataclasses_deb822 import read_deb822 from .debian import SourcePackage, BinaryPackage, TestsControl @@ -60,14 +61,20 @@ class Templates(object): return value[0] - def get_control(self, key: str, context: dict[str, str] = {}) -> BinaryPackage: - return BinaryPackage.read_rfc822(io.StringIO(self.get(key, context))) + def get_control( + self, key: str, context: dict[str, str] = {}, + ) -> typing.Iterable[BinaryPackage]: + return read_deb822(BinaryPackage, io.StringIO(self.get(key, context))) - def get_source_control(self, key: str, context: dict[str, str] = {}) -> SourcePackage: - return SourcePackage.read_rfc822(io.StringIO(self.get(key, context))) + def get_source_control( + self, key: str, context: dict[str, str] = {}, + ) -> typing.Iterable[SourcePackage]: + return read_deb822(SourcePackage, io.StringIO(self.get(key, context))) - def get_tests_control(self, key: str, context: dict[str, str] = {}) -> TestsControl: - return TestsControl.read_rfc822(io.StringIO(self.get(key, context))) + def get_tests_control( + self, key: str, context: dict[str, str] = {}, + ) -> typing.Iterable[TestsControl]: + return read_deb822(TestsControl, io.StringIO(self.get(key, context))) class TextWrapper(textwrap.TextWrapper): |