From da8bcba145a9f9246a7d3360400c28d0f906c91e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= Date: Tue, 1 Oct 2024 15:16:58 +0200 Subject: [PATCH 1/2] Add failing test --- .../command_v2/custom_commands_test.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test/integration/command_v2/custom_commands_test.py b/test/integration/command_v2/custom_commands_test.py index bf02fc46ecc..c28b9d50e43 100644 --- a/test/integration/command_v2/custom_commands_test.py +++ b/test/integration/command_v2/custom_commands_test.py @@ -345,6 +345,32 @@ def mycmd2(conan_api, parser, *args, **kwargs): assert "MYCMD1!!!!!" in c.out assert "MYCMD2!!!!!" in c.out + def test_command_verbosity_leak(self): + mycommand = textwrap.dedent(f""" + import json + from conan.cli.command import conan_command + from conan.api.output import ConanOutput + + @conan_command(group="custom commands") + def mycommand(conan_api, parser, *args, **kwargs): + \""" mycommand help \""" + parser.add_argument("foo", help="foo") + args = parser.parse_args(*args) + + out = ConanOutput() + out.title("This is my first title") + conan_api.command.run("config home") + out.title("This is my second title") + """) + + c = TestClient() + command_file_path = os.path.join(c.cache_folder, 'extensions', + 'commands', 'cmd_mycommand.py') + c.save({f"{command_file_path}": mycommand}) + c.run("mycommand foo -vquiet") + assert "This is my first title" not in c.out + assert "This is my second title" not in c.out + def test_command_reuse_interface_create(self): mycommand = textwrap.dedent(""" import json From a86968799ae08d7ae90e19532f66317409c8612a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Abril=20Rinc=C3=B3n=20Blanco?= Date: Sun, 6 Oct 2024 20:55:39 +0200 Subject: [PATCH 2/2] Save and restore global ConanOutput command on Command --- conan/api/subapi/command.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/conan/api/subapi/command.py b/conan/api/subapi/command.py index 4405f38923b..e906a620f6f 100644 --- a/conan/api/subapi/command.py +++ b/conan/api/subapi/command.py @@ -1,4 +1,5 @@ from conans.errors import ConanException +from conan.api.output import ConanOutput class CommandAPI: @@ -20,5 +21,18 @@ def run(self, cmd): command = commands[current_cmd] except KeyError: raise ConanException(f"Command {current_cmd} does not exist") + # Conan has some global state in the ConanOutput class that + # get redefined when running a command and leak to the calling scope + # if running from a custom command. + # Store the old one and restore it after the command execution as a workaround. + _conan_output_level = ConanOutput._conan_output_level + _silent_warn_tags = ConanOutput._silent_warn_tags + _warnings_as_errors = ConanOutput._warnings_as_errors - return command.run_cli(self.conan_api, args) + try: + result = command.run_cli(self.conan_api, args) + finally: + ConanOutput._conan_output_level = _conan_output_level + ConanOutput._silent_warn_tags = _silent_warn_tags + ConanOutput._warnings_as_errors = _warnings_as_errors + return result