From 355b6d1af8006c86e8da8131229350d3c3e57343 Mon Sep 17 00:00:00 2001 From: memsharded Date: Thu, 23 Jul 2026 20:08:32 +0200 Subject: [PATCH 1/2] fix UX for conan lock updgrade-config --- conan/cli/commands/lock.py | 31 +++++++++++++++---- .../lockfile/test_user_overrides.py | 5 +++ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/conan/cli/commands/lock.py b/conan/cli/commands/lock.py index 5161e43bb40..d1fe6e5c25c 100644 --- a/conan/cli/commands/lock.py +++ b/conan/cli/commands/lock.py @@ -4,7 +4,8 @@ from conan.cli.command import conan_command, OnceArgument, conan_subcommand 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, \ + add_common_install_arguments, add_lockfile_args from conan.cli.printers.graph import print_graph_packages, print_graph_basic from conan.errors import ConanException from conan.api.model import RecipeReference @@ -239,20 +240,38 @@ def lock_upgrade_config(conan_api, parser, subparser, *args): """ (Experimental) Upgrade config requires in a lockfile """ - common_graph_args(subparser) + # This is similar to common_graph_args(subparser) but without the name/version args + subparser.add_argument("path", nargs="?", help="Path to a conanconfig.yml file", + default=None) + add_common_install_arguments(subparser) + subparser.add_argument("--requires", action="append", + help='Directly provide requires instead of a conanfile') + subparser.add_argument("--tool-requires", action='append', + help='Directly provide tool-requires instead of a conanfile') + add_lockfile_args(subparser) + subparser.add_argument('--update-config-requires', action="append", help='Update config-requires from lockfile') args = parser.parse_args(*args) - validate_common_graph_args(args) + + # This is a bit repeated from validate_common_graph_args() but without name/version args + if args.path and (args.requires or args.tool_requires): + raise ConanException("--requires and --tool-requires arguments are incompatible with " + f"[path] '{args.path}' argument") + + if not args.requires and not args.tool_requires and args.path is None: + args.path = "." + + # graph build-order command does not define a build-require argument + if not args.path and getattr(args, "build_require", False): + raise ConanException("--build-require should only be used with argument") if not args.update_config_requires: raise ConanException("At least one --update-config-requires should be specified") cwd = os.getcwd() - path = conan_api.local.get_conanfile_path(args.path, cwd, py=None) if args.path else None remotes = conan_api.remotes.list(args.remote) if not args.no_remote else [] - lockfile = conan_api.lockfile.get_lockfile(lockfile=args.lockfile, conanfile_path=path, - cwd=cwd, partial=True) + lockfile = conan_api.lockfile.get_lockfile(lockfile=args.lockfile, cwd=cwd, partial=True) if lockfile is None: raise ConanException("No lockfile specified and default conan.lock not found") profile_host, profile_build = conan_api.profiles.get_profiles_from_args(args) diff --git a/test/integration/lockfile/test_user_overrides.py b/test/integration/lockfile/test_user_overrides.py index 0cb547f111d..931ba492d49 100644 --- a/test/integration/lockfile/test_user_overrides.py +++ b/test/integration/lockfile/test_user_overrides.py @@ -499,3 +499,8 @@ def _check_cache(refs): _check_cache(["config/1.0"]) c.run("config install-pkg . --lockfile=conan.lock") _check_cache(["config/2.0"]) + + def test_config_upgrade_conanfile(self): + c = TestClient(light=True) + c.run("lock upgrade-config -h") + assert "Path to a conanconfig.yml file" in c.out From e48813b6c37ac43969f5e268491b8bf239ee7c3c Mon Sep 17 00:00:00 2001 From: memsharded Date: Mon, 27 Jul 2026 12:13:38 +0200 Subject: [PATCH 2/2] review --- conan/cli/commands/lock.py | 15 ++++++++------- test/integration/lockfile/test_user_overrides.py | 11 +++++++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/conan/cli/commands/lock.py b/conan/cli/commands/lock.py index d1fe6e5c25c..b21cebba8b1 100644 --- a/conan/cli/commands/lock.py +++ b/conan/cli/commands/lock.py @@ -241,8 +241,9 @@ def lock_upgrade_config(conan_api, parser, subparser, *args): (Experimental) Upgrade config requires in a lockfile """ # This is similar to common_graph_args(subparser) but without the name/version args - subparser.add_argument("path", nargs="?", help="Path to a conanconfig.yml file", - default=None) + subparser.add_argument("path", nargs="?", default=None, + help="Path to a conanconfig.yml file " + "(defaults to current directory)") add_common_install_arguments(subparser) subparser.add_argument("--requires", action="append", help='Directly provide requires instead of a conanfile') @@ -254,18 +255,18 @@ def lock_upgrade_config(conan_api, parser, subparser, *args): help='Update config-requires from lockfile') args = parser.parse_args(*args) - # This is a bit repeated from validate_common_graph_args() but without name/version args if args.path and (args.requires or args.tool_requires): raise ConanException("--requires and --tool-requires arguments are incompatible with " f"[path] '{args.path}' argument") + if args.path and args.path.endswith(".py"): + raise ConanException(f"'{args.path}' looks like a conanfile, but 'conan lock " + "upgrade-config' expects a conanconfig.yml file. Use --requires " + "to specify recipe references instead.") + if not args.requires and not args.tool_requires and args.path is None: args.path = "." - # graph build-order command does not define a build-require argument - if not args.path and getattr(args, "build_require", False): - raise ConanException("--build-require should only be used with argument") - if not args.update_config_requires: raise ConanException("At least one --update-config-requires should be specified") diff --git a/test/integration/lockfile/test_user_overrides.py b/test/integration/lockfile/test_user_overrides.py index 931ba492d49..e3725e866da 100644 --- a/test/integration/lockfile/test_user_overrides.py +++ b/test/integration/lockfile/test_user_overrides.py @@ -504,3 +504,14 @@ def test_config_upgrade_conanfile(self): c = TestClient(light=True) c.run("lock upgrade-config -h") assert "Path to a conanconfig.yml file" in c.out + + def test_config_upgrade_rejects_conanfile_path(self): + # Regression for https://github.com/conan-io/conan/issues/20205: + # 'conan lock upgrade-config ... conanfile.py' used to try parsing the recipe as YAML. + c = TestClient(light=True) + c.save({"conanfile.py": GenConanfile("app", "1.0")}) + c.run("lock upgrade-config --update-config-requires 'whatever/*' conanfile.py", + assert_error=True) + assert "conanfile" in c.out and "conanconfig.yml" in c.out + # And no YAML parser traceback should leak through + assert "mapping values are not allowed here" not in c.out