#!/usr/bin/perl use strict; use warnings; use File::Copy; use Getopt::Long; my $dir = "."; my $rpmrelease = 0; my $patches=""; GetOptions( "patches=s" => \$patches, "release=s" => \$rpmrelease ) or die "Usage: $0 [--release ] [--patches ]\n"; # flavor -> [supported archs] my %flavor_archs = parse_config_conf(); # subset to include in kernel-syms my %syms_flavor_archs = parse_config_conf("syms"); # template name -> template body my %templates = read_spec_templates(); # config.sh variables my %vars = parse_config_sh(); my ($srcversion, $variant, $vanilla_only) = ($vars{'SRCVERSION'}, $vars{'VARIANT'}, $vars{'VANILLA_ONLY'}); $vanilla_only ||= "0"; # package name -> [summary, description] my %binary_descriptions = parse_descriptions(); # arch -> flavor -> [obsoleted packages] my %obsolete_packages = parse_old_flavors(); $patches="--patches $patches" if $patches; my $patchversion = `$dir/compute-PATCHVERSION.sh $patches`; chomp $patchversion; my $rpmversion = $patchversion; # stuff the -rcX tag into the rpm version if possible; $rpmversion =~ s/\.0-rc/.rc/; $rpmversion =~ s/-rc\d+//; $rpmversion =~ s/-/./g; $rpmrelease =~ s/-/./g; my $sources = join("", $templates{source} =~ /\nSource\d+:[^\n]*/mg); # Find all SourceN: foo.tar.bz2 lines and generate the NoSource: # lines and the %setup line my @tarballs = ($sources =~ /\nSource(\d+):[^\n]*\.tar\.bz2/mg); my $nosource = join("\n", map { "NoSource: $_" } @tarballs); # Source0 (the linux tarball) is unpacked manually @tarballs = grep { $_ > 0 } @tarballs; my $unpack_patches = join(" ", map { "-a $_" } @tarballs); # List of scripts to automatically chmod +x before build my $scripts = join(",", grep { is_script($_) } ($sources =~ /\nSource\d+:\s*([^\s]*)/mg)); my $tarball_url; if ($srcversion =~ /^(\d+)(?:\.\d+)*(-rc\d+)?$/) { $tarball_url = "http://www.kernel.org/pub/linux/kernel/v$1.x/"; $tarball_url .= "testing/" if $2; } else { # kernel.org has no tarballs for linux-next or vanilla snapshots $tarball_url = ""; } my $commit = get_commit(); my %macros = ( VARIANT => $variant, VANILLA_ONLY => $vanilla_only, SRCVERSION => $srcversion, PATCHVERSION => $patchversion, RPMVERSION => $rpmversion, TARBALL_URL => $tarball_url, RELEASE => $rpmrelease, COMMIT => $commit, SOURCES => $sources, NOSOURCE => $nosource, UNPACK_PATCHES => $unpack_patches, SCRIPTS => $scripts, YEAR => (localtime time)[5] + 1900, ); # binary spec files for my $flavor (sort keys(%flavor_archs)) { my ($summary, $description); if (!exists($binary_descriptions{"kernel-$flavor"})) { print STDERR "warning: no description for kernel-$flavor found\n"; $summary = "The Linux Kernel"; $description = "The Linux Kernel."; } else { $summary = $binary_descriptions{"kernel-$flavor"}->[0]; $description = $binary_descriptions{"kernel-$flavor"}->[1]; } do_spec('binary', "kernel-$flavor.spec", %macros, FLAVOR => $flavor, SUMMARY => $summary, DESCRIPTION => $description, ARCHS => join(" ", arch2rpm(@{$flavor_archs{$flavor}})), PROVIDES_OBSOLETES => provides_obsoletes($flavor, @{$flavor_archs{$flavor}}), PROVIDES_OBSOLETES_BASE => provides_obsoletes("$flavor-base", @{$flavor_archs{$flavor}}), ); } # kernel-source.spec do_spec('source', "kernel-source$variant.spec", %macros); # kernel-docs.spec do_spec('docs', "kernel-docs$variant.spec", %macros); # kernel-syms.spec { my $requires = ""; my %all_archs; for my $flavor (sort keys(%syms_flavor_archs)) { next if $flavor eq "vanilla"; my @archs = arch2rpm(@{$syms_flavor_archs{$flavor}}); $all_archs{$_} = 1 for @archs; $requires .= "%ifarch @archs\n"; $requires .= "Requires: kernel-$flavor-devel = \%version-\%source_rel\n"; $requires .= "%endif\n"; } chomp $requires; if (keys(%all_archs)) { do_spec('syms', "kernel-syms$variant.spec", %macros, REQUIRES => $requires, ARCHS => join(" ", sort(keys(%all_archs)))); } } exit 0; sub parse_config_conf { my @symbols = @_; my $symbols = join(' ', @symbols); my %res; for my $arch (split(/\s+/, `$dir/arch-symbols --list`)) { my @flavors = `$dir/guards $arch $symbols < $dir/config.conf`; next if @flavors == 0; chomp @flavors; @flavors = map { s/.*\///; $_ } @flavors; for my $flavor (@flavors) { $res{$flavor} ||= []; push(@{$res{$flavor}}, $arch); } } for my $flavor (keys(%res)) { $res{$flavor} = [sort @{$res{$flavor}}]; } return %res; } sub read_spec_templates { my %res; for my $template (qw(binary source syms docs)) { xopen(my $fh, '<', "$dir/kernel-$template.spec.in"); local $/ = undef; $res{$template} = <$fh>; close($fh); } return %res; } # return a hash of config.sh variables sub parse_config_sh { my %res; xopen(my $fh, '<', "$dir/config.sh"); while (<$fh>) { chomp; if (/^\s*([A-Z_]+)=(.*)/) { $res{$1} = $2; } } close($fh); return %res; } sub parse_descriptions { my %res; my $current; my $blank = ""; # 0 - expect summary, 1 - eating blank lines, 2 - reading description my $state = 0; xopen(my $fh, '<', "$dir/package-descriptions"); while (<$fh>) { next if /^\s*#/; if (/^==+\s+([^\s]+)\s+==+\s*$/) { my $package = $1; if ($current) { chomp $current->[1]; } $current = ["", ""]; $res{$package} = $current; $state = 0; next; } if (/^$/) { if ($state == 2) { $blank .= $_; } next; } # non-blank line and not === package === if ($state == 0) { chomp; $current->[0] = $_; $state = 1; } elsif ($state == 1) { $current->[1] = $_; $blank = ""; $state = 2; } else { $current->[1] .= $blank; $blank = ""; $current->[1] .= $_; } } if ($current) { chomp $current->[1]; } close($fh); return %res; } sub parse_old_flavors{ my %res; xopen(my $fh, '<', "$dir/old-flavors"); while (<$fh>) { chomp; next if /^\s*(#|$)/; if (!m:^\s*(\w+)/([\w-]+)\s+([\w-]+)\s+([\w.-]+)\s*$:) { print STDERR "$dir/old-flavors:$.: expected arch/flavor \n"; next; } my ($arch, $flavor, $old_flavor, $old_version) = ($1, $2, $3, $4); $res{$arch} ||= {}; $res{$arch}{$flavor} ||= []; push(@{$res{$arch}{$flavor}}, ["kernel-$old_flavor", $old_version]); } close($fh); return %res; } sub is_script { my $script = shift; return undef if $script =~ /\.(tar\.(gz|bz2)|in|conf)$/; return undef if $script =~ /^README/; return 1 if $script =~ /\.pl$/; open(my $fh, '<', $script) or return undef; sysread($fh, my $shebang, 2); close($fh); return 1 if $shebang eq "#!"; return undef; } sub arch2rpm { if (wantarray) { return map { _arch2rpm($_) } @_; } return _arch2rpm($_[0]); } sub _arch2rpm { my $arch = shift; return "\%ix86" if $arch eq "i386"; return "aarch64" if $arch eq "arm64"; return $arch; } sub provides_obsoletes { my $flavor = shift; my @archs = @_; my $res = ""; for my $arch (@archs) { my @packs = @{$obsolete_packages{$arch}{$flavor} || []}; next if (!@packs); my $rpmarch = arch2rpm($arch); chomp $rpmarch; $res .= "\%ifarch $rpmarch\n"; for my $pack (@packs) { my $name = $pack->[0]; my $version = $pack->[1]; $res .= "Provides: $name = $version\n"; $res .= "Obsoletes: $name <= $version\n"; } $res .= "\%endif\n"; } chomp $res; return $res; } sub get_commit { my ($commit, $fh); if (!open($fh, '<', "source-timestamp")) { print STDERR "warning: source-timestamp: $!\n"; print STDERR "warning: Cannot determine commit id\n"; return "0000000"; } while (<$fh>) { if (/^GIT Revision: ([0-9a-f]{7})/) { $commit = $1; } } close($fh); if (!$commit) { print STDERR "warning: Commit id missing in source-timestamp file\n"; return "0000000"; } return $commit; } sub do_spec { my $template = shift; my $specfile = shift; my %macros = @_; my $text = $templates{$template}; my $prev_text; do { $prev_text = $text; for my $m (keys %macros) { $text =~ s/\@$m\@/$macros{$m}/g; } } while ($prev_text ne $text); print "$specfile\n"; xopen(my $fh, '>', "$dir/$specfile"); print $fh $text; close($fh); return if $specfile eq "kernel-source$variant.spec"; my $changesfile = $specfile; $changesfile =~ s/\.spec$//; $changesfile .= ".changes"; copy("$dir/kernel-source$variant.changes", $changesfile); } sub xopen { open($_[0], $_[1], $_[2]) or die "$_[2]: $!\n"; }