From: David Mitchell Date: Tue, 14 Jul 2009 22:22:37 +0000 (+0100) Subject: add Porting/bump-perl-version X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=ae1b7029e546199472d907a2a9263b60490aa733;p=p5sagit%2Fp5-mst-13.2.git add Porting/bump-perl-version utility for bumping the value of the perl version in lots of files --- diff --git a/Porting/bump-perl-version b/Porting/bump-perl-version new file mode 100755 index 0000000..4d4df83 --- /dev/null +++ b/Porting/bump-perl-version @@ -0,0 +1,339 @@ +#!/usr/bin/perl +# +# bump-perl-version, DAPM 14 Jul 2009 +# +# A utility to find, and optionally bump, references to the perl version +# number in various files within the perl source +# +# It's designed to work in two phases. First, when run with -s (scan), +# it searches all the files in MANIFEST looking for strings that appear to +# match the current perl version (or which it knows are *supposed* to +# contain the current version), and produces a list of them to stdout, +# along with a suggested edit. For example: +# +# $ Porting/bump-perl-version -s 5.10.0 5.10.1 > /tmp/scan +# $ cat /tmp/scan +# Porting/config.sh +# +# 52: -archlib='/opt/perl/lib/5.10.0/i686-linux-64int' +# +archlib='/opt/perl/lib/5.10.1/i686-linux-64int' +# .... +# +# At this point there will be false positives. Edit the file to remove +# those changes you don't want made. Then in the second phase, feed that +# list in, and it will change those lines in the files: +# +# $ Porting/bump-perl-version -u < /tmp/scan +# +# (so line 52 of Porting/config.sh is now updated) + +# This utility 'knows' about certain files and formats, and so can spot +# 'hidden' version numbers, like PERL_SUBVERSION=9. +# +# A third variant makes use of this knowledge to check that all the things +# it knows about are at the current version: +# +# $ Porting/bump-perl-version -c 5.10.0 +# +# XXX this script hasn't been tested against a major version bump yet, +# eg 5.11.0 to 5.12.0; there may be things it missed - DAPM 14 Jul 09 +# +# Note there are various files and directories that it skips; these are +# ones that are unlikely to contain anything needing bumping, but which +# will generate lots fo false positives (eg pod/*). These are listed on +# STDERR as they are skipped. + +use strict; +use warnings; +use Getopt::Std; +use ExtUtils::Manifest; + + +sub usage { die < + -s + -u + + -c check files and warn if any known string values (eg + PERL_SUBVERSION) don't match the specified version + + -s scan files and produce list of possible change lines to stdout + + -u read in the scan file from stdin, and change all the lines specified + + C.C.C the current perl version, eg 5.10.0 + N.N.N the new perl version, eg 5.10.1 +EOF + +my %opts; +getopts('csu', \%opts) or usage; +if ($opts{u}) { + @ARGV == 0 or usage('no version version numbers should be speciied'); + # fake to stop warnings when calculating $oldx etc + @ARGV = qw(99.99.99 99.99.99); +} +elsif ($opts{c}) { + @ARGV == 1 or usage('required one version number'); + push @ARGV, $ARGV[0]; +} +else { + @ARGV == 2 or usage('require two version numbers'); +} +usage('only one of -c, -s and -u') if keys %opts > 1; + +my ($oldx, $oldy, $oldz) = $ARGV[0] =~ /^(\d+)\.(\d+)\.(\d+)$/ + or usage("bad version: $ARGV[0]"); +my ($newx, $newy, $newz) = $ARGV[1] =~ /^(\d+)\.(\d+)\.(\d+)$/ + or usage("bad version: $ARGV[1]"); + +my $old_decimal = sprintf "%d.%03d%03d", $oldx, $oldy, $oldz; # 5.011001 + +# each entry is +# 0 a regexp that matches strings that might contain versions; +# 1 a sub that returns two strings based on $1 etc values: +# * string containing captured values (for -c) +# * a string containing the replacement value +# 2 what we expect the sub to return as its first arg; undef implies +# don't match +# 3 a regex restricting which files this applies to (undef is all files) +# +# Note that @maps entries are checks in order, and only the first to match +# is used. + +my @maps = ( + [ + qr{^((?:api_)?version(?:=|\s+)'?) (\d+) ('?) (?!\.)}x, + sub { $2, "$1$newy$3" }, + $oldy, + qr/config/, + ], + [ + qr{^(subversion(?:=|\s+)'?) (\d+) ('?) (?!\.)}x, + sub { $2, "$1$newz$3" }, + $oldz, + qr/config/, + ], + [ + qr{^(api_subversion(?:=|\s+)'?) (\d+) ('?) (?!\.)}x, + sub { $2, "${1}0$3" }, + 0, + qr/config/, + ], + [ + qr{^(api_versionstring(?:=|\s+)'?) ([\d\.]+) ('?) (?!\.)}x, + sub { $2, "$1$newx.$newy.0$3" }, + "$oldx.$oldy.0", + qr/config/, + ], + [ + qr{(version\s+'?) (\d+) ('?\s+subversion\s+'?) (\d+) ('?) (?!\.)}x, + sub { "$2-$4", "$1$newy$3$newz$5" }, + "$oldy-$oldz", + qr/config/, + ], + [ + qr{\b (PERL_(?:API_)?VERSION(?:=|\s+)'?) (\d+) ('?) (?!\.)}x, + sub { $2, "$1$newy$3"}, + $oldy, + ], + [ + qr{\b (PERL_SUBVERSION(?:=|\s+)'?) (\d+) ('?) (?!\.)}x, + sub { $2, "$1$newz$3"}, + $oldz, + ], + [ + qr{\b (PERL_API_SUBVERSION(?:=|\s+)'?) (\d+) ('?) (?!\.)}x, + sub { $2, "${1}0$3"}, + 0, + ], + # these two formats are in README.vms + [ + qr{\b perl-(\d+\^\.\d+\^\.\d+) \b}x, + sub { $1, "perl-$newx^.$newy^.$newz"}, + undef, + ], + [ + qr{\b ($oldx _ $oldy _$oldz) \b}x, + sub { $1, ($newx . '_' . $newy . '_' . $newz)}, + undef, + ], + # 5.8.9 + [ + qr{\b $oldx\.$oldy\.$oldz \b}x, + sub {"", "$newx.$newy.$newz"}, + undef, + ], + + # 5.008009 + [ + qr{\b $old_decimal \b}x, + sub {"", sprintf "%d.%03d%03d", $newx, $newy, $newz }, + undef, + ], + +); + + +# files and dirs that we likely don't want to change version numbers on. + +my %SKIP_FILES = map { ($_ => 1) } qw( + Changes + MANIFEST + Porting/how_to_write_a_perldelta.pod + Porting/mergelog + Porting/mergelog-tool + pod.lst +); +my @SKIP_DIRS = qw( + ext + lib + pod + t +); + +my @mani_files = sort keys %{ExtUtils::Manifest::maniread('MANIFEST')}; +my %mani_files = map { ($_ => 1) } @mani_files; +die "No entries found in MANIFEST; aborting\n" unless @mani_files; + +if ($opts{c} or $opts{s}) { + do_scan(); +} +elsif ($opts{u}) { + do_update(); +} +else { + usage('one of -c, -s or -u must be specifcied'); +} +exit 0; + + + + +sub do_scan { + for my $file (@mani_files) { + next if grep $file =~ m{$_/}, @SKIP_DIRS; + if ($SKIP_FILES{$file}) { + warn "(skipping $file)\n"; + next; + } + open my $fh, '<', $file or die "Aborting: can't open $file: $!\n"; + my $header = 0; + + while (<$fh>) { + for my $map (@maps) { + my ($pat, $sub, $expected, $file_pat) = @$map; + + next if defined $file_pat and $file !~ $file_pat; + next unless $_ =~ $pat; + my ($got, $replacement) = $sub->(); + + if ($opts{c}) { + # only report unexpected + next unless defined $expected and $got ne $expected; + } + my $newstr = $_; + $newstr =~ s/$pat/$replacement/ + or die "Internal error: substitution failed: [$pat]\n"; + if ($_ ne $newstr) { + print "\n$file\n" unless $header; + $header=1; + printf "\n%5d: -%s +%s", $., $_, $newstr; + } + last; + } + } + } + warn "(skipped $_/*)\n" for @SKIP_DIRS; +} + +sub do_update { + + my %changes; + my $file; + my $line; + + # read in config + + while () { + next unless /\S/; + if (/^(\S+)$/) { + $file = $1; + die "No such file in MANIFEST: '$file'\n" unless $mani_files{$file}; + die "file already seen; '$file'\n" if exists $changes{$file}; + undef $line; + } + elsif (/^\s+(\d+): -(.*)/) { + my $old; + ($line, $old) = ($1,$2); + die "$.: old line without preceeding filename\n" + unless defined $file; + die "Dup line number: $line\n" if exists $changes{$file}{$line}; + $changes{$file}{$line}[0] = $old; + } + elsif (/^\s+\+(.*)/) { + my $new = $1; + die "$.: replacement line seen without old line\n" unless $line; + $changes{$file}{$line}[1] = $new; + undef $line; + } + else { + die "Unexpected line at ;line $.: $_\n"; + } + } + + # suck in file contents to memory, then update that in-memory copy + + my %contents; + for my $file (sort keys %changes) { + open my $fh, '<', $file or die "open '$file': $!\n"; + $contents{$file} = [ <$fh> ]; + chomp @{$contents{$file}}; + close $fh or die "close: '$file': $!\n"; + + my $entries = $changes{$file}; + for my $line (keys %$entries) { + die "$file: no such line: $line\n" + unless defined $contents{$file}[$line-1]; + if ($contents{$file}[$line-1] ne $entries->{$line}[0]) { + die "$file: line mismatch at line $line:\n" + . "File: [$contents{$file}[$line-1]]\n" + . "Config: [$entries->{$line}[0]]\n" + } + $contents{$file}[$line-1] = $entries->{$line}[1]; + } + } + + # check the temp files don't already exist + + for my $file (sort keys %contents) { + my $nfile = "$file-new"; + die "$nfile already exists in MANIFEST; aborting\n" + if $mani_files{$nfile}; + } + + # write out the new files + + for my $file (sort keys %contents) { + my $nfile = "$file-new"; + open my $fh, '>', $nfile or die "create '$nfile' failed: $!\n"; + print $fh $_, "\n" for @{$contents{$file}}; + close $fh or die "failed to close $nfile; aborting: $!\n"; + + my @stat = stat $file or die "Can't stat $file: $!\n"; + my $mode = $stat[2]; + die "stat $file fgailed to give a mode!\n" unless defined $mode; + chmod $mode & 0777, $nfile or die "chmod $nfile failed; aborting: $!\n"; + } + + # and rename them + + for my $file (sort keys %contents) { + my $nfile = "$file-new"; + warn "updating $file ...\n"; + rename $nfile, $file or die "rename $nfile $file: $!\n"; + } +} +