#!/usr/bin/perl # $OpenBSD: out-of-date,v 1.5 2012/05/07 15:57:51 espie Exp $ # # Copyright (c) 2005 Bernd Ahlers # # 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. use strict; use warnings; use OpenBSD::Getopt; use OpenBSD::Error; use OpenBSD::PackageInfo; use OpenBSD::PackingList; use OpenBSD::PackageName; use File::Temp; use OpenBSD::AddCreateDelete; our $opt_q; my $state = OpenBSD::AddCreateDelete::State->new("out-of-date"); $state->handle_options('q', "[-mvxq]"); sub collect_installed { my $pkg = {}; $state->progress->for_list("Collecting installed packages", [installed_packages(1)], sub { my $name = shift; my ($stem, $version) = OpenBSD::PackageName::splitname($name); my $plist = OpenBSD::PackingList->from_installation($name, \&OpenBSD::PackingList::UpdateInfoOnly); if (!defined $plist or !defined $plist->{extrainfo}->{subdir}) { $state->errsay("Package #1 has no valid packing-list", $name); return; } my $subdir = $plist->{extrainfo}->{subdir}; $subdir =~ s/mystuff\///; $subdir =~ s/\/usr\/ports\///; $pkg->{$subdir}->{name} = $name; $pkg->{$subdir}->{stem} = $stem; $pkg->{$subdir}->{version} = $version; my $sig = $plist->signature; if (ref($sig)) { $sig = $sig->string; } $pkg->{$subdir}->{signature} = $sig; if (defined $plist->{'always-update'}) { $pkg->{$subdir}->{signature} = 'always-update'; } }); return $pkg; } sub open_cmd { my $cmd = shift; open my $fh, "-|", $cmd; # my $old = select $fh; # $| = 1; # select $old; return $fh; } sub collect_port_versions { my ($pkg, $portsdir, $notfound) = @_; my @subdirs = (); for my $subdir (keys %$pkg) { my ($dir) = split(/,/, $subdir); if (-d "$portsdir/$dir") { push(@subdirs, $subdir); } else { push(@$notfound, $subdir); } } my $cmd = "cd $portsdir && SUBDIR=\"".join(' ', @subdirs) ."\" FULLPATH=Yes REPORT_PROBLEM=true make ".'show=FULLPKGNAME\${SUBPACKAGE} ' ."2>&1"; my $port = {}; my $error = {}; my $count = 0; my $total = scalar @subdirs; $state->progress->set_header("Collecting port versions"); my $fh = open_cmd($cmd); my $subdir = ""; while (<$fh>) { chomp; if (/^\=\=\=\>\s+(\S+)/) { $subdir = $1; $count++; $state->progress->show($count, $total); next; } next unless $_ or $subdir; next if defined $error->{$subdir}; if (/^(Fatal\:|\s+\()/) { push(@{$error->{$subdir}}, $_); next; } elsif (/^(Stop|\*\*\*)/) { next; } $port->{$subdir}->{name} = $_; my ($stem, $version) = OpenBSD::PackageName::splitname($_); $port->{$subdir}->{stem} = $stem; $port->{$subdir}->{version} = $version; } close($fh); $state->progress->next; return $port, $error; } sub collect_port_signatures { my $pkg = shift; my $port = shift; my $portsdir = shift; my $output = shift; my @subdirs = (); for my $dir (keys %$port) { if ($pkg->{$dir}->{name} eq $port->{$dir}->{name}) { push(@subdirs, $dir); } } my $TMPDIR = $ENV{'TMPDIR'} || "/tmp"; my $tempdir = File::Temp::tempdir("libcache.XXXXXXX", DIR => $TMPDIR, CLEANUP => 1); $ENV{'_DEPENDS_CACHE'} = $tempdir; my $cmd = "cd $portsdir && FULLPATH=Yes SUBDIR=\"".join(' ', @subdirs) ."\" REPORT_PROBLEM=true make print-package-signature"; my $count = 0; my $total = scalar @subdirs; $state->progress->set_header("Collecting port signatures"); my $fh = open_cmd($cmd); my $subdir = ""; while (<$fh>) { chomp; if (/^\=\=\=\>\s+(\S+)/) { $subdir = $1; $count++; $state->progress->show($count, $total); next; } next unless $_ or $subdir; $port->{$subdir}->{signature} = $_; } $state->progress->next; } sub split_sig { my $sig = shift; my $ret = {}; for my $item (split(/,/, $sig)) { $ret->{$item} = 1; } return $ret; } sub diff_sig { my ($dir, $pkg, $port) = @_; my $old = split_sig($pkg->{$dir}->{signature}); my $new = split_sig($port->{$dir}->{signature}); for my $key (keys %$old) { if (defined $new->{$key}) { delete $old->{$key}; delete $new->{$key}; } } return join(',', sort keys %$old), join(',', sort keys %$new); } sub find_outdated { my ($pkg, $port, $output) = @_; for my $dir (keys %$pkg) { next unless $port->{$dir}; if ($pkg->{$dir}->{name} ne $port->{$dir}->{name}) { push(@$output, sprintf("%-30s # %s -> %s\n", $dir, $pkg->{$dir}->{version}, $port->{$dir}->{version})); next; } next if $opt_q; if ($pkg->{$dir}->{signature} ne $port->{$dir}->{signature}) { push(@$output, sprintf("%-30s # %s -> %s\n", $dir, diff_sig($dir, $pkg, $port))); } } } my $portsdir = $ENV{PORTSDIR} || "/usr/ports"; my $pkg = collect_installed(); my @output = (); my @notfound = (); my ($port, $errors) = collect_port_versions($pkg, $portsdir, \@notfound); collect_port_signatures($pkg, $port, $portsdir, \@output) unless $opt_q; find_outdated($pkg, $port, \@output); $state->errsay("Outdated ports:\n"); $state->print("#1", $_) for sort @output; if ($opt_q) { $state->errsay("\nWARNING: You've used the -q option. With this,\n" . "out-of-date only looks for changed package names\nbut not " . "for changed package signatures. If you\nwant to see ALL " . "of your outdated packages,\ndon't use -q."); } if (@notfound > 0) { $state->errsay("\nPorts that can't be found in the official " . "ports tree:"); for (sort @notfound) { $state->errsay("#1", $_); } } if ((keys %$errors) > 0) { $state->errsay("\nErrors:"); for (sort keys %$errors) { $state->errsay(" #1", $_); $state->errsay(" #1", $_) for @{$errors->{$_}}; } }