Skip to content

Commit

Permalink
Allow passing patterns to --update argument (#15652)
Browse files Browse the repository at this point in the history
* Initial sketch for passing patterns to --update argument

* Simplify test

* Cleanup diff

* Revert "Cleanup diff"

This reverts commit 162e7f4.

* Revert "Simplify test"

This reverts commit 9822c2c.

* Add test

* Add debug protect to check which update checks are left to upgrade

* Fix more tests

* Remove unused import

* Add negation test

* Remove update check

* Cleaner way to handle legacy syntax support

* Allow only match on name

* Update --update help

* Wording
  • Loading branch information
AbrilRBS authored Feb 15, 2024
1 parent 2111d5a commit 0bad0d1
Show file tree
Hide file tree
Showing 9 changed files with 68 additions and 18 deletions.
2 changes: 1 addition & 1 deletion conan/api/subapi/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def load_graph_consumer(self, path, name, version, user, channel,
return deps_graph

def load_graph(self, root_node, profile_host, profile_build, lockfile=None, remotes=None,
update=False, check_update=False):
update=None, check_update=False):
""" Compute the dependency graph, starting from a root package, evaluation the graph with
the provided configuration in profile_build, and profile_host. The resulting graph is a
graph of recipes, but packages are not computed yet (package_ids) will be empty in the
Expand Down
8 changes: 4 additions & 4 deletions conan/cli/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,13 @@ def add_common_install_arguments(parser):
group.add_argument("-nr", "--no-remote", action="store_true",
help='Do not use remote, resolve exclusively in the cache')

update_help = ("Will check the remote and in case a newer version and/or revision of "
"the dependencies exists there, it will install those in the local cache. "
update_help = ("Will install newer versions and/or revisions in the local cache "
"for the given reference, or all in case no argument is supplied. "
"When using version ranges, it will install the latest version that "
"satisfies the range. Also, if using revisions, it will update to the "
"latest revision for the resolved version range.")
parser.add_argument("-u", "--update", action='store_true', default=False,
help=update_help)

parser.add_argument("-u", "--update", action="append", nargs="?", help=update_help, const="*")
add_profiles_args(parser)


Expand Down
2 changes: 1 addition & 1 deletion conan/cli/commands/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def create(conan_api, parser, *args):
# The test_package do not make the "conan create" command return a different graph or
# produce a different lockfile. The result is always the same, irrespective of test_package
run_test(conan_api, test_conanfile_path, ref, profile_host, profile_build, remotes, lockfile,
update=False, build_modes=args.build, build_modes_test=args.build_test,
update=None, build_modes=args.build, build_modes_test=args.build_test,
tested_python_requires=tested_python_requires, tested_graph=deps_graph)

conan_api.lockfile.save_lockfile(lockfile, args.lockfile_out, cwd)
Expand Down
2 changes: 1 addition & 1 deletion conan/cli/commands/export_pkg.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def export_pkg(conan_api, parser, *args):
# same as ``conan create`` the lockfile, and deps graph is the one of the exported-pkg
# not the one from test_package
run_test(conan_api, test_conanfile_path, ref, profile_host, profile_build,
remotes=remotes, lockfile=lockfile, update=False, build_modes=None)
remotes=remotes, lockfile=lockfile, update=None, build_modes=None)

conan_api.lockfile.save_lockfile(lockfile, args.lockfile_out, cwd)
return {"graph": deps_graph,
Expand Down
1 change: 0 additions & 1 deletion conan/cli/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ def install(conan_api, parser, *args):
help='Whether the provided path is a build-require')
args = parser.parse_args(*args)
validate_common_graph_args(args)

# basic paths
cwd = os.getcwd()
path = conan_api.local.get_conanfile_path(args.path, cwd, py=None) if args.path else None
Expand Down
11 changes: 6 additions & 5 deletions conans/client/graph/graph_binaries.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
RECIPE_CONSUMER, RECIPE_VIRTUAL, BINARY_SKIP,
BINARY_INVALID, BINARY_EDITABLE_BUILD, RECIPE_PLATFORM,
BINARY_PLATFORM)
from conans.client.graph.proxy import should_update_reference
from conans.errors import NoRemoteAvailable, NotFoundException, \
PackageNotFoundException, conanfile_exception_formatter, ConanConnectionError

Expand Down Expand Up @@ -60,15 +61,15 @@ def _get_package_from_remotes(self, node, remotes, update):
info = node.conanfile.info
latest_pref = self._remote_manager.get_latest_package_reference(pref, r, info)
results.append({'pref': latest_pref, 'remote': r})
if len(results) > 0 and not update:
if len(results) > 0 and not should_update_reference(node.ref, update):
break
except NotFoundException:
pass
except ConanConnectionError:
ConanOutput().error(f"Failed checking for binary '{pref}' in remote '{r.name}': "
"remote not available")
raise
if not remotes and update:
if not remotes and should_update_reference(node.ref, update):
node.conanfile.output.warning("Can't update, there are no remotes defined")

if len(results) > 0:
Expand Down Expand Up @@ -130,7 +131,7 @@ def _compatible_found(pkg_id, compatible_pkg):

conanfile.output.info(f"Checking {len(compatibles)} compatible configurations")
for package_id, compatible_package in compatibles.items():
if update:
if should_update_reference(node.ref, update):
conanfile.output.info(f"'{package_id}': "
f"{conanfile.info.dump_diff(compatible_package)}")
node._package_id = package_id # Modifying package id under the hood, FIXME
Expand Down Expand Up @@ -253,7 +254,7 @@ def _process_compatible_node(self, node, remotes, update):
if cache_latest_prev is not None:
# This binary already exists in the cache, maybe can be updated
self._evaluate_in_cache(cache_latest_prev, node, remotes, update)
elif update:
elif should_update_reference(node.ref, update):
self._evaluate_download(node, remotes, update)

def _process_locked_node(self, node, build_mode, locked_prev):
Expand Down Expand Up @@ -292,7 +293,7 @@ def _evaluate_download(self, node, remotes, update):

def _evaluate_in_cache(self, cache_latest_prev, node, remotes, update):
assert cache_latest_prev.revision
if update:
if should_update_reference(node.ref, update):
output = node.conanfile.output
try:
self._get_package_from_remotes(node, remotes, update)
Expand Down
13 changes: 10 additions & 3 deletions conans/client/graph/proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def _get_recipe(self, reference, remotes, update, check_update):

# TODO: cache2.0: check with new --update flows
# TODO: If the revision is given, then we don't need to check for updates?
if not (check_update or update):
if not (check_update or should_update_reference(reference, update)):
status = RECIPE_INCACHE
return recipe_layout, status, None

Expand All @@ -71,7 +71,7 @@ def _get_recipe(self, reference, remotes, update, check_update):
if remote_ref.revision != ref.revision:
if cache_time < remote_ref.timestamp:
# the remote one is newer
if update:
if should_update_reference(remote_ref, update):
output.info("Retrieving from remote '%s'..." % remote.name)
new_recipe_layout = self._download(remote_ref, remote)
status = RECIPE_UPDATED
Expand Down Expand Up @@ -107,7 +107,7 @@ def _find_newest_recipe_in_remotes(self, reference, remotes, update, check_updat
ref = self._remote_manager.get_latest_recipe_reference(reference, remote)
else:
ref = self._remote_manager.get_recipe_revision_reference(reference, remote)
if not update and not check_update:
if not should_update_reference(reference, update) and not check_update:
return remote, ref
results.append({'remote': remote, 'ref': ref})
except NotFoundException:
Expand Down Expand Up @@ -142,3 +142,10 @@ def _download(self, ref, remote):
output = ConanOutput(scope=str(ref))
output.info("Downloaded recipe revision %s" % ref.revision)
return recipe_layout


def should_update_reference(reference, update):
if update is None:
return False
# Legacy syntax had --update without pattern, it manifests as a "*" pattern
return any(name == "*" or reference.name == name for name in update)
5 changes: 3 additions & 2 deletions conans/client/graph/range_resolver.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from conans.client.graph.proxy import should_update_reference
from conans.errors import ConanException
from conans.model.recipe_ref import RecipeReference
from conans.model.version_range import VersionRange
Expand Down Expand Up @@ -31,7 +32,7 @@ def resolve(self, require, base_conanref, remotes, update):
search_ref = RecipeReference(ref.name, "*", ref.user, ref.channel)

resolved_ref = self._resolve_local(search_ref, version_range)
if resolved_ref is None or update:
if resolved_ref is None or should_update_reference(search_ref, update):
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):
Expand Down Expand Up @@ -84,7 +85,7 @@ def _resolve_remote(self, search_ref, version_range, remotes, update):
resolved_version = self._resolve_version(version_range, remote_results,
self._resolve_prereleases)
if resolved_version:
if not update:
if not should_update_reference(search_ref, update):
return resolved_version # Return first valid occurrence in first remote
else:
update_candidates.append(resolved_version)
Expand Down
42 changes: 42 additions & 0 deletions conans/test/integration/cache/cache2_update_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,3 +438,45 @@ def test_version_ranges(self):
self.client.assert_listed_require({"liba/1.2.0": "Downloaded (server2)"})
assert f"liba/1.2.0: Retrieving package {NO_SETTINGS_PACKAGE_ID} " \
"from remote 'server2' " in self.client.out


@pytest.mark.parametrize("update,result", [
# Not a real pattern, works to support legacy syntax
["*", {"liba/1.1": "Downloaded (default)",
"libb/1.1": "Downloaded (default)"}],
["libc", {"liba/1.0": "Cache",
"libb/1.0": "Cache"}],
["liba", {"liba/1.1": "Downloaded (default)",
"libb/1.0": "Cache"}],
["libb", {"liba/1.0": "Cache",
"libb/1.1": "Downloaded (default)"}],
["", {"liba/1.0": "Cache",
"libb/1.0": "Cache"}],
# Patterns not supported, only full name match
["lib*", {"liba/1.0": "Cache",
"libb/1.0": "Cache"}],
["liba/*", {"liba/1.0": "Cache",
"libb/1.0": "Cache"}],
# None only passes legacy --update without args,
# to ensure it works, it should be the same as passing *
[None, {"liba/1.1": "Downloaded (default)",
"libb/1.1": "Downloaded (default)"}]
])
def test_muliref_update_pattern(update, result):
tc = TestClient(light=True, default_server_user=True)
tc.save({"liba/conanfile.py": GenConanfile("liba"),
"libb/conanfile.py": GenConanfile("libb")})
tc.run("create liba --version=1.0")
tc.run("create libb --version=1.0")

tc.run("create liba --version=1.1")
tc.run("create libb --version=1.1")

tc.run("upload * -c -r default")
tc.run('remove "*/1.1" -c')

update_flag = f"--update={update}" if update is not None else "--update"

tc.run(f'install --requires="liba/[>=1.0]" --requires="libb/[>=1.0]" -r default {update_flag}')

tc.assert_listed_require(result)

0 comments on commit 0bad0d1

Please sign in to comment.