Skip to content

Commit c27b03f

Browse files
authored
Feature/update resolution (#17642)
* update resolution new conditional logic * failing test * made it work for the 2 remotes case too * deprecation warning
1 parent b53c0af commit c27b03f

File tree

5 files changed

+92
-13
lines changed

5 files changed

+92
-13
lines changed

conan/internal/conan_app.py

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from conans.client.loader import ConanFileLoader, load_python_file
99
from conans.client.remote_manager import RemoteManager
1010
from conans.client.rest.auth_manager import ConanApiAuthManager
11-
from conans.client.rest.conan_requester import ConanRequester
1211
from conan.internal.api.remotes.localdb import LocalDB
1312

1413

conan/internal/model/conf.py

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"core.download:retry_wait": "Seconds to wait between download attempts from Conan server",
2929
"core.download:download_cache": "Define path to a file download cache",
3030
"core.cache:storage_path": "Absolute path where the packages and database are stored",
31+
"core:update_policy": "(Legacy). If equal 'legacy' when multiple remotes, update based on order of remotes, only the timestamp of the first occurrence of each revision counts.",
3132
# Sources backup
3233
"core.sources:download_cache": "Folder to store the sources backup",
3334
"core.sources:download_urls": "List of URLs to download backup sources from",

conan/test/utils/tools.py

-10
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import traceback
1212
import uuid
1313
import zipfile
14-
from collections import OrderedDict
1514
from contextlib import contextmanager
1615
from inspect import getframeinfo, stack
1716
from urllib.parse import urlsplit, urlunsplit
@@ -416,15 +415,6 @@ def __init__(self, cache_folder=None, current_folder=None, servers=None, inputs=
416415
self.cache_folder = cache_folder or os.path.join(temp_folder(path_with_spaces), ".conan2")
417416

418417
self.requester_class = requester_class
419-
420-
if servers and len(servers) > 1 and not isinstance(servers, OrderedDict):
421-
raise Exception(textwrap.dedent("""
422-
Testing framework error: Servers should be an OrderedDict. e.g:
423-
servers = OrderedDict()
424-
servers["r1"] = server
425-
servers["r2"] = TestServer()
426-
"""))
427-
428418
self.servers = servers or {}
429419
if servers is not False: # Do not mess with registry remotes
430420
self.update_servers()

conans/client/graph/proxy.py

+22-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ def __init__(self, conan_app, editable_packages):
1414
self._cache = conan_app.cache
1515
self._remote_manager = conan_app.remote_manager
1616
self._resolved = {} # Cache of the requested recipes to optimize calls
17+
self._legacy_update = conan_app.global_conf.get("core:update_policy", choices=["legacy"])
1718

1819
def get_recipe(self, ref, remotes, update, check_update):
1920
"""
@@ -97,18 +98,27 @@ def _find_newest_recipe_in_remotes(self, reference, remotes, update, check_updat
9798
output = ConanOutput(scope=str(reference))
9899

99100
results = []
101+
need_update = should_update_reference(reference, update) or check_update
100102
for remote in remotes:
101103
if remote.allowed_packages and not any(reference.matches(f, is_consumer=False)
102104
for f in remote.allowed_packages):
103105
output.debug(f"Excluding remote {remote.name} because recipe is filtered out")
104106
continue
105107
output.info(f"Checking remote: {remote.name}")
106108
try:
109+
if self._legacy_update and need_update:
110+
if not getattr(ConanProxy, "update_policy_legacy_warning", None):
111+
ConanProxy.update_policy_legacy_warning = True
112+
ConanOutput().warning("The 'core:update_policy' conf is deprecated and will "
113+
"be removed in future versions", warn_tag="deprecated")
114+
refs = self._remote_manager.get_recipe_revisions_references(reference, remote)
115+
results.extend([{'remote': remote, 'ref': ref} for ref in refs])
116+
continue
107117
if not reference.revision:
108118
ref = self._remote_manager.get_latest_recipe_reference(reference, remote)
109119
else:
110120
ref = self._remote_manager.get_recipe_revision_reference(reference, remote)
111-
if not should_update_reference(reference, update) and not check_update:
121+
if not need_update:
112122
return remote, ref
113123
results.append({'remote': remote, 'ref': ref})
114124
except NotFoundException:
@@ -117,6 +127,17 @@ def _find_newest_recipe_in_remotes(self, reference, remotes, update, check_updat
117127
if len(results) == 0:
118128
return None, None
119129

130+
if self._legacy_update and need_update:
131+
# Use only the first occurence of each revision in the remotes
132+
filtered_results = []
133+
revisions = set()
134+
for r in results:
135+
ref = r["ref"]
136+
if ref.revision not in revisions:
137+
revisions.add(ref.revision)
138+
filtered_results.append(r)
139+
results = filtered_results
140+
120141
remotes_results = sorted(results, key=lambda k: k['ref'].timestamp, reverse=True)
121142
# get the latest revision from all remotes
122143
found_rrev = remotes_results[0]

test/integration/command/install/install_update_test.py

+69-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from time import sleep
44

55
from conan.api.model import RecipeReference
6-
from conan.test.utils.tools import TestClient, GenConanfile
6+
from conan.test.utils.tools import TestClient, GenConanfile, TestServer
77
from conans.util.files import load
88

99

@@ -165,3 +165,71 @@ def test_install_update_repeated_tool_requires():
165165
c.run("create libc")
166166
c.run("install libc --update -pr=profile")
167167
assert 1 == str(c.out).count("tool/0.1: Checking remote")
168+
169+
170+
class TestUpdateOldPolicy:
171+
def test_multi_remote_update_resolution(self):
172+
c = TestClient(servers={"r1": TestServer(), "r2": TestServer(), "r3": TestServer()},
173+
inputs=["admin", "password"] * 3, light=True)
174+
c.save({"conanfile.py": GenConanfile("pkg", "0.1")})
175+
c.run("export .")
176+
rev1 = c.exported_recipe_revision()
177+
c.run("upload * -r=r1 -c")
178+
# second revision
179+
c.save({"conanfile.py": GenConanfile("pkg", "0.1").with_class_attribute("auther = 'me'")})
180+
c.run("export .")
181+
rev2 = c.exported_recipe_revision()
182+
assert rev1 != rev2
183+
c.run("upload * -r=r2 -c") # By default uploads latest revisions only
184+
assert rev1 not in c.out
185+
assert rev2 in c.out
186+
# going back to the previous revision
187+
c.save({"conanfile.py": GenConanfile("pkg", "0.1")})
188+
c.run("export .") # Makes it the latest
189+
rev3 = c.exported_recipe_revision()
190+
assert rev1 == rev3
191+
c.run("upload * -r=r3 -c") # By default uploads latest revisions only
192+
assert rev3 in c.out
193+
assert rev2 not in c.out
194+
195+
# now test the --update, it will pick up the latest revision, which is r3
196+
c.run("remove * -c")
197+
c.run("graph info --requires=pkg/0.1 --update")
198+
assert f"pkg/0.1#{rev3} - Downloaded (r3)" in c.out
199+
200+
# But if we enable order-based first found timestamp, it will pick up r2
201+
c.run("remove * -c")
202+
c.run("graph info --requires=pkg/0.1 --update -cc core:update_policy=legacy")
203+
assert "The 'core:update_policy' conf is deprecated and will be removed" in c.out
204+
assert f"pkg/0.1#{rev2} - Downloaded (r2)" in c.out
205+
206+
def test_multi_remote_update_resolution_2_remotes(self):
207+
c = TestClient(servers={"r1": TestServer(), "r2": TestServer()},
208+
inputs=["admin", "password"] * 2, light=True)
209+
c.save({"conanfile.py": GenConanfile("pkg", "0.1")})
210+
c.run("export .")
211+
rev1 = c.exported_recipe_revision()
212+
c.run("upload * -r=r1 -c")
213+
# second revision
214+
c.save({"conanfile.py": GenConanfile("pkg", "0.1").with_class_attribute("auther = 'me'")})
215+
c.run("export .")
216+
rev2 = c.exported_recipe_revision()
217+
assert rev1 != rev2
218+
c.run("upload * -r=r1 -c")
219+
c.run("list *#* -r=r1")
220+
221+
# Upload the first, old revision to the other remote
222+
c.run(f"upload pkg/0.1#{rev1} -r=r2 -c")
223+
assert rev1 in c.out
224+
assert rev2 not in c.out
225+
c.run("list *#* -r=r2")
226+
227+
# now test the --update, it will pick up the latest revision, which is r3
228+
c.run("remove * -c")
229+
c.run("graph info --requires=pkg/0.1 --update")
230+
assert f"pkg/0.1#{rev1} - Downloaded (r2)" in c.out
231+
232+
# But if we enable order-based first found timestamp, it will pick up r2
233+
c.run("remove * -c")
234+
c.run("graph info --requires=pkg/0.1 --update -cc core:update_policy=legacy")
235+
assert f"pkg/0.1#{rev2} - Downloaded (r1)" in c.out

0 commit comments

Comments
 (0)