Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add include_prerelease and loose option to version range expression #3898

Merged
merged 16 commits into from
Dec 4, 2018
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 61 additions & 6 deletions conans/client/graph/range_resolver.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,78 @@
from conans.model.ref import ConanFileReference

import re
from conans.model.ref import ConanFileReference, ConanName
from conans.errors import ConanException
from conans.search.search import search_recipes


re_param = re.compile(r"^(include_prerelease|loose)\s*=\s*(True|False)$")
re_version = re.compile(r"^((?!(include_prerelease|loose))[a-zA-Z0-9_+.\-~<>=|*^\(\)\s])*$")


def _parse_versionexpr(versionexpr, output):
expression = [it.strip() for it in versionexpr.split(",")]
assert 1 <= len(expression) <= 4, "Invalid expression for version_range '{}'".format(versionexpr)
climblinne marked this conversation as resolved.
Show resolved Hide resolved

include_prerelease = False
loose = True
version_range = []

for i, expr in enumerate(expression):
climblinne marked this conversation as resolved.
Show resolved Hide resolved
match_param = re_param.match(expr)
match_version = re_version.match(expr)

if match_param == match_version:
raise ConanException("Invalid version range '{}', failed in "
"chunk '{}'".format(versionexpr, expr))

if match_version and i not in [0, 1]:
raise ConanException("Invalid version range '{}'".format(versionexpr))

if match_param and i not in [1, 2, 3]:
raise ConanException("Invalid version range '{}'".format(versionexpr))

if match_version:
version_range.append(expr)

if match_param:
if match_param.group(1) == 'loose':
climblinne marked this conversation as resolved.
Show resolved Hide resolved
loose = match_param.group(2) == "True"
elif match_param.group(1) == 'include_prerelease':
include_prerelease = match_param.group(2) == "True"
else:
raise ConanException("Unexpected version range "
"parameter '{}'".format(match_param.group(1)))

version_range = " ".join(map(str, version_range))
return version_range, loose, include_prerelease


def satisfying(list_versions, versionexpr, output):
""" returns the maximum version that satisfies the expression
if some version cannot be converted to loose SemVer, it is discarded with a msg
This provides some woraround for failing comparisons like "2.1" not matching "<=2.1"
This provides some workaround for failing comparisons like "2.1" not matching "<=2.1"
"""
from semver import SemVer, max_satisfying
version_range = versionexpr.replace(",", " ")
from semver import SemVer, Range, max_satisfying

version_range, loose, include_prerelease = _parse_versionexpr(versionexpr, output)

# Check version range expression
try:
act_range = Range(version_range, loose)
except ValueError:
raise ConanException("version range expression '%s' is not valid" % version_range)

# Validate all versions
candidates = {}
for v in list_versions:
try:
ver = SemVer(v, loose=True)
ver = SemVer(v, loose=loose)
candidates[ver] = v
except (ValueError, AttributeError):
output.warn("Version '%s' is not semver, cannot be compared with a range" % str(v))
result = max_satisfying(candidates, version_range, loose=True)

# Search best matching version in range
result = max_satisfying(candidates, act_range, loose=loose, include_prerelease=include_prerelease)
return candidates.get(result)


Expand Down
2 changes: 1 addition & 1 deletion conans/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ PyYAML>=3.11, <3.14.0
patch==1.16
fasteners>=0.14.1
six>=1.10.0
node-semver==0.2.0
node-semver==0.6.1
distro>=1.0.2, <1.2.0
pylint>=1.9.3
future==0.16.0
Expand Down
26 changes: 26 additions & 0 deletions conans/test/model/version_ranges_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,24 @@ def prereleases_versions_test(self):
result = satisfying(["4.2.2", "4.2.3-pre", "4.2.3"], "~4.2.3-", output)
self.assertEqual(result, "4.2.3")

def loose_versions_test(self):
output = TestBufferConanOutput()
result = satisfying(["4.2.2", "4.2.3-pre"], "~4.2.1,loose=False", output)
self.assertEqual(result, "4.2.2")
result = satisfying(["1.1.1", "1.1.2", "1.2", "1.2.1", "1.3", "2.1"], "1.8||1.3,loose=False", output)
self.assertEqual(result, None)
result = satisfying(["1.1.1", "1.1.2", "1.2", "1.2.1", "1.3", "2.1"], "1.8||1.3, loose = False ", output)
self.assertEqual(result, None)
result = satisfying(["1.1.1", "1.1.2", "1.2", "1.2.1", "1.3", "2.1"], "1.8||1.3", output)
self.assertEqual(result, "1.3")

def include_prerelease_versions_test(self):
output = TestBufferConanOutput()
result = satisfying(["4.2.2", "4.2.3-pre"], "~4.2.1,include_prerelease = True", output)
self.assertEqual(result, "4.2.3-pre")
result = satisfying(["4.2.2", "4.2.3-pre"], "~4.2.1", output)
self.assertEqual(result, "4.2.2")

def basic_test(self):
output = TestBufferConanOutput()
result = satisfying(["1.1", "1.2", "1.3", "2.1"], "", output)
Expand Down Expand Up @@ -86,6 +104,14 @@ def basic_test(self):
result = satisfying(["2.1.1"], ">2.1.0", output)
self.assertEqual(result, "2.1.1")

# Invalid ranges
with self.assertRaises(ConanException):
satisfying(["2.1.1"], "2.3 3.2; include_prerelease=True, loose=False", output)
with self.assertRaises(ConanException):
satisfying(["2.1.1"], "2.3 3.2, include_prerelease=Ture, loose=False", output)
with self.assertRaises(ConanException):
satisfying(["2.1.1"], "~2.3, abc, loose=False", output)


hello_content = """
from conans import ConanFile
Expand Down
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import unittest
from conans.client.graph.range_resolver import _parse_versionexpr
from conans.test.utils.tools import TestBufferConanOutput
from conans.errors import ConanException


class ParseVersionExpr(unittest.TestCase):
def test_backwards_compatibility(self):
output = TestBufferConanOutput()
self.assertEqual(_parse_versionexpr("2.3, 3.2", output), ("2.3 3.2", True, False))
self.assertEqual(_parse_versionexpr("2.3, <=3.2", output), ("2.3 <=3.2", True, False))

def test_standard_semver(self):
output = TestBufferConanOutput()
self.assertEqual(_parse_versionexpr("*", output), ("*", True, False))
self.assertEqual(_parse_versionexpr("", output), ("", True, False)) # Defaults to '*'
self.assertEqual(_parse_versionexpr("~1", output), ("~1", True, False))
self.assertEqual(_parse_versionexpr("~1.2.3-beta.2", output), ("~1.2.3-beta.2", True, False))
self.assertEqual(_parse_versionexpr("^0.0", output), ("^0.0", True, False))
self.assertEqual(_parse_versionexpr("1.2.3 - 2.3.4", output), ("1.2.3 - 2.3.4", True, False))
self.assertEqual(_parse_versionexpr(">=1.2.3 <1.(2+1).0", output), (">=1.2.3 <1.(2+1).0", True, False))
climblinne marked this conversation as resolved.
Show resolved Hide resolved

def test_only_loose(self):
output = TestBufferConanOutput()
self.assertEqual(_parse_versionexpr("2.3 ,3.2, loose=True", output), ("2.3 3.2", True, False))
self.assertEqual(_parse_versionexpr("2.3 3.2, loose=False", output), ("2.3 3.2", False, False))
self.assertEqual(_parse_versionexpr("2.3 3.2, loose = False", output), ("2.3 3.2", False, False))
self.assertEqual(_parse_versionexpr("2.3 3.2, loose = True", output), ("2.3 3.2", True, False))

def test_only_prerelease(self):
output = TestBufferConanOutput()
self.assertEqual(_parse_versionexpr("2.3, 3.2, include_prerelease=False", output), ("2.3 3.2", True, False))
self.assertEqual(_parse_versionexpr("2.3, 3.2, include_prerelease=True", output), ("2.3 3.2", True, True))

def test_both(self):
output = TestBufferConanOutput()
self.assertEqual(_parse_versionexpr("2.3, 3.2, loose=False, include_prerelease=True", output),
("2.3 3.2", False, True))
self.assertEqual(_parse_versionexpr("2.3, 3.2, include_prerelease=True, loose=False", output),
("2.3 3.2", False, True))

def test_invalid(self):
climblinne marked this conversation as resolved.
Show resolved Hide resolved
output = TestBufferConanOutput()
self.assertRaises(ConanException, _parse_versionexpr, "loose=False, include_prerelease=True", output)
self.assertRaises(ConanException, _parse_versionexpr, "2.3, 3.2, unexpected=True", output)
self.assertRaises(ConanException, _parse_versionexpr, "2.3, 3.2, loose=Other", output)
self.assertRaises(ConanException, _parse_versionexpr, "2.3, 3.2, ", output)
self.assertRaises(ConanException, _parse_versionexpr, "2.3, 3.2, 1.2.3", output)
self.assertRaises(ConanException, _parse_versionexpr, "2.3 3.2; loose=True, include_prerelease=True", output)
self.assertRaises(ConanException, _parse_versionexpr, "loose=True, 2.3 3.3", output)