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

Feature/update resolution #17642

Draft
wants to merge 5 commits into
base: develop2
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
1 change: 0 additions & 1 deletion conan/internal/conan_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from conans.client.loader import ConanFileLoader, load_python_file
from conans.client.remote_manager import RemoteManager
from conans.client.rest.auth_manager import ConanApiAuthManager
from conans.client.rest.conan_requester import ConanRequester
from conan.internal.api.remotes.localdb import LocalDB


Expand Down
1 change: 1 addition & 0 deletions conan/internal/model/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"core.download:retry_wait": "Seconds to wait between download attempts from Conan server",
"core.download:download_cache": "Define path to a file download cache",
"core.cache:storage_path": "Absolute path where the packages and database are stored",
"core:update_policy_old": "When multiple remotes, update based on order of remotes",
# Sources backup
"core.sources:download_cache": "Folder to store the sources backup",
"core.sources:download_urls": "List of URLs to download backup sources from",
Expand Down
10 changes: 0 additions & 10 deletions conan/test/utils/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import traceback
import uuid
import zipfile
from collections import OrderedDict
from contextlib import contextmanager
from urllib.parse import urlsplit, urlunsplit

Expand Down Expand Up @@ -442,15 +441,6 @@ def __init__(self, cache_folder=None, current_folder=None, servers=None, inputs=
self.cache_folder = cache_folder or os.path.join(temp_folder(path_with_spaces), ".conan2")

self.requester_class = requester_class

if servers and len(servers) > 1 and not isinstance(servers, OrderedDict):
raise Exception(textwrap.dedent("""
Testing framework error: Servers should be an OrderedDict. e.g:
servers = OrderedDict()
servers["r1"] = server
servers["r2"] = TestServer()
"""))

self.servers = servers or {}
if servers is not False: # Do not mess with registry remotes
self.update_servers()
Expand Down
12 changes: 12 additions & 0 deletions conans/client/graph/proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ def __init__(self, conan_app, editable_packages):
self._cache = conan_app.cache
self._remote_manager = conan_app.remote_manager
self._resolved = {} # Cache of the requested recipes to optimize calls
self._update_policy_old = conan_app.global_conf.get("core:update_policy_old",
check_type=bool)

def get_recipe(self, ref, remotes, update, check_update):
"""
Expand Down Expand Up @@ -117,6 +119,16 @@ def _find_newest_recipe_in_remotes(self, reference, remotes, update, check_updat
if len(results) == 0:
return None, None

if self._update_policy_old: # Use only the first occurence of each revision in the remotes
filtered_results = []
revisions = set()
for r in results:
ref = r["ref"]
if ref.revision not in revisions:
revisions.add(ref.revision)
filtered_results.append(r)
results = filtered_results

remotes_results = sorted(results, key=lambda k: k['ref'].timestamp, reverse=True)
# get the latest revision from all remotes
found_rrev = remotes_results[0]
Expand Down
75 changes: 74 additions & 1 deletion test/integration/command/install/install_update_test.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import os
import textwrap
import time
from time import sleep

from conan.api.model import RecipeReference
from conan.test.utils.tools import TestClient, GenConanfile
from conan.test.utils.tools import TestClient, GenConanfile, TestServer
from conans.util.files import load


Expand Down Expand Up @@ -165,3 +166,75 @@ def test_install_update_repeated_tool_requires():
c.run("create libc")
c.run("install libc --update -pr=profile")
assert 1 == str(c.out).count("tool/0.1: Checking remote")


def test_multi_remote_update_resolution():
c = TestClient(servers={"r1": TestServer(), "r2": TestServer(), "r3": TestServer()},
inputs=["admin", "password"] * 3, light=True)
c.save({"conanfile.py": GenConanfile("pkg", "0.1")})
c.run("export .")
rev1 = c.exported_recipe_revision()
c.run("upload * -r=r1 -c")
# second revision
c.save({"conanfile.py": GenConanfile("pkg", "0.1").with_class_attribute("auther = 'me'")})
c.run("export .")
rev2 = c.exported_recipe_revision()
assert rev1 != rev2
c.run("upload * -r=r2 -c") # By default uploads latest revisions only
assert rev1 not in c.out
assert rev2 in c.out
# going back to the previous revision
c.save({"conanfile.py": GenConanfile("pkg", "0.1")})
c.run("export .") # Makes it the latest
rev3 = c.exported_recipe_revision()
assert rev1 == rev3
c.run("upload * -r=r3 -c") # By default uploads latest revisions only
assert rev3 in c.out
assert rev2 not in c.out

# now test the --update, it will pick up the latest revision, which is r3
c.run("remove * -c")
c.run("graph info --requires=pkg/0.1 --update")
assert f"pkg/0.1#{rev3} - Downloaded (r3)" in c.out

# But if we enable order-based first found timestamp, it will pick up r2
c.run("remove * -c")
c.run("graph info --requires=pkg/0.1 --update -cc core:update_policy_old=True")
assert f"pkg/0.1#{rev2} - Downloaded (r2)" in c.out


def test_multi_remote_update_resolution_2_remotes():
c = TestClient(servers={"r1": TestServer(), "r2": TestServer()},
inputs=["admin", "password"] * 2, light=True)
c.save({"conanfile.py": GenConanfile("pkg", "0.1")})
c.run("export .")
rev1 = c.exported_recipe_revision()
c.run("upload * -r=r1 -c")
# second revision
c.save({"conanfile.py": GenConanfile("pkg", "0.1").with_class_attribute("auther = 'me'")})
time.sleep(1)
c.run("export .")
rev2 = c.exported_recipe_revision()
assert rev1 != rev2
c.run("upload * -r=r1 -c")
c.run("list *#* -r=r1")
print(c.out)

time.sleep(1)
# Upload the first, old revision to the other remote
c.run(f"upload pkg/0.1#{rev1} -r=r2 -c")
assert rev1 in c.out
assert rev2 not in c.out
c.run("list *#* -r=r2")
print(c.out)

# now test the --update, it will pick up the latest revision, which is r3
c.run("remove * -c")
c.run("graph info --requires=pkg/0.1 --update")
assert f"pkg/0.1#{rev1} - Downloaded (r2)" in c.out

# But if we enable order-based first found timestamp, it will pick up r2
c.run("remove * -c")
c.run("graph info --requires=pkg/0.1 --update -cc core:update_policy_old=True")
print(c.out)
assert f"pkg/0.1#{rev2} - Downloaded (r1)" in c.out
Loading