Skip to content

Commit

Permalink
enable trailing '_' to anchor openssl-like versions
Browse files Browse the repository at this point in the history
Openssl versions should sort e.g. [1.0.1, 1.0.1a, 1.0.1b] etc., however
this is incompatible with most other version sort conventions and therefore
conda sorts as [1.0.1a, 1.0.1b, 1.0.1]. The VersionOrder docstring
anticipates the need to anchor an openssl-like version sequence at
e.g. 1.0.1, and indicates that appending a dash to the version will
accomplish that goal. However dashes have never been allowed in versions
due to dashes being the delimiters for the legacy 'dist' string.

This commit modifies the anchoring functionality to be provided by
appending an underscore rather than a dash. Therefore a sort order
of [1.0.1_, 1.0.1a, 1.0.1b, 1.0.1] holds.

Note that previous versions of conda prior to this commit will blow up
with VersionOrder("1.0.1_") or MatchSpec("openssl=1.0.1_") being an
invalid version. Therefore this convention cannot show up in repodata
without crashing all current versions of conda. This PR also doesn't
break current versions of conda though, because
MatchSpec("openssl=1.0.1-") was never valid.
  • Loading branch information
kalefranz committed Apr 19, 2020
1 parent 07a113d commit dfb566b
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 8 deletions.
17 changes: 13 additions & 4 deletions conda/models/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ class VersionOrder(object):
< 0.960923
< 1.0
< 1.1dev1 # special case 'dev'
< 1.1_ # appended underscore is special case for openssl-like versions
< 1.1a1
< 1.1.0dev1 # special case 'dev'
== 1.1.dev1 # 0 is inserted before string
Expand All @@ -146,12 +147,12 @@ class VersionOrder(object):
In particular, openssl interprets letters as version counters rather than
pre-release identifiers. For openssl, the relation
1.0.1 < 1.0.1a => True # for openssl
1.0.1 < 1.0.1a => False # should be true for openssl
holds, whereas conda packages use the opposite ordering. You can work-around
this problem by appending a dash to plain version numbers:
this problem by appending an underscore to plain version numbers:
1.0.1a => 1.0.1post.a # ensure correct ordering for openssl
1.0.1_ < 1.0.1a => True # ensure correct ordering for openssl
"""
_cache_ = {}

Expand Down Expand Up @@ -200,7 +201,15 @@ def __init__(self, vstr):
raise InvalidVersionSpec(vstr, "duplicated local version separator '+'")

# split version
self.version = epoch + version[0].replace('_', '.').split('.')
if version[0][-1] == "_":
# If the last character of version is "-" or "_", don't split that out
# individually. Implements the instructions for openssl-like versions
# > You can work-around this problem by appending a dash to plain version numbers
split_version = version[0][:-1].replace('_', '.').split('.')
split_version[-1] += "_"
else:
split_version = version[0].replace('_', '.').split('.')
self.version = epoch + split_version

# split components into runs of numerals and non-numerals,
# convert numerals to int, handle special strings
Expand Down
8 changes: 8 additions & 0 deletions tests/models/test_match_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,9 @@ def test_canonical_string_forms(self):
assert m("numpy~=1.10.1") == "numpy~=1.10.1"
assert m("numpy ~=1.10.1 py38_0") == "numpy[version='~=1.10.1',build=py38_0]"

assert m("openssl=1.1.1_") == "openssl=1.1.1_"
assert m("openssl>=1.1.1_,!=1.1.1c") == "openssl[version='>=1.1.1_,!=1.1.1c']"

# # a full, exact spec looks like 'defaults/linux-64::numpy==1.8=py26_0'
# # can we take an old dist str and reliably parse it with MatchSpec?
# assert m("numpy-1.10-py38_0") == "numpy==1.10=py38_0"
Expand Down Expand Up @@ -388,6 +391,11 @@ def test_build_number_and_filename(self):
assert ms.get_exact_value('build') == '0'
assert ms._to_filename_do_not_use() == 'zlib-1.2.7-0.tar.bz2'

def test_openssl_match(self):
dst = Dist('defaults::openssl-1.0.1_-4')
assert MatchSpec('openssl>=1.0.1_').match(DPkg(dst))
assert not MatchSpec('openssl>=1.0.1').match(DPkg(dst))

def test_track_features_match(self):
dst = Dist('defaults::foo-1.2.3-4.tar.bz2')
a = MatchSpec(features='test')
Expand Down
29 changes: 25 additions & 4 deletions tests/models/test_version.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import absolute_import, print_function

from random import shuffle
import unittest

from conda.exceptions import InvalidVersionSpec
Expand Down Expand Up @@ -33,6 +34,7 @@ def test_version_order(self):
("1.0.4b1", [[0], [1], [0], [4, 'b', 1]]),
("1.0.4", [[0], [1], [0], [4]]),
("1.1dev1", [[0], [1], [1, 'DEV', 1]]),
("1.1_", [[0], [1], [1, '_']]),
("1.1a1", [[0], [1], [1, 'a', 1]]),
("1.1.dev1", [[0], [1], [1], [0, 'DEV', 1]]),
("1.1.a1", [[0], [1], [1], [0, 'a', 1]]),
Expand Down Expand Up @@ -97,10 +99,29 @@ def test_version_order(self):
self.assertFalse(VersionOrder("0.4.1").startswith(VersionOrder("0.4.1+1.3")))
self.assertFalse(VersionOrder("0.4.1+1").startswith(VersionOrder("0.4.1+1.3")))

# test openssl convention
openssl = [VersionOrder(k) for k in ['1.0.1', '1.0.1post.a', '1.0.1post.b',
'1.0.1post.z', '1.0.1post.za', '1.0.2']]
self.assertEqual(sorted(openssl), openssl)
def test_openssl_convention(self):
openssl = [VersionOrder(k) for k in (
'1.0.1dev',
'1.0.1_', # <- this
'1.0.1a',
'1.0.1b',
'1.0.1c',
'1.0.1d',
'1.0.1r',
'1.0.1rc',
'1.0.1rc1',
'1.0.1rc2',
'1.0.1s',
'1.0.1', # <- compared to this
'1.0.1post.a',
'1.0.1post.b',
'1.0.1post.z',
'1.0.1post.za',
'1.0.2',
)]
shuffled = openssl.copy()
shuffle(shuffled)
assert sorted(shuffled) == openssl

def test_pep440(self):
# this list must be in sorted order (slightly modified from the PEP 440 test suite
Expand Down

0 comments on commit dfb566b

Please sign in to comment.