Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 27 Mar 2001 18:37:30 -0800
From:      bmah@freebsd.org (Bruce A. Mah)
To:        freebsd-ports@freebsd.org, knu@freebsd.org
Cc:        bmah@freebsd.org
Subject:   pkg_version comparison routine
Message-ID:  <200103280237.f2S2bUP02277@bmah-freebsd-0.cisco.com>

next in thread | raw e-mail | index | archive | help
--==_Exmh_-1096974478P
Content-Type: text/plain; charset=us-ascii

Howdy.

I'm going to revisit an issue long overdue...the fact that the algorithm
used by pkg_version(1) for comparing versions doesn't work in all the
cases specified by the Porter's Handbook.  knu tried to get my attention
on this issue a number of months ago, even going so far as to submit
some patches to fix this problem.  Unfortunately I dropped the ball on
this, and I'm sure he's understandably annoyed at me.

Let me apologize now to knu for my being a bad maintainer and ignoring
his email.  I'm sorry...and I didn't mean it personally.

What I've done is to take some of knu's code and ideas and to try to
cover what the Porter's Handbook does, plus one or two other cases I've
seen (plus, admittedly, rewrite it so that my meager Perl skills stand a
chance of maintaining it in the future).  I also took a variant of his
-t feature.

I've got a new version now that handles all the forms of version 
numbers below:

	1.0.5		The usual, run-of-the mill version number.

	1.0.b2		Recommended form for "1.0beta2".

	1.0c		Not allowed by the Porter's Handbook, but
			plenty of ports use this syntax.  Should be
			"1.0.c".

	1.0.b2c		A combination of the preceding two cases.
			Again, not technically allowed.

(The use of PORTEPOCH and PORTREVISION are of course supported; they're 
actually orthogonal, as the code is implemented.)

The -t option below is a little different than what knu did originally; 
what this version does is to take two version strings and return "=", 
"<", or ">" on standard out.  (knu's version took a test and returned a 
truth value for the test.)  I've got a usage in mind where I think that 
this form of -t will be more useful.

The following is a patch against the RELENG_4 branch, but it should
apply equally well to the HEAD.  Comments would be most welcome.

Thanks,

Bruce.

? pkg_version.ps
? pkg_version.diff
Index: pkg_version.1
===================================================================
RCS file: /home/ncvs/src/usr.sbin/pkg_install/version/pkg_version.1,v
retrieving revision 1.5.2.8
diff -c -r1.5.2.8 pkg_version.1
*** pkg_version.1	2001/02/20 23:32:50	1.5.2.8
--- pkg_version.1	2001/03/28 02:13:43
***************
*** 36,41 ****
--- 36,43 ----
  .Op Fl l Ar limchar
  .Op Fl L Ar limchar
  .Op Ar index
+ .Nm pkg_version
+ .Op Fl t Ar version1 version2
  .Sh DESCRIPTION
  The
  .Nm
***************
*** 144,149 ****
--- 146,161 ----
  to the shell, it is best to quote
  .Ar limchar
  with single quotes.
+ .It Fl t
+ Test a pair of version number strings and exit.
+ The output consists of one of the single characters
+ .Li =
+ (equal),
+ .Li \&<
+ (right-hand number greater), or
+ .Li \&>
+ (left-hand number greater) on standard output.
+ This flag is mostly useful for scripts or for testing.
  .It Fl v
  Enable verbose output.  Verbose output includes some English-text
  interpretations of the version number comparisons, as well as the
***************
*** 198,214 ****
  unusable state.
  .Pp
  .Dl % pkg_version -c > do_update
  .Sh AUTHORS
  .An Bruce A. Mah Aq bmah@FreeBSD.org
  .Sh CONTRIBUTORS
  .An Nik Clayton Aq nik@FreeBSD.org ,
  .An Dominic Mitchell Aq dom@palmerharvey.co.uk ,
  .An Mark Ovens Aq marko@FreeBSD.org ,
! .An Doug Barton Aq DougB@gorean.org
  .Sh BUGS
- .Pp
- Patch levels aren't handled
- very well (i.e. version numbers of the form 1.2p3 or 1.2pl3).
  .Pp
  The commands output feature is 
  .Bf Em 
--- 210,228 ----
  unusable state.
  .Pp
  .Dl % pkg_version -c > do_update
+ .Pp
+ The following command compares two package version strings:
+ .Pp
+ .Dl % pkg_version -t 1.5 1.5.1
  .Sh AUTHORS
  .An Bruce A. Mah Aq bmah@FreeBSD.org
  .Sh CONTRIBUTORS
  .An Nik Clayton Aq nik@FreeBSD.org ,
  .An Dominic Mitchell Aq dom@palmerharvey.co.uk ,
  .An Mark Ovens Aq marko@FreeBSD.org ,
! .An Doug Barton Aq DougB@gorean.org ,
! .An Akinori MUSHA Aq knu@FreeBSD.org
  .Sh BUGS
  .Pp
  The commands output feature is 
  .Bf Em 
Index: pkg_version.pl
===================================================================
RCS file: /home/ncvs/src/usr.sbin/pkg_install/version/pkg_version.pl,v
retrieving revision 1.4.2.7
diff -c -r1.4.2.7 pkg_version.pl
*** pkg_version.pl	2001/02/16 17:52:34	1.4.2.7
--- pkg_version.pl	2001/03/28 02:13:43
***************
*** 37,43 ****
  #
  # Configuration global variables
  #
- $Version = '0.1';
  $CurrentPackagesCommand = '/usr/sbin/pkg_info -aI';
  $CatProgram = "cat ";
  $FetchProgram = "fetch -o - ";
--- 37,42 ----
***************
*** 62,70 ****
  # This function returns -1, 0, or 1, in the same manner as <=> or cmp.
  #
  sub CompareNumbers {
!     local($v1, $v2);
!     $v1 = $_[0];
!     $v2 = $_[1];
  
      # Short-cut in case of equality
      if ($v1 eq $v2) {
--- 61,67 ----
  # This function returns -1, 0, or 1, in the same manner as <=> or cmp.
  #
  sub CompareNumbers {
!     my ($v1, $v2) = @_;
  
      # Short-cut in case of equality
      if ($v1 eq $v2) {
***************
*** 73,99 ****
  
      # Loop over different components (the parts separated by dots).
      # If any component differs, we have the basis for an inequality.
!     while (1) {
! 	($p1, $v1) = split(/\./, $v1, 2);
! 	($p2, $v2) = split(/\./, $v2, 2);
! 
! 	# If we\'re out of components, they\'re equal (this probably won\'t
! 	# happen, since the short-cut case above should get this).
! 	if (($p1 eq "") && ($p2 eq "")) {
! 	    return 0;
! 	}
! 	# Check for numeric inequality.  We assume here that (for example)
! 	# 3.09 < 3.10.
! 	elsif ($p1 != $p2) {
! 	    return $p1 <=> $p2;
  	}
! 	# Check for string inequality, given numeric equality.  This
! 	# handles version numbers of the form 3.4j < 3.4k.
! 	elsif ($p1 ne $p2) {
! 	    return $p1 cmp $p2;
  	}
      }
  
  }
  
  #
--- 70,159 ----
  
      # Loop over different components (the parts separated by dots).
      # If any component differs, we have the basis for an inequality.
!     my @s1 = split(/\./, $v1);
!     my @s2 = split(/\./, $v2);
!     my ($c1, $c2);
!     do {
! 	last unless @s1 || @s2;
! 	$c1 = shift @s1;
! 	$c2 = shift @s2;
!     } while ($c1 eq $c2);
! 
!     # Look at the first components of the arrays that are left.
!     # These will determine the result of the comparison.
!     # Note that if either version doesn't have any components left,
!     # it's implicitly treated as a "0".
! 
!     # Our next set of checks looks to see if either component has a
!     # leading letter (there should be at most one leading letter per
!     # component, so that "4.0b1" is allowed, but "4.0beta1" is not).
!     if ($c1 =~ /^\D/) {
! 	if ($c2 =~ /^\D/) {
! 
! 	    # Both have a leading letter, so do an alpha comparison
! 	    # on the letters.  This isn't ideal, since we're assuming
! 	    # that "1.0.b4" > "1.0.a2".  But it's about the best we can do, 
! 	    # without encoding some explicit policy.
! 	    $letter1 = substr($c1, 0, 1);
! 	    $letter2 = substr($c2, 0, 1);
! 
! 	    if ($letter1 ne $letter2) {
! 		return $letter1 cmp $letter2;
! 	    }
! 	    else {
! 		# The letters matched equally.  Delete the leading
! 		# letters and do an alpha comparison on the remaining
! 		# characters, which according to the Porters Handbook
! 		# must be digits, so for example, "1.0.a9" < "1.0.a10".
! 		substr($c1, 0, 1) = "";
! 		substr($c2, 0, 1) = "";
! 
! 		# We copy, without further comment, the code from the
! 		# case below where we handle purely numeric comparisons.
! 		# This covers comparisons of the form "1.0.a9a" < "1.0.a9b"
! 		# (not allowed by the Porters Handbook, but seen in real
! 		# code), as well as the intended "1.0.a9" < "1.0.a10".
! 		if ($c1 != $c2) {
! 		    return $c1 <=> $c2;
! 		}
! 		else {
! 		    return $c1 cmp $c2;
! 		}
! 	    }
! 
  	}
! 	else {
! 	    # $c1 begins with a letter, but $c2 doesn't.  Let $c2
! 	    # win the comparison, so that "1.0.b1" < "1.0.1".
! 	    return -1;
  	}
      }
+     else {
+ 	if ($c2 =~ /^\D/) {
+ 	    # $c2 begins with a letter but $c1 doesn't.  Let $c1
+ 	    # win the comparison, as above.
+ 	    return 1;
+ 	}
+ 	else {
+ 	    # Neither component begins with a leading letter.
+ 	    # Check for numeric inequality.  We assume here that (for example)
+ 	    # "3.09" < "3.10", and that we aren't going to be asked to
+ 	    # decide between "3.010" and "3.10".
+ 	    if ($c1 != $c2) {
+ 		return $c1 <=> $c2;
+ 	    }
  
+ 	    # String comparison, given numeric equality.  This
+ 	    # handles comparisons of the form "3.4j" < "3.4k".  This form
+ 	    # technically isn't allowed by the Porter's Handbook, but a
+ 	    # number of ports in the FreeBSD Ports Collection as of this
+ 	    # writing use it (graphics/jpeg and graphics/xv).  So we need
+ 	    # to support it.
+ 	    else {
+ 		return $c1 cmp $c2;
+ 	    }
+ 	}
+     }
  }
  
  #
***************
*** 194,203 ****
  #
  sub PrintHelp {
      print <<"EOF"
! pkg_version $Version
! Bruce A. Mah <bmah\@freebsd.org>
! 
! Usage: pkg_version [-c] [-d debug] [-h] [-v] [index]
  -c              Show commands to update installed packages
  -d debug	Debugging output (debug controls level of output)
  -h		Help (this message)
--- 254,261 ----
  #
  sub PrintHelp {
      print <<"EOF"
! Usage:	pkg_version [-c] [-d debug] [-h] [-l limchar] [-L limchar] [-v] [index]
! 	pkg_version [-d debug] -t v1 v2
  -c              Show commands to update installed packages
  -d debug	Debugging output (debug controls level of output)
  -h		Help (this message)
***************
*** 206,218 ****
  -v		Verbose output
  index		URL or filename of index file
  		(Default is $IndexFile)
  EOF
  }
  
  #
  # Parse command-line arguments, deal with them
  #
! if (!getopts('cdhl:L:v') || ($opt_h)) {
      &PrintHelp();
      exit;
  }
--- 264,278 ----
  -v		Verbose output
  index		URL or filename of index file
  		(Default is $IndexFile)
+ 
+ -t v1 v2	Test two version strings
  EOF
  }
  
  #
  # Parse command-line arguments, deal with them
  #
! if (!getopts('cdhl:L:tv') || ($opt_h)) {
      &PrintHelp();
      exit;
  }
***************
*** 231,241 ****
  if ($opt_L) {
      $PreventFlag = $opt_L;
  }
  if ($opt_v) {
      $VerboseFlag = 1;
  }
  if ($#ARGV >= 0) {
!     $IndexFile = $ARGV[0];
  }
  
  # Determine what command to use to retrieve the index file.
--- 291,324 ----
  if ($opt_L) {
      $PreventFlag = $opt_L;
  }
+ if ($opt_t) {
+     $TestFlag = 1;
+ }
  if ($opt_v) {
      $VerboseFlag = 1;
  }
  if ($#ARGV >= 0) {
!     if ($TestFlag) {
! 	($test1, $test2) = @ARGV;
!     }
!     else {
! 	$IndexFile = $ARGV[0];
!     }
! }
! 
! # Handle test flag now
! if ($TestFlag) {
!     my $cmp = CompareVersions($test1, $test2);
!     if ($cmp < 0) {
! 	print "<\n";
!     }
!     elsif ($cmp == 0) {
! 	print "=\n";
!     }
!     else {
! 	print ">\n";
!     }
!     exit(0);
  }
  
  # Determine what command to use to retrieve the index file.




--==_Exmh_-1096974478P
Content-Type: application/pgp-signature

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.0.4 (FreeBSD)
Comment: Exmh version 2.2 06/23/2000

iD8DBQE6wU5q2MoxcVugUsMRAnScAKDZ6rqKkqhyjig+PVq7sICiIhjVKwCgg6bv
PHb4nZnOjfE+CvvVciIrRUg=
=FR6a
-----END PGP SIGNATURE-----

--==_Exmh_-1096974478P--

To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-ports" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200103280237.f2S2bUP02277>