#! /usr/bin/perl -w # # vim:sw=2:et # BEGIN { unshift @INC, "."; unshift @INC, "/usr/lib/build/"; } use Time::localtime; use Data::Dumper; use strict; my @oldspec = (); my @newspec = (); my $base_package = ""; my $icecreamforbuild = ""; my @copyrights = (); my $needsrootforbuild = 0; my $needsbinariesforbuild = 0; my $nodebuginfo = 0; my $vim_modeline; my $nosrc_result = 0; my $current_section = "header"; my $had_debug_package = 0; my %pkg_version = (); my $main_license; my %seen_licenses = (); my $main_group; my %seen_groups = (); my $build_root = $ENV{'BUILD_ROOT'}; my $disabled_packs; my $ifhandler; my $definelist; my $debug = 0; my @global_tags_list = ( 'Autoreq', 'Autoreqprov', 'BuildArch', 'BuildArchitectures', 'BuildRequires', 'Conflicts', 'DocDir', 'Enhances', 'Enhances', 'EssentialFor', 'ExcludeArch', 'ExclusiveArch', 'Freshens', 'Group', 'Name', 'NoPatch', 'NoSource', 'Obsoletes', 'Patch\d*', 'Prefix', 'PreReq', 'Provides', 'Recommends', 'Requires', 'Source\d*', 'Suggests', 'Summary', 'Supplements', 'Url', ); my $global_tags_re = '^\s*(' . join("|", @global_tags_list) . ')\s*:'; my $section_tags_re ='^\s*%(?:clean|check|prep|build|install|pre|post|preun|postun|posttrans|package|' . 'description|files|triggerin|triggerun|triggerpostun)\b'; sub unify { my %h = map {$_ => 1} @_; return grep(delete($h{$_}), @_); } sub capitalize_case($) { my ($tag) = @_; $tag = lc($tag); $tag =~ s/docdir/DocDir/i; $tag =~ s/arch/Arch/i; $tag =~ s/patch/Patch/i; $tag =~ s/source/Source/i; $tag =~ s/req/Req/i; $tag =~ s/prov/Prov/i; $tag =~ s/^(\w)/uc($1)/e; return $tag; } sub compare_arrays { my ($first, $second) = @_; return 0 unless @$first == @$second; for (my $i = 0; $i < @$first; $i++) { return 0 if $first->[$i] ne $second->[$i]; } return 1; } sub maybe_add_empty_line() { push @oldspec, "XXXBLANKLINE" if ($current_section ne "description" && $oldspec[-1] !~ /^\s*$/ && $oldspec[-1] !~ /^[#%]/); } sub change_section($) { my ($new_section) = @_; maybe_add_empty_line(); $current_section = $new_section; warn "section changed to $current_section\n" if $debug; } sub replace_single_spdx($) { my ($l) = @_; my %replace = ( "lgplv2.1only" =>"LGPL-2.1", "lgplv2.1orlater" => "LGPL-2.1+", "lgplv2.1+" => "LGPL-2.1+", "lgplv2.1" => "LGPL-2.1", "lgplv2.0+" => "LGPL-2.0+", "lgplv2.0" => "LGPL-2.0", "lgplv2+" => "LGPL-2.1+", 'lgplv3' => "LGPL-3.0", 'lgplv3+' => "LGPL-3.0+", 'gnulgplv2.1orlater' => "LGPL-2.1+", 'gnulgplv2.1' => "LGPL-2.1", 'lgplv2.0orlater' => 'LGPL-2.0+', 'lgplv2orlater' => 'LGPL-2.0+', "lgplv3only" => "LGPL-3.0", "lgplv3orlater" => "LGPL-3.0+", "gpl" => "GPL-1.0", "gpl+" => "GPL-1.0+", "gplv2only" => "GPL-2.0", "gplv2" => "GPL-2.0", "gpl2" => "GPL-2.0", "gplv2orlater" => "GPL-2.0+", "gplv2.0orlater" => "GPL-2.0+", "gplv2+" => "GPL-2.0+", "gpl-2+" => "GPL-2.0+", "gplv3only" => "GPL-3.0", "gplv3orlater" => "GPL-3.0+", "gplv3+" => "GPL-3.0+", "gplv3" => "GPL-3.0", "gpl-3+" => "GPL-3.0+", "gnugplversion3" => "GPL-3.0", "fdl1.1" => "GFDL-1.1", "gfdlv1.1" => "GFDL-1.1", 'gnufreedocumentationlicense,version1.1(fdl1.1)' => "GFDL-1.1", 'gnufreedocumentationlicense,version1.1(gfdl1.1)' => "GFDL-1.1", "fdl1.2" => "GFDL-1.2", "gfdl1.2" => "GFDL-1.2", "gfdlv1.2" => "GFDL-1.2", 'gnufreedocumentationlicense,version1.2(fdl1.2)' => "GFDL-1.2", 'gnufreedocumentationlicense,version1.2(gfdl1.2)' => "GFDL-1.2", "fdl1.3" => "GFDL-1.3", "bsd-2c" => "BSD-2-Clause", "bsd3-clause" => "BSD-3-Clause", "bsd3c" => "BSD-3-Clause", "bsd3c(orsimilar)" => "BSD-3-Clause", "bsd3-clause(orsimilar)" => "BSD-3-Clause", "bsd4c" => "BSD-4-Clause", "bsd4c(orsimilar)" => "BSD-4-Clause", "x11/mit" => "MIT", "X11mit" => "MIT", "mitlicense(orsimilar)" => "MIT", "asl2.0" => "Apache-2.0", 'apachelicense2.0' => "Apache-2.0", 'apachelicense' => "Apache-2.0", 'theapachesoftwarelicense' => "Apache-2.0", "apachesoftwarelicense.." => "Apache-2.0", 'apachesoftware license..' => "Apache-2.0", 'aslv..' => "Apache-2.0", 'aslv2' => "Apache-2.0", "artistic" => "Artistic-1.0", "artistic2.0" => "Artistic-2.0", "thephplicense,version3.01" => "PHP-3.01", "phplicense" => "PHP-3.01", "mplv1.0" => "MPL-1.0", "mplv1.1" => "MPL-1.1", "ibmpubliclicense.." => "IPL-1.0", "ibmpl" => "IPL-1.0", "eclipsepubliclicense" => "EPL-1.0", "openldap2.8" => "OLDAP-2.8", "zliblicense" => "Zlib" ); my $tl = lc $l; $tl =~ s, ,,g; if (defined $replace{$tl}) { $l = $replace{$tl}; } else { my @m = grep(lc($_) eq lc($l), values(%replace)); if (@m) { $l = $m[0]; } else { print STDERR "unknown license '$l'\n"; } } return $l; } sub replace_spdx_or($) { my ($license) = @_; # special case as or later is common in our spec files $license =~ s, or later, orlater,g; my @licenses = (); for (split(/\s+or\s+/, $license)) { push @licenses, replace_single_spdx($_); } return join(' or ', sort(@licenses)); } sub replace_spdx_and($) { my ($license) = @_; my @licenses = (); for (split(/\s*and\s*/, $license)) { push @licenses, replace_spdx_or($_); } return join(' and ', sort(@licenses)); } sub replace_spdx($) { my ($license) = @_; my @licenses = (); for (split(/\s*;\s*/, $license)) { push @licenses, replace_spdx_and($_); } return join(' ; ', sort(@licenses)); } sub set_current_pkg { my ( $arg ) = @_; print "DEBUG: set_current_pkg receiving $arg\n" if $debug; my ( @argarray ) = split ( '\s+' , $arg ); my $curpack = $base_package; my $curlang = ""; while (my $carg = shift @argarray) { next if ($carg eq "%description" || $carg eq "%package" || $carg eq "%prep"); if ($carg eq "-l") { $curlang = shift @argarray; } elsif ($carg eq "-n") { $curpack = shift @argarray; } else { $curpack = "$base_package-" if $base_package; $curpack .= $carg; } } print "DEBUG: set_current_pkg returning $curpack, $curlang\n" if $debug; return ($curpack, $curlang); } sub read_and_parse_old_spec { my ( $specfile, $base_package ) = @_; my $current_package = $base_package; my $current_lang = ""; my $check_printed = "false"; my $print_comments = "false"; my %version; my $ifhandler; $ifhandler->{"disabled"} = 0; my @readspec; open ( SPEC , "$specfile" ) || die "can't read specfile"; @readspec = ; close SPEC; chomp @readspec; while (@readspec) { $_ = shift @readspec; if ( /^\s*$/ && $current_section ne "description") { # stop preamble parsing on two blank lines if ($print_comments eq "false" && $oldspec[0] && $oldspec[-1] eq "XXXBLANKLINE") { $print_comments = "true"; push @oldspec, "XXXDOUBLELINE"; next; } push @oldspec, "XXXBLANKLINE"; next; } if ( /^# vim:/ ) { $vim_modeline = $_; next; } if ( /^#\s*needsrootforbuild\s*$/ ) { $needsrootforbuild = 1; next; } if ( /^#\s*needsbinariesforbuild\s*$/ ) { $needsbinariesforbuild = 1; next; } if ( /^#\s*norootforbuild/ ) { next; } if ( /^#\s*nodebuginfo\s*$/ ) { $nodebuginfo = 1; next; } if ( /^#\s*icecream/ ) { $icecreamforbuild = $_; $icecreamforbuild =~ s/^#\s*icecream\s*//; next; } if ( /^#\s*Copyright\s*/ ) { my $lastlineblank = 0; for (;;) { # check if line is ONLY a standard copyright line, if so, ignore. my $c = $_; $c =~ s{\s*(\d+|copyrights?|\(c\)|suse|linux|products|gmbh|nuremberg|n..?rnberg|germany|\W+)\s*}{}gi; push(@copyrights, $_) if length $c > 5; last if length $readspec[0] < 10 || $readspec[0] =~ m{modifications and additions}i || $readspec[0] !~ /^[\#\s]/ || grep { $readspec[0] =~ /^#\s*$_/ } ("needsrootforbuild","needsbinariesforbuild","nodebuginfo","icecream","usedforbuild","Commandline","MD5SUM","!BuildIgnore"); $_ = shift @readspec; } next; } # evil epoch removal next if ( /^Epoch:/ ); $_ =~ s/%{?epoch}?[:-]//g; $_ =~ s/ 0:/ /g if ( /^requires/i || /^buildreq/i ); if ( /^BuildRequires:/ ) { my $cur_buildreq = $_; $cur_buildreq =~ s/^BuildRequires:\s*//; my %aa; while ($cur_buildreq =~ m{([^,\s]+(\s*[<=>]+\s*[^,\s]+)?)}g) { $aa{$1}=1; } # ignore line if it looks like a "usedforbuild" line, i.e. # if it contains too many base packages next if (grep {$aa{$_}} qw{gcc rpm glibc bash}) > 2; for my $br (sort keys(%aa)) { push @oldspec, "BuildRequires: $br"; } next; } next if ( /^#\s*usedforbuild/ ); if ( /^%\?__\*BuildRequires:/ ) { push @oldspec, $_; next; } if ( /^#!__\*BuildRequires:/ ) { push @oldspec, $_; next; } if ( /^#!BuildIgnore:/ ) { push @oldspec, $_; next; } if ( /^#/ && $current_section ne "description") { warn "$_ $current_section\n" if $debug; if ( $print_comments eq "true" || $readspec[0] =~ /^%define/ || $readspec[0] =~ /^%if/) { push @oldspec, $_; } next; } if ( /^%debug_package/ ) { # remove, we add this ourselves next; } $print_comments = "true" unless /^#/; if ( /^%define\s*vendor\s/ || /^%define\s*distribution\s/ ) { next; } if ( /^\s*%if/ || /^\s*%\{/ || /^\s*%define/ || /^\s*%el/ || /^\s*%endif/ ) { change_section("header") if ($current_section eq "description"); push @oldspec, $_; if ( /^\s*%if\s/ ) { my @args = split (/\s+/,$_); $_ =~ s/[\{\}\"]//g for (@args); $ifhandler->{"last_if_disabled"} = 0; $ifhandler->{"last_if_if"} = 1; $ifhandler->{"depth"}++; my $if_not = 0; if ( $args[1] =~ /^\!/ ) { $args[1] =~ s/^\!//; $if_not = 1; } $args[2] = "" unless $args[2]; if ( ($args[1] eq "0") || ($args[1] eq "%name" && $args[2] eq "!=" && $args[3] eq $base_package) || ($args[1] eq "%name" && $args[2] eq "==" && $args[3] ne $base_package) || ($args[1] && !$args[3] && !$if_not && $definelist->{$args[1]} && $definelist->{$args[1]} eq "0") || ($args[2] eq "==" && $args[3] ne "0" && $definelist->{$args[1]} && $definelist->{$args[1]} eq "0") || ($args[2] eq "!=" && $args[3] eq "0" && $definelist->{$args[1]} && $definelist->{$args[1]} eq "0") || ($args[1] && !$args[3] && $if_not && $definelist->{$args[1]} && $definelist->{$args[1]} eq "1") || ($args[1] && $args[2] eq "!=" && $args[3] eq "1" && $definelist->{$args[1]} && $definelist->{$args[1]} eq "1") ) { $ifhandler->{"disabled"} = $ifhandler->{"depth"}; $ifhandler->{"last_if_disabled"} = 1; } } elsif ( /^\s*%if/ ) { $ifhandler->{"last_if_disabled"} = 0; $ifhandler->{"last_if_if"} = 0; $ifhandler->{"depth"}++; } elsif ( /^\s*%endif/ ) { $ifhandler->{"disabled"} = 0 if $ifhandler->{"disabled"} == $ifhandler->{"depth"}; $ifhandler->{"depth"}--; } elsif ( /^\s*%else/ ) { if ($ifhandler->{"disabled"} == $ifhandler->{"depth"} && $ifhandler->{"last_if_disabled"} == 1) { $ifhandler->{"disabled"} = 0; } elsif ($ifhandler->{"disabled"} == 0 && $ifhandler->{"depth"} == 1 && $ifhandler->{"last_if_if"} == 1) { $ifhandler->{"disabled"} = 1; } } elsif ( /^\s*%define\s/ ) { my @args = split (/\s+/,$_); $_ =~ s/[\{\}\"]//g for (@args); $args[2] =~ s/\Q$_\E/$definelist->{$_}/g for sort { length($b) <=> length($a) } keys (%{$definelist}); if ( $args[2] !~ /[\(\)\{\}\@\%\"\\]/ ) { $definelist->{"%".$args[1]} = $args[2] if $ifhandler->{"disabled"} == 0; $definelist->{"%{".$args[1]."}"} = $args[2] if $ifhandler->{"disabled"} == 0; $definelist->{"%{?".$args[1]."}"} = $args[2] if $ifhandler->{"disabled"} == 0; } while ($_ =~ /\\$/) { $_ = shift @readspec; push @oldspec, $_; } } next; } if ( /^%package\b/i or /^%prep\b/i ) { if (/^%package\b/i) { change_section("header"); } else { change_section("prep"); } $_ =~ s/^(%\w+)/lc($1)/e; if ($debug) { warn "key: $_ value: $definelist->{$_}\n" for (sort { length($b) <=> length($a) } keys (%{$definelist})); } push @oldspec, $_; for my $xx (sort { length($b) <=> length($a) } keys (%{$definelist})) { $_ =~ s/\Q$xx\E/$definelist->{$xx}/; } $_ =~ s/%{\?[^\}]*}//; if ($debug) { warn "after: $_\n"; } ($current_package, $current_lang) = set_current_pkg ( $_ ); if ($ifhandler->{"disabled"}) { $disabled_packs->{$current_package} = 1; warn "$current_package is disabled\n" if $debug; } next; } if ( /^%description\b/i ) { change_section("description"); push @oldspec, $_; next; } if ( /^%install\b/i ) { change_section("install"); push @oldspec, $_; next; } if ( /^%changelog\b/i ) { change_section("changelog"); # changelog comes always from *.changes. Skip what is in spec file # at the moment. next; } if (/^%files\b/i) { change_section("files"); $current_section = "files"; } if ( /^%/ ) { if ( m/$section_tags_re/oi ) { $_ =~ s/^(%\w+)/lc($1)/e; change_section("header") if (! m/\s*%files/i && !m/\s*%build/i); change_section("build") if m/\s*%build/i; warn "changed to $current_section for $_\n" if $debug; } push @oldspec, "$_"; next; } if ($current_section eq "header") { my $c_pack = $current_package; $c_pack .= "_disabled" if $ifhandler->{"disabled"}; if ( /^Vendor:/ || /^Distribution:/ || /^Packager:/ ) { next; } # remove default value of Autoreqprov if ( /^Autoreqprov\s*:\s*(.*)/i ) { next if ( lc($1) eq "on" || lc($1) eq "yes"); } # reset Release if ( /^Release\s*:\s*(.*)/i ) { # will be after Version next; } if ( /^Summary\s*:\s*(.*)\s*$/i ) { push @oldspec, sprintf("%-16s%s", "Summary:", $1); push @oldspec, "XXXPOSTSUMMARY $current_package"; next; } # remove license and print out after license later if ( /^License\s*:\s*(.*)\s*$/i || /^Copyright\s*:\s*(.*)\s*$/i ) { my $license = replace_spdx($1); $main_license = $license if (!$main_license); $seen_licenses{$current_package} = $license; next; } # remove groups and print out after summary later if ( /^Group\s*:\s*(.*)\s*$/i ) { my $group = $1; $main_group = $group if (!$main_group); $seen_groups{$current_package} = $group; next; } if ( /^BuildArchitectures\s*:/i ) { $_ =~ s/^[^:]+:/BuildArch:/; } if ( /^BuildRoot\s*:/i ) { push @oldspec, "BuildRoot: %{_tmppath}/%{name}-%{version}-build"; next; } if ( m/$global_tags_re\s*(.*)/oi ) { my ($tag, $value) = ($1, $2); $nosrc_result = 1 if ($tag =~ /(?:nosource|nopatch)/i); push @oldspec, sprintf("%-16s%s", capitalize_case($tag) . ":", $value); next; } if ( /^Version:/ ) { warn "found Version, section = $current_section\n" if $debug; $version{$c_pack} = $_; $version{$c_pack} =~ s/^Version:\s*(.*)\s*/$1/; push @oldspec, sprintf("%-16s%s","Version:",$version{$c_pack}); push @oldspec, sprintf("%-16s%s","Release:", "0"); next; } } if ( $current_section ne "changelog" ) { push @oldspec, $_; next; } } } if ($ARGV[0] eq '--debug') { $debug = 1; shift @ARGV; } my $specfile = shift ( @ARGV ); if ( ! stat($specfile) ) { die "$specfile is no file"; } my @specpath = split ('/' ,$specfile); my $specbase = pop @specpath; my $specdir = join ('/', @specpath); if ( $specdir eq "" ) { $specdir = "."; } my $xdefinelist; my $seen_name = 0; open ( SPE , "$specfile" ); while ( ) { chomp(); if ( /^%define/ ) { my @args = split (/\s+/,$_); $_ =~ s/[\{\}\"]//g for (@args); $args[2] =~ s/\Q$_\E/$xdefinelist->{$_}/ for (sort { length($b) <=> length($a) } keys (%{$xdefinelist})); if ( $args[2] !~ /[\(\)\{\}\@\%\"\\]/ ) { $xdefinelist->{"%".$args[1]} = $args[2]; $xdefinelist->{"%{".$args[1]."}"} = $args[2]; } } if ( /^\s*Name:/ ) { next if $seen_name; $seen_name = 1; $base_package = $_; $base_package =~ s/^\s*Name:\s*(\S*)\s*/$1/; $base_package =~ s/\Q$_\E/$xdefinelist->{$_}/ for (sort { length($b) <=> length($a) } keys (%{$xdefinelist})); if ($debug) { warn "DEBUG: base_package = $base_package\n"; warn Dumper($xdefinelist); } last; } } close ( SPE ); warn ("base_package is $base_package\n") if $debug; if ( ! stat ((glob("$specdir/$base_package*.spec"))[0] || "") ) { $base_package =~ s/[0-9]$//; } if ( ! stat ((glob("$specdir/$base_package*.spec"))[0] || "") ) { $base_package =~ s/\-[^\-]*$//; } warn ("base_package is $base_package\n") if $debug; if ( ! stat ((glob("$specdir/$base_package*.spec"))[0] || "") ) { $base_package = $specbase; $base_package =~ s/\.spec$//; $base_package =~ s/\-.*$//; } read_and_parse_old_spec ( $specfile, $base_package ); my $linesmoved = 1; while ($linesmoved) { $linesmoved = 0; my @firstlines = (); my @buildrequires = (); while ($oldspec[0]) { my $l = shift @oldspec; if ($l =~ m/^BuildRequires:/ ) { push(@buildrequires, $l); } else { # if there are already buildrequires, we need to sort and exit if (@buildrequires > 0) { my @sortedbrs = sort(@buildrequires); $linesmoved = !compare_arrays(\@buildrequires, \@sortedbrs); @oldspec = (@firstlines, sort(@buildrequires), $l, @oldspec); @firstlines = (); @buildrequires = (); last; } else { push(@firstlines, $l); } } } @oldspec = (@firstlines, @oldspec); } my $thisyear = localtime->year() + 1900; unshift @copyrights, "# Copyright (c) $thisyear SUSE LINUX Products GmbH, Nuernberg, Germany."; my $copy_list = join("\n", @copyrights); print $vim_modeline . "\n" if (defined $vim_modeline); print <