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

Allow passing patterns to --update argument #15652

Merged
merged 16 commits into from
Feb 15, 2024
12 changes: 10 additions & 2 deletions conan/cli/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,19 @@ def add_common_install_arguments(parser):
"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=True)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const=True does not mean that the value is const, rather, when no argument is supplied, it will append True in this case to the list

add_profiles_args(parser)


def protect_update(args):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Used to check in the tests that no leftover if update: is left, will remove before merging

if getattr(args, "update", None) is not None:
class UpdateList(list):
def __bool__(self):
assert False, "This should not be called"
setattr(args, "update", UpdateList(args.update))


def add_profiles_args(parser):
contexts = ["build", "host"]

Expand Down
4 changes: 3 additions & 1 deletion conan/cli/commands/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
from conan.cli.command import conan_command
from conan.cli.formatters.graph import format_graph_json
from conan.cli import make_abs_path
from conan.cli.args import add_lockfile_args, add_common_install_arguments, add_reference_args
from conan.cli.args import add_lockfile_args, add_common_install_arguments, add_reference_args, \
protect_update
from conan.cli.printers import print_profiles
from conan.cli.printers.graph import print_graph_packages, print_graph_basic

Expand All @@ -31,6 +32,7 @@ def build(conan_api, parser, *args):
add_common_install_arguments(parser)
add_lockfile_args(parser)
args = parser.parse_args(*args)
protect_update(args)

cwd = os.getcwd()
path = conan_api.local.get_conanfile_path(args.path, cwd, py=True)
Expand Down
5 changes: 3 additions & 2 deletions conan/cli/commands/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import shutil

from conan.api.output import ConanOutput
from conan.cli.args import add_lockfile_args, add_common_install_arguments
from conan.cli.args import add_lockfile_args, add_common_install_arguments, protect_update
from conan.cli.command import conan_command, OnceArgument
from conan.cli.commands.export import common_args_export
from conan.cli.formatters.graph import format_graph_json
Expand Down Expand Up @@ -30,6 +30,7 @@ def create(conan_api, parser, *args):
help="Same as '--build' but only for the test_package requires. By default"
" if not specified it will take the '--build' value if specified")
args = parser.parse_args(*args)
protect_update(args)

cwd = os.getcwd()
path = conan_api.local.get_conanfile_path(args.path, cwd, py=True)
Expand Down Expand Up @@ -105,7 +106,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
3 changes: 2 additions & 1 deletion conan/cli/commands/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from conan.api.output import ConanOutput, cli_out_write, Color
from conan.cli import make_abs_path
from conan.cli.args import common_graph_args, validate_common_graph_args
from conan.cli.args import common_graph_args, validate_common_graph_args, protect_update
from conan.cli.command import conan_command, conan_subcommand
from conan.cli.commands.list import prepare_pkglist_compact, print_serial
from conan.cli.formatters.graph import format_graph_html, format_graph_json, format_graph_dot
Expand Down Expand Up @@ -175,6 +175,7 @@ def graph_info(conan_api, parser, subparser, *args):
help='Whether the provided reference is a build-require')
args = parser.parse_args(*args)

protect_update(args)
# parameter validation
validate_common_graph_args(args)
if args.format in ("html", "dot") and args.filter:
Expand Down
4 changes: 2 additions & 2 deletions conan/cli/commands/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from conan.api.output import ConanOutput
from conan.cli import make_abs_path
from conan.cli.args import common_graph_args, validate_common_graph_args
from conan.cli.args import common_graph_args, validate_common_graph_args, protect_update
from conan.cli.command import conan_command
from conan.cli.formatters.graph import format_graph_json
from conan.cli.printers import print_profiles
Expand Down Expand Up @@ -40,7 +40,7 @@ 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)

protect_update(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
3 changes: 2 additions & 1 deletion conan/cli/commands/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from conan.api.output import ConanOutput
from conan.cli.command import conan_command, OnceArgument
from conan.cli.commands.create import test_package, _check_tested_reference_matches
from conan.cli.args import add_lockfile_args, add_common_install_arguments
from conan.cli.args import add_lockfile_args, add_common_install_arguments, protect_update
from conan.cli.formatters.graph import format_graph_json
from conan.cli.printers import print_profiles
from conan.cli.printers.graph import print_graph_basic, print_graph_packages
Expand All @@ -22,6 +22,7 @@ def test(conan_api, parser, *args):
add_common_install_arguments(parser)
add_lockfile_args(parser)
args = parser.parse_args(*args)
protect_update(args)

cwd = os.getcwd()
ref = RecipeReference.loads(args.reference)
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
19 changes: 16 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,16 @@ 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
if not isinstance(update, list):
assert False
AbrilRBS marked this conversation as resolved.
Show resolved Hide resolved
# Legacy syntax had --update without pattern, that manifests as a True in the list
# (Usually one element, but support having more than one just in case)
# Note that it can't be false, the True is a flag set by Conan to update all
if any(isinstance(u, bool) for u in update):
return True
AbrilRBS marked this conversation as resolved.
Show resolved Hide resolved
return any(reference.matches(pattern, is_consumer=False) for pattern 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):
AbrilRBS marked this conversation as resolved.
Show resolved Hide resolved
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
32 changes: 32 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,35 @@ 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", [["*", {"liba/1.1": "Downloaded (default)",
"libb/1.1": "Downloaded (default)"}],
["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"}],
# None only passes legacy --update, no args, tp ensure it works
[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)