3 # bump-perl-version, DAPM 14 Jul 2009
5 # A utility to find, and optionally bump, references to the perl version
6 # number in various files within the perl source
8 # It's designed to work in two phases. First, when run with -s (scan),
9 # it searches all the files in MANIFEST looking for strings that appear to
10 # match the current perl version (or which it knows are *supposed* to
11 # contain the current version), and produces a list of them to stdout,
12 # along with a suggested edit. For example:
14 # $ Porting/bump-perl-version -s 5.10.0 5.10.1 > /tmp/scan
18 # 52: -archlib='/opt/perl/lib/5.10.0/i686-linux-64int'
19 # +archlib='/opt/perl/lib/5.10.1/i686-linux-64int'
22 # At this point there will be false positives. Edit the file to remove
23 # those changes you don't want made. Then in the second phase, feed that
24 # list in, and it will change those lines in the files:
26 # $ Porting/bump-perl-version -u < /tmp/scan
28 # (so line 52 of Porting/config.sh is now updated)
30 # This utility 'knows' about certain files and formats, and so can spot
31 # 'hidden' version numbers, like PERL_SUBVERSION=9.
33 # A third variant makes use of this knowledge to check that all the things
34 # it knows about are at the current version:
36 # $ Porting/bump-perl-version -c 5.10.0
38 # XXX this script hasn't been tested against a major version bump yet,
39 # eg 5.11.0 to 5.12.0; there may be things it missed - DAPM 14 Jul 09
41 # Note there are various files and directories that it skips; these are
42 # ones that are unlikely to contain anything needing bumping, but which
43 # will generate lots fo false positives (eg pod/*). These are listed on
44 # STDERR as they are skipped.
50 use ExtUtils::Manifest;
53 sub usage { die <<EOF }
62 -c check files and warn if any known string values (eg
63 PERL_SUBVERSION) don't match the specified version
65 -s scan files and produce list of possible change lines to stdout
67 -u read in the scan file from stdin, and change all the lines specified
69 -i scan files and make changes inplace
71 C.C.C the current perl version, eg 5.10.0
72 N.N.N the new perl version, eg 5.10.1
76 getopts('csui', \%opts) or usage;
78 @ARGV == 0 or usage('no version version numbers should be specified');
79 # fake to stop warnings when calculating $oldx etc
80 @ARGV = qw(99.99.99 99.99.99);
83 @ARGV == 1 or usage('required one version number');
87 @ARGV == 2 or usage('require two version numbers');
89 usage('only one of -c, -s, -u and -i') if keys %opts > 1;
91 my ($oldx, $oldy, $oldz) = $ARGV[0] =~ /^(\d+)\.(\d+)\.(\d+)$/
92 or usage("bad version: $ARGV[0]");
93 my ($newx, $newy, $newz) = $ARGV[1] =~ /^(\d+)\.(\d+)\.(\d+)$/
94 or usage("bad version: $ARGV[1]");
96 my $old_decimal = sprintf "%d.%03d%03d", $oldx, $oldy, $oldz; # 5.011001
99 # 0 a regexp that matches strings that might contain versions;
100 # 1 a sub that returns two strings based on $1 etc values:
101 # * string containing captured values (for -c)
102 # * a string containing the replacement value
103 # 2 what we expect the sub to return as its first arg; undef implies
105 # 3 a regex restricting which files this applies to (undef is all files)
107 # Note that @maps entries are checks in order, and only the first to match
112 qr{^((?:api_)?version(?:=|\s+)'?) (\d+) ('?) (?!\.)}x,
113 sub { $2, "$1$newy$3" },
118 qr{^(subversion(?:=|\s+)'?) (\d+) ('?) (?!\.)}x,
119 sub { $2, "$1$newz$3" },
124 qr{^(api_subversion(?:=|\s+)'?) (\d+) ('?) (?!\.)}x,
125 sub { $2, ($newy % 2) ? "$1$newz$3" : "${1}0$3" },
126 ($oldy % 2) ? $oldz : 0,
130 qr{^(api_versionstring(?:=|\s+)'?) ([\d\.]+) ('?) (?!\.)}x,
131 sub { $2, ($newy % 2) ? "$1$newx.$newy.$newz$3": "$1$newx.$newy.0$3" },
132 ($oldy % 2) ? "$oldx.$oldy.$oldz" : "$oldx.$oldy.0",
136 qr{(version\s+'?) (\d+) ('?\s+subversion\s+'?) (\d+) ('?) (?!\.)}x,
137 sub { "$2-$4", "$1$newy$3$newz$5" },
142 qr{\b (PERL_(?:API_)?VERSION(?:=|\s+)'?) (\d+) ('?) (?!\.)}x,
143 sub { $2, "$1$newy$3"},
147 qr{\b (PERL_SUBVERSION(?:=|\s+)'?) (\d+) ('?) (?!\.)}x,
148 sub { $2, "$1$newz$3"},
149 ($oldy % 2) ? $oldz : 0,
152 qr{\b (PERL_API_SUBVERSION(?:=|\s+)'?) (\d+) ('?) (?!\.)}x,
153 sub { $2, ($newy % 2) ? "$1$newz$3" : "${1}0$3" },
156 # these two formats are in README.vms
158 qr{\b perl-(\d+\^\.\d+\^\.\d+) \b}x,
159 sub { $1, "perl-$newx^.$newy^.$newz"},
163 qr{\b ($oldx _ $oldy _$oldz) \b}x,
164 sub { $1, ($newx . '_' . $newy . '_' . $newz)},
169 qr{ $oldx\.$oldy\.$oldz \b}x,
170 sub {"", "$newx.$newy.$newz"},
176 qr{ $old_decimal \b}x,
177 sub {"", sprintf "%d.%03d%03d", $newx, $newy, $newz },
181 # perl511, perl511.dll, perl511.lib, perl511s.lib, libperl511.a
183 qr{\b ((?:lib)?) perl (\d\d\d) (s?) \b }x,
184 sub {$2, "$1perl$newx$newy$3" },
186 qr/makedef|win32|hints/, # makedef.pl, README.win32, win32/*, hints/*
189 # microperl locations should be bumped for major versions
191 qr{(/)(\d\.\d{2})(["'/])},
192 sub { $2, "$1$newx.$newy$3" },
197 # rename perl-5^.15^.1.dirperl-5_15_1.dir in README.vms
199 qr{\sperl-(\d+)_(\d+)_(\d+)\.dir}x,
200 sub { " perl-${1}_${2}_${3}.dir", " perl-${newx}_${newy}_${newz}.dir" },
201 " perl-${oldx}_${oldy}_{$oldz}.dir",
208 # files and dirs that we likely don't want to change version numbers on.
210 my %SKIP_FILES = map { ($_ => 1) } qw(
213 Porting/Maintainers.pl
214 Porting/acknowledgements.pl
215 Porting/epigraphs.pod
216 Porting/how_to_write_a_perldelta.pod
217 Porting/release_managers_guide.pod
218 Porting/release_schedule.pod
219 Porting/bump-perl-version
231 my @mani_files = sort keys %{ExtUtils::Manifest::maniread('MANIFEST')};
232 my %mani_files = map { ($_ => 1) } @mani_files;
233 die "No entries found in MANIFEST; aborting\n" unless @mani_files;
235 if ($opts{c} or $opts{s} or $opts{i}) {
242 usage('one of -c, -s or -u must be specified');
250 for my $file (@mani_files) {
251 next if grep $file =~ m{$_/}, @SKIP_DIRS;
252 if ($SKIP_FILES{$file}) {
253 warn "(skipping $file)\n";
256 open my $fh, '<', $file;
258 my @stat = stat $file;
260 my $file_changed = 0;
261 my $new_contents = '';
263 while (my $line = <$fh>) {
265 for my $map (@maps) {
266 my ($pat, $sub, $expected, $file_pat) = @$map;
268 next if defined $file_pat and $file !~ $file_pat;
269 next unless $line =~ $pat;
270 my ($got, $replacement) = $sub->();
273 # only report unexpected
274 next unless defined $expected and $got ne $expected;
276 $line =~ s/$pat/$replacement/
277 or die "Internal error: substitution failed: [$pat]\n";
279 $new_contents .= $line if $opts{i};
280 if ($line ne $oldline) {
283 print "\n$file\n" unless $header;
285 printf "\n%5d: -%s +%s", $., $oldline, $line;
289 if ($opts{i} && $file_changed) {
290 warn "Updating $file inplace\n";
291 open my $fh, '>', $file;
293 print $fh $new_contents;
295 chmod $mode & 0777, $file;
298 warn "(skipped $_/*)\n" for @SKIP_DIRS;
313 die "No such file in MANIFEST: '$file'\n" unless $mani_files{$file};
314 die "file already seen; '$file'\n" if exists $changes{$file};
317 elsif (/^\s+(\d+): -(.*)/) {
319 ($line, $old) = ($1,$2);
320 die "$.: old line without preceding filename\n"
321 unless defined $file;
322 die "Dup line number: $line\n" if exists $changes{$file}{$line};
323 $changes{$file}{$line}[0] = $old;
325 elsif (/^\s+\+(.*)/) {
327 die "$.: replacement line seen without old line\n" unless $line;
328 $changes{$file}{$line}[1] = $new;
332 die "Unexpected line at ;line $.: $_\n";
336 # suck in file contents to memory, then update that in-memory copy
339 for my $file (sort keys %changes) {
340 open my $fh, '<', $file;
342 $contents{$file} = [ <$fh> ];
343 chomp @{$contents{$file}};
346 my $entries = $changes{$file};
347 for my $line (keys %$entries) {
348 die "$file: no such line: $line\n"
349 unless defined $contents{$file}[$line-1];
350 if ($contents{$file}[$line-1] ne $entries->{$line}[0]) {
351 die "$file: line mismatch at line $line:\n"
352 . "File: [$contents{$file}[$line-1]]\n"
353 . "Config: [$entries->{$line}[0]]\n"
355 $contents{$file}[$line-1] = $entries->{$line}[1];
359 # check the temp files don't already exist
361 for my $file (sort keys %contents) {
362 my $nfile = "$file-new";
363 die "$nfile already exists in MANIFEST; aborting\n"
364 if $mani_files{$nfile};
367 # write out the new files
369 for my $file (sort keys %contents) {
370 my $nfile = "$file-new";
371 open my $fh, '>', $nfile;
373 print $fh $_, "\n" for @{$contents{$file}};
376 my @stat = stat $file;
378 die "stat $file fgailed to give a mode!\n" unless defined $mode;
379 chmod $mode & 0777, $nfile;
384 for my $file (sort keys %contents) {
385 my $nfile = "$file-new";
386 warn "updating $file ...\n";
387 rename $nfile, $file;