3 # Copyright 1998 Bruce A. Mah
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions
10 # 1. Redistributions of source code must retain the above copyright
11 # notice, this list of conditions and the following disclaimer.
12 # 2. Redistributions in binary form must reproduce the above copyright
13 # notice, this list of conditions and the following disclaimer in the
14 # documentation and/or other materials provided with the distribution.
16 # THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY EXPRESS OR
17 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 # IN NO EVENT SHALL THE DEVELOPERS BE LIABLE FOR ANY DIRECT, INDIRECT,
20 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21 # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 # A package version-checking utility for FreeBSD.
31 # $FreeBSD: src/usr.sbin/pkg_install/version/pkg_version.pl,v 1.4.2.14 2002/06/03 15:34:59 bmah Exp $
32 # $DragonFly: src/usr.sbin/pkg_install/version/Attic/pkg_version.pl,v 1.2 2003/06/17 04:29:59 dillon Exp $
39 # Configuration global variables
41 $AllCurrentPackagesCommand = '/usr/sbin/pkg_info -aI';
42 $SelectedCurrentPackagesCommand = '/usr/sbin/pkg_info -I';
44 $FetchProgram = "fetch -o - ";
45 $OriginCommand = '/usr/sbin/pkg_info -qo';
46 $GetPkgNameCommand = 'make -V PKGNAME';
48 #$IndexFile = "ftp://ftp.freebsd.org/pub/FreeBSD/branches/-current/ports/INDEX";
49 $PortsDirectory = $ENV{PORTSDIR} || '/usr/ports';
50 $IndexFile = "$PortsDirectory/INDEX";
51 $ShowCommandsFlag = 0;
61 # Try to figure out the relationship between two program version numbers.
62 # Detecting equality is easy, but determining order is a little difficult.
63 # This function returns -1, 0, or 1, in the same manner as <=> or cmp.
68 # Short-cut in case of equality
73 # Loop over different components (the parts separated by dots).
74 # If any component differs, we have the basis for an inequality.
75 my @s1 = split(/\./, $v1);
76 my @s2 = split(/\./, $v2);
79 last unless @s1 || @s2;
84 # Look at the first components of the arrays that are left.
85 # These will determine the result of the comparison.
86 # Note that if either version doesn't have any components left,
87 # it's implicitly treated as a "0".
89 # Our next set of checks looks to see if either component has a
90 # leading letter (there should be at most one leading letter per
91 # component, so that "4.0b1" is allowed, but "4.0beta1" is not).
95 # Both have a leading letter, so do an alpha comparison
96 # on the letters. This isn't ideal, since we're assuming
97 # that "1.0.b4" > "1.0.a2". But it's about the best we can do,
98 # without encoding some explicit policy.
99 my ($letter1, $letter2);
100 $letter1 = substr($c1, 0, 1);
101 $letter2 = substr($c2, 0, 1);
103 if ($letter1 ne $letter2) {
104 return $letter1 cmp $letter2;
107 # The letters matched equally. Delete the leading
108 # letters and invoke ourselves on the remainining
109 # characters, which according to the Porters Handbook
110 # must be digits, so for example, "1.0.a9" < "1.0.a10".
111 substr($c1, 0, 1) = "";
112 substr($c2, 0, 1) = "";
113 return &CompareNumbers($c1, $c2);
118 # $c1 begins with a letter, but $c2 doesn't. Let $c2
119 # win the comparison, so that "1.0.b1" < "1.0.1".
125 # $c2 begins with a letter but $c1 doesn't. Let $c1
126 # win the comparison, as above.
130 # Neither component begins with a leading letter.
131 # See if either component has no characters left. If so,
132 # let the other component win.
140 # Check for numeric inequality. We assume here that (for example)
141 # "3.09" < "3.10", and that we aren't going to be asked to
142 # decide between "3.010" and "3.10".
147 # String comparison, given numeric equality. This
148 # handles comparisons of the form "3.4j" < "3.4k". This form
149 # technically isn't allowed by the Porter's Handbook, but a
150 # number of ports in the FreeBSD Ports Collection as of this
151 # writing use it (graphics/jpeg and graphics/xv). So we need
154 # What we actually do is to strip off the leading digits and
155 # invoke ourselves on the remainder. This allows us to handle
156 # comparisons of the form "1.1p1" < "1.1p2". Again, not
157 # technically allowed by the Porters Handbook, but lots of ports
172 return &CompareNumbers($c1, $c2);
182 # Try to figure out the relationship between two program "full
183 # versions", which is defined as the
184 # ${PORTVERSION}[_${PORTREVISION}][,${PORTEPOCH}]
185 # part of a package's name.
187 # Key points: ${PORTEPOCH} supercedes ${PORTVERSION}
188 # supercedes ${PORTREVISION}. See the commit log for revision
189 # 1.349 of ports/Mk/bsd.port.mk for more information.
191 sub CompareVersions {
192 local($fv1, $fv2, $v1, $v2, $r1, $r2, $e1, $e2, $rc);
197 # Shortcut check for equality before invoking the parsing
203 ($v1, $r1, $e1) = &GetVersionComponents($fv1);
204 ($v2, $r2, $e2) = &GetVersionComponents($fv2);
206 # Port revision and port epoch numbers default to zero if not
221 # Check epoch, port version, and port revision, in that
223 $rc = &CompareNumbers($e1, $e2);
225 $rc = &CompareNumbers($v1, $v2);
227 $rc = &CompareNumbers($r1, $r2);
236 # GetVersionComponents
238 # Parse out the version number, revision number, and epoch number
239 # of a port's version string and return them as a three-element array.
241 # Syntax is: ${PORTVERSION}[_${PORTREVISION}][,${PORTEPOCH}]
243 sub GetVersionComponents {
244 local ($fullversion, $version, $revision, $epoch);
246 $fullversion = $_[0];
248 $fullversion =~ /([^_,]+)/;
251 if ($fullversion =~ /_([^_,]+)/) {
255 if ($fullversion =~ /,([^_,]+)/) {
259 return($version, $revision, $epoch);
265 # Get the name and version number of a package. Returns a two element
266 # array, first element is name, second element is full version string.,
268 sub GetNameAndVersion {
269 local($fullname, $name, $fullversion);
272 # If no hyphens then no version numbers
273 return ($fullname, "", "", "", "") if $fullname !~ /-/;
275 # Match (and group) everything after hyphen(s). Because the
276 # regexp is 'greedy', the first .* will try and match everything up
277 # to (but not including) the last hyphen
278 $fullname =~ /(.+)-(.+)/;
282 return ($name, $fullversion);
288 # Print usage information
292 Usage: pkg_version [-c] [-d] [-h] [-l limchar] [-L limchar] [-s string]
294 pkg_version [-d debug] -t v1 v2
295 -c Show commands to update installed packages
296 -d Enable debugging output
297 -h Help (this message)
298 -l limchar Limit output to status flags that match
299 -L limchar Limit output to status flags that DON\'T match
300 -s string Limit output to packages matching a string
302 index URL or filename of index file
303 (Default is $IndexFile)
305 -t v1 v2 Test two version strings
310 # Parse command-line arguments, deal with them
312 if (!getopts('cdhl:L:s:tv') || ($opt_h)) {
317 $ShowCommandsFlag = $opt_c;
318 $LimitFlag = "<?"; # note that if the user specifies -l, we
319 # deal with this *after* setting a default
329 $PreventFlag = $opt_L;
335 $StringFlag = $opt_s;
342 ($test1, $test2) = @ARGV;
345 $IndexFile = $ARGV[0];
349 # Handle test flag now
351 my $cmp = CompareVersions($test1, $test2);
364 # Determine what command to use to retrieve the index file.
365 if ($IndexFile =~ m-^((http|ftp)://|file:/)-) {
366 $IndexPackagesCommand = $FetchProgram . $IndexFile;
369 $IndexPackagesCommand = $CatProgram . $IndexFile;
373 # Get the current list of installed packages
377 print STDERR "$SelectedCurrentPackagesCommand *$StringFlag*\n";
379 open CURRENT, "$SelectedCurrentPackagesCommand \\*$StringFlag\\*|";
382 print STDERR "$AllCurrentPackagesCommand\n";
384 open CURRENT, "$AllCurrentPackagesCommand|";
387 ($packageString, $rest) = split;
389 ($packageName, $packageFullversion) = &GetNameAndVersion($packageString);
390 $currentPackages{$packageString}{'name'} = $packageName;
391 $currentPackages{$packageString}{'fullversion'} = $packageFullversion;
396 # Iterate over installed packages, get origin directory (if it
397 # exists) and PORTVERSION
400 foreach $packageString (sort keys %currentPackages) {
402 open ORIGIN, "$OriginCommand $packageString|";
406 # If there is an origin variable for this package, then store it.
410 # Try to get the version out of the makefile.
411 # The chdir needs to be successful or our make -V invocation
413 unless (chdir "$PortsDirectory/$origin" and -r "Makefile") {
414 $currentPackages{$packageString}->{orphaned} = $origin;
418 open PKGNAME, "$GetPkgNameCommand|";
419 $pkgname = <PKGNAME>;
422 if ($pkgname ne "") {
425 $pkgname =~ /(.+)-(.+)/;
428 $currentPackages{$packageString}{'origin'} = $origin;
429 $currentPackages{$packageString}{'portversion'} = $portversion;
436 # Slurp in the index file
439 print STDERR "$IndexPackagesCommand\n";
442 open INDEX, "$IndexPackagesCommand|";
444 ($packageString, $packagePath, $rest) = split(/\|/);
446 ($packageName, $packageFullversion) = &GetNameAndVersion($packageString);
447 $indexPackages{$packageName}{'name'} = $packageName;
448 $indexPackages{$packageName}{'path'} = $packagePath;
449 if (defined $indexPackages{$packageName}{'fullversion'}) {
450 $indexPackages{$packageName}{'fullversion'} .= "|" . $packageFullversion;
453 $indexPackages{$packageName}{'fullversion'} = $packageFullversion;
455 $indexPackages{$packageName}{'refcount'}++;
460 # If we're doing commands output, cripple the output so that users
461 # can't just pipe the output to sh(1) and expect this to work.
463 if ($ShowCommandsFlag) {
465 echo "The commands output of pkg_version cannot be executed without editing."
466 echo "You MUST save this output to a file and then edit it, taking into"
467 echo "account package dependencies and the fact that some packages cannot"
468 echo "or should not be upgraded."
476 # Prior versions of pkg_version used commas (",") as delimiters
477 # when there were multiple versions of a package installed.
478 # The new package version number syntax uses commas as well,
479 # so we've used vertical bars ("|") internally, and convert them
480 # to commas before we output anything so the reports look the
481 # same as they did before.
483 foreach $packageString (sort keys %currentPackages) {
484 $~ = "STDOUT_VERBOSE" if $VerboseFlag;
485 $~ = "STDOUT_COMMANDS" if $ShowCommandsFlag;
487 $packageNameVer = $packageString;
488 $packageName = $currentPackages{$packageString}{'name'};
490 $currentVersion = $currentPackages{$packageString}{'fullversion'};
492 if ($currentPackages{$packageString}->{orphaned}) {
494 next if $ShowCommandsFlag;
496 $Comment = "orphaned: $currentPackages{$packageString}->{orphaned}";
498 } elsif (defined $currentPackages{$packageString}{'portversion'}) {
500 $portVersion = $currentPackages{$packageString}{'portversion'};
502 $portPath = "$PortsDirectory/$currentPackages{$packageString}{'origin'}";
505 $rc = &CompareVersions($currentVersion, $portVersion);
509 $Comment = "up-to-date with port";
513 $Comment = "needs updating (port has $portVersion)";
517 $Comment = "succeeds port (port has $portVersion)";
521 $Comment = "Comparison failed";
525 elsif (defined $indexPackages{$packageName}{'fullversion'}) {
527 $indexVersion = $indexPackages{$packageName}{'fullversion'};
528 $indexRefcount = $indexPackages{$packageName}{'refcount'};
530 $portPath = $indexPackages{$packageName}{'path'};
532 if ($indexRefcount > 1) {
534 $Comment = "multiple versions (index has $indexVersion)";
535 $Comment =~ s/\|/,/g;
541 &CompareVersions($currentVersion, $indexVersion);
545 $Comment = "up-to-date with index";
549 $Comment = "needs updating (index has $indexVersion)"
553 $Comment = "succeeds index (index has $indexVersion)";
557 $Comment = "Comparison failed";
562 next if $ShowCommandsFlag;
564 $Comment = "unknown in index";
567 # Having figured out what to print, now determine, based on the
568 # $LimitFlag and $PreventFlag variables, if we should print or not.
569 if ((not $LimitFlag) and (not $PreventFlag)) {
571 } elsif ($PreventFlag) {
572 if ($versionCode !~ m/[$PreventFlag]/o) {
573 if (not $LimitFlag) {
576 write if $versionCode =~ m/[$LimitFlag]/o;
580 # Must mean that there is a LimitFlag
581 write if $versionCode =~ m/[$LimitFlag]/o;
590 # $CommentChar is in the formats because you can't put a literal '#' in
591 # a format specification
593 # General report (no output flags)
595 @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @<
596 $packageName, $versionCode
600 # Verbose report (-v flag)
601 format STDOUT_VERBOSE =
602 @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< @< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
603 $packageNameVer, $versionCode, $Comment
607 # Report that includes commands to update program (-c flag)
608 format STDOUT_COMMANDS =
611 @< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
612 $CommentChar, $packageName
613 @< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
614 $CommentChar, $Comment
617 cd @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
619 make clean && make && pkg_delete -f @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<