From e2d89ff206e043aa9c2485969dab962d044acc57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n?= Date: Tue, 21 Nov 2023 14:55:28 +0100 Subject: [PATCH 1/8] Add core:warnings_as_errors configuration option --- conan/api/output.py | 16 +- conan/internal/conan_app.py | 6 +- conans/model/conf.py | 1 + .../integration/command_v2/test_output.py | 197 ++++++++++++++++++ .../command_v2/test_output_level.py | 144 ------------- 5 files changed, 216 insertions(+), 148 deletions(-) create mode 100644 conans/test/integration/command_v2/test_output.py delete mode 100644 conans/test/integration/command_v2/test_output_level.py diff --git a/conan/api/output.py b/conan/api/output.py index 179d983cb13..a01eedfa1c7 100644 --- a/conan/api/output.py +++ b/conan/api/output.py @@ -51,6 +51,7 @@ class ConanOutput: # Singleton _conan_output_level = LEVEL_STATUS _silent_warn_tags = [] + _warnings_as_errors = False def __init__(self, scope=""): self.stream = sys.stderr @@ -63,6 +64,10 @@ def __init__(self, scope=""): def define_silence_warnings(cls, warnings): cls._silent_warn_tags = warnings or [] + @classmethod + def set_warnings_as_errors(cls, value): + cls._warnings_as_errors = value + @classmethod def define_log_level(cls, v): """ @@ -204,11 +209,16 @@ def success(self, msg): return self def warning(self, msg, warn_tag=None): - if self._conan_output_level <= LEVEL_WARNING: - if warn_tag is not None and warn_tag in self._silent_warn_tags: + def _skip_warning(): + return (warn_tag is not None and warn_tag in self._silent_warn_tags + or warn_tag is None and "unknown" in self._silent_warn_tags) + if self._conan_output_level <= LEVEL_WARNING or (self._warnings_as_errors and self._conan_output_level <= LEVEL_ERROR): + if _skip_warning(): return self warn_tag_msg = "" if warn_tag is None else f"{warn_tag}: " - self._write_message(f"WARN: {warn_tag_msg}{msg}", Color.YELLOW) + message_type = "ERROR" if self._warnings_as_errors else "WARN" + message_color = Color.RED if self._warnings_as_errors else Color.YELLOW + self._write_message(f"{message_type}: {warn_tag_msg}{msg}", message_color) return self def error(self, msg): diff --git a/conan/internal/conan_app.py b/conan/internal/conan_app.py index b42e16a61fc..dc2190f4d8c 100644 --- a/conan/internal/conan_app.py +++ b/conan/internal/conan_app.py @@ -44,8 +44,12 @@ def __init__(self, cache_folder, global_conf): home_paths = HomePaths(self.cache_folder) self.hook_manager = HookManager(home_paths.hooks_path) - # Wraps an http_requester to inject proxies, certs, etc + + ConanOutput.set_warnings_as_errors(global_conf.get("core:warnings_as_errors", + default=False, check_type=bool)) ConanOutput.define_silence_warnings(global_conf.get("core:skip_warnings", check_type=list)) + + # Wraps an http_requester to inject proxies, certs, etc self.requester = ConanRequester(global_conf, cache_folder) # To handle remote connections rest_client_factory = RestApiClientFactory(self.requester, global_conf) diff --git a/conans/model/conf.py b/conans/model/conf.py index 2c6803a35bf..780f93844ef 100644 --- a/conans/model/conf.py +++ b/conans/model/conf.py @@ -12,6 +12,7 @@ BUILT_IN_CONFS = { "core:required_conan_version": "Raise if current version does not match the defined range.", "core:non_interactive": "Disable interactive user input, raises error if input necessary", + "core:warnings_as_errors": "Treat warnings as errors", "core:skip_warnings": "Do not show warnings in this list", "core:default_profile": "Defines the default host profile ('default' by default)", "core:default_build_profile": "Defines the default build profile ('default' by default)", diff --git a/conans/test/integration/command_v2/test_output.py b/conans/test/integration/command_v2/test_output.py new file mode 100644 index 00000000000..153192d54b9 --- /dev/null +++ b/conans/test/integration/command_v2/test_output.py @@ -0,0 +1,197 @@ +import json +import textwrap + +from conans.test.assets.genconanfile import GenConanfile +from conans.test.utils.tools import TestClient + + +class TestOutputLevel: + def test_invalid_output_level(self): + t = TestClient() + t.save({"conanfile.py": GenConanfile("foo", "1.0")}) + t.run("create . -vfooling", assert_error=True) + assert "Invalid argument '-vfooling'" + + def test_output_level(self): + lines = ("self.output.trace('This is a trace')", + "self.output.debug('This is a debug')", + "self.output.verbose('This is a verbose')", + "self.output.info('This is a info')", + "self.output.highlight('This is a highlight')", + "self.output.success('This is a success')", + "self.output.warning('This is a warning')", + "self.output.error('This is a error')", + ) + + t = TestClient() + t.save({"conanfile.py": GenConanfile("foo", "1.0").with_package(*lines)}) + + # By default, it prints > info + t.run("create .") + assert "This is a trace" not in t.out + assert "This is a debug" not in t.out + assert "This is a verbose" not in t.out + assert "This is a info" in t.out + assert "This is a highlight" in t.out + assert "This is a success" in t.out + assert "This is a warning" in t.out + assert "This is a error" in t.out + + # Check if -v argument is equal to VERBOSE level + t.run("create . -v") + assert "This is a trace" not in t.out + assert "This is a debug" not in t.out + assert "This is a verbose" in t.out + assert "This is a info" in t.out + assert "This is a highlight" in t.out + assert "This is a success" in t.out + assert "This is a warning" in t.out + assert "This is a error" in t.out + + # Print also verbose traces + t.run("create . -vverbose") + assert "This is a trace" not in t.out + assert "This is a debug" not in t.out + assert "This is a verbose" in t.out + assert "This is a info" in t.out + assert "This is a highlight" in t.out + assert "This is a success" in t.out + assert "This is a warning" in t.out + assert "This is a error" in t.out + + # Print also debug traces + t.run("create . -vv") + assert "This is a trace" not in t.out + assert "This is a debug" in t.out + assert "This is a verbose" in t.out + assert "This is a info" in t.out + assert "This is a highlight" in t.out + assert "This is a success" in t.out + assert "This is a warning" in t.out + assert "This is a error" in t.out + t.run("create . -vdebug") + assert "This is a trace" not in t.out + assert "This is a debug" in t.out + assert "This is a verbose" in t.out + assert "This is a info" in t.out + assert "This is a highlight" in t.out + assert "This is a success" in t.out + assert "This is a warning" in t.out + assert "This is a error" in t.out + + # Print also "trace" traces + t.run("create . -vvv") + assert "This is a trace" in t.out + assert "This is a debug" in t.out + assert "This is a verbose" in t.out + assert "This is a info" in t.out + assert "This is a highlight" in t.out + assert "This is a success" in t.out + assert "This is a warning" in t.out + assert "This is a error" in t.out + t.run("create . -vtrace") + assert "This is a trace" in t.out + assert "This is a debug" in t.out + assert "This is a verbose" in t.out + assert "This is a info" in t.out + assert "This is a highlight" in t.out + assert "This is a success" in t.out + assert "This is a warning" in t.out + assert "This is a error" in t.out + + # With notice + t.run("create . -vstatus") + assert "This is a trace" not in t.out + assert "This is a debug" not in t.out + assert "This is a verbose" not in t.out + assert "This is a info" in t.out + assert "This is a highlight" in t.out + assert "This is a success" in t.out + assert "This is a warning" in t.out + assert "This is a error" in t.out + + # With notice + t.run("create . -vnotice") + assert "This is a trace" not in t.out + assert "This is a debug" not in t.out + assert "This is a verbose" not in t.out + assert "This is a info" not in t.out + assert "This is a highlight" in t.out + assert "This is a success" in t.out + assert "This is a warning" in t.out + assert "This is a error" in t.out + + # With warnings + t.run("create . -vwarning") + assert "This is a trace" not in t.out + assert "This is a debug" not in t.out + assert "This is a verbose" not in t.out + assert "This is a info" not in t.out + assert "This is a highlight" not in t.out + assert "This is a success" not in t.out + assert "This is a warning" in t.out + assert "This is a error" in t.out + + # With errors + t.run("create . -verror") + assert "This is a trace" not in t.out + assert "This is a debug" not in t.out + assert "This is a verbose" not in t.out + assert "This is a info" not in t.out + assert "This is a highlight" not in t.out + assert "This is a success" not in t.out + assert "This is a warning" not in t.out + assert "This is a error" in t.out + + +class TestWarningHandling: + lines = ("self.output.warning('Untagged warning')", + "self.output.warning('Tagged warning', warn_tag='tag')") + + def test_warning_as_error(self): + t = TestClient() + t.save({"conanfile.py": GenConanfile("foo", "1.0").with_package(*self.lines)}) + + t.save_home({"global.conf": "core:warnings_as_errors=False"}) + t.run("create . -vwarning") + assert "WARN: Untagged warning" in t.out + assert "WARN: tag: Tagged warning" in t.out + + t.save_home({"global.conf": "core:warnings_as_errors=True"}) + t.run("create . -vwarning") + assert "ERROR: Untagged warning" in t.out + assert "ERROR: tag: Tagged warning" in t.out + + t.save_home({"global.conf": """core:warnings_as_errors=True\ncore:skip_warnings=["tag"]"""}) + t.run("create . -verror") + assert "ERROR: Untagged warning" in t.out + assert "ERROR: tag: Tagged warning" not in t.out + + t.save_home({"global.conf": "core:warnings_as_errors=False"}) + t.run("create . -verror") + assert "ERROR: Untagged warning" not in t.out + assert "ERROR: tag: Tagged warning" not in t.out + + def test_skip_warnings(self): + t = TestClient() + t.save({"conanfile.py": GenConanfile("foo", "1.0").with_package(*self.lines)}) + + t.save_home({"global.conf": "core:skip_warnings=[]"}) + t.run("create . -vwarning") + assert "WARN: Untagged warning" in t.out + assert "WARN: tag: Tagged warning" in t.out + + t.save_home({"global.conf": "core:skip_warnings=['tag']"}) + t.run("create . -vwarning") + assert "WARN: Untagged warning" in t.out + assert "WARN: tag: Tagged warning" not in t.out + + t.save_home({"global.conf": "core:skip_warnings=['unknown']"}) + t.run("create . -vwarning") + assert "WARN: Untagged warning" not in t.out + assert "WARN: tag: Tagged warning" in t.out + + t.save_home({"global.conf": "core:skip_warnings=['unknown', 'tag']"}) + t.run("create . -vwarning") + assert "WARN: Untagged warning" not in t.out + assert "WARN: tag: Tagged warning" not in t.out diff --git a/conans/test/integration/command_v2/test_output_level.py b/conans/test/integration/command_v2/test_output_level.py deleted file mode 100644 index f485424924e..00000000000 --- a/conans/test/integration/command_v2/test_output_level.py +++ /dev/null @@ -1,144 +0,0 @@ -import json - -from conans.test.assets.genconanfile import GenConanfile -from conans.test.utils.tools import TestClient - - -def test_invalid_output_level(): - t = TestClient() - t.save({"conanfile.py": GenConanfile()}) - t.run("create . --name foo --version 1.0 -vfooling", assert_error=True) - assert "Invalid argument '-vfooling'" - - -def test_output_level(): - - lines = ("self.output.trace('This is a trace')", - "self.output.debug('This is a debug')", - "self.output.verbose('This is a verbose')", - "self.output.info('This is a info')", - "self.output.highlight('This is a highlight')", - "self.output.success('This is a success')", - "self.output.warning('This is a warning')", - "self.output.error('This is a error')", - ) - - t = TestClient() - t.save({"conanfile.py": GenConanfile().with_package(*lines)}) - - # By default, it prints > info - t.run("create . --name foo --version 1.0") - assert "This is a trace" not in t.out - assert "This is a debug" not in t.out - assert "This is a verbose" not in t.out - assert "This is a info" in t.out - assert "This is a highlight" in t.out - assert "This is a success" in t.out - assert "This is a warning" in t.out - assert "This is a error" in t.out - - # Check if -v argument is equal to VERBOSE level - t.run("create . --name foo --version 1.0 -v") - assert "This is a trace" not in t.out - assert "This is a debug" not in t.out - assert "This is a verbose" in t.out - assert "This is a info" in t.out - assert "This is a highlight" in t.out - assert "This is a success" in t.out - assert "This is a warning" in t.out - assert "This is a error" in t.out - - # Print also verbose traces - t.run("create . --name foo --version 1.0 -vverbose") - assert "This is a trace" not in t.out - assert "This is a debug" not in t.out - assert "This is a verbose" in t.out - assert "This is a info" in t.out - assert "This is a highlight" in t.out - assert "This is a success" in t.out - assert "This is a warning" in t.out - assert "This is a error" in t.out - - # Print also debug traces - t.run("create . --name foo --version 1.0 -vv") - assert "This is a trace" not in t.out - assert "This is a debug" in t.out - assert "This is a verbose" in t.out - assert "This is a info" in t.out - assert "This is a highlight" in t.out - assert "This is a success" in t.out - assert "This is a warning" in t.out - assert "This is a error" in t.out - t.run("create . --name foo --version 1.0 -vdebug") - assert "This is a trace" not in t.out - assert "This is a debug" in t.out - assert "This is a verbose" in t.out - assert "This is a info" in t.out - assert "This is a highlight" in t.out - assert "This is a success" in t.out - assert "This is a warning" in t.out - assert "This is a error" in t.out - - # Print also "trace" traces - t.run("create . --name foo --version 1.0 -vvv") - assert "This is a trace" in t.out - assert "This is a debug" in t.out - assert "This is a verbose" in t.out - assert "This is a info" in t.out - assert "This is a highlight" in t.out - assert "This is a success" in t.out - assert "This is a warning" in t.out - assert "This is a error" in t.out - t.run("create . --name foo --version 1.0 -vtrace") - assert "This is a trace" in t.out - assert "This is a debug" in t.out - assert "This is a verbose" in t.out - assert "This is a info" in t.out - assert "This is a highlight" in t.out - assert "This is a success" in t.out - assert "This is a warning" in t.out - assert "This is a error" in t.out - - # With notice - t.run("create . --name foo --version 1.0 -vstatus") - assert "This is a trace" not in t.out - assert "This is a debug" not in t.out - assert "This is a verbose" not in t.out - assert "This is a info" in t.out - assert "This is a highlight" in t.out - assert "This is a success" in t.out - assert "This is a warning" in t.out - assert "This is a error" in t.out - - # With notice - t.run("create . --name foo --version 1.0 -vnotice") - assert "This is a trace" not in t.out - assert "This is a debug" not in t.out - assert "This is a verbose" not in t.out - assert "This is a info" not in t.out - assert "This is a highlight" in t.out - assert "This is a success" in t.out - assert "This is a warning" in t.out - assert "This is a error" in t.out - - # With warnings - t.run("create . --name foo --version 1.0 -vwarning") - assert "This is a trace" not in t.out - assert "This is a debug" not in t.out - assert "This is a verbose" not in t.out - assert "This is a info" not in t.out - assert "This is a highlight" not in t.out - assert "This is a success" not in t.out - assert "This is a warning" in t.out - assert "This is a error" in t.out - - # With errors - t.run("create . --name foo --version 1.0 -verror") - assert "This is a trace" not in t.out - assert "This is a debug" not in t.out - assert "This is a verbose" not in t.out - assert "This is a info" not in t.out - assert "This is a highlight" not in t.out - assert "This is a success" not in t.out - assert "This is a warning" not in t.out - assert "This is a error" in t.out From 3b504fcfe731eb22e7b74c02faac2f899816a844 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n?= Date: Wed, 22 Nov 2023 15:27:51 +0100 Subject: [PATCH 2/8] Extract the configuration of the ConanApp class to a new method --- conan/internal/conan_app.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/conan/internal/conan_app.py b/conan/internal/conan_app.py index dc2190f4d8c..38f8cab84d3 100644 --- a/conan/internal/conan_app.py +++ b/conan/internal/conan_app.py @@ -38,17 +38,13 @@ def __init__(self, requester, cmd_wrapper, global_conf, cache): class ConanApp(object): def __init__(self, cache_folder, global_conf): - + self._configure(global_conf) self.cache_folder = cache_folder self.cache = ClientCache(self.cache_folder, global_conf) home_paths = HomePaths(self.cache_folder) self.hook_manager = HookManager(home_paths.hooks_path) - ConanOutput.set_warnings_as_errors(global_conf.get("core:warnings_as_errors", - default=False, check_type=bool)) - ConanOutput.define_silence_warnings(global_conf.get("core:skip_warnings", check_type=list)) - # Wraps an http_requester to inject proxies, certs, etc self.requester = ConanRequester(global_conf, cache_folder) # To handle remote connections @@ -65,3 +61,9 @@ def __init__(self, cache_folder, global_conf): cmd_wrap = CmdWrapper(home_paths.wrapper_path) conanfile_helpers = ConanFileHelpers(self.requester, cmd_wrap, global_conf, self.cache) self.loader = ConanFileLoader(self.pyreq_loader, conanfile_helpers) + + @staticmethod + def _configure(global_conf): + ConanOutput.set_warnings_as_errors(global_conf.get("core:warnings_as_errors", + default=False, check_type=bool)) + ConanOutput.define_silence_warnings(global_conf.get("core:skip_warnings", check_type=list)) From f2221ece35634dbc8aea5ae7d52a10fca563352e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n?= Date: Fri, 15 Dec 2023 16:49:03 +0100 Subject: [PATCH 3/8] Raise on error() --- conan/api/output.py | 16 +++++++++++----- conan/cli/cli.py | 10 +++++----- conans/client/downloaders/file_downloader.py | 2 +- conans/client/rest/file_uploader.py | 2 +- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/conan/api/output.py b/conan/api/output.py index a01eedfa1c7..39e2c1f7ae7 100644 --- a/conan/api/output.py +++ b/conan/api/output.py @@ -216,14 +216,20 @@ def _skip_warning(): if _skip_warning(): return self warn_tag_msg = "" if warn_tag is None else f"{warn_tag}: " - message_type = "ERROR" if self._warnings_as_errors else "WARN" - message_color = Color.RED if self._warnings_as_errors else Color.YELLOW - self._write_message(f"{message_type}: {warn_tag_msg}{msg}", message_color) + output = f"{warn_tag_msg}{msg}" + + if self._warnings_as_errors: + self.error(output) + else: + self._write_message(f"WARN: {output}", Color.YELLOW) return self - def error(self, msg): + def error(self, msg, error_type=None): if self._conan_output_level <= LEVEL_ERROR: - self._write_message("ERROR: {}".format(msg), Color.RED) + if self._warnings_as_errors and error_type != "context": + raise ConanException(msg) + else: + self._write_message("ERROR: {}".format(msg), Color.RED) return self def flush(self): diff --git a/conan/cli/cli.py b/conan/cli/cli.py index d733ce19b79..de9affcb85f 100644 --- a/conan/cli/cli.py +++ b/conan/cli/cli.py @@ -208,20 +208,20 @@ def exception_exit_error(exception): if exception is None: return SUCCESS if isinstance(exception, ConanInvalidConfiguration): - output.error(exception) + output.error(exception, error_type="context") return ERROR_INVALID_CONFIGURATION if isinstance(exception, ConanException): - output.error(exception) + output.error(exception, error_type="context") return ERROR_GENERAL if isinstance(exception, SystemExit): if exception.code != 0: - output.error("Exiting with code: %d" % exception.code) + output.error("Exiting with code: %d" % exception.code, error_type="context") return exception.code assert isinstance(exception, Exception) - output.error(traceback.format_exc()) + output.error(traceback.format_exc(), error_type="context") msg = exception_message_safe(exception) - output.error(msg) + output.error(msg, error_type="context") return ERROR_UNEXPECTED diff --git a/conans/client/downloaders/file_downloader.py b/conans/client/downloaders/file_downloader.py index 2fbcf3529d8..2ff95a98768 100644 --- a/conans/client/downloaders/file_downloader.py +++ b/conans/client/downloaders/file_downloader.py @@ -44,7 +44,7 @@ def download(self, url, file_path, retry=2, retry_wait=0, verify_ssl=True, auth= if counter == retry: raise else: - self._output.error(exc) + self._output.warning(exc, warn_tag="network") self._output.info(f"Waiting {retry_wait} seconds to retry...") time.sleep(retry_wait) diff --git a/conans/client/rest/file_uploader.py b/conans/client/rest/file_uploader.py index eab1819edf2..bba570a91e0 100644 --- a/conans/client/rest/file_uploader.py +++ b/conans/client/rest/file_uploader.py @@ -76,7 +76,7 @@ def upload(self, url, abs_path, auth=None, dedup=False, retry=None, retry_wait=N raise else: if self._output: - self._output.error(exc) + self._output.warning(exc, warn_tag="network") self._output.info("Waiting %d seconds to retry..." % retry_wait) time.sleep(retry_wait) From eeaf179d2f1481581151fc5c61c01b378f6f4506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n?= Date: Sun, 17 Dec 2023 21:16:21 +0100 Subject: [PATCH 4/8] Warnings as errors now raises on most .error() calls --- conan/cli/cli.py | 7 ++++--- conan/internal/api/detect_api.py | 13 +++++++------ conan/internal/cache/db/cache_database.py | 5 +++-- conan/internal/deploy.py | 2 +- conan/internal/integrity_check.py | 12 ++++++------ conan/tools/files/files.py | 4 ++-- conans/client/cmd/export.py | 2 +- conans/client/generators/__init__.py | 2 +- conans/client/graph/build_mode.py | 2 +- conans/client/graph/install_graph.py | 2 +- conans/client/installer.py | 2 +- conans/client/migrations.py | 3 ++- conans/client/remote_manager.py | 12 ++++++------ conans/client/rest/auth_manager.py | 6 +++--- conans/client/rest/rest_client_v2.py | 2 +- conans/client/userio.py | 2 +- conans/migrations.py | 5 +++-- conans/model/graph_lock.py | 2 +- 18 files changed, 45 insertions(+), 40 deletions(-) diff --git a/conan/cli/cli.py b/conan/cli/cli.py index de9affcb85f..433078a859c 100644 --- a/conan/cli/cli.py +++ b/conan/cli/cli.py @@ -51,8 +51,8 @@ def _add_commands(self): try: self._add_command(module_name, module_name.replace("cmd_", "")) except Exception as e: - ConanOutput().error("Error loading custom command " - "'{}.py': {}".format(module_name, e)) + ConanOutput().error(f"Error loading custom command '{module_name}.py': {e}", + error_type="context") # layers for folder in os.listdir(custom_commands_path): layer_folder = os.path.join(custom_commands_path, folder) @@ -67,7 +67,8 @@ def _add_commands(self): self._add_command(module_path, module_name.replace("cmd_", ""), package=folder) except Exception as e: - ConanOutput().error(f"Error loading custom command {module_path}: {e}") + ConanOutput().error(f"Error loading custom command {module_path}: {e}", + error_type="context") def _add_command(self, import_path, method_name, package=None): try: diff --git a/conan/internal/api/detect_api.py b/conan/internal/api/detect_api.py index 7ddfb4de167..8684ab1bb9e 100644 --- a/conan/internal/api/detect_api.py +++ b/conan/internal/api/detect_api.py @@ -5,6 +5,7 @@ import textwrap from conan.api.output import ConanOutput +from conans.errors import ConanException from conans.model.version import Version from conans.util.files import save from conans.util.runners import check_output_runner, detect_runner @@ -205,6 +206,7 @@ def default_msvc_runtime(compiler): def default_cppstd(compiler, compiler_version): """ returns the default cppstd for the compiler-version. This is not detected, just the default """ + def _clang_cppstd_default(version): if version >= "16": return "gnu17" @@ -264,18 +266,17 @@ def detect_compiler(): if "gcc" in command or "g++" in command or "c++" in command: gcc, gcc_version = _gcc_compiler(command) if platform.system() == "Darwin" and gcc is None: - output.error("%s detected as a frontend using apple-clang. " - "Compiler not supported" % command) + raise ConanException("%s detected as a frontend using apple-clang. " + "Compiler not supported" % command) return gcc, gcc_version if platform.system() == "SunOS" and command.lower() == "cc": return _sun_cc_compiler(command) - if platform.system() == "Windows" and command.rstrip('"').endswith(("cl", "cl.exe")) \ - and "clang" not in command: + if (platform.system() == "Windows" and command.rstrip('"').endswith(("cl", "cl.exe")) + and "clang" not in command): return _msvc_cl_compiler(command) # I am not able to find its version - output.error("Not able to automatically detect '%s' version" % command) - return None, None + raise ConanException("Not able to automatically detect '%s' version" % command) if platform.system() == "Windows": version = _detect_vs_ide_version() diff --git a/conan/internal/cache/db/cache_database.py b/conan/internal/cache/db/cache_database.py index 6987066b53e..16cefc6350f 100644 --- a/conan/internal/cache/db/cache_database.py +++ b/conan/internal/cache/db/cache_database.py @@ -4,6 +4,7 @@ from conan.api.output import ConanOutput from conan.internal.cache.db.packages_table import PackagesDBTable from conan.internal.cache.db.recipes_table import RecipesDBTable +from conans.errors import ConanException from conans.model.package_ref import PkgReference from conans.model.recipe_ref import RecipeReference from conans.model.version import Version @@ -13,8 +14,8 @@ class CacheDatabase: def __init__(self, filename): version = sqlite3.sqlite_version - if Version(version) < "3.7.11": # Not an exception, in case some false positives - ConanOutput().error(f"Your sqlite3 '{version} < 3.7.11' version is not supported") + if Version(version) < "3.7.11": + raise ConanException(f"Your sqlite3 '{version} < 3.7.11' version is not supported") self._recipes = RecipesDBTable(filename) self._packages = PackagesDBTable(filename) if not os.path.isfile(filename): diff --git a/conan/internal/deploy.py b/conan/internal/deploy.py index 6edbba3e88a..ec68ee0c777 100644 --- a/conan/internal/deploy.py +++ b/conan/internal/deploy.py @@ -77,7 +77,7 @@ def _deploy_single(dep, conanfile, output_folder, folder_name): except Exception as e: if "WinError 1314" in str(e): ConanOutput().error("full_deploy: Symlinks in Windows require admin privileges " - "or 'Developer mode = ON'") + "or 'Developer mode = ON'", error_type="context") raise ConanException(f"full_deploy: The copy of '{dep}' files failed: {e}.\nYou can " f"use 'tools.deployer:symlinks' conf to disable symlinks") dep.set_deploy_folder(new_folder) diff --git a/conan/internal/integrity_check.py b/conan/internal/integrity_check.py index b382d8aebc6..28c5a1259fd 100644 --- a/conan/internal/integrity_check.py +++ b/conan/internal/integrity_check.py @@ -41,11 +41,11 @@ def _recipe_corrupted(self, ref: RecipeReference): if not k.startswith("export_source")} if read_manifest != expected_manifest: - output.error(f"{ref}: Manifest mismatch") - output.error(f"Folder: {layout.export()}") + output.error(f"{ref}: Manifest mismatch", error_type="context") + output.error(f"Folder: {layout.export()}", error_type="context") diff = read_manifest.difference(expected_manifest) for fname, (h1, h2) in diff.items(): - output.error(f" '{fname}' (manifest: {h1}, file: {h2})") + output.error(f" '{fname}' (manifest: {h1}, file: {h2})", error_type="context") return True output.info(f"{ref}: Integrity checked: ok") @@ -55,10 +55,10 @@ def _package_corrupted(self, ref: PkgReference): read_manifest, expected_manifest = layout.package_manifests() if read_manifest != expected_manifest: - output.error(f"{ref}: Manifest mismatch") - output.error(f"Folder: {layout.package()}") + output.error(f"{ref}: Manifest mismatch", error_type="context") + output.error(f"Folder: {layout.package()}", error_type="context") diff = read_manifest.difference(expected_manifest) for fname, (h1, h2) in diff.items(): - output.error(f" '{fname}' (manifest: {h1}, file: {h2})") + output.error(f" '{fname}' (manifest: {h1}, file: {h2})", error_type="context") return True output.info(f"{ref}: Integrity checked: ok") diff --git a/conan/tools/files/files.py b/conan/tools/files/files.py index b4fda884738..f7e030b38fe 100644 --- a/conan/tools/files/files.py +++ b/conan/tools/files/files.py @@ -339,7 +339,7 @@ def print_progress(_, __): try: z.extract(file_, full_path) except Exception as e: - output.error("Error extract %s\n%s" % (file_.filename, str(e))) + output.error(f"Error extract {file_.filename}\n{str(e)}", error_type="context") else: # duplicated for, to avoid a platform check for each zipped file for file_ in zip_info: extracted_size += file_.file_size @@ -352,7 +352,7 @@ def print_progress(_, __): perm = file_.external_attr >> 16 & 0xFFF os.chmod(os.path.join(full_path, file_.filename), perm) except Exception as e: - output.error("Error extract %s\n%s" % (file_.filename, str(e))) + output.error(f"Error extract {file_.filename}\n{str(e)}", error_type="context") output.writeln("") diff --git a/conans/client/cmd/export.py b/conans/client/cmd/export.py index 9bfe33013af..dc725894825 100644 --- a/conans/client/cmd/export.py +++ b/conans/client/cmd/export.py @@ -78,7 +78,7 @@ def cmd_export(app, global_conf, conanfile_path, name, version, user, channel, g clean_dirty(source_folder) except BaseException as e: scoped_output.error("Unable to delete source folder. Will be marked as corrupted " - "for deletion") + "for deletion", error_type="context") scoped_output.warning(str(e)) set_dirty(source_folder) diff --git a/conans/client/generators/__init__.py b/conans/client/generators/__init__.py index 36d8b1997d4..9cd2782e454 100644 --- a/conans/client/generators/__init__.py +++ b/conans/client/generators/__init__.py @@ -95,7 +95,7 @@ def write_generators(conanfile, app): continue except Exception as e: # When a generator fails, it is very useful to have the whole stacktrace - conanfile.output.error(traceback.format_exc()) + conanfile.output.error(traceback.format_exc(), error_type="context") raise ConanException(f"Error in generator '{generator_name}': {str(e)}") from e finally: # restore the generators attribute, so it can raise diff --git a/conans/client/graph/build_mode.py b/conans/client/graph/build_mode.py index efb7d669377..08e099eff3f 100644 --- a/conans/client/graph/build_mode.py +++ b/conans/client/graph/build_mode.py @@ -103,4 +103,4 @@ def should_build_missing(self, conanfile): def report_matches(self): for pattern in self._unused_patterns: - ConanOutput().error("No package matching '%s' pattern found." % pattern) + ConanOutput().error(f"No package matching '{pattern}' pattern found.", error_type="context") diff --git a/conans/client/graph/install_graph.py b/conans/client/graph/install_graph.py index 3a92e19ba37..fcb4f169857 100644 --- a/conans/client/graph/install_graph.py +++ b/conans/client/graph/install_graph.py @@ -303,7 +303,7 @@ def _raise_missing(self, missing): missing_prefs_str = list(sorted([str(pref) for pref in missing_prefs])) out = ConanOutput() for pref_str in missing_prefs_str: - out.error("Missing binary: %s" % pref_str) + out.error(f"Missing binary: {pref_str}", error_type="context") out.writeln("") # Report details just the first one diff --git a/conans/client/installer.py b/conans/client/installer.py index c9085272c3b..33a57a62d99 100644 --- a/conans/client/installer.py +++ b/conans/client/installer.py @@ -97,7 +97,7 @@ def _build(self, conanfile, pref): conanfile.output.success("Package '%s' built" % pref.package_id) conanfile.output.info("Build folder %s" % conanfile.build_folder) except Exception as exc: - conanfile.output.error("\nPackage '%s' build failed" % pref.package_id) + conanfile.output.error(f"\nPackage '{pref.package_id}' build failed", error_type="context") conanfile.output.warning("Build folder %s" % conanfile.build_folder) if isinstance(exc, ConanException): raise exc diff --git a/conans/client/migrations.py b/conans/client/migrations.py index f7ea7af9008..34c7d236cab 100644 --- a/conans/client/migrations.py +++ b/conans/client/migrations.py @@ -69,7 +69,8 @@ def _migrate_pkg_db_lru(cache_folder, old_version): f"INTEGER DEFAULT '{lru}' NOT NULL;") except Exception: ConanOutput().error(f"Could not complete the 2.0.14 DB migration." - " Please manually remove your .conan2 cache and reinstall packages") + " Please manually remove your .conan2 cache and reinstall packages", + error_type="context") raise else: # generate the back-migration script undo_lru = textwrap.dedent("""\ diff --git a/conans/client/remote_manager.py b/conans/client/remote_manager.py index 54287df4f10..2c002afb0d8 100644 --- a/conans/client/remote_manager.py +++ b/conans/client/remote_manager.py @@ -63,7 +63,7 @@ def get_recipe(self, ref, remote, metadata=None): f"no conanmanifest.txt") self._signer.verify(ref, download_export, files=zipped_files) except BaseException: # So KeyboardInterrupt also cleans things - ConanOutput(scope=str(ref)).error(f"Error downloading from remote '{remote.name}'") + ConanOutput(scope=str(ref)).error(f"Error downloading from remote '{remote.name}'", error_type="context") self._cache.remove_recipe_layout(layout) raise export_folder = layout.export() @@ -91,7 +91,7 @@ def get_recipe_metadata(self, ref, remote, metadata): self._call_remote(remote, "get_recipe", ref, download_export, metadata, only_metadata=True) except BaseException: # So KeyboardInterrupt also cleans things - output.error(f"Error downloading metadata from remote '{remote.name}'") + output.error(f"Error downloading metadata from remote '{remote.name}'", error_type="context") raise def get_recipe_sources(self, ref, layout, remote): @@ -134,8 +134,8 @@ def get_package_metadata(self, pref, remote, metadata): self._call_remote(remote, "get_package", pref, download_pkg_folder, metadata, only_metadata=True) except BaseException as e: # So KeyboardInterrupt also cleans things - output.error("Exception while getting package metadata: %s" % str(pref.package_id)) - output.error("Exception: %s %s" % (type(e), str(e))) + output.error(f"Exception while getting package metadata: {str(pref.package_id)}", error_type="context") + output.error(f"Exception: {type(e)} {str(e)}", error_type="context") raise def _get_package(self, layout, pref, remote, scoped_output, metadata): @@ -166,8 +166,8 @@ def _get_package(self, layout, pref, remote, scoped_output, metadata): raise PackageNotFoundException(pref) except BaseException as e: # So KeyboardInterrupt also cleans things self._cache.remove_package_layout(layout) - scoped_output.error("Exception while getting package: %s" % str(pref.package_id)) - scoped_output.error("Exception: %s %s" % (type(e), str(e))) + scoped_output.error(f"Exception while getting package: {str(pref.package_id)}", error_type="context") + scoped_output.error(f"Exception: {type(e)} {str(e)}", error_type="context") raise def search_recipes(self, remote, pattern): diff --git a/conans/client/rest/auth_manager.py b/conans/client/rest/auth_manager.py index 84de9b03e02..cad3f6f8e5e 100644 --- a/conans/client/rest/auth_manager.py +++ b/conans/client/rest/auth_manager.py @@ -78,9 +78,9 @@ def _retry_with_new_token(self, user, remote, method_name, *args, **kwargs): except AuthenticationException: out = ConanOutput() if user is None: - out.error('Wrong user or password') + out.error('Wrong user or password', error_type="context") else: - out.error('Wrong password for user "%s"' % user) + out.error(f'Wrong password for user "{user}"', error_type="context") out.info('You can change username with "conan remote login "') else: return self.call_rest_api_method(remote, method_name, *args, **kwargs) @@ -98,7 +98,7 @@ def _clear_user_tokens_in_db(self, user, remote): self._localdb.store(user, token=None, refresh_token=None, remote_url=remote.url) except Exception as e: out = ConanOutput() - out.error('Your credentials could not be stored in local cache\n') + out.error('Your credentials could not be stored in local cache\n', error_type="context") out.debug(str(e) + '\n') @staticmethod diff --git a/conans/client/rest/rest_client_v2.py b/conans/client/rest/rest_client_v2.py index 5922d0a8ae7..109c6148cf7 100644 --- a/conans/client/rest/rest_client_v2.py +++ b/conans/client/rest/rest_client_v2.py @@ -147,7 +147,7 @@ def _upload_files(self, files, urls): except (AuthenticationException, ForbiddenException): raise except Exception as exc: - output.error("\nError uploading file: %s, '%s'" % (filename, exc)) + output.error(f"\nError uploading file: {filename}, '{exc}'", error_type="context") failed.append(filename) if failed: diff --git a/conans/client/userio.py b/conans/client/userio.py index f3a2385299f..56f9ae273bb 100644 --- a/conans/client/userio.py +++ b/conans/client/userio.py @@ -129,5 +129,5 @@ def request_boolean(self, msg, default_option=None): elif s.lower() in ['no', 'n']: ret = False else: - self._out.error("%s is not a valid answer" % s) + self._out.warning(f"{s} is not a valid answer") return ret diff --git a/conans/migrations.py b/conans/migrations.py index 03560e65a98..682c85e4aad 100644 --- a/conans/migrations.py +++ b/conans/migrations.py @@ -30,7 +30,7 @@ def migrate(self): self._apply_back_migrations() self._update_version_file() except Exception as e: - ConanOutput().error(str(e)) + ConanOutput().error(str(e), error_type="context") raise ConanMigrationError(e) def _update_version_file(self): @@ -81,5 +81,6 @@ def _apply_back_migrations(self): migrate_method(self.conf_path) except Exception as e: ConanOutput().error(f"There was an error running downgrade migration: {e}. " - f"Recommended to remove the cache and start from scratch") + f"Recommended to remove the cache and start from scratch", + error_type="context") os.remove(migration) diff --git a/conans/model/graph_lock.py b/conans/model/graph_lock.py index 789bb081824..cdf2886e86b 100644 --- a/conans/model/graph_lock.py +++ b/conans/model/graph_lock.py @@ -224,7 +224,7 @@ def resolve_locked(self, node, require, resolve_prereleases): msg = f"Override defined for {require.ref}, but multiple possible overrides" \ f" {overrides}. You might need to apply the 'conan graph build-order'" \ f" overrides for correctly building this package with this lockfile" - ConanOutput().error(msg) + ConanOutput().error(msg, error_type="context") raise def _resolve_overrides(self, require): From 0564f7804d78db2fa47bd4fba97250a6eddfdf32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n?= Date: Mon, 18 Dec 2023 10:45:51 +0100 Subject: [PATCH 5/8] Revert some new ConanExceptions --- conan/internal/api/detect_api.py | 8 ++++---- conan/internal/cache/db/cache_database.py | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/conan/internal/api/detect_api.py b/conan/internal/api/detect_api.py index 8684ab1bb9e..0f6ad396313 100644 --- a/conan/internal/api/detect_api.py +++ b/conan/internal/api/detect_api.py @@ -5,7 +5,6 @@ import textwrap from conan.api.output import ConanOutput -from conans.errors import ConanException from conans.model.version import Version from conans.util.files import save from conans.util.runners import check_output_runner, detect_runner @@ -266,8 +265,8 @@ def detect_compiler(): if "gcc" in command or "g++" in command or "c++" in command: gcc, gcc_version = _gcc_compiler(command) if platform.system() == "Darwin" and gcc is None: - raise ConanException("%s detected as a frontend using apple-clang. " - "Compiler not supported" % command) + output.error("%s detected as a frontend using apple-clang. " + "Compiler not supported" % command) return gcc, gcc_version if platform.system() == "SunOS" and command.lower() == "cc": return _sun_cc_compiler(command) @@ -276,7 +275,8 @@ def detect_compiler(): return _msvc_cl_compiler(command) # I am not able to find its version - raise ConanException("Not able to automatically detect '%s' version" % command) + output.error("Not able to automatically detect '%s' version" % command) + return None, None if platform.system() == "Windows": version = _detect_vs_ide_version() diff --git a/conan/internal/cache/db/cache_database.py b/conan/internal/cache/db/cache_database.py index 16cefc6350f..fffa02486a0 100644 --- a/conan/internal/cache/db/cache_database.py +++ b/conan/internal/cache/db/cache_database.py @@ -4,7 +4,6 @@ from conan.api.output import ConanOutput from conan.internal.cache.db.packages_table import PackagesDBTable from conan.internal.cache.db.recipes_table import RecipesDBTable -from conans.errors import ConanException from conans.model.package_ref import PkgReference from conans.model.recipe_ref import RecipeReference from conans.model.version import Version @@ -15,7 +14,7 @@ class CacheDatabase: def __init__(self, filename): version = sqlite3.sqlite_version if Version(version) < "3.7.11": - raise ConanException(f"Your sqlite3 '{version} < 3.7.11' version is not supported") + ConanOutput().error(f"Your sqlite3 '{version} < 3.7.11' version is not supported") self._recipes = RecipesDBTable(filename) self._packages = PackagesDBTable(filename) if not os.path.isfile(filename): From df4d43b85414971d4f60d1e61ee5082524620331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n?= Date: Mon, 18 Dec 2023 11:13:31 +0100 Subject: [PATCH 6/8] Revert error->warning --- conans/client/userio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conans/client/userio.py b/conans/client/userio.py index 56f9ae273bb..33c9628ff49 100644 --- a/conans/client/userio.py +++ b/conans/client/userio.py @@ -129,5 +129,5 @@ def request_boolean(self, msg, default_option=None): elif s.lower() in ['no', 'n']: ret = False else: - self._out.warning(f"{s} is not a valid answer") + self._out.error(f"{s} is not a valid answer") return ret From 974b5683b2ccc960a498da57a04a09826b2ac3f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n=20Blanco?= Date: Mon, 18 Dec 2023 11:40:50 +0100 Subject: [PATCH 7/8] Merge from release/2.0, add missing tests, change to exception --- conan/api/output.py | 2 +- conan/cli/cli.py | 14 +-- conan/internal/deploy.py | 2 +- conan/internal/integrity_check.py | 12 +-- conan/tools/files/files.py | 4 +- conans/client/cmd/export.py | 2 +- conans/client/generators/__init__.py | 2 +- conans/client/graph/install_graph.py | 2 +- conans/client/installer.py | 2 +- conans/client/migrations.py | 2 +- conans/client/remote_manager.py | 12 +-- conans/client/rest/auth_manager.py | 6 +- conans/client/rest/rest_client_v2.py | 2 +- conans/migrations.py | 4 +- conans/model/graph_lock.py | 2 +- .../integration/command_v2/test_output.py | 85 ++++++++++++++++--- 16 files changed, 106 insertions(+), 49 deletions(-) diff --git a/conan/api/output.py b/conan/api/output.py index 39e2c1f7ae7..ad8e541c379 100644 --- a/conan/api/output.py +++ b/conan/api/output.py @@ -226,7 +226,7 @@ def _skip_warning(): def error(self, msg, error_type=None): if self._conan_output_level <= LEVEL_ERROR: - if self._warnings_as_errors and error_type != "context": + if self._warnings_as_errors and error_type != "exception": raise ConanException(msg) else: self._write_message("ERROR: {}".format(msg), Color.RED) diff --git a/conan/cli/cli.py b/conan/cli/cli.py index 433078a859c..18feeb203b9 100644 --- a/conan/cli/cli.py +++ b/conan/cli/cli.py @@ -52,7 +52,7 @@ def _add_commands(self): self._add_command(module_name, module_name.replace("cmd_", "")) except Exception as e: ConanOutput().error(f"Error loading custom command '{module_name}.py': {e}", - error_type="context") + error_type="exception") # layers for folder in os.listdir(custom_commands_path): layer_folder = os.path.join(custom_commands_path, folder) @@ -68,7 +68,7 @@ def _add_commands(self): package=folder) except Exception as e: ConanOutput().error(f"Error loading custom command {module_path}: {e}", - error_type="context") + error_type="exception") def _add_command(self, import_path, method_name, package=None): try: @@ -209,20 +209,20 @@ def exception_exit_error(exception): if exception is None: return SUCCESS if isinstance(exception, ConanInvalidConfiguration): - output.error(exception, error_type="context") + output.error(exception, error_type="exception") return ERROR_INVALID_CONFIGURATION if isinstance(exception, ConanException): - output.error(exception, error_type="context") + output.error(exception, error_type="exception") return ERROR_GENERAL if isinstance(exception, SystemExit): if exception.code != 0: - output.error("Exiting with code: %d" % exception.code, error_type="context") + output.error("Exiting with code: %d" % exception.code, error_type="exception") return exception.code assert isinstance(exception, Exception) - output.error(traceback.format_exc(), error_type="context") + output.error(traceback.format_exc(), error_type="exception") msg = exception_message_safe(exception) - output.error(msg, error_type="context") + output.error(msg, error_type="exception") return ERROR_UNEXPECTED diff --git a/conan/internal/deploy.py b/conan/internal/deploy.py index ec68ee0c777..026580e2be6 100644 --- a/conan/internal/deploy.py +++ b/conan/internal/deploy.py @@ -77,7 +77,7 @@ def _deploy_single(dep, conanfile, output_folder, folder_name): except Exception as e: if "WinError 1314" in str(e): ConanOutput().error("full_deploy: Symlinks in Windows require admin privileges " - "or 'Developer mode = ON'", error_type="context") + "or 'Developer mode = ON'", error_type="exception") raise ConanException(f"full_deploy: The copy of '{dep}' files failed: {e}.\nYou can " f"use 'tools.deployer:symlinks' conf to disable symlinks") dep.set_deploy_folder(new_folder) diff --git a/conan/internal/integrity_check.py b/conan/internal/integrity_check.py index 28c5a1259fd..fa52d536255 100644 --- a/conan/internal/integrity_check.py +++ b/conan/internal/integrity_check.py @@ -41,11 +41,11 @@ def _recipe_corrupted(self, ref: RecipeReference): if not k.startswith("export_source")} if read_manifest != expected_manifest: - output.error(f"{ref}: Manifest mismatch", error_type="context") - output.error(f"Folder: {layout.export()}", error_type="context") + output.error(f"{ref}: Manifest mismatch", error_type="exception") + output.error(f"Folder: {layout.export()}", error_type="exception") diff = read_manifest.difference(expected_manifest) for fname, (h1, h2) in diff.items(): - output.error(f" '{fname}' (manifest: {h1}, file: {h2})", error_type="context") + output.error(f" '{fname}' (manifest: {h1}, file: {h2})", error_type="exception") return True output.info(f"{ref}: Integrity checked: ok") @@ -55,10 +55,10 @@ def _package_corrupted(self, ref: PkgReference): read_manifest, expected_manifest = layout.package_manifests() if read_manifest != expected_manifest: - output.error(f"{ref}: Manifest mismatch", error_type="context") - output.error(f"Folder: {layout.package()}", error_type="context") + output.error(f"{ref}: Manifest mismatch", error_type="exception") + output.error(f"Folder: {layout.package()}", error_type="exception") diff = read_manifest.difference(expected_manifest) for fname, (h1, h2) in diff.items(): - output.error(f" '{fname}' (manifest: {h1}, file: {h2})", error_type="context") + output.error(f" '{fname}' (manifest: {h1}, file: {h2})", error_type="exception") return True output.info(f"{ref}: Integrity checked: ok") diff --git a/conan/tools/files/files.py b/conan/tools/files/files.py index f7e030b38fe..a588e70948a 100644 --- a/conan/tools/files/files.py +++ b/conan/tools/files/files.py @@ -339,7 +339,7 @@ def print_progress(_, __): try: z.extract(file_, full_path) except Exception as e: - output.error(f"Error extract {file_.filename}\n{str(e)}", error_type="context") + output.error(f"Error extract {file_.filename}\n{str(e)}", error_type="exception") else: # duplicated for, to avoid a platform check for each zipped file for file_ in zip_info: extracted_size += file_.file_size @@ -352,7 +352,7 @@ def print_progress(_, __): perm = file_.external_attr >> 16 & 0xFFF os.chmod(os.path.join(full_path, file_.filename), perm) except Exception as e: - output.error(f"Error extract {file_.filename}\n{str(e)}", error_type="context") + output.error(f"Error extract {file_.filename}\n{str(e)}", error_type="exception") output.writeln("") diff --git a/conans/client/cmd/export.py b/conans/client/cmd/export.py index dc725894825..660ab924668 100644 --- a/conans/client/cmd/export.py +++ b/conans/client/cmd/export.py @@ -78,7 +78,7 @@ def cmd_export(app, global_conf, conanfile_path, name, version, user, channel, g clean_dirty(source_folder) except BaseException as e: scoped_output.error("Unable to delete source folder. Will be marked as corrupted " - "for deletion", error_type="context") + "for deletion", error_type="exception") scoped_output.warning(str(e)) set_dirty(source_folder) diff --git a/conans/client/generators/__init__.py b/conans/client/generators/__init__.py index 9cd2782e454..c3f55c07b3d 100644 --- a/conans/client/generators/__init__.py +++ b/conans/client/generators/__init__.py @@ -95,7 +95,7 @@ def write_generators(conanfile, app): continue except Exception as e: # When a generator fails, it is very useful to have the whole stacktrace - conanfile.output.error(traceback.format_exc(), error_type="context") + conanfile.output.error(traceback.format_exc(), error_type="exception") raise ConanException(f"Error in generator '{generator_name}': {str(e)}") from e finally: # restore the generators attribute, so it can raise diff --git a/conans/client/graph/install_graph.py b/conans/client/graph/install_graph.py index fcb4f169857..002f2da0a44 100644 --- a/conans/client/graph/install_graph.py +++ b/conans/client/graph/install_graph.py @@ -303,7 +303,7 @@ def _raise_missing(self, missing): missing_prefs_str = list(sorted([str(pref) for pref in missing_prefs])) out = ConanOutput() for pref_str in missing_prefs_str: - out.error(f"Missing binary: {pref_str}", error_type="context") + out.error(f"Missing binary: {pref_str}", error_type="exception") out.writeln("") # Report details just the first one diff --git a/conans/client/installer.py b/conans/client/installer.py index 33a57a62d99..5617b99159b 100644 --- a/conans/client/installer.py +++ b/conans/client/installer.py @@ -97,7 +97,7 @@ def _build(self, conanfile, pref): conanfile.output.success("Package '%s' built" % pref.package_id) conanfile.output.info("Build folder %s" % conanfile.build_folder) except Exception as exc: - conanfile.output.error(f"\nPackage '{pref.package_id}' build failed", error_type="context") + conanfile.output.error(f"\nPackage '{pref.package_id}' build failed", error_type="exception") conanfile.output.warning("Build folder %s" % conanfile.build_folder) if isinstance(exc, ConanException): raise exc diff --git a/conans/client/migrations.py b/conans/client/migrations.py index 34c7d236cab..58e3dd5afec 100644 --- a/conans/client/migrations.py +++ b/conans/client/migrations.py @@ -70,7 +70,7 @@ def _migrate_pkg_db_lru(cache_folder, old_version): except Exception: ConanOutput().error(f"Could not complete the 2.0.14 DB migration." " Please manually remove your .conan2 cache and reinstall packages", - error_type="context") + error_type="exception") raise else: # generate the back-migration script undo_lru = textwrap.dedent("""\ diff --git a/conans/client/remote_manager.py b/conans/client/remote_manager.py index 2c002afb0d8..7a079046c33 100644 --- a/conans/client/remote_manager.py +++ b/conans/client/remote_manager.py @@ -63,7 +63,7 @@ def get_recipe(self, ref, remote, metadata=None): f"no conanmanifest.txt") self._signer.verify(ref, download_export, files=zipped_files) except BaseException: # So KeyboardInterrupt also cleans things - ConanOutput(scope=str(ref)).error(f"Error downloading from remote '{remote.name}'", error_type="context") + ConanOutput(scope=str(ref)).error(f"Error downloading from remote '{remote.name}'", error_type="exception") self._cache.remove_recipe_layout(layout) raise export_folder = layout.export() @@ -91,7 +91,7 @@ def get_recipe_metadata(self, ref, remote, metadata): self._call_remote(remote, "get_recipe", ref, download_export, metadata, only_metadata=True) except BaseException: # So KeyboardInterrupt also cleans things - output.error(f"Error downloading metadata from remote '{remote.name}'", error_type="context") + output.error(f"Error downloading metadata from remote '{remote.name}'", error_type="exception") raise def get_recipe_sources(self, ref, layout, remote): @@ -134,8 +134,8 @@ def get_package_metadata(self, pref, remote, metadata): self._call_remote(remote, "get_package", pref, download_pkg_folder, metadata, only_metadata=True) except BaseException as e: # So KeyboardInterrupt also cleans things - output.error(f"Exception while getting package metadata: {str(pref.package_id)}", error_type="context") - output.error(f"Exception: {type(e)} {str(e)}", error_type="context") + output.error(f"Exception while getting package metadata: {str(pref.package_id)}", error_type="exception") + output.error(f"Exception: {type(e)} {str(e)}", error_type="exception") raise def _get_package(self, layout, pref, remote, scoped_output, metadata): @@ -166,8 +166,8 @@ def _get_package(self, layout, pref, remote, scoped_output, metadata): raise PackageNotFoundException(pref) except BaseException as e: # So KeyboardInterrupt also cleans things self._cache.remove_package_layout(layout) - scoped_output.error(f"Exception while getting package: {str(pref.package_id)}", error_type="context") - scoped_output.error(f"Exception: {type(e)} {str(e)}", error_type="context") + scoped_output.error(f"Exception while getting package: {str(pref.package_id)}", error_type="exception") + scoped_output.error(f"Exception: {type(e)} {str(e)}", error_type="exception") raise def search_recipes(self, remote, pattern): diff --git a/conans/client/rest/auth_manager.py b/conans/client/rest/auth_manager.py index cad3f6f8e5e..68680dfc459 100644 --- a/conans/client/rest/auth_manager.py +++ b/conans/client/rest/auth_manager.py @@ -78,9 +78,9 @@ def _retry_with_new_token(self, user, remote, method_name, *args, **kwargs): except AuthenticationException: out = ConanOutput() if user is None: - out.error('Wrong user or password', error_type="context") + out.error('Wrong user or password', error_type="exception") else: - out.error(f'Wrong password for user "{user}"', error_type="context") + out.error(f'Wrong password for user "{user}"', error_type="exception") out.info('You can change username with "conan remote login "') else: return self.call_rest_api_method(remote, method_name, *args, **kwargs) @@ -98,7 +98,7 @@ def _clear_user_tokens_in_db(self, user, remote): self._localdb.store(user, token=None, refresh_token=None, remote_url=remote.url) except Exception as e: out = ConanOutput() - out.error('Your credentials could not be stored in local cache\n', error_type="context") + out.error('Your credentials could not be stored in local cache\n', error_type="exception") out.debug(str(e) + '\n') @staticmethod diff --git a/conans/client/rest/rest_client_v2.py b/conans/client/rest/rest_client_v2.py index 109c6148cf7..cc05c303865 100644 --- a/conans/client/rest/rest_client_v2.py +++ b/conans/client/rest/rest_client_v2.py @@ -147,7 +147,7 @@ def _upload_files(self, files, urls): except (AuthenticationException, ForbiddenException): raise except Exception as exc: - output.error(f"\nError uploading file: {filename}, '{exc}'", error_type="context") + output.error(f"\nError uploading file: {filename}, '{exc}'", error_type="exception") failed.append(filename) if failed: diff --git a/conans/migrations.py b/conans/migrations.py index 682c85e4aad..c455a0bb42b 100644 --- a/conans/migrations.py +++ b/conans/migrations.py @@ -30,7 +30,7 @@ def migrate(self): self._apply_back_migrations() self._update_version_file() except Exception as e: - ConanOutput().error(str(e), error_type="context") + ConanOutput().error(str(e), error_type="exception") raise ConanMigrationError(e) def _update_version_file(self): @@ -82,5 +82,5 @@ def _apply_back_migrations(self): except Exception as e: ConanOutput().error(f"There was an error running downgrade migration: {e}. " f"Recommended to remove the cache and start from scratch", - error_type="context") + error_type="exception") os.remove(migration) diff --git a/conans/model/graph_lock.py b/conans/model/graph_lock.py index cdf2886e86b..f58c940b06e 100644 --- a/conans/model/graph_lock.py +++ b/conans/model/graph_lock.py @@ -224,7 +224,7 @@ def resolve_locked(self, node, require, resolve_prereleases): msg = f"Override defined for {require.ref}, but multiple possible overrides" \ f" {overrides}. You might need to apply the 'conan graph build-order'" \ f" overrides for correctly building this package with this lockfile" - ConanOutput().error(msg, error_type="context") + ConanOutput().error(msg, error_type="exception") raise def _resolve_overrides(self, require): diff --git a/conans/test/integration/command_v2/test_output.py b/conans/test/integration/command_v2/test_output.py index 153192d54b9..75e3afc1e56 100644 --- a/conans/test/integration/command_v2/test_output.py +++ b/conans/test/integration/command_v2/test_output.py @@ -3,11 +3,12 @@ from conans.test.assets.genconanfile import GenConanfile from conans.test.utils.tools import TestClient +from conans.util.env import environment_update class TestOutputLevel: def test_invalid_output_level(self): - t = TestClient() + t = TestClient(light=True) t.save({"conanfile.py": GenConanfile("foo", "1.0")}) t.run("create . -vfooling", assert_error=True) assert "Invalid argument '-vfooling'" @@ -23,7 +24,7 @@ def test_output_level(self): "self.output.error('This is a error')", ) - t = TestClient() + t = TestClient(light=True) t.save({"conanfile.py": GenConanfile("foo", "1.0").with_package(*lines)}) # By default, it prints > info @@ -144,13 +145,54 @@ def test_output_level(self): assert "This is a error" in t.out +def test_output_level_envvar(): + lines = ("self.output.trace('This is a trace')", + "self.output.debug('This is a debug')", + "self.output.verbose('This is a verbose')", + "self.output.info('This is a info')", + "self.output.highlight('This is a highlight')", + "self.output.success('This is a success')", + "self.output.warning('This is a warning')", + "self.output.error('This is a error')", + ) + + t = TestClient(light=True) + t.save({"conanfile.py": GenConanfile().with_package(*lines)}) + + # Check if -v argument is equal to VERBOSE level + with environment_update({"CONAN_LOG_LEVEL": "verbose"}): + t.run("create . --name foo --version 1.0") + assert "This is a trace" not in t.out + assert "This is a debug" not in t.out + assert "This is a verbose" in t.out + assert "This is a info" in t.out + assert "This is a highlight" in t.out + assert "This is a success" in t.out + assert "This is a warning" in t.out + assert "This is a error" in t.out + + # Check if -v argument is equal to VERBOSE level + with environment_update({"CONAN_LOG_LEVEL": "error"}): + t.run("create . --name foo --version 1.0") + assert "This is a trace" not in t.out + assert "This is a debug" not in t.out + assert "This is a verbose" not in t.out + assert "This is a info" not in t.out + assert "This is a highlight" not in t.out + assert "This is a success" not in t.out + assert "This is a warning" not in t.out + assert "This is a error" in t.out + + class TestWarningHandling: - lines = ("self.output.warning('Untagged warning')", - "self.output.warning('Tagged warning', warn_tag='tag')") + warning_lines = ("self.output.warning('Tagged warning', warn_tag='tag')", + "self.output.warning('Untagged warning')") + error_lines = ("self.output.error('Tagged error', error_type='exception')", + "self.output.error('Untagged error')") def test_warning_as_error(self): - t = TestClient() - t.save({"conanfile.py": GenConanfile("foo", "1.0").with_package(*self.lines)}) + t = TestClient(light=True) + t.save({"conanfile.py": GenConanfile("foo", "1.0").with_package(*self.warning_lines)}) t.save_home({"global.conf": "core:warnings_as_errors=False"}) t.run("create . -vwarning") @@ -158,14 +200,15 @@ def test_warning_as_error(self): assert "WARN: tag: Tagged warning" in t.out t.save_home({"global.conf": "core:warnings_as_errors=True"}) - t.run("create . -vwarning") - assert "ERROR: Untagged warning" in t.out - assert "ERROR: tag: Tagged warning" in t.out + t.run("create . -vwarning", assert_error=True) + assert "ConanException: tag: Tagged warning" in t.out + # We bailed early, didn't get a chance to print this one + assert "Untagged warning" not in t.out t.save_home({"global.conf": """core:warnings_as_errors=True\ncore:skip_warnings=["tag"]"""}) - t.run("create . -verror") - assert "ERROR: Untagged warning" in t.out - assert "ERROR: tag: Tagged warning" not in t.out + t.run("create . -verror", assert_error=True) + assert "ConanException: Untagged warning" in t.out + assert "Tagged warning" not in t.out t.save_home({"global.conf": "core:warnings_as_errors=False"}) t.run("create . -verror") @@ -173,8 +216,8 @@ def test_warning_as_error(self): assert "ERROR: tag: Tagged warning" not in t.out def test_skip_warnings(self): - t = TestClient() - t.save({"conanfile.py": GenConanfile("foo", "1.0").with_package(*self.lines)}) + t = TestClient(light=True) + t.save({"conanfile.py": GenConanfile("foo", "1.0").with_package(*self.warning_lines)}) t.save_home({"global.conf": "core:skip_warnings=[]"}) t.run("create . -vwarning") @@ -195,3 +238,17 @@ def test_skip_warnings(self): t.run("create . -vwarning") assert "WARN: Untagged warning" not in t.out assert "WARN: tag: Tagged warning" not in t.out + + def test_exception_errors(self): + t = TestClient(light=True) + t.save({"conanfile.py": GenConanfile("foo", "1.0").with_package(*self.error_lines)}) + + t.save_home({"global.conf": "core:warnings_as_errors=False"}) + t.run("create .") + assert "ERROR: Tagged error" in t.out + assert "ERROR: Untagged error" in t.out + + t.save_home({"global.conf": "core:warnings_as_errors=True"}) + t.run("create .", assert_error=True) + assert "ERROR: Tagged error" in t.out + assert "ConanException: Untagged error" in t.out From 35e70a5de3052b09e142905dc567c4b79b45206c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Rinc=C3=B3n=20Blanco?= Date: Mon, 18 Dec 2023 12:28:48 +0100 Subject: [PATCH 8/8] Fix tests --- .../test/integration/command/upload/upload_complete_test.py | 4 ++-- conans/test/integration/remote/broken_download_test.py | 2 +- conans/test/integration/remote/retry_test.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/conans/test/integration/command/upload/upload_complete_test.py b/conans/test/integration/command/upload/upload_complete_test.py index ff9e20b6272..a293688157e 100644 --- a/conans/test/integration/command/upload/upload_complete_test.py +++ b/conans/test/integration/command/upload/upload_complete_test.py @@ -166,7 +166,7 @@ def test_upload_error(self): client.run("install --requires=hello0/1.2.1@frodo/stable --build='*' -r default") self._set_global_conf(client, retry=3, retry_wait=0) client.run("upload hello* --confirm -r default") - self.assertEqual(str(client.out).count("ERROR: Pair file, error!"), 5) + self.assertEqual(str(client.out).count("WARN: network: Pair file, error!"), 5) def _set_global_conf(self, client, retry=None, retry_wait=None): lines = [] @@ -223,7 +223,7 @@ def test_upload_error_with_config(self): client.run("install --requires=hello0/1.2.1@frodo/stable --build='*'") self._set_global_conf(client, retry=3, retry_wait=0) client.run("upload hello* --confirm -r default") - self.assertEqual(str(client.out).count("ERROR: Pair file, error!"), 5) + self.assertEqual(str(client.out).count("WARN: network: Pair file, error!"), 5) def test_upload_same_package_dont_compress(self): client = self._get_client() diff --git a/conans/test/integration/remote/broken_download_test.py b/conans/test/integration/remote/broken_download_test.py index ee7b0f617f5..bd23e7f4b7f 100644 --- a/conans/test/integration/remote/broken_download_test.py +++ b/conans/test/integration/remote/broken_download_test.py @@ -112,7 +112,7 @@ def DownloadFilesBrokenRequesterTimesOne(*args, **kwargs): client = TestClient(servers=servers, inputs=["admin", "password"], requester_class=DownloadFilesBrokenRequesterTimesOne) client.run("install --requires=lib/1.0@lasote/stable") - assert "ERROR: Error downloading file" in client.out + assert "WARN: network: Error downloading file" in client.out assert 'Fake connection error exception' in client.out assert 1 == str(client.out).count("Waiting 0 seconds to retry...") diff --git a/conans/test/integration/remote/retry_test.py b/conans/test/integration/remote/retry_test.py index 7fa6dd74663..e9829a7338d 100644 --- a/conans/test/integration/remote/retry_test.py +++ b/conans/test/integration/remote/retry_test.py @@ -106,7 +106,7 @@ def put(self, *args, **kwargs): uploader.upload(url="fake", abs_path=self.filename, retry=2) output_lines = output.getvalue().splitlines() counter = Counter(output_lines) - self.assertEqual(counter["ERROR: any exception"], 2) + self.assertEqual(counter["WARN: network: any exception"], 2) self.assertEqual(counter["Waiting 0 seconds to retry..."], 2) def test_error_500(self): @@ -118,5 +118,5 @@ def test_error_500(self): uploader.upload(url="fake", abs_path=self.filename, retry=2) output_lines = output.getvalue().splitlines() counter = Counter(output_lines) - self.assertEqual(counter["ERROR: 500 Server Error: content"], 2) + self.assertEqual(counter["WARN: network: 500 Server Error: content"], 2) self.assertEqual(counter["Waiting 0 seconds to retry..."], 2)