diff options
author | Martin Wilke <miwi@FreeBSD.org> | 2007-12-14 20:52:18 +0000 |
---|---|---|
committer | Martin Wilke <miwi@FreeBSD.org> | 2007-12-14 20:52:18 +0000 |
commit | 255190e3c15db142ae191ac2504172b688f84275 (patch) | |
tree | 61da183e40ce5c86eecebbb20dec7021b56b273e /ports-mgmt/portupdate-scan | |
parent | 768f02433aafabfc80d892a8450914fc3e1b1300 (diff) | |
download | ports-255190e3c15db142ae191ac2504172b688f84275.tar.gz ports-255190e3c15db142ae191ac2504172b688f84275.zip |
Notes
Diffstat (limited to 'ports-mgmt/portupdate-scan')
-rw-r--r-- | ports-mgmt/portupdate-scan/Makefile | 30 | ||||
-rw-r--r-- | ports-mgmt/portupdate-scan/pkg-descr | 10 | ||||
-rw-r--r-- | ports-mgmt/portupdate-scan/src/portupdate-scan | 352 | ||||
-rw-r--r-- | ports-mgmt/portupdate-scan/src/portupdate-scan.8 | 77 |
4 files changed, 469 insertions, 0 deletions
diff --git a/ports-mgmt/portupdate-scan/Makefile b/ports-mgmt/portupdate-scan/Makefile new file mode 100644 index 000000000000..3398a2454610 --- /dev/null +++ b/ports-mgmt/portupdate-scan/Makefile @@ -0,0 +1,30 @@ +# New ports collection makefile for: portupdate-scan +# Date created: 08 November 2007 +# Whom: Alex Stangl <alex@stangl.us> +# +# $FreeBSD$ +# +# This port is self contained in the files directory. + +PORTNAME= portupdate-scan +PORTVERSION= 0.1 +CATEGORIES= ports-mgmt +MASTER_SITES= # none +DISTFILES= # none + +MAINTAINER= alex@stangl.us +COMMENT= Display pertinent parts of {PORTSDIR}/UPDATING + +NO_BUILD= yes +USE_PERL5_RUN= yes + +PLIST_FILES= sbin/portupdate-scan +SRC= ${.CURDIR}/src + +MAN8= portupdate-scan.8 + +do-install: + ${INSTALL_SCRIPT} ${SRC}/portupdate-scan ${PREFIX}/sbin/portupdate-scan + ${INSTALL_MAN} ${SRC}/portupdate-scan.8 ${MAN8PREFIX}/man/man8 + +.include <bsd.port.mk> diff --git a/ports-mgmt/portupdate-scan/pkg-descr b/ports-mgmt/portupdate-scan/pkg-descr new file mode 100644 index 000000000000..cfcc65b820c0 --- /dev/null +++ b/ports-mgmt/portupdate-scan/pkg-descr @@ -0,0 +1,10 @@ +portupdate-scan simplifies dealing with /usr/ports/UPDATING when you have so +many ports installed that it is difficult to know which sections are relevant. + +It reads /usr/ports/UPDATING, attempting for each block to determine whether +the affected ports are installed. It omits blocks that do not apply. +It handles wildcards and other special cases, however it cannot handle +all variants of phrases used on the APPLIES: line. +In uncertain cases, it errs on the side of reporting. + +Alex Stangl <alex@stangl.us> diff --git a/ports-mgmt/portupdate-scan/src/portupdate-scan b/ports-mgmt/portupdate-scan/src/portupdate-scan new file mode 100644 index 000000000000..bff797c883ec --- /dev/null +++ b/ports-mgmt/portupdate-scan/src/portupdate-scan @@ -0,0 +1,352 @@ +#!/usr/bin/perl -w + +# Copyright (c) 2007 Alex Stangl <alex@stangl.us> +# +# 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. + +# Check /usr/ports/UPDATING file for all sections matching packages +# that are installed, and outputting those sections. +# Intended to be used prior to upgrading ports +# Usage: portupdatescan [-dDhmuvV] [--help] [--version] +# Created: 2006/12/21 Alex Stangl +# Last updated: 2007/11/09 Alex Stangl + +use Text::ParseWords; +use Getopt::Std; +use strict; + +# Display usage and exit +sub HELP_MESSAGE() { + print <<EOF; +Usage: portupdate-scan [-dhmuvV] [-D portsdir] [--help] [--version] + -d display additional debugging info + -D portsdir override default port directory + -h, --help display this help and exit + -m display detailed port install info for MIXED + -u display information about uninstalled ports (Default off) + -v display verbose information (e.g., more debugging info) + -V, --version output version information and exit +EOF + exit; +} + +sub VERSION_MESSAGE() { + print "portupdate-scan 0.1\n"; +} + +# Fetch cmdline args, display usage if appropriate +$Getopt::Std::STANDARD_HELP_VERSION=1; # std, not paranoia behavior +# our($opt_d, $opt_h, $opt_m, $opt_u, $opt_v, $opt_V); +my %opt; # map of command-line options +HELP_MESSAGE() unless getopts("dD:hmuvV", \%opt); +HELP_MESSAGE() if $opt{h}; +VERSION_MESSAGE() && exit if $opt{V}; + +my $portsdir = $opt{D} || "/usr/ports"; # ports directory + +my $portIndexFile = "$portsdir/INDEX"; # port index file +my $movedFile = "$portsdir/MOVED"; # file w/ port renames/deletes +my $updatingFile = "$portsdir/UPDATING"; # file w/ ports update news + +my @portlist = `pkg_info -aoq` + or die "Error trying to execute pkg_info -aoq: $!"; +my %installedPorts = + map {chomp($_);$_, 1} @portlist; # map of installed ports -> 1 + +my %allPorts; # map of all portnames -> its INDEX line +my %fromTo; # map of old -> new portname +my %toFrom; # map of new -> old portname +my %deletedPorts; # map of deleted ports +my %substmap = ( # map of port name substitutions + 'xorg' => 'x11/xorg', + 'Xorg' => 'x11/xorg', + 'automake' => 'devel/automake*', +); +my %glob2regexp = ( # map glob-style regexp chars to regular expr + '*' => '.*', + '?' => '.', +); + +# Process a single block from /usr/ports/UPDATING +sub processBlock(@) { + my ($affects, $remainder, $line); + my $index = 0; + foreach $line (@_) { + if ($line =~ /^\s*AFFECTS:/) { + $affects = $line; + $remainder = $index; + } elsif ($affects and $line =~ /^\s*AUTHOR/) { + last; + } elsif ($affects) { + $affects .= $line; + } + ++$index; + } + + return unless defined $remainder; + if (checkline($affects, $_[$remainder - 1])) { + print "$_[$remainder++]\n" while $remainder < @_; + } +} + +# Given a port name, attempt to find and return an existing port of that +# name, or a different name, taking into account /usr/ports/MOVED +sub findPortByName($@) { + my $name = $_[0]; + print "+ findPortByName $name\n" if $opt{d}; + $_[1]{$name} = 1; + return $name if $allPorts{$name}; + my $retval = checkaliases($name, $_[1], \%fromTo); + return $retval if defined $retval; + return checkaliases($name, $_[1], \%toFrom); +} + +sub checkaliases($\@\@) { + my $name = $_[0]; + my ($alias, $retval); + foreach $alias (@{$_[2]{$name}}) { + $retval = findPortByName($alias, $_[1]) if not exists $_[1]{$alias}; + print "+ Checking alias $alias for $name\n" if $opt{d}; + if (defined $retval and $retval ne "") { + print "+ returns $retval\n" if $opt{d}; + return $retval; + } + } +} + + +# Recursively parse package name string, possibly containing +# {a,b,c} or * metacharacters, requiring expansion +sub parseexpr($); # forward declaration needed because of recursion +sub parseexpr($) { + my @retval; + my $sepExp = "(,\\s+and\\s+|,\\s+or\\s+|,\\s+|\\s+and\\s+|\\s+or\\s+|\\s+)"; + if ($_[0] =~ /{.*,.*}/) + { + # Handle expression like textproc/{senna,p5-Senna} + # to produce textproc/senna, textproc/p5-Senna + my ($prefix, $subexpr, $suffix) = ($_[0] =~ /([^{]*){([^}]*)}(.*)/); + + # Parse portname fragment contained within braces + # Breaks string at "," and discards quotes and single backslashes + my @exprs = "ewords(",", 0, $subexpr); + my $line; + foreach $line (@exprs) { + push @retval, parseexpr($prefix . $line . $suffix); + } + } elsif ($_[0] =~ /\[\d{1,4}\]/) { + # Handle expressions like net/openldap2[34]-server + # to produce net/openldap23-server, net/openldap24-server + my ($prefix, $subexpr, $suffix) = ($_[0] =~ /([^[]*)\[([^\]]+)\](.*)/); + + my $char; + foreach $char (split //, $subexpr) { + push @retval, parseexpr($prefix . $char . $suffix); + } + } elsif ($_[0] =~ /\[.+\]/) { + # Handle expressions like textproc/docproj[-jadetex] + # to produce textproc/docproj, textproc/docproj-jadetex + my ($prefix, $subexpr, $suffix) = ($_[0] =~ /([^[]*)\[([^\]]+)\](.*)/); + push @retval, parseexpr($prefix . $suffix); + push @retval, parseexpr($prefix . $subexpr . $suffix); + } elsif ($_[0] =~ /$sepExp/) { + # Handle "dir1/port1 and dir2/port2" recursively. + my ($prefix, $sep, $suffix) = ($_[0] =~ /(.*?)$sepExp(.*)/); + push @retval, parseexpr($prefix) if $prefix ne ""; + push @retval, parseexpr($suffix) if $suffix ne ""; + } elsif ($_[0] =~ /\*/ or $_[0] =~ /\?/) { + push @retval, expandwildcard($_[0]); + } else { + # Perform canned substitution, if appropriate, else use input string + my $subst = $substmap{$_[0]}; + push @retval, $subst ? parseexpr($subst) : $_[0]; + } + return @retval; +} + +# Parse a single AFFECTS: line +sub parseline($) { + return ("ALL") if $_[0] =~ /^\s+AFFECTS:\s*everybody\s*$/i + or $_[0] =~ /^\s+AFFECTS:\s*everyone\s*$/i + or $_[0] =~ /^\s+AFFECTS:\s*all\s*$/i; + if ($_[0] =~ s/.*([uU]sers|[tT]esters) of[ \t]*(.*)/$2/) { + my @retval = parseexpr($_[0]); + print "+ parseline returns @retval for $_[0]\n" if $opt{d}; + return @retval; + } +} + +# For arg "PREFIX*SUFFIX", expand by returning all portnames +# beginning with "PREFIX" and ending with "SUFFIX". +# If no matching current portnames found, deleted portnames are checked. +# If no current or deleted portnames match, the expression itself is returned. +sub expandwildcard($) { + my (@retval, $key); + my $globpattern = $_[0]; + $globpattern = "*/$globpattern" if $globpattern !~ /.+\/.+/; + my $pattern = glob2pat($globpattern); + + foreach $key (keys %allPorts) { + push @retval, $key if $key =~ /$pattern/; + } + return @retval if @retval; + + # No current ports matched, so try checking for matching deleted ports + foreach $key (keys %deletedPorts) { + push @retval, $key if $key =~ /$pattern/; + } + push @retval, $_[0] if @retval == 0; # push expr if no expansion + return @retval; +} + +# Convert glob-style pattern to standard regular-expression pattern +sub glob2pat($) { + (my $globstr = $_[0]) =~ s/(.)/$glob2regexp{$1} || "\Q$1"/ge; + return '^' . $globstr . '$'; +} + +# Possibilities when evaluating a line: +# 0. Not able to make sense of line (find any known port names) +# 1. Able to find 1 or more port names, each either +# 1a. Is installed +# 1b. Is unknown +# 1c. Is known, but not installed +# +# Conservatively, output line unless all apparent port names on +# line are known and are not installed. +sub checkline($$) { + # Copy args for readability and because we will mutate localCopy + my ($localCopy, $prevLine) = @_; + my @ret = parseline($localCopy); + if ($#ret == -1) { + print "(NOT RECOGNIZED!) $_[1]\n"; + return 1; + } + + # boolean accumulator flags tracking whether all known, all deleted, etc. + my ($allKnown, $allUnknown, $allInstalled, $allNotInstalled, $allDeleted) = (1,1,1,1,1); + + my ($line, @details); + foreach $line (@ret) { + if ($line eq "ALL") { + $allUnknown = $allDeleted = 0; + last; + } + my $alias = findPortByName($line, \()); + if ($alias) { + $allUnknown = $allDeleted = 0; + if ($installedPorts{$alias}) { + $allDeleted = $allNotInstalled = 0; + push @details, logPortFinding("installed", $line, $alias); + } else { + $allDeleted = $allInstalled = 0; + push @details, logPortFinding("NOT installed", $line, $alias); + } + } elsif ($deletedPorts{$line}) { + push @details, logPortFinding("Deleted", $line, $alias); + $allUnknown = 0; + } else { + $allKnown = $allDeleted = 0; + push @details, logPortFinding("UNKNOWN", $line, $alias); + } + } + + if ($allDeleted) { + printall(\@details) if $opt{v}; + return 0 unless $opt{u}; + print "(ALL Deleted) $_[1]\n"; + return 1; + } elsif ($allUnknown) { + printall(\@details) if $opt{v}; + print "(ALL Unknown) $_[1]\n"; + return 1; + } elsif ($allKnown && $allInstalled) { + printall(\@details) if $opt{v}; + print "(ALL Installed) $_[1]\n"; + return 1; + } elsif ($allKnown && $allNotInstalled) { + printall(\@details) if $opt{v}; + return 0 unless $opt{u}; + print "(ALL NOT Installed) $_[1]\n"; + return 1; + } else { + printall(\@details) if $opt{m} || $opt{v}; + print "(MIXED) $_[1]\n"; + return 1; + } +} + +# Log detailed port discovery, if verbose enabled +sub logPortFinding($$$) { + my ($type, $line, $alias) = @_; + my $aliasexpr = (not defined $alias or $alias eq $line or $alias eq "") ? "" : " ($alias)"; + return "+ Found $type $line$aliasexpr\n"; +} + +sub printall(@) { + my $line; + foreach $line (@{$_[0]}) { + print $line; + } +} + +# Main entry point. First, open INDEX and read all ports into allPorts +MAIN:{ + open(IDX, $portIndexFile) or die "Can't open $portIndexFile: $!"; + while(<IDX>) { + chomp; + my ($portLine) = ($_ =~ /^[^|]*\|\/usr\/ports\/([^|]*)/) + or die "$_ is not correctly formed!"; + $allPorts{$portLine} = $_; + print "+ Processed INDEX line $_\n" if $opt{d} and $opt{v}; + print "+ Generated from INDEX portLine $portLine\n" if $opt{d}; + } + close(IDX); + + # Now open /usr/ports/MOVED to read ports which have moved + open(MOVED, $movedFile) or die "Can't open $movedFile: $!"; + while(<MOVED>) { + chomp; + if ($_ !~ /^\s*#/) { + my ($from, $to) = ($_ =~ /^([^|]*)\|([^|]*)/) + or die "$_ is not a correctly formed MOVED line"; + + if ($to eq "") { + $deletedPorts{$from} = 1; + } else { + push @{$fromTo{$from}}, $to; + push @{$toFrom{$to}}, $from; + } + } + } + close(MOVED); + + # Parse /usr/ports/UPDATING into logical blocks, pass each to processBlock + my $prevLine; + my @bufferBlock; + open(UPD, $updatingFile) or die "Can't open $updatingFile: $!"; + while(<UPD>) { + chomp; + if (/^\s*AFFECTS:/ && @bufferBlock > 0) { + processBlock(@bufferBlock); + @bufferBlock = (); + } + push @bufferBlock, $prevLine if defined $prevLine; + $prevLine = $_; + } + close(UPD); + + # Process residue and exit + push @bufferBlock, $prevLine; + processBlock(@bufferBlock); +} diff --git a/ports-mgmt/portupdate-scan/src/portupdate-scan.8 b/ports-mgmt/portupdate-scan/src/portupdate-scan.8 new file mode 100644 index 000000000000..446b6d2d7131 --- /dev/null +++ b/ports-mgmt/portupdate-scan/src/portupdate-scan.8 @@ -0,0 +1,77 @@ +.\" Man page for portupdate-scan +.\" +.\" Copyright (c) 2007 Alex Stangl <alex@stangl.us> +.\" +.\" 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 November 5, 2007 +.Os +.Dt PORTUPDATE-SCAN 8 +.Sh NAME +.Nm portupdate-scan +.Nd scan /usr/ports/UPDATING, showing sections for installed ports +.Sh SYNOPSIS +.Nm +.Op Fl dhmuVv +.Op Fl D Ar portdir +.Op Fl -help +.Op Fl -version +.Sh DESCRIPTION +Reads port-related files, including /usr/ports/UPDATING, and outputs +those sections of UPDATING that might be pertinent to this system. +Only sections that apply to recognize ports that are known, and not +installed are omitted. +.Pp +The following options are available: +.Bl -tag -width indent +.It Fl D Ar portsdir +use portsdir as ports directory instead of /usr/ports +.It Fl d +output additional debugging information +.It Fl h , -help +output command usage information and exit +.It Fl m +for MIXED sections, show breakdown of which ports are installed and not installed. The verbose information from the v flag is a superset of this. +.It Fl u +output information on uninstalled ports (default: off) +.It Fl v +output more verbose information +.It Fl V , -version +output version information and exit +.El +.Sh EXIT STATUS +.Ex -std +.Sh FILES +.Bl -tag -width /usr/ports/UPDATING -compact +.It Pa /usr/ports/UPDATING +port update news +.It Pa /usr/ports/INDEX +port index file +.It Pa /usr/ports/MOVED +record of port renames and deletions +.Sh EXAMPLES +.Pp +Basic normal operation: +.Pp +.Dl portupdate-scan +.Pp +To include details of installed/uninstalled ports for MIXED sections: +.Pp +.Dl portupdate-scan -m +.Sh SEE ALSO +.Xr ports 7 +.Sh AUTHORS +.An "Alex Stangl" Aq alex@stangl.us +.Sh BUGS +The concept of machine-interpreting the APPLIES: line in /usr/ports/UPDATING, +which were intended for human consumption, is dubious. It would be nice to +evolve some more robust method for communicating this information. |