#!/usr/bin/python3

import sys
import json
import locale
import os
import os.path
import pathlib
import subprocess
import re
import tempfile
from typing import Any

from debian_linux import config
from debian_linux.debian import \
    PackageRelationEntry, PackageRelationGroup, \
    VersionLinux, BinaryPackage, TestsControl
from debian_linux.gencontrol import Gencontrol as Base, PackagesBundle, \
    iter_featuresets, iter_flavours
from debian_linux.utils import Templates

locale.setlocale(locale.LC_CTYPE, "C.UTF-8")


class Gencontrol(Base):
    disable_installer: bool
    disable_signed: bool

    tests_control_headers: TestsControl | None

    config_schema = {
        'build': {
            'signed-code': config.SchemaItemBoolean(),
            'vdso': config.SchemaItemBoolean(),
        },
        'description': {
            'parts': config.SchemaItemList(),
        },
        'image': {
            'configs': config.SchemaItemList(),
            'check-size': config.SchemaItemInteger(),
            'check-size-with-dtb': config.SchemaItemBoolean(),
            'check-uncompressed-size': config.SchemaItemInteger(),
            'depends': config.SchemaItemList(','),
            'provides': config.SchemaItemList(','),
            'suggests': config.SchemaItemList(','),
            'recommends': config.SchemaItemList(','),
            'conflicts': config.SchemaItemList(','),
            'breaks': config.SchemaItemList(','),
        },
        'packages': {
            'docs': config.SchemaItemBoolean(),
            'installer': config.SchemaItemBoolean(),
            'libc-dev': config.SchemaItemBoolean(),
            'meta': config.SchemaItemBoolean(),
            'tools-unversioned': config.SchemaItemBoolean(),
            'tools-versioned': config.SchemaItemBoolean(),
            'source': config.SchemaItemBoolean(),
        }
    }

    env_flags = [
        ('DEBIAN_KERNEL_DISABLE_INSTALLER', 'disable_installer', 'installer modules'),
        ('DEBIAN_KERNEL_DISABLE_SIGNED', 'disable_signed', 'signed code'),
    ]

    def __init__(self, config_dirs=["debian/config", "debian/config.local"],
                 template_dirs=["debian/templates"]) -> None:
        super(Gencontrol, self).__init__(
            config.ConfigCoreHierarchy(self.config_schema, config_dirs),
            Templates(template_dirs),
            VersionLinux)
        self.process_changelog()
        self.config_dirs = config_dirs

        for env, attr, desc in self.env_flags:
            setattr(self, attr, False)
            if os.getenv(env):
                if self.changelog[0].distribution == 'UNRELEASED':
                    import warnings
                    warnings.warn(f'Disable {desc} on request ({env} set)')
                    setattr(self, attr, True)
                else:
                    raise RuntimeError(
                        f'Unable to disable {desc} in release build ({env} set)')

    def _setup_makeflags(self, names, makeflags, data) -> None:
        for src, dst, optional in names:
            if src in data or not optional:
                makeflags[dst] = data[src]

    def do_main_setup(self, vars, makeflags) -> None:
        super(Gencontrol, self).do_main_setup(vars, makeflags)
        makeflags.update({
            'VERSION': self.version.linux_version,
            'UPSTREAMVERSION': self.version.linux_upstream,
            'ABINAME': self.abiname,
            'SOURCEVERSION': self.version.complete,
        })
        makeflags['SOURCE_BASENAME'] = vars['source_basename']
        makeflags['SOURCE_SUFFIX'] = vars['source_suffix']

        # Prepare to generate debian/tests/control
        self.tests_control = self.templates.get_tests_control('main.tests-control', vars)
        self.tests_control_image = None
        self.tests_control_headers = None

    def do_main_makefile(self, makeflags) -> None:
        for featureset in iter_featuresets(self.config):
            makeflags_featureset = makeflags.copy()
            makeflags_featureset['FEATURESET'] = featureset

            self.bundle.makefile.add_rules(f'source_{featureset}',
                                           'source', makeflags_featureset)
            self.bundle.makefile.add_deps('source', [f'source_{featureset}'])

        makeflags = makeflags.copy()
        makeflags['ALL_FEATURESETS'] = ' '.join(iter_featuresets(self.config))
        super().do_main_makefile(makeflags)

    def do_main_packages(self, vars, makeflags) -> None:
        self.bundle.add('main', (), makeflags, vars)

        # Only build the metapackages if their names won't exactly match
        # the packages they depend on
        do_meta = self.config.merge('packages').get('meta', True) \
            and vars['source_suffix'] != '-' + vars['version']

        if self.config.merge('packages').get('docs', True):
            self.bundle.add('docs', (), makeflags, vars)
            if do_meta:
                self.bundle.add('docs.meta', (), makeflags, vars)
        if self.config.merge('packages').get('source', True):
            self.bundle.add('sourcebin', (), makeflags, vars)
            if do_meta:
                self.bundle.add('sourcebin.meta', (), makeflags, vars)

        if self.config.merge('packages').get('libc-dev', True):
            libcdev_kernelarches = set()
            libcdev_multiarches = set()
            for arch in iter(self.config['base', ]['arches']):
                libcdev_kernelarch = self.config['base', arch]['kernel-arch']
                libcdev_multiarch = subprocess.check_output(
                    ['dpkg-architecture', '-f', '-a', arch,
                     '-q', 'DEB_HOST_MULTIARCH'],
                    stderr=subprocess.DEVNULL,
                    encoding='utf-8').strip()
                libcdev_kernelarches.add(libcdev_kernelarch)
                libcdev_multiarches.add(f'{libcdev_multiarch}:{libcdev_kernelarch}')

            libcdev_makeflags = makeflags.copy()
            libcdev_makeflags['ALL_LIBCDEV_KERNELARCHES'] = ' '.join(sorted(libcdev_kernelarches))
            libcdev_makeflags['ALL_LIBCDEV_MULTIARCHES'] = ' '.join(sorted(libcdev_multiarches))

            self.bundle.add('libc-dev', (), libcdev_makeflags, vars)

    def do_indep_featureset_setup(self, vars, makeflags, featureset) -> None:
        makeflags['LOCALVERSION'] = vars['localversion']
        kernel_arches = set()
        for arch in iter(self.config['base', ]['arches']):
            if self.config.get_merge('base', arch, featureset, None,
                                     'flavours'):
                kernel_arches.add(self.config['base', arch]['kernel-arch'])
        makeflags['ALL_KERNEL_ARCHES'] = ' '.join(sorted(list(kernel_arches)))

        vars['featureset_desc'] = ''
        if featureset != 'none':
            desc = self.config[('description', None, featureset)]
            desc_parts = desc['parts']
            vars['featureset_desc'] = (' with the %s featureset' %
                                       desc['part-short-%s' % desc_parts[0]])

    def do_indep_featureset_packages(self, featureset, vars, makeflags) -> None:
        self.bundle.add('headers.featureset', (featureset, ), makeflags, vars)

    arch_makeflags = (
        ('kernel-arch', 'KERNEL_ARCH', False),
    )

    def do_arch_setup(self, vars, makeflags, arch) -> None:
        config_base = self.config.merge('base', arch)

        self._setup_makeflags(self.arch_makeflags, makeflags, config_base)

        try:
            gnu_type = subprocess.check_output(
                ['dpkg-architecture', '-f', '-a', arch,
                 '-q', 'DEB_HOST_GNU_TYPE'],
                stderr=subprocess.DEVNULL,
                encoding='utf-8')
        except subprocess.CalledProcessError:
            # This sometimes happens for the newest ports :-/
            print('W: Unable to get GNU type for %s' % arch, file=sys.stderr)
        else:
            vars['gnu-type-package'] = gnu_type.strip().replace('_', '-')

    def do_arch_packages(self, arch, vars, makeflags) -> None:
        if not self.disable_signed:
            build_signed = self.config.merge('build', arch) \
                                      .get('signed-code', False)
        else:
            build_signed = False

        if build_signed:
            # Make sure variables remain
            vars['signedtemplate_binaryversion'] = '@signedtemplate_binaryversion@'
            vars['signedtemplate_sourceversion'] = '@signedtemplate_sourceversion@'

            self.bundle.add('signed-template', (arch,), makeflags, vars, arch=arch)

            bundle_signed = self.bundles[f'signed-{arch}'] = \
                PackagesBundle(f'signed-{arch}', self.templates)
            bundle_signed.packages['source'] = \
                self.templates.get_source_control('signed.source.control', vars)[0]

            with bundle_signed.open('source/lintian-overrides', 'w') as f:
                f.write(self.substitute(
                    self.templates.get('signed.source.lintian-overrides'), vars))

            with bundle_signed.open('changelog.head', 'w') as f:
                dist = self.changelog[0].distribution
                urgency = self.changelog[0].urgency
                f.write(f'''\
linux-signed-{vars['arch']} (@signedtemplate_sourceversion@) {dist}; urgency={urgency}

  * Sign kernel from {self.changelog[0].source} @signedtemplate_binaryversion@
''')

        if self.config['base', arch].get('featuresets') and \
           self.config.merge('packages').get('source', True):
            self.bundle.add('config', (arch, ), makeflags, vars)

        if self.config.merge('packages').get('tools-unversioned', True):
            self.bundle.add('tools-unversioned', (arch, ), makeflags, vars)

        if self.config.merge('packages').get('tools-versioned', True):
            self.bundle.add('tools-versioned', (arch, ), makeflags, vars)

    def do_featureset_setup(self, vars, makeflags, arch, featureset) -> None:
        vars['localversion_headers'] = vars['localversion']
        makeflags['LOCALVERSION_HEADERS'] = vars['localversion_headers']

        self.default_flavour = self.config.merge('base', arch, featureset) \
                                          .get('default-flavour')
        if self.default_flavour is not None:
            if featureset != 'none':
                raise RuntimeError("default-flavour set for %s %s,"
                                   " but must only be set for featureset none"
                                   % (arch, featureset))
            if self.default_flavour \
               not in iter_flavours(self.config, arch, featureset):
                raise RuntimeError("default-flavour %s for %s %s does not exist"
                                   % (self.default_flavour, arch, featureset))

        self.quick_flavour = self.config.merge('base', arch, featureset) \
                                        .get('quick-flavour')

    flavour_makeflags_base = (
        ('compiler', 'COMPILER', False),
        ('compiler-filename', 'COMPILER', True),
        ('kernel-arch', 'KERNEL_ARCH', False),
        ('cflags', 'KCFLAGS', True),
        ('kernel-deb-arch', 'KERNEL_DEB_ARCH', True),
        ('kernel-gnu-type', 'KERNEL_GNU_TYPE', True),
        ('compat-deb-arch', 'COMPAT_DEB_ARCH', True),
        ('compat-gnu-type', 'COMPAT_GNU_TYPE', True),
    )

    flavour_makeflags_build = (
        ('image-file', 'IMAGE_FILE', True),
    )

    flavour_makeflags_image = (
        ('install-stem', 'IMAGE_INSTALL_STEM', True),
    )

    flavour_makeflags_other = (
        ('localversion', 'LOCALVERSION', False),
        ('localversion-image', 'LOCALVERSION_IMAGE', True),
    )

    def do_flavour_setup(self, vars, makeflags, arch, featureset, flavour) -> None:
        config_base = self.config.merge('base', arch, featureset, flavour)
        config_build = self.config.merge('build', arch, featureset, flavour)
        config_description = self.config.merge('description', arch, featureset,
                                               flavour)
        config_image = self.config.merge('image', arch, featureset, flavour)

        vars['flavour'] = vars['localversion'][1:]
        vars['class'] = config_description['hardware']
        vars['longclass'] = (config_description.get('hardware-long')
                             or vars['class'])

        vars['localversion-image'] = vars['localversion']
        override_localversion = config_image.get('override-localversion', None)
        if override_localversion is not None:
            vars['localversion-image'] = (vars['localversion_headers'] + '-'
                                          + override_localversion)
        vars['image-stem'] = config_image.get('install-stem')

        self._setup_makeflags(self.flavour_makeflags_base, makeflags,
                              config_base)
        self._setup_makeflags(self.flavour_makeflags_build, makeflags,
                              config_build)
        self._setup_makeflags(self.flavour_makeflags_image, makeflags,
                              config_image)
        self._setup_makeflags(self.flavour_makeflags_other, makeflags, vars)

    def do_flavour_packages(self, arch, featureset,
                            flavour, vars, makeflags) -> None:
        ruleid = (arch, featureset, flavour)

        packages_headers = (
            self.bundle.add('headers', ruleid, makeflags, vars, arch=arch)
        )
        assert len(packages_headers) == 1

        do_meta = self.config.merge('packages').get('meta', True)
        config_entry_base = self.config.merge('base', arch, featureset,
                                              flavour)
        config_entry_build = self.config.merge('build', arch, featureset,
                                               flavour)
        config_entry_description = self.config.merge('description', arch,
                                                     featureset, flavour)
        config_entry_packages = self.config.merge('packages', arch, featureset,
                                                  flavour)

        def config_entry_image(key, *args, **kwargs) -> Any:
            return self.config.get_merge(
                'image', arch, featureset, flavour, key, *args, **kwargs)

        compiler = config_entry_base.get('compiler', 'gcc')

        relation_compiler = PackageRelationEntry(compiler)

        relation_compiler_header = PackageRelationGroup([relation_compiler])

        # Generate compiler build-depends for native:
        # gcc-13 [arm64] <!cross !pkg.linux.nokernel>
        self.bundle.packages['source']['Build-Depends-Arch'].merge([
            PackageRelationEntry(
                relation_compiler,
                arches={arch},
                restrictions='<!cross !pkg.linux.nokernel>',
            )
        ])

        # Generate compiler build-depends for cross:
        # gcc-13-aarch64-linux-gnu [arm64] <cross !pkg.linux.nokernel>
        self.bundle.packages['source']['Build-Depends-Arch'].merge([
            PackageRelationEntry(
                relation_compiler,
                name=f'{relation_compiler.name}-{vars["gnu-type-package"]}',
                arches={arch},
                restrictions='<cross !pkg.linux.nokernel>',
            )
        ])

        # Generate compiler build-depends for kernel:
        # gcc-13-hppa64-linux-gnu [hppa] <!pkg.linux.nokernel>
        if gnutype := config_entry_base.get('kernel-gnu-type'):
            self.bundle.packages['source']['Build-Depends-Arch'].merge([
                PackageRelationEntry(
                    relation_compiler,
                    name=f'{relation_compiler.name}-{gnutype}',
                    arches={arch},
                    restrictions='<!pkg.linux.nokernel>',
                )
            ])

        # Generate compiler build-depends for compat:
        # gcc-arm-linux-gnueabihf [arm64] <!pkg.linux.nokernel>
        # XXX: Linux uses various definitions for this, all ending with "gcc", not $CC
        if gnutype := config_entry_base.get('compat-gnu-type'):
            self.bundle.packages['source']['Build-Depends-Arch'].merge([
                PackageRelationEntry(
                    f'gcc-{gnutype}',
                    arches={arch},
                    restrictions='<!pkg.linux.nokernel>',
                )
            ])

        packages_own = []

        if not self.disable_signed:
            build_signed = config_entry_build.get('signed-code')
        else:
            build_signed = False

        if build_signed:
            bundle_signed = self.bundles[f'signed-{arch}']
        else:
            bundle_signed = self.bundle

        vars.setdefault('desc', None)

        packages_image = []

        if build_signed:
            packages_image.extend(
                bundle_signed.add('signed.image', ruleid, makeflags, vars, arch=arch))
            packages_image.extend(
                self.bundle.add('image-unsigned', ruleid, makeflags, vars, arch=arch))

        else:
            packages_image.extend(bundle_signed.add('image', ruleid, makeflags, vars, arch=arch))

        for field in ('Depends', 'Provides', 'Suggests', 'Recommends',
                      'Conflicts', 'Breaks'):
            for i in config_entry_image(field.lower(), ()):
                for package_image in packages_image:
                    package_image.setdefault(field).merge(
                        PackageRelationGroup(i, arches={arch})
                    )

        for field in ('Depends', 'Suggests', 'Recommends'):
            for i in config_entry_image(field.lower(), ()):
                group = PackageRelationGroup(i, arches={arch})
                for entry in group:
                    if entry.operator is not None:
                        entry.operator = -entry.operator
                        for package_image in packages_image:
                            package_image.setdefault('Breaks').append(PackageRelationGroup([entry]))

        desc_parts = self.config.get_merge('description', arch, featureset,
                                           flavour, 'parts')
        if desc_parts:
            # XXX: Workaround, we need to support multiple entries of the same
            # name
            parts = list(set(desc_parts))
            parts.sort()
            for package_image in packages_image:
                desc = package_image['Description']
                for part in parts:
                    desc.append(config_entry_description['part-long-' + part])
                    desc.append_short(config_entry_description
                                      .get('part-short-' + part, ''))

        packages_headers[0]['Depends'].merge(relation_compiler_header)
        packages_own.extend(packages_image)
        packages_own.extend(packages_headers)

        # The image meta-packages will depend on signed linux-image
        # packages where applicable, so should be built from the
        # signed source packages The header meta-packages will also be
        # built along with the signed packages, to create a dependency
        # relationship that ensures src:linux and src:linux-signed-*
        # transition to testing together.
        if do_meta:
            packages_meta = (
                bundle_signed.add('image.meta', ruleid, makeflags, vars, arch=arch)
            )
            assert len(packages_meta) == 1
            packages_meta += (
                bundle_signed.add(build_signed and 'signed.headers.meta' or 'headers.meta',
                                  ruleid, makeflags, vars, arch=arch)
            )
            assert len(packages_meta) == 2

            if flavour == self.default_flavour \
               and not self.vars['source_suffix']:
                packages_meta[0].setdefault('Provides') \
                                .append('linux-image-generic')
                packages_meta[1].setdefault('Provides') \
                                .append('linux-headers-generic')

            packages_own.extend(packages_meta)

        if config_entry_build.get('vdso', False):
            makeflags['VDSO'] = True

        packages_own.extend(
            self.bundle.add('image-dbg', ruleid, makeflags, vars, arch=arch)
        )
        if do_meta:
            packages_own.extend(
                self.bundle.add('image-dbg.meta', ruleid, makeflags, vars, arch=arch)
            )

        # In a quick build, only build the quick flavour (if any).
        if flavour != self.quick_flavour:
            for package in packages_own:
                package['Build-Profiles'][0].neg.add('pkg.linux.quick')

        tests_control = self.templates.get_tests_control('image.tests-control', vars)[0]
        tests_control['Depends'].merge(
            PackageRelationGroup(package_image['Package'],
                                 arches={arch}))
        if self.tests_control_image:
            for i in tests_control['Depends']:
                self.tests_control_image['Depends'].merge(i)
        else:
            self.tests_control_image = tests_control
            self.tests_control.append(tests_control)

        if flavour == (self.quick_flavour or self.default_flavour):
            if not self.tests_control_headers:
                self.tests_control_headers = \
                        self.templates.get_tests_control('headers.tests-control', vars)[0]
                self.tests_control.append(self.tests_control_headers)
            assert self.tests_control_headers is not None
            self.tests_control_headers['Architecture'].add(arch)
            self.tests_control_headers['Depends'].merge(
                PackageRelationGroup(packages_headers[0]['Package'],
                                     arches={arch}))

        def get_config(*entry_name) -> Any:
            entry_real = ('image',) + entry_name
            entry = self.config.get(entry_real, None)
            if entry is None:
                return None
            return entry.get('configs', None)

        def check_config_default(fail, f) -> list[str]:
            for d in self.config_dirs[::-1]:
                f1 = d + '/' + f
                if os.path.exists(f1):
                    return [f1]
            if fail:
                raise RuntimeError("%s unavailable" % f)
            return []

        def check_config_files(files) -> list[str]:
            ret = []
            for f in files:
                for d in self.config_dirs[::-1]:
                    f1 = d + '/' + f
                    if os.path.exists(f1):
                        ret.append(f1)
                        break
                else:
                    raise RuntimeError("%s unavailable" % f)
            return ret

        def check_config(default, fail, *entry_name) -> list[str]:
            configs = get_config(*entry_name)
            if configs is None:
                return check_config_default(fail, default)
            return check_config_files(configs)

        kconfig = check_config('config', True)
        # XXX: We have no way to override kernelarch-X configs
        kconfig.extend(check_config_default(False,
                       "kernelarch-%s/config" % config_entry_base['kernel-arch']))
        kconfig.extend(check_config("%s/config" % arch, True, arch))
        kconfig.extend(check_config("%s/config.%s" % (arch, flavour), False,
                                    arch, None, flavour))
        kconfig.extend(check_config("featureset-%s/config" % featureset, False,
                                    None, featureset))
        kconfig.extend(check_config("%s/%s/config" % (arch, featureset), False,
                                    arch, featureset))
        kconfig.extend(check_config("%s/%s/config.%s" %
                                    (arch, featureset, flavour), False,
                                    arch, featureset, flavour))
        makeflags['KCONFIG'] = ' '.join(kconfig)
        makeflags['KCONFIG_OPTIONS'] = ''
        # Add "salt" to fix #872263
        makeflags['KCONFIG_OPTIONS'] += \
            ' -o "BUILD_SALT=\\"%(abiname)s%(localversion)s\\""' % vars

        merged_config = ('debian/build/config.%s_%s_%s' %
                         (arch, featureset, flavour))
        self.bundle.makefile.add_cmds(merged_config,
                                      ["$(MAKE) -f debian/rules.real %s %s" %
                                       (merged_config, makeflags)])

        if not self.disable_installer and config_entry_packages.get('installer'):
            with tempfile.TemporaryDirectory(prefix='linux-gencontrol') as config_dir:
                base_path = pathlib.Path('debian/installer').absolute()
                config_path = pathlib.Path(config_dir)
                (config_path / 'modules').symlink_to(base_path / 'modules')
                (config_path / 'package-list').symlink_to(base_path / 'package-list')

                with (config_path / 'kernel-versions').open('w') as versions:
                    versions.write(f'{arch} - {vars["flavour"]} - - -\n')

                # Add udebs using kernel-wedge
                kw_env = os.environ.copy()
                kw_env['KW_DEFCONFIG_DIR'] = config_dir
                kw_env['KW_CONFIG_DIR'] = config_dir
                kw_proc = subprocess.Popen(
                    ['kernel-wedge', 'gen-control', vars['abiname']],
                    stdout=subprocess.PIPE,
                    text=True,
                    env=kw_env)
                udeb_packages_base = BinaryPackage.read_rfc822(kw_proc.stdout)
                kw_proc.wait()
                if kw_proc.returncode != 0:
                    raise RuntimeError('kernel-wedge exited with code %d' %
                                       kw_proc.returncode)

            udeb_packages = []
            for package_base in udeb_packages_base:
                package = package_base.copy()
                # kernel-wedge currently chokes on Build-Profiles so add it now
                package['Build-Profiles'] = (
                    '<!noudeb !pkg.linux.nokernel !pkg.linux.quick>')
                package.meta['rules-target'] = 'installer'
                udeb_packages.append(package)

            makeflags_local = makeflags.copy()
            makeflags_local['IMAGE_PACKAGE_NAME'] = udeb_packages[0]['Package']

            bundle_signed.add_packages(
                udeb_packages,
                (arch, featureset, flavour),
                makeflags_local, arch=arch,
            )

            if build_signed:
                udeb_packages = []
                # XXX This is a hack to exclude the udebs from
                # the package list while still being able to
                # convince debhelper and kernel-wedge to go
                # part way to building them.
                for package_base in udeb_packages_base:
                    package = package_base.copy()
                    # kernel-wedge currently chokes on Build-Profiles so add it now
                    package['Build-Profiles'] = (
                        '<pkg.linux.udeb-unsigned-test-build !noudeb'
                        ' !pkg.linux.nokernel !pkg.linux.quick>')
                    package.meta['rules-target'] = 'installer-test'
                    udeb_packages.append(package)

                self.bundle.add_packages(
                    udeb_packages,
                    (arch, featureset, flavour),
                    makeflags, arch=arch, check_packages=False,
                )

    def process_changelog(self) -> None:
        version = self.version = self.changelog[0].version

        if self.changelog[0].distribution == 'UNRELEASED':
            self.abiname = f'{version.linux_upstream}+unreleased'
        elif self.changelog[0].distribution == 'experimental':
            self.abiname = f'{version.linux_upstream}'
        elif version.linux_revision_backports:
            self.abiname = f'{version.linux_upstream_full}+bpo'
        else:
            self.abiname = f'{version.linux_upstream_full}+rpt'

        self.vars = {
            'upstreamversion': self.version.linux_upstream,
            'version': self.version.linux_version,
            'version_complete': self.version.complete,
            'source_basename': re.sub(r'-[\d.]+$', '',
                                      self.changelog[0].source),
            'source_upstream': self.version.upstream,
            'source_package': self.changelog[0].source,
            'abiname': self.abiname,
        }
        self.vars['source_suffix'] = \
            self.changelog[0].source[len(self.vars['source_basename']):]
        self.config['version', ] = {'source': self.version.complete,
                                    'upstream': self.version.linux_upstream,
                                    'abiname_base': self.abiname,
                                    'abiname': self.abiname}

        distribution = self.changelog[0].distribution
        if distribution in ('unstable', ):
            if version.linux_revision_experimental or \
               version.linux_revision_backports or \
               version.linux_revision_other:
                raise RuntimeError("Can't upload to %s with a version of %s" %
                                   (distribution, version))
        if distribution in ('experimental', ):
            if not version.linux_revision_experimental:
                raise RuntimeError("Can't upload to %s with a version of %s" %
                                   (distribution, version))
        if distribution.endswith('-security') or distribution.endswith('-lts'):
            if version.linux_revision_backports or \
               version.linux_revision_other:
                raise RuntimeError("Can't upload to %s with a version of %s" %
                                   (distribution, version))
        if distribution.endswith('-backports'):
            if not version.linux_revision_backports:
                raise RuntimeError("Can't upload to %s with a version of %s" %
                                   (distribution, version))

    def write(self) -> None:
        self.write_config()
        super().write()
        self.write_tests_control()
        self.write_signed()

    def write_config(self) -> None:
        f = open("debian/config.defines.dump", 'wb')
        self.config.dump(f)
        f.close()

    def write_signed(self) -> None:
        for bundle in self.bundles.values():
            pkg_sign_entries = {}

            for p in bundle.packages.values():
                if pkg_sign_pkg := p.meta.get('sign-package'):
                    pkg_sign_entries[pkg_sign_pkg] = {
                        'trusted_certs': [],
                        'files': [
                            {
                                'sig_type': e.split(':', 1)[-1],
                                'file': e.split(':', 1)[0],
                            }
                            for e in p.meta['sign-files'].split()
                        ],
                    }

            if pkg_sign_entries:
                with bundle.path('files.json').open('w') as f:
                    json.dump({'packages': pkg_sign_entries}, f, indent=2)

    def write_tests_control(self) -> None:
        self.bundle.write_rfc822(open("debian/tests/control", 'w'),
                                 self.tests_control)


if __name__ == '__main__':
    Gencontrol()()
