Here is an improved version of portbump(1). I'm using it often now, and it has most bugs I was aware of fixed. There are some tests also; not included for now but ready to be imported under ${PORTSDIR}/tests/. I think it's in usable form now and in conjuction with sqlports it could save hours of porter work. So - okay to import it as ${PORTSDIR}/bin/portbump?
-- WBR, Vadim Zhukov ### CUT HERE ### #!/usr/bin/perl package Util; use strict; use warnings; our @EXPORT = qw(phash); use subs qw(phash); # prints hash in compact form, for debugging purposes sub phash($) { my $h = shift; "{ ".join (", ", map { $_."=>".(ref($h->{$_}) eq 'HASH' ? phash($h->{$_}) : $h->{$_}) } (sort keys %{$h})) . " }"; } sub spc_per_tab() { 8 } sub expand_tabs($) { my $line = shift; while ($line =~ /\t/) { my $pos = $-[0] + spc_per_tab - 1; $pos -= $pos % spc_per_tab; $line = $` . (' ' x (($pos - $-[0]) || spc_per_tab)) . $'; } return $line; } ################################################################# package PortHandler; use strict; use warnings; our @EXPORT = qw(new update); sub new($$) { my ($class, $dir) = (shift, shift); my $this = { dir => $dir, shlibs => {}, verbose => 0 }; # # Get actual information about subpackages (including their # REVISIONs) and shared libraries. # open (my $dumpvars, '-|', "make", "SUBDIR=$dir", "dump-vars") or die "cannot run make dump-vars"; while (<$dumpvars>) { chomp; next unless /^[^,]*(?:,-([^.]+))?\.([^=.]+)=(.*)$/; my ($subpkg, $var, $value) = ($1, $2, $3); $subpkg //= ""; if ($var eq "MULTI_PACKAGES") { $this->{mpkgs} = { map { $_ => 1 } split(/\s+/, $value) }; } elsif ($var eq "SHARED_LIBS") { # perhaps direct " = split (...)" would be enough? $this->{shlibs} = { %{$this->{shlibs}}, split(/\s+/, $value) }; } } close $dumpvars; if (scalar(keys %{$this->{mpkgs}}) == 1 and exists($this->{mpkgs}->{"-"})) { $this->{mpkgs} = { "" => 1 }; } return bless($this, $class); } sub verbose($;$) { my $this = shift; my $rv = $this->{verbose}; $this->{verbose} = $_[0] if defined $_[0]; return $rv; } # Formats and returns string of "var = value" with whitespace adjustment # done like in the sample given line. sub _adj_whitespace($$$$) { my ($this, $var, $value, $wssample) = @_; unless (defined($wssample) and $wssample =~ /^( *)([A-Za-z0-9_-]+)(\s*)[\+\?\!]*=(\s*)/) { return "$var =\t$value"; } my $start_ws = $1 // ""; my $before_eq_ws = $3 // ""; my $after_eq_ws = $4 // ""; my $svalue_pos = $+[4]; my $line = $start_ws.$var.$before_eq_ws."="; my $line_exp = Util::expand_tabs($line); my $wssample_exp = Util::expand_tabs($wssample); my $svalue_pos_exp = $svalue_pos + (length($wssample_exp) - length($wssample)); my $elen = length($line_exp); if ($elen > $svalue_pos_exp) { # too long anyway, just add a tab and be done with it $line .= "\t"; } elsif ($elen < $svalue_pos_exp) { if ($after_eq_ws =~ /^\t*$/) { # tab-based separation while ($elen < $svalue_pos_exp) { my $n_spc_to_add = ($svalue_pos_exp - $elen); $n_spc_to_add %= Util::spc_per_tab; $n_spc_to_add ||= Util::spc_per_tab; $elen += $n_spc_to_add; $line .= "\t"; } } else { # space-based separation $line .= ' ' x ($svalue_pos_exp - length($line_exp)); } } return $line.$value; } sub _is_mpkg_port($) { my $this = shift; return 1 if scalar(keys %{$this->{mpkgs}}) != 1; my $k = each %{$this->{mpkgs}}; return 1 if $k ne ""; return 0; } sub _add_new_revs($$$$) { my ($this, $out, $lineno, $bumppkgs) = (shift, shift, shift, shift); # Note: $lineno is the input file's line number, not output's one. if ($this->{maxrevsin}->{count} > 1) { return 0 unless $lineno == $this->{maxrevsin}->{blockend}; } if ($this->{has_global_rev}) { return 0 unless $this->_is_mpkg_port; } my $nchanges = 0; for my $subpkg(sort keys %{$bumppkgs}) { if ($this->{maxrevsin}->{count} > 1 or $lineno == $this->{newrevplace}->{$subpkg}->{blockend}) { my $line = $this->_adj_whitespace( "REVISION" . $subpkg, "0", $this->{newrevplace}->{$subpkg}->{wssample}); print $out $line, "\n"; $nchanges++; } } return $nchanges; } # # Parse makefile, searching for places where new REVISION marks # should be added, and with what whitespace. # sub parse_for_revisions($$) { my ($this, $in) = (shift, shift); # subpkg => { # line => number of line where subpackage is mentioned # wssample => a line from block to look for whitespace sample in # blockend => block ending line number # } $this->{newrevplace} = {}; $this->{maxrevsin} = { blockend => 0, count => 0 }; my $revsincurblock = 0; my ($block1begin, $block1end) = (0, 0); my @mentionedsubpkgs; $this->{has_global_rev} = 0; while (<$in>) { if (/^ *REVISION(\s*)[\+\?\!]*=/) { $this->{has_global_rev} = 1; } if (/^ *(V|DISTNAME|(?:FULL)?PKGNAME|REVISION)(-[A-Za-z_0-9]*)?(\s*)[\+\?\!]*=(\s*)(.*)$/) { my $var = $1; my $subpkg = $2 // ""; $this->{newrevplace}->{$subpkg} //= {}; $this->{newrevplace}->{$subpkg}->{line} = $in->input_line_number(); $this->{newrevplace}->{$subpkg}->{wssample} = $_; delete $this->{newrevplace}->{$subpkg}->{blockend}; push(@mentionedsubpkgs, $subpkg); if ($var eq "REVISION") { if (++$revsincurblock > $this->{maxrevsin}->{count}) { $this->{maxrevsin}->{blockend} = 0; $this->{maxrevsin}->{count} = $revsincurblock; } } $block1begin = $in->input_line_number() if !$block1begin; } elsif (/^\s*$/) { for my $subpkg(@mentionedsubpkgs) { $this->{newrevplace}->{$subpkg}->{blockend} = $in->input_line_number(); } $this->{maxrevsin}->{blockend} = $in->input_line_number() if $this->{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)/) { $block1begin = $in->input_line_number() if !$block1begin; } } for my $subpkg(@mentionedsubpkgs) { $this->{newrevplace}->{$subpkg}->{blockend} = $in->input_line_number(); } if ($this->{maxrevsin}->{blockend} == 0) { $this->{maxrevsin}->{blockend} = $block1end ? $block1end : $in->input_line_number(); } } sub update($$$$$) { my ($this, $in, $out, $bumppkgs, $removerevs) = @_; my $defbumped = 0; my $shlib_block = 0; my $nchanges = 0; while (<$in>) { chomp; if ($shlib_block or /^ *SHARED_LIBS/) { my $shline = $_; $shlib_block or $shline =~ s/^ *SHARED_LIBS\s*\+?=\s*//; $shlib_block = $shline =~ s/\\$//; # XXX will misbehave after "<...> # \" $shline =~ s/\s*#.*//; #$shline =~ s/\s*$//; $shline =~ s/^\s*//; # XXX will break in "libfoo 0.0 libbar" case my %lineshlibs = split(/\s+/, $shline); for my $lib (keys %lineshlibs) { # Some ports define SHARED_LIBS in # subpackage-dependant way, i.e., # add them only if the corresponding # subpackage should be built. my $v = $this->{shlibs}->{$lib} // next; printf STDERR "%-30s: changing shared library ". "%s version to %s\n", $this->{dir}, $lib, $v if $this->{verbose}; $nchanges++ if $_ =~ s/($lib\s+)[0-9]+\.[0-9]+/$1$v/g; } } elsif (/^ *REVISION(-[a-zA-Z0-9_]+)?.*=\s*([0-9]*)$/) { my $subpkg = $1 // ""; if ($removerevs) { $nchanges++; next; } elsif (!defined($bumppkgs)) { print $out "$_\n"; next; } elsif (exists $bumppkgs->{$subpkg} or ($subpkg eq "" and scalar(keys %{$this->{mpkgs}}) == scalar(keys %{$bumppkgs}))) { my $rev = $2 // -1; my $newrev = $rev + 1; printf STDERR "%-30s: changing %s to %d\n", $this->{dir}, $_, $newrev if $this->{verbose}; $nchanges++ if s/[0-9]*$/$newrev/; delete $bumppkgs->{$subpkg}; $defbumped = 1 if $subpkg eq ""; } } $nchanges += $this->_add_new_revs($out, $in->input_line_number(), $bumppkgs) unless $defbumped; print $out "$_\n"; } return $nchanges; } ################################################################# package main; use strict; use warnings; use v5.14; use Getopt::Std; $Getopt::Std::STANDARD_HELP_VERSION = 1; sub usage { print join("\n", @_)."\n" if scalar @_; print STDERR "usage: portbump [-dMmrnv] [dir] ...\n"; print STDERR " portbump [-dMmrnov] [dir]\n"; exit 1; } our ($opt_d, $opt_M, $opt_m, $opt_n, $opt_o, $opt_r, $opt_v) = (0, 0, 0, 0, undef, undef, 0); getopts('dMmno:rv') or 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; my %allpkgs; # dir => { subpkg => 1, ... }; my %newrevplace; scalar(@ARGV) or @ARGV = ("."); for (@ARGV) { # zap any FLAVOR information to make it easier to feed from of sqlports s/,+[^,-]*/,/g; # XXX handle "-" subpackage case? if (/^(.*),(-.+)$/) { my $subdir = $1 || "."; if (defined $allpkgs{$subdir}) { if (scalar($allpkgs{$subdir}) == 0) { die "mixed non-subpackaged and subpackaged for $subdir"; } elsif (exists $allpkgs{$subdir}->{$2}) { # XXX maybe just ignore? $opt_v and print STDERR "double bump of \"$_\" requested, ignoring"; } } else { $allpkgs{$subdir} = {}; } $allpkgs{$subdir}->{$2} = 1; } else { if (defined $allpkgs{$_}) { die "mixed non-subpackaged and subpackaged for $_"; } $allpkgs{$_} = {}; } } if (defined($opt_o) and scalar(keys %allpkgs) > 1) { usage "cannot use -o if more than one port is being processed"; } if ($opt_v) { print STDERR "port directories to visit:\n"; for my $dir (keys %allpkgs) { print STDERR "\t$dir\n"; } } for my $dir (keys %allpkgs) { my $port = PortHandler->new($dir); $port->verbose(1) if $opt_v; # # Bump library versions, if requested. # if ($opt_M or $opt_m) { for my $lib (keys %{$port->{shlibs}}) { my ($major, $minor) = split(/\./, $port->{shlibs}->{$lib}); if ($opt_M) { $major++; $minor = 0; } else { $minor++; } $port->{shlibs}->{$lib} = "${major}.${minor}"; } } # # Read port information, choose what subpackages to bump. # open (my $in, '<', "$dir/Makefile") or die "cannot open input file $dir/Makefile"; $port->parse_for_revisions($in); my $bumppkgs; if (!$opt_r) { $bumppkgs = undef; } elsif (scalar(keys %{$allpkgs{$dir}}) != 0) { for my $subpkg (keys %{$allpkgs{$dir}}) { next if exists $port->{mpkgs}->{$subpkg}; die "there is no $dir,$subpkg package"; } $bumppkgs = $allpkgs{$dir}; } else { $bumppkgs = $port->{mpkgs}; } # # Actual update process. # open (my $out, '>', $opt_o // "$dir/Makefile.bump") or die "cannot open output file $dir/Makefile.bump"; seek($in, 0, 0); $in->input_line_number(0); my $nchanges = $port->update($in, $out, $bumppkgs, $opt_d); close($in); close($out); if (!defined $opt_o) { if (!$nchanges) { print STDERR "nothing to do in $dir\n" if $opt_v; unlink "$dir/Makefile.bump"; } elsif (!$opt_n) { rename("$dir/Makefile.bump", "$dir/Makefile") or die "cannot move $dir/Makefile.bump to $dir/Makefile" } } } ### CUT HERE ### .\" $OpenBSD$ .\" .\" Copyright (c) 2014 Vadim Zhukov .\" .\" Permission to use, copy, modify, and distribute this software for any .\" purpose with or without fee is hereby granted, provided that the above .\" copyright notice and this permission notice appear in all copies. .\" .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .Dd $Mdocdate$ .Dt PORTBUMP 1 .Os .Sh NAME .Nm portbump .Nd tweaks port revisions and library versions .Sh SYNOPSIS .Nm .Op Fl Mmnrv .Op Ar portref ... .Pp .Nm .Op Fl Mmnrv .Op Fl o Ar file .Op Ar portref .Sh DESCRIPTION .Nm is used to increase .Ev REVISION and .Ev SHARED_LIBS values in .Ox ports. It also can remove .Ev REVISION marks. .Pp .Ar portref has the same syntax as .Ev FULLPKGPATH port variable, see .Xr bsd.port.mk 5 . Actually, you can feed a list of pkgpaths as parameters; flavor information will be ignored. .Nm doesn't care about actual subdirectory portion of .Ar portref , so you can freely pass .Sq . , .Sq ../foo or anything else like this. .Pp If subpackage is not specified in port reference, then all subpackages of a port will be processed. If no port references are given, the port in current directory will be processed. .Pp The following options are available: .Bl -tag -width Ds .It Fl d Delete all REVISION marks in .Pa Makefile . Mutally exclusive with .Fl r . .It Fl M Increment by one major component of all .Ev SHARED_LIBS , resetting minor one to zero if needed. Mutally exclusive with .Fl m . .It Fl m Increment by one minor component of all .Ev SHARED_LIBS . Mutally exclusive with .Fl M . .It Fl n Do not replace .Pa Makefile but save modified version in the .Pa Makefile.bump instead. .It Fl o Ar file Send modified Makefile contents to a given file instead of creating .Pa Makefile.bump in port's directory. This could be only used if not more than one port is specified; multiple subpackages of a single port could be specified, though. .It Fl r Increment .Ev REVISION values for all subpackages, or only for given ones. This is a default mode of operation, unless .Fl M or .Fl m flag is specified. The .Fl r flag could be combined explicitly with either of those two, though. Mutally exclusive with .Fl d . .It Fl v Enable printing of diagnostic messages to stdandard error output. .El .Sh HISTORY .Nm first appeared in .Ox 5.6 .