diff --git a/conans/model/version_range.py b/conans/model/version_range.py index 2674792d0b2..d4a749f1322 100644 --- a/conans/model/version_range.py +++ b/conans/model/version_range.py @@ -16,14 +16,11 @@ def __init__(self, expression, prerelease): self.conditions = [] for e in expressions: e = e.strip() - if e[-1] == "-": # Include pre-releases - e = e[:-1] - self.prerelease = True self.conditions.extend(self._parse_expression(e)) @staticmethod def _parse_expression(expression): - if expression == "" or expression == "*": + if expression in ("", "*"): return [_Condition(">=", Version("0.0.0"))] elif len(expression) == 1: raise ConanException(f'Error parsing version range "{expression}"') @@ -42,10 +39,14 @@ def _parse_expression(expression): if version == "": raise ConanException(f'Error parsing version range "{expression}"') if operator == "~": # tilde minor + if "-" not in version: + version += "-" v = Version(version) index = 1 if len(v.main) > 1 else 0 return [_Condition(">=", v), _Condition("<", v.upper_bound(index))] elif operator == "^": # caret major + if "-" not in version: + version += "-" v = Version(version) def first_non_zero(main): @@ -57,6 +58,8 @@ def first_non_zero(main): initial_index = first_non_zero(v.main) return [_Condition(">=", v), _Condition("<", v.upper_bound(initial_index))] else: + if (operator == ">=" or operator == "<") and "-" not in version: + version += "-" return [_Condition(operator, Version(version))] def _valid(self, version, conf_resolve_prepreleases): diff --git a/conans/test/integration/graph/version_ranges/test_version_range_conf.py b/conans/test/integration/graph/version_ranges/test_version_range_conf.py index ffba5836e0a..bfd02790bf5 100644 --- a/conans/test/integration/graph/version_ranges/test_version_range_conf.py +++ b/conans/test/integration/graph/version_ranges/test_version_range_conf.py @@ -39,23 +39,23 @@ def test_version_range_conf_explicit_expression(): tc.run("create base/conanfile.py --version=1.5.1") tc.run("create base/conanfile.py --version=2.5.0-pre") - tc.save({"v1/conanfile.py": GenConanfile("pkg", "1.0").with_requires("base/[>1- <2]"), - "v2/conanfile.py": GenConanfile("pkg", "2.0").with_requires("base/[>2- <3]")}) + tc.save({"v1/conanfile.py": GenConanfile("pkg", "1.0").with_requires("base/[>1 <2, include_prerelease]"), + "v2/conanfile.py": GenConanfile("pkg", "2.0").with_requires("base/[>2 <3, include_prerelease]")}) tc.save({"global.conf": "core.version_ranges:resolve_prereleases=False"}, path=tc.cache.cache_folder) tc.run("create v1/conanfile.py") - assert "base/[>1- <2]: base/1.5.1" in tc.out + assert "base/[>1 <2, include_prerelease]: base/1.5.1" in tc.out tc.run("create v2/conanfile.py", assert_error=True) - assert "Package 'base/[>2- <3]' not resolved" in tc.out + assert "Package 'base/[>2 <3, include_prerelease]' not resolved" in tc.out tc.save({"global.conf": "core.version_ranges:resolve_prereleases=True"}, path=tc.cache.cache_folder) tc.run("create v1/conanfile.py") - assert "base/[>1- <2]: base/1.5.1" in tc.out + assert "base/[>1 <2, include_prerelease]: base/1.5.1" in tc.out tc.run("create v2/conanfile.py") - assert "base/[>2- <3]: base/2.5.0-pre" in tc.out + assert "base/[>2 <3, include_prerelease]: base/2.5.0-pre" in tc.out tc.save({"global.conf": "core.version_ranges:resolve_prereleases=None"}, path=tc.cache.cache_folder) tc.run("create v1/conanfile.py") - assert "base/[>1- <2]: base/1.5.1" in tc.out + assert "base/[>1 <2, include_prerelease]: base/1.5.1" in tc.out tc.run("create v2/conanfile.py") - assert "base/[>2- <3]: base/2.5.0-pre" in tc.out + assert "base/[>2 <3, include_prerelease]: base/2.5.0-pre" in tc.out diff --git a/conans/test/unittests/model/version/test_version_parse.py b/conans/test/unittests/model/version/test_version_parse.py index 78d99901bfa..e68f77dddb0 100644 --- a/conans/test/unittests/model/version/test_version_parse.py +++ b/conans/test/unittests/model/version/test_version_parse.py @@ -27,7 +27,8 @@ ("+build2", '', None, "build2"), ("1.2.3-pre.4-5-6+build.7-8-9", - "1.2.3", "pre.4-5-6", "build.7-8-9") + "1.2.3", "pre.4-5-6", "build.7-8-9"), + ("1-", "1", "", None) ] diff --git a/conans/test/unittests/model/version/test_version_range.py b/conans/test/unittests/model/version/test_version_range.py index aed37a168b2..1e411616424 100644 --- a/conans/test/unittests/model/version/test_version_range.py +++ b/conans/test/unittests/model/version/test_version_range.py @@ -6,16 +6,16 @@ values = [ ['>1.0.0', [[['>', '1.0.0']]], ["1.0.1"], ["0.1"]], - ['<2.0', [[['<', '2.0']]], ["1.0.1"], ["2.1"]], - ['>1 <2.0', [[['>', '1'], ['<', '2.0']]], ["1.5.1"], ["0.1", "2.1"]], + ['<2.0', [[['<', '2.0-']]], ["1.0.1"], ["2.1"]], + ['>1 <2.0', [[['>', '1'], ['<', '2.0-']]], ["1.5.1"], ["0.1", "2.1"]], # tilde - ['~2.5', [[['>=', '2.5'], ['<', '2.6-']]], ["2.5.0", "2.5.3"], ["2.7", "2.6.1"]], - ['~2.5.1', [[['>=', '2.5.1'], ['<', '2.6.0-']]], ["2.5.1", "2.5.3"], ["2.5", "2.6.1"]], - ['~1', [[['>=', '1'], ['<', '2-']]], ["1.3", "1.8.1"], ["0.8", "2.2"]], + ['~2.5', [[['>=', '2.5-'], ['<', '2.6-']]], ["2.5.0", "2.5.3"], ["2.7", "2.6.1"]], + ['~2.5.1', [[['>=', '2.5.1-'], ['<', '2.6.0-']]], ["2.5.1", "2.5.3"], ["2.5", "2.6.1"]], + ['~1', [[['>=', '1-'], ['<', '2-']]], ["1.3", "1.8.1"], ["0.8", "2.2"]], # caret - ['^1.2', [[['>=', '1.2'], ['<', '2.0-']]], ["1.2.1", "1.51"], ["1", "2", "2.0.1"]], - ['^1.2.3', [[['>=', '1.2.3'], ["<", '2.0.0-']]], ["1.2.3", "1.2.4"], ["2", "2.1", "2.0.1"]], - ['^0.1.2', [[['>=', '0.1.2'], ['<', '0.2.0-']]], ["0.1.3", "0.1.44"], ["1", "0.3", "0.2.1"]], + ['^1.2', [[['>=', '1.2-'], ['<', '2.0-']]], ["1.2.1", "1.51"], ["1", "2", "2.0.1"]], + ['^1.2.3', [[['>=', '1.2.3-'], ["<", '2.0.0-']]], ["1.2.3", "1.2.4"], ["2", "2.1", "2.0.1"]], + ['^0.1.2', [[['>=', '0.1.2-'], ['<', '0.2.0-']]], ["0.1.3", "0.1.44"], ["1", "0.3", "0.2.1"]], # Identity ['1.0.0', [[["=", "1.0.0"]]], ["1.0.0"], ["2", "1.0.1"]], ['=1.0.0', [[["=", "1.0.0"]]], ["1.0.0"], ["2", "1.0.1"]], @@ -24,17 +24,20 @@ ['', [[[">=", "0.0.0"]]], ["1.0", "a.b"], []], # Unions ['1.0.0 || 2.1.3', [[["=", "1.0.0"]], [["=", "2.1.3"]]], ["1.0.0", "2.1.3"], ["2", "1.0.1"]], - ['>1 <2.0 || ^3.2 ', [[['>', '1'], ['<', '2.0']], - [['>=', '3.2'], ['<', '4.0-']]], ["1.5", "3.3"], ["2.1", "0.1", "5"]], + ['>1 <2.0 || ^3.2 ', [[['>', '1'], ['<', '2.0-']], + [['>=', '3.2-'], ['<', '4.0-']]], ["1.5", "3.3"], ["2.1", "0.1", "5"]], # pre-releases ['', [[[">=", "0.0.0"]]], ["1.0"], ["1.0-pre.1"]], ['*, include_prerelease=True', [[[">=", "0.0.0"]]], ["1.0", "1.0-pre.1"], []], - ['*-', [[[">=", "0.0.0"]]], ["1.0", "1.0-pre.1"], []], - ['>1- <2.0', [[['>', '1'], ['<', '2.0']]], ["1.5.1-pre1"], ["2.1-pre1"]], - ['>1- <2.0 || ^3.2 ', [[['>', '1'], ['<', '2.0']], - [['>=', '3.2'], ['<', '4.0-']]], ["1.5-a1", "3.3"], ["3.3-a1"]], - ['^1.1.2-', [[['>=', '1.1.2'], ['<', '2.0.0-']]], ["1.2.3", "1.2.0-alpha1"], ["2.0.0-alpha1"]], - ['~1.1.2-', [[['>=', '1.1.2'], ['<', '1.2.0-']]], ["1.1.3", "1.1.3-alpha1"], ["1.2.0-alpha1"]], + ['>1- <2.0', [[['>', '1-'], ['<', '2.0-']]], + ["1.0", "1.1", "1.9"], ["1-pre.1", "1.5.1-pre1", "2.1-pre1"]], + ['>1- <2.0 || ^3.2 ', [[['>', '1-'], ['<', '2.0-']], [['>=', '3.2-'], ['<', '4.0-']]], + ["1.0", "1.2", "3.3"], ["1-pre.1", "1.5-a1", "3.3-a1"]], + ['^1.1.2', [[['>=', '1.1.2-'], ['<', '2.0.0-']]], ["1.2.3"], ["1.2.0-alpha1", "2.0.0-alpha1"]], + ['~1.1.2', [[['>=', '1.1.2-'], ['<', '1.2.0-']]], ["1.1.3"], ["1.1.3-alpha1", "1.2.0-alpha1"]], + ['>=2.0-pre.0 <3', [[['>=', '2.0-pre.0'], ['<', '3-']]], ["2.1"], ["2.0-pre.1","2.0-alpha.1"]], + # Or explicitly + ['>=2.0-pre.0, include_prerelease', [[['>=', '2.0-pre.0']]], ["2.1", "2.0-pre.1"], ["1.5"]] ] @@ -58,10 +61,6 @@ def test_range(version_range, conditions, versions_in, versions_out): ['*', False, ["1.5.1"], ["1.5.1-pre1", "2.1-pre1"]], ['*', None, ["1.5.1"], ["1.5.1-pre1", "2.1-pre1"]], - ['*-', True, ["1.5.1", "1.5.1-pre1", "2.1-pre1"], []], - ['*-', False, ["1.5.1"], ["1.5.1-pre1", "2.1-pre1"]], - ['*-', None, ["1.5.1", "1.5.1-pre1", "2.1-pre1"], []], - ['*, include_prerelease', True, ["1.5.1", "1.5.1-pre1", "2.1-pre1"], []], ['*, include_prerelease', False, ["1.5.1"], ["1.5.1-pre1", "2.1-pre1"]], ['*, include_prerelease', None, ["1.5.1", "1.5.1-pre1", "2.1-pre1"], []], @@ -72,11 +71,21 @@ def test_range(version_range, conditions, versions_in, versions_out): ['>1- <2.0', True, ["1.5.1", "1.5.1-pre1"], ["2.1-pre1"]], ['>1- <2.0', False, ["1.5.1"], ["1.5.1-pre1", "2.1-pre1"]], - ['>1- <2.0', None, ["1.5.1", "1.5.1-pre1"], ["2.1-pre1"]], + ['>1- <2.0, include_prerelease', None, ["1.5.1", "1.5.1-pre1"], ["2.1-pre1"]], ['>1 <2.0, include_prerelease', True, ["1.5.1", "1.5.1-pre1"], ["2.1-pre1"]], ['>1 <2.0, include_prerelease', False, ["1.5.1"], ["1.5.1-pre1", "2.1-pre1"]], ['>1 <2.0, include_prerelease', None, ["1.5.1", "1.5.1-pre1"], ["2.1-pre1"]], + + # Summary of new behaviors + ['>=1 <2.0', False, ["1.0", "1.1", "1.9"], ["0.9", "1.0-pre.1", "1.1-pre.1", "2.0-pre", "2.0"]], + # OLD + # ['>=1 <2.0', True, ["1.0", "1.1-pre.1", "1.1", "1.9", "2.0-pre"], ["0.9", "1.0-pre.1", "2.0"]], + # NEW + ['>=1 <2.0', True, ["1.0-pre.1", "1.0", "1.1-pre.1", "1.1", "1.9"], ["0.9", "2.0", "2.0-pre"]], + ['>1 <=2.0', False, ["1.1", "1.9", "2.0"], ["0.9", "1.0-pre.1", "1.0", "1.1-pre.1", "2.0-pre"]], + # This should be old and new behaviors remain the same + ['>1 <=2.0', True, ["1.1-pre.1", "1.1", "1.9", "2.0", "2.0-pre"], ["0.9", "1.0", "1.0-pre.1"]], ]) def test_range_prereleases_conf(version_range, resolve_prereleases, versions_in, versions_out): r = VersionRange(version_range) @@ -87,6 +96,7 @@ def test_range_prereleases_conf(version_range, resolve_prereleases, versions_in, for v in versions_out: assert not r.contains(Version(v), resolve_prereleases), f"Expected '{version_range}' NOT to contain '{v}' (conf.ranges_resolve_prereleases={resolve_prereleases})" + @pytest.mark.parametrize("version_range", [ ">= 1.0", # https://github.com/conan-io/conan/issues/12692 ">=0.0.1 < 1.0" # https://github.com/conan-io/conan/issues/14612