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

Add core:warnings_as_errors configuration option #15149

Merged
merged 9 commits into from
Dec 18, 2023
26 changes: 21 additions & 5 deletions conan/api/output.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
"""
Expand Down Expand Up @@ -204,16 +209,27 @@ 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)
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 != "exception":
raise ConanException(msg)
else:
self._write_message("ERROR: {}".format(msg), Color.RED)
return self

def flush(self):
Expand Down
17 changes: 9 additions & 8 deletions conan/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,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="exception")
# layers
for folder in os.listdir(custom_commands_path):
layer_folder = os.path.join(custom_commands_path, folder)
Expand All @@ -74,7 +74,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="exception")

def _add_command(self, import_path, method_name, package=None):
try:
Expand Down Expand Up @@ -205,20 +206,20 @@ def exception_exit_error(exception):
if exception is None:
return SUCCESS
if isinstance(exception, ConanInvalidConfiguration):
output.error(exception)
output.error(exception, error_type="exception")
return ERROR_INVALID_CONFIGURATION
if isinstance(exception, ConanException):
output.error(exception)
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)
output.error("Exiting with code: %d" % exception.code, error_type="exception")
return exception.code

assert isinstance(exception, Exception)
output.error(traceback.format_exc())
output.error(traceback.format_exc(), error_type="exception")
msg = exception_message_safe(exception)
output.error(msg)
output.error(msg, error_type="exception")
return ERROR_UNEXPECTED


Expand Down
5 changes: 3 additions & 2 deletions conan/internal/api/detect_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,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"
Expand Down Expand Up @@ -269,8 +270,8 @@ def detect_compiler():
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
Expand Down
2 changes: 1 addition & 1 deletion conan/internal/cache/db/cache_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class CacheDatabase:

def __init__(self, filename):
version = sqlite3.sqlite_version
if Version(version) < "3.7.11": # Not an exception, in case some false positives
if Version(version) < "3.7.11":
ConanOutput().error(f"Your sqlite3 '{version} < 3.7.11' version is not supported")
self._recipes = RecipesDBTable(filename)
self._packages = PackagesDBTable(filename)
Expand Down
10 changes: 8 additions & 2 deletions conan/internal/conan_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,14 @@ 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)

# Wraps an http_requester to inject proxies, certs, etc
ConanOutput.define_silence_warnings(global_conf.get("core:skip_warnings", check_type=list))
self.requester = ConanRequester(global_conf, cache_folder)
# To handle remote connections
rest_client_factory = RestApiClientFactory(self.requester, global_conf)
Expand All @@ -61,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))
2 changes: 1 addition & 1 deletion conan/internal/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,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="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)
Expand Down
12 changes: 6 additions & 6 deletions conan/internal/integrity_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -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="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})")
output.error(f" '{fname}' (manifest: {h1}, file: {h2})", error_type="exception")
return True
output.info(f"{ref}: Integrity checked: ok")

Expand All @@ -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="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})")
output.error(f" '{fname}' (manifest: {h1}, file: {h2})", error_type="exception")
return True
output.info(f"{ref}: Integrity checked: ok")
4 changes: 2 additions & 2 deletions conan/tools/files/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -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="exception")
else: # duplicated for, to avoid a platform check for each zipped file
for file_ in zip_info:
extracted_size += file_.file_size
Expand All @@ -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="exception")
output.writeln("")


Expand Down
2 changes: 1 addition & 1 deletion conans/client/cmd/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -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="exception")
scoped_output.warning(str(e))
set_dirty(source_folder)

Expand Down
2 changes: 1 addition & 1 deletion conans/client/downloaders/file_downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion conans/client/generators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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="exception")
raise ConanException(f"Error in generator '{generator_name}': {str(e)}") from e
finally:
# restore the generators attribute, so it can raise
Expand Down
4 changes: 4 additions & 0 deletions conans/client/graph/build_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,7 @@ def should_build_missing(self, conanfile):
for pattern in self.build_missing_patterns:
if ref_matches(conanfile.ref, pattern, is_consumer=False):
return True

def report_matches(self):
for pattern in self._unused_patterns:
ConanOutput().error(f"No package matching '{pattern}' pattern found.", error_type="context")
2 changes: 1 addition & 1 deletion conans/client/graph/install_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -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="exception")
out.writeln("")

# Report details just the first one
Expand Down
2 changes: 1 addition & 1 deletion conans/client/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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="exception")
conanfile.output.warning("Build folder %s" % conanfile.build_folder)
if isinstance(exc, ConanException):
raise exc
Expand Down
3 changes: 2 additions & 1 deletion conans/client/migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,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="exception")
raise
else: # generate the back-migration script
undo_lru = textwrap.dedent("""\
Expand Down
12 changes: 6 additions & 6 deletions conans/client/remote_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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="exception")
self._cache.remove_recipe_layout(layout)
raise
export_folder = layout.export()
Expand Down Expand Up @@ -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="exception")
raise

def get_recipe_sources(self, ref, layout, remote):
Expand Down Expand Up @@ -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="exception")
output.error(f"Exception: {type(e)} {str(e)}", error_type="exception")
raise

def _get_package(self, layout, pref, remote, scoped_output, metadata):
Expand Down Expand Up @@ -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="exception")
scoped_output.error(f"Exception: {type(e)} {str(e)}", error_type="exception")
raise

def search_recipes(self, remote, pattern):
Expand Down
6 changes: 3 additions & 3 deletions conans/client/rest/auth_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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="exception")
else:
out.error('Wrong password for user "%s"' % user)
out.error(f'Wrong password for user "{user}"', error_type="exception")
out.info('You can change username with "conan remote login <remote> <username>"')
else:
return self.call_rest_api_method(remote, method_name, *args, **kwargs)
Expand All @@ -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="exception")
out.debug(str(e) + '\n')

@staticmethod
Expand Down
2 changes: 1 addition & 1 deletion conans/client/rest/file_uploader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion conans/client/rest/rest_client_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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="exception")
failed.append(filename)

if failed:
Expand Down
2 changes: 1 addition & 1 deletion conans/client/userio.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
memsharded marked this conversation as resolved.
Show resolved Hide resolved
self._out.error(f"{s} is not a valid answer")
return ret
5 changes: 3 additions & 2 deletions conans/migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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="exception")
raise ConanMigrationError(e)

def _update_version_file(self):
Expand Down Expand Up @@ -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="exception")
os.remove(migration)
1 change: 1 addition & 0 deletions conans/model/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
Expand Down
Loading