#!/usr/bin/python3 from __future__ import annotations import dataclasses import json import locale import os import os.path import pathlib import subprocess import re import tempfile from typing import cast from debian_linux.config_v2 import ( Config, ConfigMerged, ConfigMergedDebianarch, ConfigMergedFeatureset, ConfigMergedFlavour, ) from debian_linux.dataclasses_deb822 import read_deb822, write_deb822 from debian_linux.debian import \ PackageBuildprofile, \ PackageRelationEntry, PackageRelationGroup, \ VersionLinux, BinaryPackage from debian_linux.gencontrol import Gencontrol as Base, PackagesBundle, \ MakeFlags from debian_linux.utils import Templates locale.setlocale(locale.LC_CTYPE, "C.UTF-8") class Gencontrol(Base): env_flags = [ ] def __init__( self, config_dirs=[ pathlib.Path('debian/config'), pathlib.Path('debian/config.local'), ], template_dirs=["debian/templates"], ) -> None: super().__init__( Config.read_orig(config_dirs).merged, Templates(template_dirs), VersionLinux) self.config_dirs = config_dirs self.process_changelog() 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, config: ConfigMerged, vars: dict[str, str], makeflags: MakeFlags, ) -> None: super().do_main_setup(config, 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'] def do_main_makefile( self, config: ConfigMerged, vars: dict[str, str], makeflags: MakeFlags, ) -> None: for featureset in self.config.root_featuresets: makeflags_featureset = makeflags.copy() makeflags_featureset['FEATURESET'] = featureset.name self.bundle.makefile.add_rules(f'source_{featureset.name}', 'source', makeflags_featureset) self.bundle.makefile.add_deps('source', [f'source_{featureset.name}']) makeflags = makeflags.copy() makeflags['ALL_FEATURESETS'] = ' '.join(i.name for i in self.config.root_featuresets) super().do_main_makefile(config, vars, makeflags) def do_main_packages( self, config: ConfigMerged, vars: dict[str, str], makeflags: 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 = config.packages.meta \ and vars['source_suffix'] != '-' + vars['version'] if config.packages.source: self.bundle.add('sourcebin', (), makeflags, vars) if do_meta: self.bundle.add('sourcebin.meta', (), makeflags, vars) if config.packages.libc_dev: libcdev_kernelarches = set() libcdev_multiarches = set() for kernelarch in self.config.kernelarchs: libcdev_kernelarches.add(kernelarch.name) for debianarch in kernelarch.debianarchs: libcdev_multiarches.add( f'{debianarch.defs_debianarch.multiarch}:{kernelarch.name}' ) 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, config: ConfigMergedFeatureset, vars: dict[str, str], makeflags: MakeFlags, ) -> None: makeflags['LOCALVERSION'] = vars['localversion'] kernel_arches = set() for kernelarch in self.config.kernelarchs: for debianarch in kernelarch.debianarchs: for featureset in debianarch.featuresets: if config.name_featureset in featureset.name: kernel_arches.add(kernelarch.name) makeflags['ALL_KERNEL_ARCHES'] = ' '.join(sorted(list(kernel_arches))) vars['featureset_desc'] = '' if config.name_featureset != 'none': desc = config.description vars['featureset_desc'] = (' with the %s featureset' % desc.short[desc.parts[0]]) def do_indep_featureset_packages( self, config: ConfigMergedFeatureset, vars: dict[str, str], makeflags: MakeFlags, ) -> None: self.bundle.add('headers.featureset', (config.name_featureset, ), makeflags, vars) def do_arch_setup( self, config: ConfigMergedDebianarch, vars: dict[str, str], makeflags: MakeFlags, ) -> None: makeflags['KERNEL_ARCH'] = config.name_kernelarch def do_arch_packages( self, config: ConfigMergedDebianarch, vars: dict[str, str], makeflags: MakeFlags, ) -> None: arch = config.name if config.packages.source and list(config.featuresets): self.bundle.add('config', (arch, ), makeflags, vars) if config.packages.tools_unversioned: self.bundle.add('tools-unversioned', (arch, ), makeflags, vars) if config.packages.tools_versioned: self.bundle.add('tools-versioned', (arch, ), makeflags, vars) def do_featureset_setup( self, featureset: ConfigMergedFeatureset, vars: dict[str, str], makeflags: MakeFlags, ) -> None: vars['localversion_headers'] = vars['localversion'] makeflags['LOCALVERSION_HEADERS'] = vars['localversion_headers'] def do_flavour_setup( self, config: ConfigMergedFlavour, vars: dict[str, str], makeflags: MakeFlags, ) -> None: vars['flavour'] = vars['localversion'][1:] vars['class'] = config.description.hardware or '' vars['longclass'] = config.description.hardware_long or vars['class'] vars['localversion-image'] = vars['localversion'] vars['image-stem'] = cast(str, config.build.kernel_stem) if t := config.build.cflags: makeflags['KCFLAGS'] = t makeflags['COMPILER'] = config.build.compiler if t := config.build.compiler_gnutype: makeflags['KERNEL_GNU_TYPE'] = t if t := config.build.compiler_gnutype_compat: makeflags['COMPAT_GNU_TYPE'] = t makeflags['IMAGE_FILE'] = config.build.kernel_file makeflags['IMAGE_INSTALL_STEM'] = config.build.kernel_stem makeflags['LOCALVERSION'] = vars['localversion'] makeflags['LOCALVERSION_IMAGE'] = vars['localversion-image'] def do_flavour_packages( self, config: ConfigMergedFlavour, vars: dict[str, str], makeflags: MakeFlags, ) -> None: arch = config.name_debianarch ruleid = (arch, config.name_featureset, config.name_flavour) packages_headers = ( self.bundle.add('headers', ruleid, makeflags, vars, arch=arch) ) assert len(packages_headers) == 1 do_meta = config.packages.meta relation_compiler = PackageRelationEntry(cast(str, config.build.compiler)) relation_compiler_header = PackageRelationGroup([relation_compiler]) # Generate compiler build-depends for native: # gcc-N [arm64] self.bundle.source.build_depends_arch.merge([ PackageRelationEntry( relation_compiler, arches={arch}, restrictions='', ) ]) # Generate compiler build-depends for cross: # gcc-N-aarch64-linux-gnu [arm64] self.bundle.source.build_depends_arch.merge([ PackageRelationEntry( relation_compiler, name=f'{relation_compiler.name}-{config.defs_debianarch.gnutype_package}', arches={arch}, restrictions='', ) ]) # Generate compiler build-depends for kernel: # gcc-N-hppa64-linux-gnu [hppa] if gnutype := config.build.compiler_gnutype: if gnutype != config.defs_debianarch.gnutype: self.bundle.source.build_depends_arch.merge([ PackageRelationEntry( relation_compiler, name=f'{relation_compiler.name}-{gnutype.replace("_", "-")}', arches={arch}, restrictions='', ) ]) # Generate compiler build-depends for compat: # gcc-arm-linux-gnueabihf [arm64] # XXX: Linux uses various definitions for this, all ending with "gcc", not $CC if gnutype := config.build.compiler_gnutype_compat: if gnutype != config.defs_debianarch.gnutype: self.bundle.source.build_depends_arch.merge([ PackageRelationEntry( f'gcc-{gnutype.replace("_", "-")}', arches={arch}, restrictions='', ) ]) packages_own = [] vars.setdefault('desc', '') packages_image = ( self.bundle.add('image', ruleid, makeflags, vars, arch=arch) ) for field in ('Depends', 'Provides', 'Suggests', 'Recommends', 'Conflicts', 'Breaks'): for i in getattr(config.relations.image, field.lower(), []): for package_image in packages_image: getattr(package_image, field.lower()).merge( PackageRelationGroup(i, arches={arch}) ) for field in ('Depends', 'Suggests', 'Recommends'): for i in getattr(config.relations.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.breaks.append(PackageRelationGroup([entry])) if desc_parts := config.description.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.description.long[part]) desc.append_short(config.description.short[part]) packages_headers[0].depends.merge(relation_compiler_header) packages_own.extend(packages_image) packages_own.extend(packages_headers) if do_meta: packages_meta = ( self.bundle.add('image.meta', ruleid, makeflags, vars, arch=arch) ) packages_meta += ( self.bundle.add('headers.meta', ruleid, makeflags, vars, arch=arch) ) assert len(packages_meta) > 0 packages_own.extend(packages_meta) if config.build.enable_vdso: makeflags['VDSO'] = True if ( config.defs_flavour.is_default # XXX and not self.vars['source_suffix'] ): packages_own.extend( self.bundle.add('image-extra-dev', ruleid, makeflags, vars, arch=arch) ) # In a quick build, only build the quick flavour (if any). if not config.defs_flavour.is_quick: for package in packages_own: package.build_profiles[0].neg.add('pkg.linux.quick') kconfig = [] for c in config.config: for d in self.config_dirs: if (f := d / c).exists(): kconfig.append(str(f)) makeflags['KCONFIG'] = ' '.join(kconfig) makeflags['KCONFIG_OPTIONS'] = '' # Add "salt" to fix #872263 makeflags['KCONFIG_OPTIONS'] += \ ' -o "BUILD_SALT=\\"%(source_basename)s%(abiname)s%(localversion)s\\""' % vars merged_config = ('debian/build/config.%s_%s_%s' % (config.name_debianarch, config.name_featureset, config.name_flavour)) self.bundle.makefile.add_cmds(merged_config, ["$(MAKE) -f debian/rules.real %s %s" % (merged_config, makeflags)]) 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}' 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']):] 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: super().write() if __name__ == '__main__': Gencontrol()()