From bbd753fec7343bbc5e7bb39b0d51551849faf744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n=20Blanco?= Date: Fri, 3 Mar 2023 19:38:21 +0100 Subject: [PATCH 01/17] Add core.ranges_resolve_prereleases conf --- conans/client/conf/required_version.py | 2 +- conans/client/graph/range_resolver.py | 24 +++++++----- conans/model/conf.py | 1 + conans/model/version_range.py | 16 +++++--- .../model/version/test_version_range.py | 39 ++++++++++++++++++- 5 files changed, 64 insertions(+), 18 deletions(-) diff --git a/conans/client/conf/required_version.py b/conans/client/conf/required_version.py index ee4ae37fc30..90d0f45ff4c 100644 --- a/conans/client/conf/required_version.py +++ b/conans/client/conf/required_version.py @@ -10,7 +10,7 @@ def validate_conan_version(required_range): version_range = VersionRange(required_range) for conditions in version_range.condition_sets: conditions.prerelease = True - if clientver not in version_range: + if not version_range.contains(clientver): raise ConanException("Current Conan version ({}) does not satisfy " "the defined one ({}).".format(clientver, required_range)) diff --git a/conans/client/graph/range_resolver.py b/conans/client/graph/range_resolver.py index 481a83ed5f3..8b800299ff2 100644 --- a/conans/client/graph/range_resolver.py +++ b/conans/client/graph/range_resolver.py @@ -25,12 +25,16 @@ def resolve(self, require, base_conanref, remotes, update): require.ref = previous_ref return + # If the conf is None, do whatever the range specifies + # True: Force prerelease resolving, False: Force no prerelease resolving + resolve_prereleases = self._cache.new_confg.get('core.ranges_resolve_prereleases', default=None, check_type=bool) + ref = require.ref search_ref = RecipeReference(ref.name, "*", ref.user, ref.channel) - resolved_ref = self._resolve_local(search_ref, version_range) + resolved_ref = self._resolve_local(search_ref, version_range, resolve_prereleases) if resolved_ref is None or update: - remote_resolved_ref = self._resolve_remote(search_ref, version_range, remotes, update) + remote_resolved_ref = self._resolve_remote(search_ref, version_range, remotes, update, resolve_prereleases) if resolved_ref is None or (remote_resolved_ref is not None and resolved_ref.version < remote_resolved_ref.version): resolved_ref = remote_resolved_ref @@ -44,7 +48,7 @@ def resolve(self, require, base_conanref, remotes, update): self.resolved_ranges[require.ref] = resolved_ref require.ref = resolved_ref - def _resolve_local(self, search_ref, version_range): + def _resolve_local(self, search_ref, version_range, resolve_prereleases): pattern = str(search_ref) local_found = self._cached_cache.get(pattern) if local_found is None: @@ -55,7 +59,7 @@ def _resolve_local(self, search_ref, version_range): and ref.channel == search_ref.channel] self._cached_cache[pattern] = local_found if local_found: - return self._resolve_version(version_range, local_found) + return self._resolve_version(version_range, local_found, resolve_prereleases) def _search_remote_recipes(self, remote, search_ref): pattern = str(search_ref) @@ -69,22 +73,22 @@ def _search_remote_recipes(self, remote, search_ref): pattern_cached.update({remote.name: results}) return results - def _resolve_remote(self, search_ref, version_range, remotes, update): + def _resolve_remote(self, search_ref, version_range, remotes, update, resolve_prereleases): update_candidates = [] for remote in remotes: remote_results = self._search_remote_recipes(remote, search_ref) - resolved_version = self._resolve_version(version_range, remote_results) + resolved_version = self._resolve_version(version_range, remote_results, resolve_prereleases) if resolved_version: if not update: - return resolved_version # Return first valid occurence in first remote + return resolved_version # Return first valid occurrence in first remote else: update_candidates.append(resolved_version) if len(update_candidates) > 0: # pick latest from already resolved candidates - resolved_version = self._resolve_version(version_range, update_candidates) + resolved_version = self._resolve_version(version_range, update_candidates, resolve_prereleases) return resolved_version @staticmethod - def _resolve_version(version_range, refs_found): + def _resolve_version(version_range, refs_found, resolve_prereleases): for ref in reversed(sorted(refs_found)): - if ref.version in version_range: + if version_range.contains(ref.version, resolve_prereleases): return ref diff --git a/conans/model/conf.py b/conans/model/conf.py index 1bf9483750b..4ea4da60e7a 100644 --- a/conans/model/conf.py +++ b/conans/model/conf.py @@ -13,6 +13,7 @@ "core:default_profile": "Defines the default host profile ('default' by default)", "core:default_build_profile": "Defines the default build profile (None by default)", "core:allow_uppercase_pkg_names": "Temporarily (will be removed in 2.X) allow uppercase names", + "core.ranges_resolve_prereleases": "Whether version ranges can resolve to pre-releases or not", "core.upload:retry": "Number of retries in case of failure when uploading to Conan server", "core.upload:retry_wait": "Seconds to wait between upload attempts to Conan server", "core.download:parallel": "Number of concurrent threads to download packages", diff --git a/conans/model/version_range.py b/conans/model/version_range.py index 1bdadbe77ad..788b67dbd79 100644 --- a/conans/model/version_range.py +++ b/conans/model/version_range.py @@ -56,9 +56,14 @@ def first_non_zero(main): else: return [_Condition(operator, Version(version))] - def valid(self, version): + def valid(self, version, conf_resolve_prepreleases=None): if version.pre: - if not self.prerelease: + # Follow the expression desires only if core.ranges_resolve_prereleases is None, + # else force to the conf's value + if conf_resolve_prepreleases is None: + if not self.prerelease: + return False + elif conf_resolve_prepreleases is False: return False for condition in self.conditions: if condition.operator == ">": @@ -83,7 +88,7 @@ class VersionRange: def __init__(self, expression): self._expression = expression tokens = expression.split(",") - prereleases = None + prereleases = False for t in tokens[1:]: if "include_prerelease" in t: prereleases = True @@ -96,9 +101,10 @@ def __init__(self, expression): def __str__(self): return self._expression - def __contains__(self, version): + def contains(self, version, conf_resolve_prerelease=None): assert isinstance(version, Version), type(version) for condition_set in self.condition_sets: - if condition_set.valid(version): + if condition_set.valid(version, conf_resolve_prerelease): return True return False + diff --git a/conans/test/unittests/model/version/test_version_range.py b/conans/test/unittests/model/version/test_version_range.py index 1392d783dcd..88379640e1b 100644 --- a/conans/test/unittests/model/version/test_version_range.py +++ b/conans/test/unittests/model/version/test_version_range.py @@ -47,10 +47,45 @@ def test_range(version_range, conditions, versions_in, versions_out): assert condition.version == expected_condition[1] for v in versions_in: - assert Version(v) in r + assert r.contains(Version(v)) for v in versions_out: - assert Version(v) not in r + assert not r.contains(Version(v)) + + +@pytest.mark.parametrize("version_range, resolve_prereleases, versions_in, versions_out", [ + ['*', 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"]], + + ['*-', 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"], []], + + ['>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', 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', 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"]], +]) +def test_range_prereleases_conf(version_range, resolve_prereleases, versions_in, versions_out): + r = VersionRange(version_range) + + for v in versions_in: + assert r.contains(Version(v), resolve_prereleases), f"Expected '{version_range}' to contain '{v}' (conf.ranges_resolve_prereleases={resolve_prereleases})" + + 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})" def test_wrong_range_syntax(): From 30c11ab7c862b97cb0471986961dbbd85e855cc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n=20Blanco?= Date: Fri, 3 Mar 2023 20:20:54 +0100 Subject: [PATCH 02/17] Add integration tests --- conans/client/graph/range_resolver.py | 2 +- .../version_ranges/version_range_conf.py | 108 ++++++++++++++++++ 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 conans/test/integration/graph/version_ranges/version_range_conf.py diff --git a/conans/client/graph/range_resolver.py b/conans/client/graph/range_resolver.py index 8b800299ff2..ca6e1a5e47a 100644 --- a/conans/client/graph/range_resolver.py +++ b/conans/client/graph/range_resolver.py @@ -27,7 +27,7 @@ def resolve(self, require, base_conanref, remotes, update): # If the conf is None, do whatever the range specifies # True: Force prerelease resolving, False: Force no prerelease resolving - resolve_prereleases = self._cache.new_confg.get('core.ranges_resolve_prereleases', default=None, check_type=bool) + resolve_prereleases = self._cache.new_config.get('core.ranges_resolve_prereleases', default=None, check_type=bool) ref = require.ref search_ref = RecipeReference(ref.name, "*", ref.user, ref.channel) diff --git a/conans/test/integration/graph/version_ranges/version_range_conf.py b/conans/test/integration/graph/version_ranges/version_range_conf.py new file mode 100644 index 00000000000..52452e3cd59 --- /dev/null +++ b/conans/test/integration/graph/version_ranges/version_range_conf.py @@ -0,0 +1,108 @@ +from utils.tools import TestClient +import textwrap + + +def test_version_range_conf_nonexplicit_expression(): + tc = TestClient() + + base = textwrap.dedent(""" + from conan import ConanFile + + class Base(ConanFile): + name = "base" + """) + + tc.save({"base/conanfile.py": base}) + tc.run("create base/conanfile.py --version=1.5.1") + tc.run("create base/conanfile.py --version=2.5.0-pre") + + conanfilev1 = textwrap.dedent(""" + from conan import ConanFile + + class Package(ConanFile): + name = "pkg" + version = "1.0" + requires = "base/[>1 <2]" + """) + + conanfilev2 = textwrap.dedent(""" + from conan import ConanFile + + class Package(ConanFile): + name = "pkg" + version = "2.0" + requires = "base/[>2 <3]" + """) + + tc.save({"v1/conanfile.py": conanfilev1, "v2/conanfile.py": conanfilev2}) + + tc.save({"global.conf": "core.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 + tc.run("create v2/conanfile.py", assert_error=True) + assert "Package 'base/[>2 <3]' not resolved" in tc.out + + tc.save({"global.conf": "core.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 + tc.run("create v2/conanfile.py") + assert "base/[>2 <3]: base/2.5.0-pre" in tc.out + + tc.save({"global.conf": "core.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 + tc.run("create v2/conanfile.py") + assert "Package 'base/[>2 <3]' not resolved" in tc.out + + +def test_version_range_conf_explicit_expression(): + tc = TestClient() + + base = textwrap.dedent(""" + from conan import ConanFile + + class Base(ConanFile): + name = "base" + """) + + tc.save({"base/conanfile.py": base}) + tc.run("create base/conanfile.py --version=1.5.1") + tc.run("create base/conanfile.py --version=2.5.0-pre") + + conanfilev1 = textwrap.dedent(""" + from conan import ConanFile + + class Package(ConanFile): + name = "pkg" + version = "1.0" + requires = "base/[>1- <2]" + """) + + conanfilev2 = textwrap.dedent(""" + from conan import ConanFile + + class Package(ConanFile): + name = "pkg" + version = "2.0" + requires = "base/[>2- <3]" + """) + + tc.save({"v1/conanfile.py": conanfilev1, "v2/conanfile.py": conanfilev2}) + + tc.save({"global.conf": "core.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 + tc.run("create v2/conanfile.py", assert_error=True) + assert "Package 'base/[>2- <3]' not resolved" in tc.out + + tc.save({"global.conf": "core.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 + tc.run("create v2/conanfile.py") + assert "base/[>2- <3]: base/2.5.0-pre" in tc.out + + tc.save({"global.conf": "core.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 + tc.run("create v2/conanfile.py") + assert "base/[>2- <3]: base/2.5.0-pre" in tc.out From 536e7f54411501f36c613c53d249185b5500eeea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n=20Blanco?= Date: Fri, 3 Mar 2023 21:53:24 +0100 Subject: [PATCH 03/17] Update graph_builder range check usages --- conan/api/subapi/graph.py | 2 +- conans/client/graph/graph_builder.py | 24 +++++++++++++----------- conans/client/graph/python_requires.py | 2 +- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/conan/api/subapi/graph.py b/conan/api/subapi/graph.py index 0c886a95de7..41990cf3478 100644 --- a/conan/api/subapi/graph.py +++ b/conan/api/subapi/graph.py @@ -167,7 +167,7 @@ def load_graph(self, root_node, profile_host, profile_build, lockfile=None, remo assert profile_build is not None remotes = remotes or [] - builder = DepsGraphBuilder(app.proxy, app.loader, app.range_resolver, remotes, + builder = DepsGraphBuilder(app.proxy, app.loader, app.range_resolver, app.cache, remotes, update, check_update) deps_graph = builder.load_graph(root_node, profile_host, profile_build, lockfile) return deps_graph diff --git a/conans/client/graph/graph_builder.py b/conans/client/graph/graph_builder.py index 079841ad35f..7078c5b6436 100644 --- a/conans/client/graph/graph_builder.py +++ b/conans/client/graph/graph_builder.py @@ -17,10 +17,11 @@ class DepsGraphBuilder(object): - def __init__(self, proxy, loader, resolver, remotes, update, check_update): + def __init__(self, proxy, loader, resolver, cache, remotes, update, check_update): self._proxy = proxy self._loader = loader self._resolver = resolver + self._cache = cache self._remotes = remotes # TODO: pass as arg to load_graph() self._update = update self._check_update = check_update @@ -42,6 +43,7 @@ def load_graph(self, root_node, profile_host, profile_build, graph_lock=None): dep_graph.add_node(root_node) open_requires = deque((r, root_node) for r in root_node.conanfile.requires.values()) + resolve_prereleases = self._cache.new_config.get('core.ranges_resolve_prereleases', default=None, check_type=bool) try: while open_requires: # Fetch the first waiting to be expanded (depth-first) @@ -49,7 +51,7 @@ def load_graph(self, root_node, profile_host, profile_build, graph_lock=None): if require.override: continue new_node = self._expand_require(require, node, dep_graph, profile_host, - profile_build, graph_lock) + profile_build, graph_lock, resolve_prereleases) if new_node: self._initialize_requires(new_node, dep_graph, graph_lock) open_requires.extendleft((r, new_node) @@ -62,7 +64,7 @@ def load_graph(self, root_node, profile_host, profile_build, graph_lock=None): dep_graph.resolved_ranges = self._resolver.resolved_ranges return dep_graph - def _expand_require(self, require, node, graph, profile_host, profile_build, graph_lock): + def _expand_require(self, require, node, graph, profile_host, profile_build, graph_lock, resolve_prereleases): # Handle a requirement of a node. There are 2 possibilities # node -(require)-> new_node (creates a new node in the graph) # node -(require)-> previous (creates a diamond with a previously existing node) @@ -82,12 +84,12 @@ def _expand_require(self, require, node, graph, profile_host, profile_build, gra require.ref = prev_ref else: self._conflicting_version(require, node, prev_require, prev_node, - prev_ref, base_previous) + prev_ref, base_previous, resolve_prereleases) if prev_node is None: # new node, must be added and expanded (node -> new_node) new_node = self._create_new_node(node, require, graph, profile_host, profile_build, - graph_lock) + graph_lock, resolve_prereleases) return new_node else: # print("Closing a loop from ", node, "=>", prev_node) @@ -99,7 +101,7 @@ def _expand_require(self, require, node, graph, profile_host, profile_build, gra @staticmethod def _conflicting_version(require, node, - prev_require, prev_node, prev_ref, base_previous): + prev_require, prev_node, prev_ref, base_previous, resolve_prereleases): version_range = require.version_range prev_version_range = prev_require.version_range if prev_node is None else None if version_range: @@ -107,7 +109,7 @@ def _conflicting_version(require, node, if prev_version_range is not None: pass # Do nothing, evaluate current as it were a fixed one else: - if prev_ref.version in version_range: + if version_range.contains(prev_ref.version, resolve_prereleases): require.ref = prev_ref else: raise GraphError.conflict(node, require, prev_node, prev_require, base_previous) @@ -215,7 +217,7 @@ def _resolve_recipe(self, ref, graph_lock): return new_ref, dep_conanfile, recipe_status, remote @staticmethod - def _resolved_system_tool(node, require, profile_build, profile_host): + def _resolved_system_tool(node, require, profile_build, profile_host, resolve_prereleases): if node.context == CONTEXT_HOST and not require.build: # Only for tool_requires return system_tool = profile_build.system_tools if node.context == CONTEXT_BUILD \ @@ -225,13 +227,13 @@ def _resolved_system_tool(node, require, profile_build, profile_host): for d in system_tool: if require.ref.name == d.name: if version_range: - if d.version in version_range: + if version_range.contains(d.version, resolve_prereleases): return d, ConanFile(str(d)), RECIPE_SYSTEM_TOOL, None elif require.ref.version == d.version: return d, ConanFile(str(d)), RECIPE_SYSTEM_TOOL, None - def _create_new_node(self, node, require, graph, profile_host, profile_build, graph_lock): - resolved = self._resolved_system_tool(node, require, profile_build, profile_host) + def _create_new_node(self, node, require, graph, profile_host, profile_build, graph_lock, resolve_prereleases): + resolved = self._resolved_system_tool(node, require, profile_build, profile_host, resolve_prereleases) if resolved is None: try: diff --git a/conans/client/graph/python_requires.py b/conans/client/graph/python_requires.py index 0524a767358..7cbcc2f55f8 100644 --- a/conans/client/graph/python_requires.py +++ b/conans/client/graph/python_requires.py @@ -83,7 +83,7 @@ def _resolve_py_requires(self, py_requires_refs, graph_lock, loader, remotes, up for py_requires_ref in py_requires_refs: py_requires_ref = RecipeReference.loads(py_requires_ref) requirement = Requirement(py_requires_ref) - resolved_ref = self._resolve_ref(requirement, graph_lock, remotes, update) + resolved_ref = self._resolve_ref(requirement, graph_lock, remotes, update, resolve_prereleases) try: py_require = self._cached_py_requires[resolved_ref] except KeyError: From 99c57db636c690ec29ea9e7f7dc72c2091f3d2b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n=20Blanco?= Date: Mon, 6 Mar 2023 19:27:55 +0100 Subject: [PATCH 04/17] Add resolve prerelease logic to graph_lock.py --- conans/client/graph/graph_builder.py | 11 ++++++----- conans/model/graph_lock.py | 12 ++++++------ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/conans/client/graph/graph_builder.py b/conans/client/graph/graph_builder.py index 7078c5b6436..06f0bdf53b0 100644 --- a/conans/client/graph/graph_builder.py +++ b/conans/client/graph/graph_builder.py @@ -39,11 +39,12 @@ def load_graph(self, root_node, profile_host, profile_build, graph_lock=None): root_node.conanfile.settings_target = None self._prepare_node(root_node, profile_host, profile_build, Options()) - self._initialize_requires(root_node, dep_graph, graph_lock) + resolve_prereleases = self._cache.new_config.get('core.ranges_resolve_prereleases', + default=None, check_type=bool) + self._initialize_requires(root_node, dep_graph, graph_lock, resolve_prereleases) dep_graph.add_node(root_node) open_requires = deque((r, root_node) for r in root_node.conanfile.requires.values()) - resolve_prereleases = self._cache.new_config.get('core.ranges_resolve_prereleases', default=None, check_type=bool) try: while open_requires: # Fetch the first waiting to be expanded (depth-first) @@ -53,7 +54,7 @@ def load_graph(self, root_node, profile_host, profile_build, graph_lock=None): new_node = self._expand_require(require, node, dep_graph, profile_host, profile_build, graph_lock, resolve_prereleases) if new_node: - self._initialize_requires(new_node, dep_graph, graph_lock) + self._initialize_requires(new_node, dep_graph, graph_lock, resolve_prereleases) open_requires.extendleft((r, new_node) for r in reversed(new_node.conanfile.requires.values())) self._remove_overrides(dep_graph) @@ -159,12 +160,12 @@ def _prepare_node(node, profile_host, profile_build, down_options): node.conanfile.requires.tool_require(str(tool_require), raise_if_duplicated=False) - def _initialize_requires(self, node, graph, graph_lock): + def _initialize_requires(self, node, graph, graph_lock, resolve_prereleases): # Introduce the current requires to define overrides # This is the first pass over one recipe requires if graph_lock is not None: for require in node.conanfile.requires.values(): - graph_lock.resolve_locked(node, require) + graph_lock.resolve_locked(node, require, resolve_prereleases) for require in node.conanfile.requires.values(): self._resolve_alias(node, require, graph) diff --git a/conans/model/graph_lock.py b/conans/model/graph_lock.py index 7ad1f6f34fc..526ebd1d0cf 100644 --- a/conans/model/graph_lock.py +++ b/conans/model/graph_lock.py @@ -201,12 +201,12 @@ def serialize(self): result["alias"] = {repr(k): repr(v) for k, v in self._alias.items()} return result - def resolve_locked(self, node, require): + def resolve_locked(self, node, require, resolve_prereleases): if require.build or node.context == CONTEXT_BUILD: locked_refs = self._build_requires.refs() else: locked_refs = self._requires.refs() - self._resolve(require, locked_refs) + self._resolve(require, locked_refs, resolve_prereleases) def resolve_prev(self, node): if node.context == CONTEXT_BUILD: @@ -216,14 +216,14 @@ def resolve_prev(self, node): if prevs: return prevs.get(node.package_id) - def _resolve(self, require, locked_refs): + def _resolve(self, require, locked_refs, resolve_prereleases): version_range = require.version_range ref = require.ref matches = [r for r in locked_refs if r.name == ref.name and r.user == ref.user and r.channel == ref.channel] if version_range: for m in matches: - if m.version in version_range: + if version_range.contains(m.version, resolve_prereleases): require.ref = m break else: @@ -250,6 +250,6 @@ def _resolve(self, require, locked_refs): if ref not in matches and not self.partial: raise ConanException(f"Requirement '{repr(ref)}' not in lockfile") - def resolve_locked_pyrequires(self, require): + def resolve_locked_pyrequires(self, require, resolve_prereleases=None): locked_refs = self._python_requires.refs() # CHANGE - self._resolve(require, locked_refs) + self._resolve(require, locked_refs, resolve_prereleases) From 8d4f3902719e17f54c62eb3ece757984d4023503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n=20Blanco?= Date: Wed, 8 Mar 2023 23:50:36 +0100 Subject: [PATCH 05/17] Make pythonrequires not be affected by prerelease conf --- conans/client/graph/python_requires.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conans/client/graph/python_requires.py b/conans/client/graph/python_requires.py index 7cbcc2f55f8..c91f9f65f47 100644 --- a/conans/client/graph/python_requires.py +++ b/conans/client/graph/python_requires.py @@ -66,7 +66,7 @@ def load_py_requires(self, conanfile, loader, graph_lock, remotes, update, check py_requires_refs = [py_requires_refs, ] py_requires = self._resolve_py_requires(py_requires_refs, graph_lock, loader, remotes, - update, check_update) + update, check_update, None) if hasattr(conanfile, "python_requires_extend"): py_requires_extend = conanfile.python_requires_extend if isinstance(py_requires_extend, str): @@ -78,7 +78,7 @@ def load_py_requires(self, conanfile, loader, graph_lock, remotes, update, check conanfile.python_requires = py_requires def _resolve_py_requires(self, py_requires_refs, graph_lock, loader, remotes, update, - check_update): + check_update, resolve_prereleases): result = PyRequires() for py_requires_ref in py_requires_refs: py_requires_ref = RecipeReference.loads(py_requires_ref) From deb5aa0f92445ed580c02b799cd0d55d4b766cf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n=20Blanco?= Date: Thu, 9 Mar 2023 00:57:40 +0100 Subject: [PATCH 06/17] Actually fix the python_requires --- conans/client/graph/python_requires.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/conans/client/graph/python_requires.py b/conans/client/graph/python_requires.py index c91f9f65f47..0524a767358 100644 --- a/conans/client/graph/python_requires.py +++ b/conans/client/graph/python_requires.py @@ -66,7 +66,7 @@ def load_py_requires(self, conanfile, loader, graph_lock, remotes, update, check py_requires_refs = [py_requires_refs, ] py_requires = self._resolve_py_requires(py_requires_refs, graph_lock, loader, remotes, - update, check_update, None) + update, check_update) if hasattr(conanfile, "python_requires_extend"): py_requires_extend = conanfile.python_requires_extend if isinstance(py_requires_extend, str): @@ -78,12 +78,12 @@ def load_py_requires(self, conanfile, loader, graph_lock, remotes, update, check conanfile.python_requires = py_requires def _resolve_py_requires(self, py_requires_refs, graph_lock, loader, remotes, update, - check_update, resolve_prereleases): + check_update): result = PyRequires() for py_requires_ref in py_requires_refs: py_requires_ref = RecipeReference.loads(py_requires_ref) requirement = Requirement(py_requires_ref) - resolved_ref = self._resolve_ref(requirement, graph_lock, remotes, update, resolve_prereleases) + resolved_ref = self._resolve_ref(requirement, graph_lock, remotes, update) try: py_require = self._cached_py_requires[resolved_ref] except KeyError: From 4bc424c4fe6483094f21761934117d8876ae953d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n=20Blanco?= Date: Thu, 9 Mar 2023 01:28:25 +0100 Subject: [PATCH 07/17] Fix sneaky old api usage --- conans/client/graph/graph_builder.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conans/client/graph/graph_builder.py b/conans/client/graph/graph_builder.py index 06f0bdf53b0..981ec8234ba 100644 --- a/conans/client/graph/graph_builder.py +++ b/conans/client/graph/graph_builder.py @@ -116,8 +116,8 @@ def _conflicting_version(require, node, raise GraphError.conflict(node, require, prev_node, prev_require, base_previous) elif prev_version_range is not None: - # TODO: CHeck user/channel conflicts first - if require.ref.version not in prev_version_range: + # TODO: Check user/channel conflicts first + if not prev_version_range.contains(require.ref.version): raise GraphError.conflict(node, require, prev_node, prev_require, base_previous) else: def _conflicting_refs(ref1, ref2): From 5c4e79ae3017822943657745ae04dd4d417c17a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n=20Blanco?= Date: Thu, 9 Mar 2023 10:44:52 +0100 Subject: [PATCH 08/17] Actually change the conf name to core.version_ranges:resolve_prereleases --- conans/client/graph/graph_builder.py | 2 +- conans/client/graph/range_resolver.py | 2 +- conans/model/conf.py | 2 +- conans/model/version_range.py | 2 +- .../graph/version_ranges/version_range_conf.py | 12 ++++++------ 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/conans/client/graph/graph_builder.py b/conans/client/graph/graph_builder.py index 981ec8234ba..d979cb06b9d 100644 --- a/conans/client/graph/graph_builder.py +++ b/conans/client/graph/graph_builder.py @@ -39,7 +39,7 @@ def load_graph(self, root_node, profile_host, profile_build, graph_lock=None): root_node.conanfile.settings_target = None self._prepare_node(root_node, profile_host, profile_build, Options()) - resolve_prereleases = self._cache.new_config.get('core.ranges_resolve_prereleases', + resolve_prereleases = self._cache.new_config.get('core.version_ranges:resolve_prereleases', default=None, check_type=bool) self._initialize_requires(root_node, dep_graph, graph_lock, resolve_prereleases) dep_graph.add_node(root_node) diff --git a/conans/client/graph/range_resolver.py b/conans/client/graph/range_resolver.py index ca6e1a5e47a..90e18d69151 100644 --- a/conans/client/graph/range_resolver.py +++ b/conans/client/graph/range_resolver.py @@ -27,7 +27,7 @@ def resolve(self, require, base_conanref, remotes, update): # If the conf is None, do whatever the range specifies # True: Force prerelease resolving, False: Force no prerelease resolving - resolve_prereleases = self._cache.new_config.get('core.ranges_resolve_prereleases', default=None, check_type=bool) + resolve_prereleases = self._cache.new_config.get('core.version_ranges:resolve_prereleases', default=None, check_type=bool) ref = require.ref search_ref = RecipeReference(ref.name, "*", ref.user, ref.channel) diff --git a/conans/model/conf.py b/conans/model/conf.py index 4ea4da60e7a..1c8b8089d5c 100644 --- a/conans/model/conf.py +++ b/conans/model/conf.py @@ -13,7 +13,7 @@ "core:default_profile": "Defines the default host profile ('default' by default)", "core:default_build_profile": "Defines the default build profile (None by default)", "core:allow_uppercase_pkg_names": "Temporarily (will be removed in 2.X) allow uppercase names", - "core.ranges_resolve_prereleases": "Whether version ranges can resolve to pre-releases or not", + "core.version_ranges:resolve_prereleases": "Whether version ranges can resolve to pre-releases or not", "core.upload:retry": "Number of retries in case of failure when uploading to Conan server", "core.upload:retry_wait": "Seconds to wait between upload attempts to Conan server", "core.download:parallel": "Number of concurrent threads to download packages", diff --git a/conans/model/version_range.py b/conans/model/version_range.py index 788b67dbd79..9021d15ea39 100644 --- a/conans/model/version_range.py +++ b/conans/model/version_range.py @@ -58,7 +58,7 @@ def first_non_zero(main): def valid(self, version, conf_resolve_prepreleases=None): if version.pre: - # Follow the expression desires only if core.ranges_resolve_prereleases is None, + # Follow the expression desires only if core.version_ranges:resolve_prereleases is None, # else force to the conf's value if conf_resolve_prepreleases is None: if not self.prerelease: diff --git a/conans/test/integration/graph/version_ranges/version_range_conf.py b/conans/test/integration/graph/version_ranges/version_range_conf.py index 52452e3cd59..ca7b6066cd9 100644 --- a/conans/test/integration/graph/version_ranges/version_range_conf.py +++ b/conans/test/integration/graph/version_ranges/version_range_conf.py @@ -36,19 +36,19 @@ class Package(ConanFile): tc.save({"v1/conanfile.py": conanfilev1, "v2/conanfile.py": conanfilev2}) - tc.save({"global.conf": "core.ranges_resolve_prereleases=False"}, path=tc.cache.cache_folder) + 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 tc.run("create v2/conanfile.py", assert_error=True) assert "Package 'base/[>2 <3]' not resolved" in tc.out - tc.save({"global.conf": "core.ranges_resolve_prereleases=True"}, path=tc.cache.cache_folder) + 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 tc.run("create v2/conanfile.py") assert "base/[>2 <3]: base/2.5.0-pre" in tc.out - tc.save({"global.conf": "core.ranges_resolve_prereleases=None"}, path=tc.cache.cache_folder) + 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 tc.run("create v2/conanfile.py") @@ -89,19 +89,19 @@ class Package(ConanFile): tc.save({"v1/conanfile.py": conanfilev1, "v2/conanfile.py": conanfilev2}) - tc.save({"global.conf": "core.ranges_resolve_prereleases=False"}, path=tc.cache.cache_folder) + 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 tc.run("create v2/conanfile.py", assert_error=True) assert "Package 'base/[>2- <3]' not resolved" in tc.out - tc.save({"global.conf": "core.ranges_resolve_prereleases=True"}, path=tc.cache.cache_folder) + 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 tc.run("create v2/conanfile.py") assert "base/[>2- <3]: base/2.5.0-pre" in tc.out - tc.save({"global.conf": "core.ranges_resolve_prereleases=None"}, path=tc.cache.cache_folder) + 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 tc.run("create v2/conanfile.py") From 69685bc74b8392af177628497e920e0bbcaf3acd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n=20Blanco?= Date: Thu, 9 Mar 2023 13:55:33 +0100 Subject: [PATCH 09/17] Add =None default value to public method --- conans/model/graph_lock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conans/model/graph_lock.py b/conans/model/graph_lock.py index 526ebd1d0cf..7b87e8ca226 100644 --- a/conans/model/graph_lock.py +++ b/conans/model/graph_lock.py @@ -201,7 +201,7 @@ def serialize(self): result["alias"] = {repr(k): repr(v) for k, v in self._alias.items()} return result - def resolve_locked(self, node, require, resolve_prereleases): + def resolve_locked(self, node, require, resolve_prereleases=None): if require.build or node.context == CONTEXT_BUILD: locked_refs = self._build_requires.refs() else: From 5d0bfd47e7582cea10bf9e280368eec8c81be476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n=20Blanco?= Date: Mon, 13 Mar 2023 16:57:30 +0100 Subject: [PATCH 10/17] Address some of the PR reviews --- conans/client/graph/graph_builder.py | 26 ++++++++++++++------------ conans/client/graph/range_resolver.py | 23 ++++++++++++----------- conans/model/graph_lock.py | 2 +- conans/model/version_range.py | 6 +++--- 4 files changed, 30 insertions(+), 27 deletions(-) diff --git a/conans/client/graph/graph_builder.py b/conans/client/graph/graph_builder.py index d979cb06b9d..1a53be72b1b 100644 --- a/conans/client/graph/graph_builder.py +++ b/conans/client/graph/graph_builder.py @@ -25,6 +25,9 @@ def __init__(self, proxy, loader, resolver, cache, remotes, update, check_update self._remotes = remotes # TODO: pass as arg to load_graph() self._update = update self._check_update = check_update + self._resolve_prereleases = self._cache.new_config.get('core.version_ranges:' + 'resolve_prereleases', + default=None, check_type=bool) def load_graph(self, root_node, profile_host, profile_build, graph_lock=None): assert profile_host is not None @@ -39,9 +42,7 @@ def load_graph(self, root_node, profile_host, profile_build, graph_lock=None): root_node.conanfile.settings_target = None self._prepare_node(root_node, profile_host, profile_build, Options()) - resolve_prereleases = self._cache.new_config.get('core.version_ranges:resolve_prereleases', - default=None, check_type=bool) - self._initialize_requires(root_node, dep_graph, graph_lock, resolve_prereleases) + self._initialize_requires(root_node, dep_graph, graph_lock) dep_graph.add_node(root_node) open_requires = deque((r, root_node) for r in root_node.conanfile.requires.values()) @@ -52,9 +53,9 @@ def load_graph(self, root_node, profile_host, profile_build, graph_lock=None): if require.override: continue new_node = self._expand_require(require, node, dep_graph, profile_host, - profile_build, graph_lock, resolve_prereleases) + profile_build, graph_lock) if new_node: - self._initialize_requires(new_node, dep_graph, graph_lock, resolve_prereleases) + self._initialize_requires(new_node, dep_graph, graph_lock) open_requires.extendleft((r, new_node) for r in reversed(new_node.conanfile.requires.values())) self._remove_overrides(dep_graph) @@ -65,7 +66,7 @@ def load_graph(self, root_node, profile_host, profile_build, graph_lock=None): dep_graph.resolved_ranges = self._resolver.resolved_ranges return dep_graph - def _expand_require(self, require, node, graph, profile_host, profile_build, graph_lock, resolve_prereleases): + def _expand_require(self, require, node, graph, profile_host, profile_build, graph_lock): # Handle a requirement of a node. There are 2 possibilities # node -(require)-> new_node (creates a new node in the graph) # node -(require)-> previous (creates a diamond with a previously existing node) @@ -85,12 +86,12 @@ def _expand_require(self, require, node, graph, profile_host, profile_build, gra require.ref = prev_ref else: self._conflicting_version(require, node, prev_require, prev_node, - prev_ref, base_previous, resolve_prereleases) + prev_ref, base_previous, self._resolve_prereleases) if prev_node is None: # new node, must be added and expanded (node -> new_node) new_node = self._create_new_node(node, require, graph, profile_host, profile_build, - graph_lock, resolve_prereleases) + graph_lock) return new_node else: # print("Closing a loop from ", node, "=>", prev_node) @@ -160,12 +161,12 @@ def _prepare_node(node, profile_host, profile_build, down_options): node.conanfile.requires.tool_require(str(tool_require), raise_if_duplicated=False) - def _initialize_requires(self, node, graph, graph_lock, resolve_prereleases): + def _initialize_requires(self, node, graph, graph_lock): # Introduce the current requires to define overrides # This is the first pass over one recipe requires if graph_lock is not None: for require in node.conanfile.requires.values(): - graph_lock.resolve_locked(node, require, resolve_prereleases) + graph_lock.resolve_locked(node, require, self._resolve_prereleases) for require in node.conanfile.requires.values(): self._resolve_alias(node, require, graph) @@ -233,8 +234,9 @@ def _resolved_system_tool(node, require, profile_build, profile_host, resolve_pr elif require.ref.version == d.version: return d, ConanFile(str(d)), RECIPE_SYSTEM_TOOL, None - def _create_new_node(self, node, require, graph, profile_host, profile_build, graph_lock, resolve_prereleases): - resolved = self._resolved_system_tool(node, require, profile_build, profile_host, resolve_prereleases) + def _create_new_node(self, node, require, graph, profile_host, profile_build, graph_lock): + resolved = self._resolved_system_tool(node, require, profile_build, profile_host, + self._resolve_prereleases) if resolved is None: try: diff --git a/conans/client/graph/range_resolver.py b/conans/client/graph/range_resolver.py index 90e18d69151..d4bbd24b5c3 100644 --- a/conans/client/graph/range_resolver.py +++ b/conans/client/graph/range_resolver.py @@ -12,6 +12,9 @@ def __init__(self, conan_app): self._cached_cache = {} # Cache caching of search result, so invariant wrt installations self._cached_remote_found = {} # dict {ref (pkg/*): {remote_name: results (pkg/1, pkg/2)}} self.resolved_ranges = {} + self._resolve_prereleases = self._cache.new_config.get('core.version_ranges' + ':resolve_prereleases', + default=None, check_type=bool) def resolve(self, require, base_conanref, remotes, update): version_range = require.version_range @@ -25,16 +28,12 @@ def resolve(self, require, base_conanref, remotes, update): require.ref = previous_ref return - # If the conf is None, do whatever the range specifies - # True: Force prerelease resolving, False: Force no prerelease resolving - resolve_prereleases = self._cache.new_config.get('core.version_ranges:resolve_prereleases', default=None, check_type=bool) - ref = require.ref search_ref = RecipeReference(ref.name, "*", ref.user, ref.channel) - resolved_ref = self._resolve_local(search_ref, version_range, resolve_prereleases) + resolved_ref = self._resolve_local(search_ref, version_range) if resolved_ref is None or update: - remote_resolved_ref = self._resolve_remote(search_ref, version_range, remotes, update, resolve_prereleases) + remote_resolved_ref = self._resolve_remote(search_ref, version_range, remotes, update) if resolved_ref is None or (remote_resolved_ref is not None and resolved_ref.version < remote_resolved_ref.version): resolved_ref = remote_resolved_ref @@ -48,7 +47,7 @@ def resolve(self, require, base_conanref, remotes, update): self.resolved_ranges[require.ref] = resolved_ref require.ref = resolved_ref - def _resolve_local(self, search_ref, version_range, resolve_prereleases): + def _resolve_local(self, search_ref, version_range): pattern = str(search_ref) local_found = self._cached_cache.get(pattern) if local_found is None: @@ -59,7 +58,7 @@ def _resolve_local(self, search_ref, version_range, resolve_prereleases): and ref.channel == search_ref.channel] self._cached_cache[pattern] = local_found if local_found: - return self._resolve_version(version_range, local_found, resolve_prereleases) + return self._resolve_version(version_range, local_found, self._resolve_prereleases) def _search_remote_recipes(self, remote, search_ref): pattern = str(search_ref) @@ -73,18 +72,20 @@ def _search_remote_recipes(self, remote, search_ref): pattern_cached.update({remote.name: results}) return results - def _resolve_remote(self, search_ref, version_range, remotes, update, resolve_prereleases): + def _resolve_remote(self, search_ref, version_range, remotes, update): update_candidates = [] for remote in remotes: remote_results = self._search_remote_recipes(remote, search_ref) - resolved_version = self._resolve_version(version_range, remote_results, resolve_prereleases) + resolved_version = self._resolve_version(version_range, remote_results, + self._resolve_prereleases) if resolved_version: if not update: return resolved_version # Return first valid occurrence in first remote else: update_candidates.append(resolved_version) if len(update_candidates) > 0: # pick latest from already resolved candidates - resolved_version = self._resolve_version(version_range, update_candidates, resolve_prereleases) + resolved_version = self._resolve_version(version_range, update_candidates, + self._resolve_prereleases) return resolved_version @staticmethod diff --git a/conans/model/graph_lock.py b/conans/model/graph_lock.py index 7b87e8ca226..526ebd1d0cf 100644 --- a/conans/model/graph_lock.py +++ b/conans/model/graph_lock.py @@ -201,7 +201,7 @@ def serialize(self): result["alias"] = {repr(k): repr(v) for k, v in self._alias.items()} return result - def resolve_locked(self, node, require, resolve_prereleases=None): + def resolve_locked(self, node, require, resolve_prereleases): if require.build or node.context == CONTEXT_BUILD: locked_refs = self._build_requires.refs() else: diff --git a/conans/model/version_range.py b/conans/model/version_range.py index 9021d15ea39..91e115d3a25 100644 --- a/conans/model/version_range.py +++ b/conans/model/version_range.py @@ -56,7 +56,7 @@ def first_non_zero(main): else: return [_Condition(operator, Version(version))] - def valid(self, version, conf_resolve_prepreleases=None): + def _valid(self, version, conf_resolve_prepreleases): if version.pre: # Follow the expression desires only if core.version_ranges:resolve_prereleases is None, # else force to the conf's value @@ -101,10 +101,10 @@ def __init__(self, expression): def __str__(self): return self._expression - def contains(self, version, conf_resolve_prerelease=None): + def contains(self, version, resolve_prerelease=None): assert isinstance(version, Version), type(version) for condition_set in self.condition_sets: - if condition_set.valid(version, conf_resolve_prerelease): + if condition_set._valid(version, resolve_prerelease): return True return False From ed52f3f5a45dca11d758bb32b57c028f67a66444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n=20Blanco?= Date: Mon, 13 Mar 2023 18:13:06 +0100 Subject: [PATCH 11/17] Contains documentation --- conans/client/conf/required_version.py | 4 +++- conans/model/version_range.py | 11 ++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/conans/client/conf/required_version.py b/conans/client/conf/required_version.py index 90d0f45ff4c..318c0044c46 100644 --- a/conans/client/conf/required_version.py +++ b/conans/client/conf/required_version.py @@ -10,7 +10,9 @@ def validate_conan_version(required_range): version_range = VersionRange(required_range) for conditions in version_range.condition_sets: conditions.prerelease = True - if not version_range.contains(clientver): + # TODO: Think what's the better approach for resolving prereleases. + # Listening to the global conf does not sound useful here + if not version_range.contains(clientver, resolve_prerelease=None): raise ConanException("Current Conan version ({}) does not satisfy " "the defined one ({}).".format(clientver, required_range)) diff --git a/conans/model/version_range.py b/conans/model/version_range.py index 91e115d3a25..df0abb30ea6 100644 --- a/conans/model/version_range.py +++ b/conans/model/version_range.py @@ -101,7 +101,16 @@ def __init__(self, expression): def __str__(self): return self._expression - def contains(self, version, resolve_prerelease=None): + def contains(self, version: Version, resolve_prerelease: bool | None): + """ + Whether is inside the version range + + :param version: Version to check against + :param resolve_prerelease: If ``True``, ensure prereleases can be resolved in this range + If ``False``, prerelases can NOT be resolved in this range + If ``None``, prereleases are resolved only if this version range expression says so + :return: Whether the version is inside the range + """ assert isinstance(version, Version), type(version) for condition_set in self.condition_sets: if condition_set._valid(version, resolve_prerelease): From 8a7734cb5d3d8cfe3cd271c6c5ead6a2ac9c23d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n=20Blanco?= Date: Mon, 13 Mar 2023 18:14:51 +0100 Subject: [PATCH 12/17] Add 2 missing Nones to .contains --- conans/test/unittests/model/version/test_version_range.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/conans/test/unittests/model/version/test_version_range.py b/conans/test/unittests/model/version/test_version_range.py index 88379640e1b..5d1fef7146f 100644 --- a/conans/test/unittests/model/version/test_version_range.py +++ b/conans/test/unittests/model/version/test_version_range.py @@ -47,10 +47,10 @@ def test_range(version_range, conditions, versions_in, versions_out): assert condition.version == expected_condition[1] for v in versions_in: - assert r.contains(Version(v)) + assert r.contains(Version(v), None) for v in versions_out: - assert not r.contains(Version(v)) + assert not r.contains(Version(v), None) @pytest.mark.parametrize("version_range, resolve_prereleases, versions_in, versions_out", [ From b79a36aed70f02809257eca84c82e9c688406b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n=20Blanco?= Date: Tue, 14 Mar 2023 12:29:41 +0100 Subject: [PATCH 13/17] Fix type hinting --- conans/model/version_range.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/conans/model/version_range.py b/conans/model/version_range.py index df0abb30ea6..62e98947adc 100644 --- a/conans/model/version_range.py +++ b/conans/model/version_range.py @@ -1,4 +1,5 @@ from collections import namedtuple +from typing import Optional from conans.errors import ConanException from conans.model.recipe_ref import Version @@ -101,7 +102,7 @@ def __init__(self, expression): def __str__(self): return self._expression - def contains(self, version: Version, resolve_prerelease: bool | None): + def contains(self, version: Version, resolve_prerelease: Optional[bool]): """ Whether is inside the version range From 9913ad251221510b97673f49b216c84a1efe1210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n=20Blanco?= Date: Tue, 14 Mar 2023 16:33:59 +0100 Subject: [PATCH 14/17] Add missing resolution --- conans/client/graph/graph_builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conans/client/graph/graph_builder.py b/conans/client/graph/graph_builder.py index 1a53be72b1b..ba77654408f 100644 --- a/conans/client/graph/graph_builder.py +++ b/conans/client/graph/graph_builder.py @@ -118,7 +118,7 @@ def _conflicting_version(require, node, elif prev_version_range is not None: # TODO: Check user/channel conflicts first - if not prev_version_range.contains(require.ref.version): + if not prev_version_range.contains(require.ref.version, resolve_prereleases): raise GraphError.conflict(node, require, prev_node, prev_require, base_previous) else: def _conflicting_refs(ref1, ref2): From 52305dc1cd598f1bd8c919c6e82bbfc98ddf4613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n=20Blanco?= Date: Tue, 14 Mar 2023 16:39:47 +0100 Subject: [PATCH 15/17] conan minimumversion should listen to the required --- conans/client/conf/required_version.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/conans/client/conf/required_version.py b/conans/client/conf/required_version.py index 318c0044c46..86005747778 100644 --- a/conans/client/conf/required_version.py +++ b/conans/client/conf/required_version.py @@ -10,8 +10,6 @@ def validate_conan_version(required_range): version_range = VersionRange(required_range) for conditions in version_range.condition_sets: conditions.prerelease = True - # TODO: Think what's the better approach for resolving prereleases. - # Listening to the global conf does not sound useful here if not version_range.contains(clientver, resolve_prerelease=None): raise ConanException("Current Conan version ({}) does not satisfy " "the defined one ({}).".format(clientver, required_range)) From 423d2bdadc074cf52023181126056bf473a182ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n=20Blanco?= Date: Tue, 14 Mar 2023 21:29:30 +0100 Subject: [PATCH 16/17] Use GenConanfile --- .../version_ranges/version_range_conf.py | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/conans/test/integration/graph/version_ranges/version_range_conf.py b/conans/test/integration/graph/version_ranges/version_range_conf.py index ca7b6066cd9..8d195002bc7 100644 --- a/conans/test/integration/graph/version_ranges/version_range_conf.py +++ b/conans/test/integration/graph/version_ranges/version_range_conf.py @@ -1,3 +1,4 @@ +from conans.test.assets.genconanfile import GenConanfile from utils.tools import TestClient import textwrap @@ -69,25 +70,8 @@ class Base(ConanFile): tc.run("create base/conanfile.py --version=1.5.1") tc.run("create base/conanfile.py --version=2.5.0-pre") - conanfilev1 = textwrap.dedent(""" - from conan import ConanFile - - class Package(ConanFile): - name = "pkg" - version = "1.0" - requires = "base/[>1- <2]" - """) - - conanfilev2 = textwrap.dedent(""" - from conan import ConanFile - - class Package(ConanFile): - name = "pkg" - version = "2.0" - requires = "base/[>2- <3]" - """) - - tc.save({"v1/conanfile.py": conanfilev1, "v2/conanfile.py": conanfilev2}) + 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({"global.conf": "core.version_ranges:resolve_prereleases=False"}, path=tc.cache.cache_folder) tc.run("create v1/conanfile.py") From 26dcf0ef56a52664f875f6990fc096ff58d75354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n=20Blanco?= Date: Tue, 14 Mar 2023 23:29:38 +0100 Subject: [PATCH 17/17] Fix type missmatch --- conans/client/graph/graph_builder.py | 4 +--- conans/client/graph/range_resolver.py | 4 +--- .../version_ranges/version_range_conf.py | 24 ++++--------------- 3 files changed, 6 insertions(+), 26 deletions(-) diff --git a/conans/client/graph/graph_builder.py b/conans/client/graph/graph_builder.py index ba77654408f..c2b1bf3074b 100644 --- a/conans/client/graph/graph_builder.py +++ b/conans/client/graph/graph_builder.py @@ -25,9 +25,7 @@ def __init__(self, proxy, loader, resolver, cache, remotes, update, check_update self._remotes = remotes # TODO: pass as arg to load_graph() self._update = update self._check_update = check_update - self._resolve_prereleases = self._cache.new_config.get('core.version_ranges:' - 'resolve_prereleases', - default=None, check_type=bool) + self._resolve_prereleases = self._cache.new_config.get('core.version_ranges:resolve_prereleases') def load_graph(self, root_node, profile_host, profile_build, graph_lock=None): assert profile_host is not None diff --git a/conans/client/graph/range_resolver.py b/conans/client/graph/range_resolver.py index d4bbd24b5c3..7f7b8858599 100644 --- a/conans/client/graph/range_resolver.py +++ b/conans/client/graph/range_resolver.py @@ -12,9 +12,7 @@ def __init__(self, conan_app): self._cached_cache = {} # Cache caching of search result, so invariant wrt installations self._cached_remote_found = {} # dict {ref (pkg/*): {remote_name: results (pkg/1, pkg/2)}} self.resolved_ranges = {} - self._resolve_prereleases = self._cache.new_config.get('core.version_ranges' - ':resolve_prereleases', - default=None, check_type=bool) + self._resolve_prereleases = self._cache.new_config.get('core.version_ranges:resolve_prereleases') def resolve(self, require, base_conanref, remotes, update): version_range = require.version_range diff --git a/conans/test/integration/graph/version_ranges/version_range_conf.py b/conans/test/integration/graph/version_ranges/version_range_conf.py index 8d195002bc7..0e111e4d792 100644 --- a/conans/test/integration/graph/version_ranges/version_range_conf.py +++ b/conans/test/integration/graph/version_ranges/version_range_conf.py @@ -17,25 +17,8 @@ class Base(ConanFile): tc.run("create base/conanfile.py --version=1.5.1") tc.run("create base/conanfile.py --version=2.5.0-pre") - conanfilev1 = textwrap.dedent(""" - from conan import ConanFile - - class Package(ConanFile): - name = "pkg" - version = "1.0" - requires = "base/[>1 <2]" - """) - - conanfilev2 = textwrap.dedent(""" - from conan import ConanFile - - class Package(ConanFile): - name = "pkg" - version = "2.0" - requires = "base/[>2 <3]" - """) - - tc.save({"v1/conanfile.py": conanfilev1, "v2/conanfile.py": conanfilev2}) + 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({"global.conf": "core.version_ranges:resolve_prereleases=False"}, path=tc.cache.cache_folder) tc.run("create v1/conanfile.py") @@ -52,7 +35,8 @@ class Package(ConanFile): 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 - tc.run("create v2/conanfile.py") + + tc.run("create v2/conanfile.py", assert_error=True) assert "Package 'base/[>2 <3]' not resolved" in tc.out