summaryrefslogtreecommitdiffstats
path: root/debian/lib
diff options
context:
space:
mode:
Diffstat (limited to 'debian/lib')
-rw-r--r--debian/lib/python/debian_linux/config_v2.py16
-rw-r--r--debian/lib/python/debian_linux/dataclasses_deb822.py234
-rw-r--r--debian/lib/python/debian_linux/debian.py366
-rw-r--r--debian/lib/python/debian_linux/firmware.py34
-rw-r--r--debian/lib/python/debian_linux/gencontrol.py124
-rw-r--r--debian/lib/python/debian_linux/test_debian.py2
-rw-r--r--debian/lib/python/debian_linux/utils.py19
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 1c32bc8864..744c3276c2 100644
--- a/debian/lib/python/debian_linux/config_v2.py
+++ b/debian/lib/python/debian_linux/config_v2.py
@@ -304,6 +304,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 78c5cdec7b..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):