| #!/usr/bin/perl |
| |
| use strict; |
| use warnings; |
| |
| use File::Copy; |
| use Getopt::Long qw(:config no_ignore_case); |
| |
| my $dir = "."; |
| my $rpmrelease; |
| my $patches=""; |
| |
| GetOptions( |
| "patches=s" => \$patches, |
| "release=s" => \$rpmrelease |
| ) or die "Usage: $0 [--release <release>] [--patches <dir>]\n"; |
| |
| # flavor -> [supported archs] |
| my %flavor_archs = parse_config_conf(); |
| # subset to include in kernel-syms |
| my %syms_flavor_archs = parse_config_conf("syms"); |
| |
| my %all_archs = parse_config_conf("needs_updating"); |
| my @all_archs; |
| for my $flavor (keys(%all_archs)) { |
| push(@all_archs, arch2rpm(@{$all_archs{$flavor}})); |
| } |
| @all_archs = sort(uniq(@all_archs)); |
| my $all_archs = join(" ", @all_archs); |
| |
| # template name -> template body |
| my %templates = read_spec_templates(); |
| |
| my @kmps = read_kmps(); |
| |
| # config.sh variables |
| my %vars = parse_config_sh(); |
| my ($srcversion, $variant, $obs_build_variant) = |
| ($vars{'SRCVERSION'}, $vars{'VARIANT'}, $vars{'OBS_BUILD_VARIANT'}); |
| $obs_build_variant = ($obs_build_variant ? $variant : "" ); |
| my $compress_modules = 'none'; |
| my $compress_vmlinux = 'gz'; |
| if (defined($vars{'COMPRESS_MODULES'})) { |
| $compress_modules = $vars{'COMPRESS_MODULES'}; |
| } |
| if (defined($vars{'COMPRESS_VMLINUX'})) { |
| $compress_vmlinux = $vars{'COMPRESS_VMLINUX'}; |
| } |
| sub detect_false { |
| my $arg = $_[0]; |
| return "" if not $arg; |
| return $arg =~ /^(0+|no|none)$/i ? "" : $arg; |
| } |
| my $build_dtbs = detect_false $vars{'BUILD_DTBS'}; |
| my $multibuild = detect_false $vars{'MULTIBUILD'}; |
| my $livepatch = detect_false $vars{'LIVEPATCH'}; |
| my $livepatch_rt = detect_false $vars{'LIVEPATCH_RT'}; |
| sub to_bool { |
| return detect_false($_[0]) ? 1 : 0 ; |
| } |
| my $sb_efi_only = to_bool $vars{'SB_EFI_ONLY'}; |
| my $split_base = to_bool $vars{'SPLIT_BASE'}; |
| my $split_optional = to_bool $vars{'SPLIT_OPTIONAL'}; |
| my $supported_modules_check = to_bool $vars{'SUPPORTED_MODULES_CHECK'}; |
| my $build_pdf = to_bool $vars{'BUILD_PDF'}; |
| my $build_html = to_bool $vars{'BUILD_HTML'}; |
| my $generate_compile_commands = to_bool $vars{'GENERATE_COMPILE_COMMANDS'}; |
| |
| if (!defined ($rpmrelease)) { |
| $rpmrelease = $vars{'RELEASE'} || 0; |
| } |
| |
| # package name -> [summary, description, extra kmp deps] |
| 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("\n", $templates{source} =~ /^Source\d+:[^\n]*/msg); |
| # Do not include the signature and keyring as source in the binary packages |
| # The sources are not really included anyway, and for non-upstream tarballs these files do not exist |
| $sources = join("\n", grep { $_ !~ /[.](?:keyring|tar[.]sign)\s*$/ } $sources =~ /^[^\n]*/msg); |
| # Find all SourceN: foo.tar.(bz2|xz) lines and generate the NoSource: |
| # lines and the %setup line |
| my @tarballs = ($sources =~ /^Source(\d+):[^\n]*\.tar\.(?:bz2|xz)/msg); |
| my $nosource = $sources; |
| $nosource =~ s/^Source(\d+):.*?$/NoSource: $1/mg; |
| |
| # 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 = "https://www.kernel.org/pub/linux/kernel/v$1.x/"; |
| $tarball_url = "" if $2; # kernel.org has no tarballs for rc kernels |
| # rc tarballs only available from git as https://git.kernel.org/torvalds/t/linux-*.gz |
| } else { |
| # kernel.org has no tarballs for linux-next or vanilla snapshots |
| $tarball_url = ""; |
| } |
| |
| my $commit = get_commit(); |
| my $commit_full = get_commit(1); |
| |
| my %macros = ( |
| VARIANT => $variant, |
| OBS_BUILD_VARIANT => $obs_build_variant . "%{nil}", |
| SRCVERSION => $srcversion, |
| PATCHVERSION => $patchversion, |
| RPMVERSION => $rpmversion, |
| TARBALL_URL => $tarball_url, |
| RELEASE => $rpmrelease, |
| COMMIT => $commit, |
| COMMIT_FULL => $commit_full, |
| SOURCES => $sources . "\n# These files are found in the kernel-source package:\n" . $nosource, |
| UNPACK_PATCHES => $unpack_patches, |
| SCRIPTS => $scripts, |
| LIVEPATCH => $livepatch, |
| LIVEPATCH_RT => $livepatch_rt, |
| SB_EFI_ONLY => $sb_efi_only, |
| SPLIT_BASE => $split_base, |
| SPLIT_OPTIONAL => $split_optional, |
| SUPPORTED_MODULES_CHECK => $supported_modules_check, |
| BUILD_PDF => $build_pdf, |
| BUILD_HTML => $build_html, |
| YEAR => (localtime time)[5] + 1900, |
| COMPRESS_MODULES => $compress_modules, |
| COMPRESS_VMLINUX => $compress_vmlinux, |
| GENERATE_COMPILE_COMMANDS => $generate_compile_commands, |
| ); |
| |
| # binary spec files |
| my $kmp_definitions = ""; |
| my @kmp_definitions; |
| for my $kmp (@kmps) { |
| my ($summary, $description, $deps); |
| if (!exists($binary_descriptions{$kmp})) { |
| print STDERR "warning: no description for $kmp found\n"; |
| ($summary = $kmp) =~ s/-kmp$//; |
| $summary .= " kernel modules"; |
| $description = "$summary."; |
| $deps = ""; |
| } else { |
| $summary = $binary_descriptions{$kmp}->[0]; |
| $description = $binary_descriptions{$kmp}->[1]; |
| $deps = $binary_descriptions{$kmp}->[2]; |
| } |
| push(@kmp_definitions, expand_template("kmp", |
| KMP_NAME => $kmp, |
| KMP_SUMMARY => $summary, |
| KMP_DESCRIPTION => $description, |
| KMP_DEPS => $deps)); |
| } |
| $kmp_definitions = join("\n", @kmp_definitions); |
| |
| 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]; |
| } |
| |
| my %obsolete_macros; |
| for my $subpac ("", "-base", "-extra", "-devel", "-hmac", "-optional") { |
| (my $macro = "PROVIDES_OBSOLETES" . uc($subpac)) =~ s/-/_/; |
| $obsolete_macros{$macro} = |
| provides_obsoletes($flavor, $subpac, @{$flavor_archs{$flavor}}); |
| } |
| |
| do_spec('binary', "kernel-$flavor.spec", %macros, |
| FLAVOR => $flavor, |
| SUMMARY => $summary, |
| DESCRIPTION => $description, |
| ARCHS => join(" ", arch2rpm(@{$flavor_archs{$flavor}})), |
| COMMON_DEPS => $templates{common_deps}, |
| KMPS => join(" ", @kmps), |
| KMP_DEFINITIONS => $kmp_definitions, |
| %obsolete_macros |
| ); |
| } |
| # kernel-source.spec |
| do_spec('source', "kernel-source$variant.spec", %macros); |
| |
| if ($variant eq "") { |
| # kernel-docs.spec |
| do_spec('docs', "kernel-docs$variant.spec", %macros); |
| } |
| |
| # kernel-syms.spec |
| { |
| my $requires = ""; |
| my %syms_archs; |
| my $syms_archs; |
| for my $flavor (sort keys(%syms_flavor_archs)) { |
| next if $flavor eq "vanilla"; |
| my @archs = arch2rpm(@{$syms_flavor_archs{$flavor}}); |
| $syms_archs{$_} = 1 for @archs; |
| $requires .= "%ifarch @archs\n"; |
| $requires .= "Requires: kernel-$flavor-devel = \%version-\%source_rel\n"; |
| $requires .= "%endif\n"; |
| } |
| chomp $requires; |
| $syms_archs = join(" ", sort(keys(%syms_archs))); |
| if (keys(%syms_archs)) { |
| do_spec('syms', "kernel-syms$variant.spec", %macros, |
| REQUIRES => $requires, |
| ARCHS => $syms_archs); |
| } |
| } |
| |
| # kernel-obs-*.spec |
| if (!$variant || $obs_build_variant) { |
| my @default_archs; |
| my $flavor = $obs_build_variant; |
| if ($flavor) { |
| $flavor =~ s/^-//; |
| } else { |
| $flavor = 'default'; |
| } |
| |
| @default_archs = arch2rpm(@{$flavor_archs{$flavor}}); |
| # No kernel-obs-* for 32bit ppc and x86 |
| @default_archs = grep { $_ ne "ppc" && $_ ne '%ix86' } @default_archs; |
| my $default_archs = join(" ", @default_archs); |
| do_spec('obs-build', "kernel-obs-build.spec", %macros, |
| ARCHS => $default_archs); |
| do_spec('obs-qa', "kernel-obs-qa.spec", %macros, |
| ARCHS => $default_archs); |
| } |
| |
| # dtb-*.spec |
| if ((!$variant || $obs_build_variant) && $build_dtbs) { |
| do_spec('dtb', "dtb.spec.in", %macros); |
| print "./mkspec-dtb $all_archs\n"; |
| system("./mkspec-dtb $all_archs\n"); |
| unlink("$dir/dtb.spec.in"); |
| if ($?) { |
| exit(($? >> 8) || ($? & 127 + 128) || 1); |
| } |
| } |
| |
| copy_changes(); |
| |
| # _constraints |
| { |
| my @packages = map { "<package>kernel-$_</package>\n<package>kernel-source$variant:kernel-$_</package>" } sort keys(%flavor_archs); |
| my $packages = join("\n", @packages); |
| do_spec('constraints', "_constraints", |
| BINARY_PACKAGES_XML => $packages, |
| VARIANT => $variant); |
| } |
| |
| 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 obs-build obs-qa)) { |
| xopen(my $fh, '<', "$dir/kernel-$template.spec.in"); |
| local $/ = undef; |
| $res{$template} = <$fh>; |
| close($fh); |
| next unless $template eq "binary"; |
| if ($res{$template} =~ /^# BEGIN COMMON DEPS\n?(.*)^# END COMMON DEPS/ms) { |
| $res{common_deps} = $1; |
| } else { |
| print STDERR "warning: Expected # BEGIN COMMON DEPS in kernel-binary.spec.in\n"; |
| $res{common_deps} = ""; |
| } |
| if ($res{$template} =~ s/^# BEGIN KMP\n?(.*)^# END KMP/\@KMP_DEFINITIONS\@/ms) { |
| $res{kmp} = $1; |
| } else { |
| print STDERR "warning: Expected # BEGIN KMP in kernel-binary.spec.in\n"; |
| $res{kmp} = ""; |
| } |
| } |
| { |
| xopen(my $fh, '<', "$dir/constraints.in"); |
| local $/ = undef; |
| $res{constraints} = <$fh>; |
| close($fh); |
| xopen($fh, '<', "$dir/dtb.spec.in.in"); |
| $res{dtb} = <$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_]+)=(.*)/) { |
| my ($key, $val) = ($1, $2); |
| $val =~ s/^"(.*)"$/$1/; |
| $res{$key} = $val; |
| } |
| } |
| 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 == 0) { |
| $state++; |
| } elsif ($state == 2) { |
| $blank .= $_; |
| } |
| next; |
| } |
| # non-blank line and not === package === |
| if ($state == 0) { |
| chomp; |
| if (s/^Requires: *//) { |
| # foo-kmp is a shorthand for another kmp |
| # from the same specfile |
| s/-kmp/-kmp-%build_flavor = %version-%release/g; |
| s/^/Requires: /; |
| if ($current->[2]) { |
| $current->[2] .= "\n"; |
| } |
| $current->[2] .= $_; |
| } else { |
| # The Summary: keyword is optional |
| s/^Summary: *//; |
| if ($current->[0]) { |
| print STDERR "warning: multi-line summary\n"; |
| } |
| $current->[0] = $_; |
| } |
| } elsif ($state == 1) { |
| $current->[1] = $_; |
| $blank = ""; |
| $state++; |
| } else { |
| $current->[1] .= $blank; |
| $blank = ""; |
| $current->[1] .= $_; |
| } |
| } |
| if ($current) { |
| chomp $current->[1]; |
| } |
| close($fh); |
| return %res; |
| } |
| |
| sub read_kmps { |
| my %res; |
| |
| open(my $fh, '-|', "$dir/guards", "--list", "--with-guards", |
| "-c", "$dir/supported.conf") or die "Error running guards: $!\n"; |
| while (<$fh>) { |
| my @guards = split(' '); |
| pop(@guards); |
| for my $g (@guards) { |
| if ($g =~ /^(?:\+|-!)(.*-kmp)$/) { |
| $res{$1} = 1; |
| } |
| } |
| } |
| close($fh) or die "Error running guards: $!\n"; |
| return sort(keys(%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 <old flavor> <old version>\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 $subpac = shift; |
| my @archs = @_; |
| my $res = ""; |
| |
| for my $arch (@archs) { |
| my @packs = @{$obsolete_packages{$arch}{$flavor} || []}; |
| my $printed; |
| |
| next if (!@packs); |
| my $rpmarch = arch2rpm($arch); |
| chomp $rpmarch; |
| for my $pack (@packs) { |
| my $name = $pack->[0] . $subpac; |
| my $version = $pack->[1]; |
| if (!$printed) { |
| $res .= "\%ifarch $rpmarch\n"; |
| $printed = 1; |
| } |
| $res .= "Provides: $name = $version\n"; |
| $res .= "Obsoletes: $name <= $version\n"; |
| } |
| $res .= "\%endif\n" if $printed; |
| } |
| chomp $res; |
| return $res; |
| } |
| |
| sub get_commit { |
| my ($commit, $fh, $full); |
| |
| $full = $_[0] // 0; |
| |
| if (!open($fh, '<', "source-timestamp")) { |
| print STDERR "warning: source-timestamp: $!\n"; |
| print STDERR "warning: Cannot determine commit id\n"; |
| return "0000000"; |
| } |
| while (<$fh>) { |
| if ($full ? /^GIT Revision: ([0-9a-f]{40})/ : /^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 expand_template { |
| my $template = shift; |
| my %macros = @_; |
| |
| my $text = $templates{$template}; |
| my $prev_text; |
| do { |
| $prev_text = $text; |
| for my $m (keys %macros) { |
| if ($macros{$m} eq "") { |
| # Do not generate empty lines |
| $text =~ s/^\@$m\@\n//mg; |
| } |
| $text =~ s/\@$m\@/$macros{$m}/g; |
| } |
| } while ($prev_text ne $text); |
| return $text; |
| } |
| |
| sub do_spec { |
| my $template = shift; |
| my $specfile = shift; |
| my %macros = @_; |
| |
| my $text = expand_template($template, %macros); |
| print "$specfile\n"; |
| xopen(my $fh, '>', "$dir/$specfile"); |
| print $fh $text; |
| close($fh); |
| } |
| |
| sub copy_changes { |
| |
| opendir(my $dh, $dir) or die "$dir: $!\n"; |
| xopen(my $fh, '>', "$dir/_multibuild") if $multibuild; |
| print $fh "<multibuild>\n" if $fh; |
| |
| foreach my $name (sort readdir $dh) { |
| next unless $name =~ /\.spec$/; |
| next if $name eq "kernel-source$variant.spec"; |
| |
| $name =~ s/\.spec$//; |
| copy("$dir/kernel-source$variant.changes", "$dir/$name.changes"); |
| print $fh "\t<package>$name</package>\n" if $fh; |
| } |
| print $fh "</multibuild>\n" if $fh; |
| close($fh) if $fh; |
| closedir($dh); |
| } |
| |
| sub xopen { |
| open($_[0], $_[1], $_[2]) or die "$_[2]: $!\n"; |
| } |
| |
| sub uniq { |
| my %seen; |
| return grep { !$seen{$_}++ } @_; |
| } |