Here is a first attempt on automated WANTLIB tweaking. Features: * Removal, addition and updating WANTLIB items:
-w foo Removes all foo* ("foo", "foo>=2" and so on) items; -W foo Replaces all foo* items with "foo", or add "foo" to the list if there is none; * Takes into account ${WANTLIB} and ${MOD*} variables expansion, and refuses to operate if it sees any other variable expansion inside WANTLIB declaration. * WANTLIB tweaking done inside .if or .for block is detected and portbump refuses to tweak such port as well. * Could be fed with a list of subpackages as usual. * Properly handles PKG_ARCH (given that bsd.port.mk contains a not-yet-committed fix for dump-vars, see the end of the patch), forcing WANTLIB-foo to be set to an empty value. * Tries to use same whitespace as existing WANTLIB items do. This version contains some (disabled) debug printout lines, they will be gone in the final version. Questions regarding further polishing: 1. You can specify "-W foo>=2" or whatever, it'll replace all foo* items as well; but note that "-w foo>=2" will also remove all foo* items. What behaviour is more preferred: a) remove exact matches only; b) don't allow "-w foo=>2" at all; c) other option. 2. In some Makefiles WANTLIB-foo lines are placed near LIB_DEPENDS-foo or other ones. I see no good way to automatically detect this layout. Should there be an option to respect such layout, or is it fine to totally reformat WANTLIB placement in this case? -- WBR, Vadim Zhukov Index: bin/portbump =================================================================== RCS file: /cvs/ports/infrastructure/bin/portbump,v retrieving revision 1.7 diff -u -p -r1.7 portbump --- bin/portbump 18 Apr 2015 18:59:48 -0000 1.7 +++ bin/portbump 20 Apr 2015 18:23:45 -0000 @@ -47,7 +47,7 @@ my $_rev_neighbors_plain = join('|', @_R sub new { my ($class, $dir) = (shift, shift); - my $self = { dir => $dir, shlibs => {}, verbose => 0 }; + my $self = { dir => $dir, shlibs => {}, verbose => 0, noarch => {} }; # # Get actual information about subpackages (including their @@ -66,6 +66,10 @@ sub new { } elsif ($var eq "SHARED_LIBS") { # perhaps direct " = split (...)" would be enough? $self->{shlibs} = { %{$self->{shlibs}}, split(/\s+/, $value) }; + } elsif ($var eq "SUBPACKAGE") { + $self->{defsubpkg} = $value; + } elsif ($var eq "PKG_ARCH") { + $self->{noarch}->{"-".$subpkg} = 1 if $value eq "*"; } } close $dumpvars; @@ -165,11 +169,92 @@ sub _add_new_revs { return $nchanges; } +sub _put_wantlib_lines { + my ($self, $out, $bumppkgs, $wl_add, $wl_del, $empty_line) + = @_; + + my %wl_del_hash = map { my $v = $_; $v =~ s/[<>=].*//; $v => 1; } + @$wl_add, @$wl_del; + my %sorted_subpkgs_hash = map { $_ => 1 } + keys(%{$self->{wantlib}}), keys(%{$self->{noarch}}); + my @sorted_subpkgs = sort { + return -1 if $a eq ""; + return 1 if $b eq ""; + return -1 if $a eq $self->{defsubpkg}; + return 1 if $b eq $self->{defsubpkg}; + $a cmp $b; + } keys %sorted_subpkgs_hash; + + my $defpkgchanged = 0; + my $nlines = 0; + for my $subpkg (@sorted_subpkgs) { + if (exists $self->{noarch}->{$subpkg}) { + print $out "\nWANTLIB${subpkg} = # no-arch package\n"; + $nlines++; + next; + } + + $self->{wantlib}->{$subpkg}->{wssample} =~ /^ *WANTLIB(?:-[A-Za-z_0-9]*)?(\s*)[\+\?\!]*=/; + my $ws = $1; + + my @libs = grep { + my $wstem = $_; + $wstem =~ s/[<>=].*//; + !exists $wl_del_hash{$wstem} + } @{$self->{wantlib}->{$subpkg}->{libs}}; + next if $subpkg eq "" and scalar(@libs) == 0; + if ($subpkg eq "" or !$defpkgchanged or + !$self->{wantlib}->{$subpkg}->{inheritsglobal}) { + push(@libs, @$wl_add); + } + @libs = sort @libs; + + print $out "\n" unless $empty_line; + $empty_line = 0; + $nlines++; + my $line = "WANTLIB${subpkg}${ws}= "; + my $expanded = 0; + for my $w (@libs) { + if ($w =~ /^\$/) { + $expanded = 1; + } else { + if ($expanded) { + print $out $line."\n"; + $nlines++; + $line = "WANTLIB${subpkg}${ws}+="; + } + $expanded = 0; + } + if (length($line) + 1 + length($w) > 72) { + print $out $line."\n"; + $nlines++; + $line = "WANTLIB${subpkg}${ws}+="; + } + $line .= " ".$w; + } + print $out $line."\n"; + $nlines++; + $defpkgchanged = 1 if $subpkg eq ""; + } + return $nlines; +} + +sub _init_wantlib { + my ($self, $subpkg) = @_; + $self->{wantlib}->{$subpkg} //= {}; + $self->{wantlib}->{$subpkg}->{libs} //= []; + $self->{wantlib}->{$subpkg}->{wssample} //= "WANTLIB += foo"; + $self->{wantlib}->{$subpkg}->{permitline} //= -1; + $self->{wantlib}->{$subpkg}->{reqmanual} //= 0; + $self->{wantlib}->{$subpkg}->{wantlibline} //= 0; + $self->{wantlib}->{$subpkg}->{inheritsglobal} //= []; +} + # -# Parse makefile, searching for places where new REVISION marks -# should be added, and with what whitespace. +# Search for places where new REVISION and WANTLIB marks should be added, +# in given makefile, and with what whitespace. # -sub parse_for_revisions { +sub process_makefile { my ($self, $in) = (shift, shift); # subpkg => { @@ -183,14 +268,66 @@ sub parse_for_revisions { my $revsincurblock = 0; my ($block1begin, $block1end) = (0, 0); + # subpkg => { + # libs => [ wantlib items ... ] + # wssample => a line to look for whitespace sample in + # permitline => number of last line where PERMIT_* variable was set + 1 + # reqmanual => 1 if WANTLIB-foo requires manual intervention + # wantlibline => number of last line where WANTLIB$subpkg is mentioned + # inheritsglobal => 1 if WANTLIB-foo inherits WANTLIB + # } + $self->{wantlib} = {}; + + # indicator if we're in .if or .for block + my $looplevel = 0; + + # list of PERMIT_* variables assigned inside .if/.for, + # used to set permitline at the end of .if/.for block. + my @permits_in_loop = (); + + # indicator if last non-empty line was a PERMIT_* one + my $last_was_permit = 0; + + # indicator if we're continuing WANTLIB line + my $wantlib_block = 0; + my ($w_subpkg, $w_value); + my @mentionedsubpkgs; $self->{has_global_rev} = 0; while (<$in>) { + chomp; + if (/^ *REVISION(\s*)[\+\?\!]*=/) { $self->{has_global_rev} = 1; } - if (/^ *(${_rev_neighbors_plain})(-[A-Za-z_0-9]*)?(\s*)[\+\?\!]*=(\s*)(.*)$/o) { + if (/^ *PERMIT_(?:PACKAGE_(?:CDROM|FTP)|DISTFILES_FTP)(-[A-Za-z_0-9]*)?\b/) { + $last_was_permit = 1; + for my $subpkg(keys %{$self->{wantlib}}) { + $self->{wantlib}->{$subpkg}->{permitline}++ + if $self->{wantlib}->{$subpkg}->{permitline} + == $in->input_line_number(); + } + } elsif (/^ *(?:#.*)?#/) { + for my $subpkg(keys %{$self->{wantlib}}) { + $self->{wantlib}->{$subpkg}->{permitline}++ + if $self->{wantlib}->{$subpkg}->{permitline} + == $in->input_line_number(); + } + } else { + $last_was_permit = 0; + } + + if (/^\. *(if|for)/) { + $looplevel++; + } elsif (/^\. *end(if|for)/) { + $looplevel--; + while (scalar @permits_in_loop) { + my $subpkg = shift @permits_in_loop; + $self->_init_wantlib($subpkg); + $self->{wantlib}->{$subpkg}->{permitline} = $in->input_line_number(); + } + } elsif (/^ *(${_rev_neighbors_plain})(-[A-Za-z_0-9]*)?(\s*)[\+\?\!]*=(\s*)(.*)$/o) { my $var = $1; my $subpkg = $2 // ""; $self->{newrevplace}->{$subpkg} //= {}; @@ -205,6 +342,43 @@ sub parse_for_revisions { } } $block1begin = $in->input_line_number() if !$block1begin; + } elsif ($wantlib_block or /^ *WANTLIB(-[A-Za-z_0-9]*)?\s*[\+\?\!]*=\s*(.*)$/) { + if ($wantlib_block) { + $w_value .= " ".$_; + } else { + $w_subpkg = $1 // ""; + $w_value = $2; + $self->_init_wantlib($w_subpkg); + $self->{wantlib}->{$w_subpkg}->{wssample} = $_; + $self->{wantlib}->{$w_subpkg}->{reqmanual} = 1 + if !exists $self->{mpkgs}->{$w_subpkg}; + } + $wantlib_block = $w_value =~ s/\s*\\$//; + next if $wantlib_block; + + if ($looplevel) { + $self->{wantlib}->{$w_subpkg}->{reqmanual} = 1; + } else { + $self->{wantlib}->{$w_subpkg}->{line} = $in->input_line_number(); + next if $self->{wantlib}->{$w_subpkg}->{reqmanual}; + while ($w_value =~ /(\S+)/g) { + my $w = $1; + if ($w !~ /^\$[\{\(].*[\}\)]$/) { + # no problems + } elsif ($w =~ /^\$[\{\(]WANTLIB[\}\)]$/) { + $self->{wantlib}->{$w_subpkg}->{inheritsglobal} = 1; + } elsif ($w !~ /^\$[\{\(]MOD.*[\}\)]$/) { + $self->{wantlib}->{$w_subpkg}->{reqmanual} = 1; + } + push @{$self->{wantlib}->{$w_subpkg}->{libs}}, $w; + } + delete $self->{wantlib}->{$w_subpkg}->{blockend}; + } + } elsif (/^ *PERMIT_(?:PACKAGE_(?:CDROM|FTP)|DISTFILES_FTP)(-[A-Za-z_0-9]*)?\b/) { + my $subpkg = $1 // ""; + $self->_init_wantlib($subpkg); + $self->{wantlib}->{$subpkg}->{permitline} = $in->input_line_number(); + push(@permits_in_loop, $subpkg) if $looplevel; } elsif (/^\s*$/) { for my $subpkg(@mentionedsubpkgs) { $self->{newrevplace}->{$subpkg}->{blockend} = $in->input_line_number(); @@ -213,7 +387,6 @@ sub parse_for_revisions { if $self->{maxrevsin}->{blockend} == 0; @mentionedsubpkgs = (); $revsincurblock = 0; - $block1end = $in->input_line_number() if $block1begin && !$block1end; } elsif (!/^ *(\#|BROKEN|COMES_WITH|IGNORE|NOT_FOR_ARCHS|ONLY_FOR_ARCHS|SHARED_ONLY)/) { @@ -227,10 +400,42 @@ sub parse_for_revisions { $self->{maxrevsin}->{blockend} = $block1end ? $block1end : $in->input_line_number(); } + + my $wstart = -1; + # first, try to find a place for WANTLIB after PERMIT_* lines + for my $subpkg(keys %{$self->{wantlib}}) { + if ($self->{wantlib}->{$subpkg}->{permitline} > $wstart) { + $wstart = $self->{wantlib}->{$subpkg}->{permitline}; + } + } + # next, find first WANTLIB line and use it + if ($wstart == -1) { + $wstart = $in->input_line_number(); + for my $subpkg(keys %{$self->{wantlib}}) { + if ($self->{wantlib}->{$subpkg}->{line} < $wstart) { + $wstart = $self->{wantlib}->{$subpkg}->{line}; + } + } + } + # finally, try to anchor to @_REV_NEIGHBORS items + if ($wstart == $in->input_line_number()) { + $wstart = -1; + for my $subpkg(keys %{$self->{newrevplace}}) { + if ($self->{newrevplace}->{$subpkg}->{line} > $wstart) { + $wstart = $self->{newrevplace}->{$subpkg}->{line}; + } + } + if ($wstart == -1) { + $self->{wantlib}->{""}->{reqmanual} = 1; + } else { + $wstart++; + } + } + $self->{wantlib_start} = $wstart; } { my %manual_noticed; -sub _update_libs { +sub _update_shlibs { my ($self, $shline) = @_; my @splitres = split(/\s+/, $shline); my $nchanges = 0; @@ -256,15 +461,36 @@ sub _update_libs { } } sub update { - my ($self, $in, $out, $bumppkgs, $removerevs, $bumplibs) = @_; + my ($self, $in, $out, $bumppkgs, $bumprevs, $removerevs, $bumpshlibs, + $wl_add, $wl_del) = @_; + if (scalar(@$wl_add) or scalar(@$wl_del)) { + for my $subpkg (keys %$bumppkgs) { + next unless exists $bumppkgs->{$subpkg}; + next unless $self->{wantlib}->{$subpkg}->{reqmanual}; + printf STDERR "%s requires manual WANTLIB handling\n", + $self->{dir}; + return -1; + } + } + +my $tout; +open($tout, '>', '/dev/null'); +#$tout = $out; my $defbumped = 0; - my $shlib_block = 0; + my ($shlib_block, $wantlib_block) = (0, 0); my $nchanges = 0; + my $wl_ws_removal; + my ($empty_line, $prev_line_was_empty) = (1, 1); + while (<$in>) { chomp; + $prev_line_was_empty = $empty_line; + $empty_line = 0; if ($shlib_block or /^ *SHARED_LIBS/) { +print $tout "wl_ws_removal reset to undef in shlib block\n" if defined $wl_ws_removal; + $wl_ws_removal = undef; my $shline = $_; # N.B.: Some ports define SHARED_LIBS in subpackage- @@ -274,21 +500,32 @@ sub update { # for additional tasks. $shlib_block or $shline =~ s/^ *SHARED_LIBS(?:\S+)?\s*\+?=\s*//; - $shlib_block = $shline =~ s/\\$//; - if ($bumplibs) { + $shlib_block = $shline =~ s/\s*\\$//; + if ($bumpshlibs) { # XXX will misbehave after "<...> # \" $shline =~ s/\\s*#.*//; $shline =~ s/^\s*//; - $nchanges += $self->_update_libs($shline); + $nchanges += $self->_update_shlibs($shline); + } + } elsif ($wantlib_block or /^ *WANTLIB(?:-[a-zA-Z0-9_]*)?\s*[\+\?\!]*=/) { + my $line = $_; + $wantlib_block = $line =~ s/\s*\\$//; + if (scalar(@$wl_add) or scalar(@$wl_del)) { +print $tout "wl_ws_removal=0 AFTER $_\n"; + $_ = undef; + $empty_line = $prev_line_was_empty; + $wl_ws_removal = 0 unless defined $wl_ws_removal; } } elsif (/^ *REVISION(-[a-zA-Z0-9_]+)?.*=\s*([0-9]*)$/) { +print $tout "wl_ws_removal reset to undef in REVISION block\n" if defined $wl_ws_removal; + $wl_ws_removal = undef; my $subpkg = $1 // ""; if ($removerevs) { $nchanges++; - next; - } elsif (!defined($bumppkgs)) { - print $out "$_\n"; - next; + $_ = undef; + $empty_line = $prev_line_was_empty; + } elsif (!$bumprevs) { + # do nothing } elsif (exists $bumppkgs->{$subpkg} or ($subpkg eq "" and scalar(keys %{$self->{mpkgs}}) == scalar(keys %{$bumppkgs}))) { @@ -301,19 +538,49 @@ sub update { delete $bumppkgs->{$subpkg}; $defbumped = 1 if $subpkg eq ""; } + } elsif (/^\s*$/) { +print $tout "EMPTY LINE\n"; + $empty_line = 1; + if (defined $wl_ws_removal) { +print $tout "CHECKING: wl_ws_removal=$wl_ws_removal\n"; + $_ = undef if $wl_ws_removal > 0; + $wl_ws_removal++; + } + } elsif (defined($wl_ws_removal) and $wl_ws_removal != -1) { +print $tout "wl_ws_removal reset to undef in ELSE block\n" if defined $wl_ws_removal; + $wl_ws_removal = undef; + } + if (!$empty_line and defined($wl_ws_removal) and $wl_ws_removal == -1) { +print $tout "PRINTING EXTRA EMPTY LINE\n"; + print $out "\n"; + $wl_ws_removal = undef; + } + + if ($bumprevs and !$defbumped) { + my $n = $self->_add_new_revs($out, + $in->input_line_number(), $bumppkgs); + $wl_ws_removal = undef if $n; + $nchanges += $n; } - $nchanges += $self->_add_new_revs($out, $in->input_line_number(), $bumppkgs) - unless $defbumped; + print $out "$_\n" if defined $_; - print $out "$_\n"; + if ($in->input_line_number() == $self->{wantlib_start} and + (scalar(@$wl_add) or scalar(@$wl_del))) { + $nchanges += $self->_put_wantlib_lines($out, + $bumppkgs, $wl_add, $wl_del, $empty_line); + $wl_ws_removal = 0; +print $tout "wl_ws_removal=0 AFTER PUTTING\n"; + } } - for my $subpkg(sort keys %{$bumppkgs}) { - next if defined $self->{newrevplace}->{$subpkg}->{blockend}; - print STDERR "can't find a suitable place for ". - "REVISION${subpkg} mark in ".$self->{dir}."\n"; - exit 2; + if ($bumprevs) { + for my $subpkg(sort keys %{$bumppkgs}) { + next if defined $self->{newrevplace}->{$subpkg}->{blockend}; + print STDERR "can't find a suitable place for ". + "REVISION${subpkg} mark in ".$self->{dir}."\n"; + return -1; + } } return $nchanges; @@ -337,20 +604,25 @@ sub usage { our ($opt_d, $opt_M, $opt_m, $opt_n, $opt_o, $opt_r, $opt_v) = (0, 0, 0, 0, undef, undef, 0); +my (@wl_add, @wl_del); -eval { getopts('dMmno:rv', { +eval { getopts('dMmno:rW:w:v', { 'd' => sub { $opt_d++; }, 'M' => sub { $opt_M++; }, 'm' => sub { $opt_m++; }, 'n' => sub { $opt_n++; }, 'o' => sub { $opt_o = shift; }, 'v' => sub { $opt_v++; }, - 'r' => sub { $opt_r++; }, + 'r' => sub { $opt_r = 1; }, + 'W' => sub { push(@wl_add, shift); }, + 'w' => sub { push(@wl_del, shift); }, }) } // usage $@; $opt_d && $opt_r and usage "cannot mix -d and -r options"; $opt_m && $opt_M and usage "cannot mix -M and -m options"; -!$opt_M && !$opt_m && !$opt_d && !defined($opt_r) and $opt_r = 1; +!defined($opt_r) && !$opt_M && !$opt_m && !$opt_d + && scalar(@wl_add) == 0 && scalar(@wl_del) == 0 + and $opt_r = 1; my %allpkgs; # dir => { subpkg => 1, ... }; @@ -396,6 +668,7 @@ if ($opt_v) { } } +my $exitstatus = 0; for my $dir (keys %allpkgs) { my $port = PortHandler->new($dir); $port->verbose(1) if $opt_v; @@ -424,12 +697,10 @@ for my $dir (keys %allpkgs) { open (my $in, '<', "$dir/Makefile") or die "cannot open input file $dir/Makefile"; - $port->parse_for_revisions($in); + $port->process_makefile($in); my $bumppkgs; - if (!$opt_r) { - $bumppkgs = undef; - } elsif (scalar(keys %{$allpkgs{$dir}}) != 0) { + if (scalar(keys %{$allpkgs{$dir}}) != 0) { for my $subpkg (keys %{$allpkgs{$dir}}) { next if exists $port->{mpkgs}->{$subpkg}; die "there is no $dir,$subpkg package"; @@ -443,20 +714,27 @@ for my $dir (keys %allpkgs) { # Actual update process. # - open (my $out, '>', $opt_o // "$dir/Makefile.bump") or - die "cannot open output file $dir/Makefile.bump"; + my $outpath = $opt_o // "$dir/Makefile.bump"; + open (my $out, '>', $outpath) or + die "cannot open output file $outpath"; seek($in, 0, 0); $in->input_line_number(0); - my $nchanges = $port->update($in, $out, $bumppkgs, $opt_d, $opt_m|$opt_M); + my $nchanges = $port->update($in, $out, $bumppkgs, $opt_r, $opt_d, + $opt_m|$opt_M, \@wl_add, \@wl_del); close($in); close($out); - if (!defined $opt_o) { + if ($nchanges == -1) { + # warning message should be printed already + unlink $outpath; + $exitstatus = 1; + } elsif (!defined $opt_o) { if (!$nchanges) { print STDERR "nothing to do in $dir\n" if $opt_v; - unlink "$dir/Makefile.bump"; + unlink $outpath; } elsif (!$opt_n) { - rename("$dir/Makefile.bump", "$dir/Makefile") or - die "cannot move $dir/Makefile.bump to $dir/Makefile" + rename($outpath, "$dir/Makefile") or + die "cannot move $outpath to $dir/Makefile" } } } +exit $exitstatus; Index: mk/bsd.port.mk =================================================================== RCS file: /cvs/ports/infrastructure/mk/bsd.port.mk,v retrieving revision 1.1289 diff -u -p -r1.1289 bsd.port.mk --- mk/bsd.port.mk 5 Apr 2015 13:32:16 -0000 1.1289 +++ mk/bsd.port.mk 20 Apr 2015 18:23:49 -0000 @@ -129,14 +129,14 @@ _ALL_VARIABLES += HOMEPAGE DISTNAME \ CONFIGURE_STYLE USE_LIBTOOL SEPARATE_BUILD \ SHARED_LIBS TARGETS PSEUDO_FLAVOR \ MAINTAINER AUTOCONF_VERSION AUTOMAKE_VERSION CONFIGURE_ARGS \ - PKG_ARCH GH_ACCOUNT GH_COMMIT GH_PROJECT GH_TAGNAME PORTROACH \ + GH_ACCOUNT GH_COMMIT GH_PROJECT GH_TAGNAME PORTROACH \ MAKEFILE_LIST _ALL_VARIABLES_PER_ARCH += BROKEN # and stuff needing to be MULTI_PACKAGE'd _ALL_VARIABLES_INDEXED += COMMENT PKGNAME \ ONLY_FOR_ARCHS NOT_FOR_ARCHS PKGSPEC PREFIX \ PERMIT_PACKAGE_FTP PERMIT_PACKAGE_CDROM WANTLIB CATEGORIES DESCR \ - EPOCH REVISION STATIC_PLIST + EPOCH REVISION STATIC_PLIST PKG_ARCH .endif # special purpose user settings PATCH_CHECK_ONLY ?= No