Perl 5.8.6 upgrade

Anton Berezin tobez at FreeBSD.org
Thu Feb 3 16:27:53 PST 2005


On Thu, Feb 03, 2005 at 09:32:06AM -0500, Daren Desjardins wrote:
> Anton Berezin wrote:
> >On Thu, Feb 03, 2005 at 03:37:12PM +0300, A K wrote:

> >>You can copy `5.8.5' dirs under /usr/local/lib/perl5/site_perl to
> >>`5.8.6' (or add 5.8.5 to PERL5LIB) as a desperate measure but that is
> >>not supported and will probably break some modules.

> >Actually, this should work just fine, if at the same time the
> >corresponding /var/db/pkg/ entries are modified accordingly, and all
> >relevant scripts in LOCALBASE/bin and X11BASE/bin have their shebangs
> >modified.

> >mat and I discussed a possibility of making such tool yesterday.  This
> >can be done, and it will probably make 99% of all minor version upgrades
> >as painless as it can get.

> Such a script, would be extremely valuable.

Please test.

----------8<-----------8<-----------8<-----------8<-----------8<------
#! /usr/local/bin/perl -w
# ----------------------------------------------------------------------------
# "THE BEER-WARE LICENSE" (Revision 42)
# <tobez at FreeBSD.org> wrote this file.  As long as you retain this notice you
# can do whatever you want with this stuff. If we meet some day, and you think
# this stuff is worth it, you can buy me a beer in return.   Anton Berezin
# ----------------------------------------------------------------------------
#
# $FreeBSD$
# $Id: perl-after-upgrade,v 1.6 2005/02/04 00:24:43 tobez Exp $
#
=pod

=head1 NAME

perl-after-upgrade -- fixup FreeBSD packages that depend on perl

=head1 SYNOPSIS

  perl-after-upgrade
  perl-after-upgrade -f

=head1 DESCRIPTION

The standard procedure after a perl port (either lang/perl5 or
lang/perl5.8) upgrade is to basically reinstall all other packages that
depend on perl.  This is always a painful exercise.  The
perl-after-upgrade utility makes this process mostly unnecessary.

The tool goes through the list of installed packages, looks for those
that depend on perl, moves files around, modifies shebang lines in those
scripts in which it is necessary to do so, and updates the package
database.

The correct way of using the tool is:

=over 4

=item o go root;

=item o make sure the existing dependencies are in order.

Use pkgdb utility from sysutils/portupgrade (pkgdb -F);

=item o build an up-to-date perl port;

=item o install it.

Do B<not> deinstall older perl first.  Use make install
FORCE_PKG_REGISTER=yes;

=item o run perl-after-upgrade utility.

Do not specify any arguments at first, so it does nothing.  Pay
attention to the produced output and especially to errorlist at the end,
if any;

=item o run the utility again, with B<-f> command line option.

This will actually do the work.  Again, pay attention to the output
produced;

=item o fix any reported errors;

=item o review the files left in the older perl installation.

This is typically /usr/local/lib/perl5/site_perl/5.X.Y/.  There is no
reason to look at /usr/local/lib/perl5/5.X.Y, since there will be plenty
of files left (those were installed by perl port itself).  There should
be very little, if any, files in that directory and its subdirectories,
excepting any modules installed from CPAN and a number of .ph files;

=item o check that things work as they should;

=item o remove backup files from the package database.

Those will be /var/db/pkg/*/+CONTENTS.bak;

=item o run pkgdb -F again.

It might fixup a couple of things.  At the very least, it will ask about
unregistering one of currently installed perl packages.  Do what it
suggests;

=item o note which perl modules installed from CPAN you will want to
reinstall;

=item o remove old perl by hand:

rm -rf /usr/local/lib/perl5/5.X.Y

It is better to avoid removing newly installed perl by accident;

=item o that's all.

=back

=head1 BUGS

The perl-after-upgrade script does not handle perl modules installed via
CPAN.

=head1 COPYRIGHT AND LICENSE

Copyright 2005 by Anton Berezin

 "THE BEER-WARE LICENSE" (Revision 42)
 <tobez at FreeBSD.org> wrote this module.  As long as you retain this
 notice you can do whatever you want with this stuff. If we meet some
 day, and you think this stuff is worth it, you can buy me a beer in
 return.

 Anton Berezin

NO WARRANTY OF ANY KIND, USE AT YOUR OWN RISK.

=head1 CREDITS

Thanks to Mathieu Arnold for discussion.

=head1 SEE ALSO

perl(1).

=cut
package FreeBSD::Package;
use IO::File;
use File::Copy;

sub new
{
	my ($pkg, %p) = @_;
	my $pkgdir = $p{pkgdir} || return undef;
	my $name = $pkgdir;
	$name =~ s|.*/||;
	my $c = IO::File->new("< $pkgdir/+CONTENTS");
	return undef unless $c;
	my @lines;
	while (<$c>) {
		chomp;
		push @lines, $_;
	}
	my $me = bless {
		pkgdir => $pkgdir,
		lines  => \@lines,
		name   => $name,
	}, $pkg;
	return $me;
}

sub name
{
	return $_[0]->{name};
}

sub lines
{
	my $me = shift;
	if (@_ && @_ == 1 && ref(@_) eq 'ARRAY') {
		$me->{lines} = [@{$_[0]}];
		$me->{changed} = 1;
	} elsif (@_) {
		$me->{lines} = [@_];
		$me->{changed} = 1;
	} else {
		return @{$me->{lines}};
	}
}

sub write_back
{
	my ($me) = @_;

	return unless $me->{changed};
	my $file = "$me->{pkgdir}/+CONTENTS";
	copy($file, "$file.bak");
	my $c = IO::File->new("> $file");
	return unless $c;
	for (@{$me->{lines}}) {
		print $c "$_\n";
	}
}

package FreeBSD::Package::DB;
use strict;

sub new
{
	my ($pkg, %p) = @_;
	my $me = bless {
		dbdir => $p{dbdir} || $ENV{PKG_DBDIR} || "/var/db/pkg",
	}, $pkg;
	$me->{packages} = [ grep { -d } glob "$me->{dbdir}/*" ];
	$me->reset;
	return $me;
}

sub next
{
	my ($me) = @_;
	while (1) {
		$me->{current}++;
		if ($me->{current} >= @{$me->{packages}}) {
			$me->reset;
			return undef;
		}
		my $pkg = FreeBSD::Package->new(pkgdir => $me->{packages}->[$me->{current}]);
		return $pkg if $pkg;
	}
}

sub reset
{
	my ($me) = @_;
	$me->{current} = -1;
}

package main;
use Config;
use File::Temp qw/tempfile/;
use File::Copy;

my $target =
	$Config::Config{PERL_REVISION} . "." .
	$Config::Config{PERL_VERSION} . "." .
	$Config::Config{PERL_SUBVERSION};
my $source = "";
if ($Config::Config{api_revision} < $Config::Config{PERL_REVISION}) {
	$source = ".[";
	for ($Config::Config{api_revision} .. $Config::Config{PERL_REVISION}) {
		$source .= $_;
	}
	$source .= "]\\.\\d+\\.\\d+";
} elsif ($Config::Config{api_revision} > $Config::Config{PERL_REVISION}) {
	die "internal error, this perl is too old\n";
} else {
	$source .= "$Config::Config{PERL_REVISION}\\.";
	if ($Config::Config{api_version} < $Config::Config{PERL_VERSION}) {
		$source .= "[";
		for ($Config::Config{api_version} .. $Config::Config{PERL_VERSION}) {
			$source .= $_;
		}
		$source .= "]\\.\\d+";
	} elsif ($Config::Config{api_version} > $Config::Config{PERL_VERSION}) {
		die "internal error, this perl is too old\n";
	} else {
		$source .= "$Config::Config{PERL_VERSION}\\.";
		if ($Config::Config{api_subversion} < $Config::Config{PERL_SUBVERSION}) {
			$source .= "[";
			for ($Config::Config{api_subversion} .. $Config::Config{PERL_SUBVERSION}) {
				$source .= $_;
			}
			$source .= "]";
		} elsif ($Config::Config{api_subversion} > $Config::Config{PERL_SUBVERSION}) {
			die "internal error, this perl is too old\n";
		} else {
			$source .= "$Config::Config{PERL_SUBVERSION}\\.";
		}
	}
}

my $dry_run = 1;
my @errors;

sub fix_file
{
	my ($file, $perlver, $target) = @_;
	return if $file =~ /\.gz$/;
	my $sf = IO::File->new("< $file");
	return "" unless $sf;
	my $line = <$sf>;
	my $new_md5 = "";
	if ($line && $line =~ s|^(\s*#!\s*[\w/]+perl)\Q$perlver\E\b|$1$target|) {
		if ($dry_run) {
			$new_md5 = 1;
		} else {
			my $dir = $file;
			$dir =~ s|/[^/]+$||;
			my ($fh, $fn) = tempfile(DIR=> $dir);
			if ($fh) {
				print $fh $line;
				while (<$sf>) {
					print $fh $_;
				}
				close $fh;
				$new_md5 = `/sbin/md5 -q $fn`;
				chomp $new_md5;
				unlink $file or do {
					push @errors, "Failed to unlink $file: $!";
					unlink $fn;
					return "";
				};
				rename $fn, $file or do {
					push @errors, "Failed to rename $fn to $file: $!";
					return "";
				};
			} else {
				push @errors, "Failed to modify $file: $!";
			}
		}
		return $new_md5;
	}
}

sub mkdir_recur
{
	my ($dir) = @_;
	$dir =~ s|/+$||;
	my $orig = $dir;
	if ($dir =~ m|^$|) {
		return 1;
	} else {
		$dir =~ s|/[^/]+$||;
		my $r = mkdir_recur($dir);
		return $r unless $r;
		mkdir $orig, 0777;
		my $e = $!;
		unless (-d $orig) {
			push @errors, "Could not create directory $orig: $e";
			return 0;
		}
		return 1;
	}
}

if (@ARGV && $ARGV[0] eq "-f") {
	$dry_run = 0;
}

#
my $db = FreeBSD::Package::DB->new;
my ($fixed, $skipped, $tot_moved, $tot_modified) = (0,0,0,0);
while (my $pkg = $db->next) {
	my $prefix = "";
	my @lines;
	my $perlver = "";
	my $new_md5;
	my ($adjusted, $moved, $modified) = (0,0,0);
	for ($pkg->lines) {
		if (/^([^@]\S+)\s*$/) {
			last unless $perlver;
			my $l = $_;
			$new_md5 = fix_file("$prefix/$_", $perlver, $target);
			$modified++ if $new_md5;
			my $from = "$prefix/$l";
			my $to = $from;
			if ($to =~ s|/perl5/$source/|/perl5/$target/|g or $to =~ s|/perl5/site_perl/$source/|/perl5/site_perl/$target/|g) {
				my $dir = $to;
				$dir =~ s|/[^/]+$||;
				unless ($dry_run) {
					if (mkdir_recur($dir)) {
						move($from, $to);
					} else {
						push @errors, "   could not move $from to $to";
					}
				}
				$moved++;
			}
			$_ = $l;
		} elsif (/^\@comment\s+MD5:[\da-f]+\s*$/ && $new_md5) {
			s|MD5:(\S+)|MD5:$new_md5|;
			$new_md5 = "";
		} else {
			$new_md5 = "";
		}
		if (/^\@cwd\s+(\S+)\s*$/) {
			$prefix = $1;
		} elsif (/^\@pkgdep\s+perl-($source)\S*\s*$/) {
			if ($target ne $1) {
				$perlver = $1;
				s|perl-\Q$perlver\E|perl-$target|;
			}
		}
		if (s|/perl5/$source/|/perl5/$target/|g || s|/perl5/site_perl/$source/|/perl5/site_perl/$target/|g) {
			last unless $perlver;
			$adjusted++;
		}
		push @lines, $_;
	}
	if ($perlver) {
		unless ($dry_run) {
			$pkg->lines(@lines);
			$pkg->write_back;
		}
		$fixed++;
		$tot_modified += $modified;
		$tot_moved += $moved;
		print $pkg->name, ": $moved moved, $modified modified, $adjusted adjusted\n";
	} else {
		$skipped++;
	}
}
print "\n---\n";
print "Fixed $fixed packages ($tot_moved files moved, $tot_modified files modified)\n";
print "Skipped $skipped packages\n";
if (@errors) {
	print "The script has encountered following problems:\n";
	for (@errors) {
		print "$_\n";
	}
	print "\n---\n";
	print "Fixed $fixed packages ($tot_moved files moved, $tot_modified files modified)\n";
	print "Skipped $skipped packages\n";
}
----------8<-----------8<-----------8<-----------8<-----------8<------

Cheers,
\Anton.
-- 
The moronity of the universe is a monotonically increasing function. --
Jarkko Hietaniemi


More information about the freebsd-perl mailing list