From d7235948c22df64166db50f0b143022c18194444 Mon Sep 17 00:00:00 2001 From: Eli Schwartz Date: Thu, 15 Jun 2023 16:27:18 -0400 Subject: [PATCH] Feature checks: fall back to reporting insufficiently portable features When projects do not specify a minimum meson version, we used to avoid giving them the benefit of the Feature checks framework. Instead: - warn for features that were added after the most recent semver bump, since they aren't portable to the range of versions people might use these days - warn for features that were deprecated before the upcoming semver bump, i.e. all deprecated features, since they aren't portable to upcoming semver-compatible versions people might be imminently upgrading to --- mesonbuild/interpreter/interpreter.py | 2 + mesonbuild/interpreterbase/decorators.py | 68 +++++++++++++++--------- mesonbuild/utils/universal.py | 6 ++- 3 files changed, 51 insertions(+), 25 deletions(-) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 92315ff0fd31..7bcbf8c6de02 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -1178,6 +1178,8 @@ def func_project(self, node: mparser.FunctionNode, args: T.Tuple[str, T.List[str # for things like deprecation testing. if kwargs['meson_version']: self.handle_meson_version(kwargs['meson_version'], node) + else: + mesonlib.project_meson_versions[self.subproject] = mesonlib.NoProjectVersion() # Load "meson.options" before "meson_options.txt", and produce a warning if # it is being used with an old version. I have added check that if both diff --git a/mesonbuild/interpreterbase/decorators.py b/mesonbuild/interpreterbase/decorators.py index 6524aa92dd4f..fe6f6a53bd1b 100644 --- a/mesonbuild/interpreterbase/decorators.py +++ b/mesonbuild/interpreterbase/decorators.py @@ -3,7 +3,7 @@ from __future__ import annotations -from .. import mesonlib, mlog +from .. import coredata, mesonlib, mlog from .disabler import Disabler from .exceptions import InterpreterException, InvalidArguments from ._unholder import _unholder @@ -585,7 +585,7 @@ def __init__(self, feature_name: str, feature_version: str, extra_message: str = self.extra_message = extra_message @staticmethod - def get_target_version(subproject: str) -> str: + def get_target_version(subproject: str) -> T.Union[str, mesonlib.NoProjectVersion]: # Don't do any checks if project() has not been parsed yet if subproject not in mesonlib.project_meson_versions: return '' @@ -593,7 +593,7 @@ def get_target_version(subproject: str) -> str: @staticmethod @abc.abstractmethod - def check_version(target_version: str, feature_version: str) -> bool: + def check_version(target_version: T.Union[str, mesonlib.NoProjectVersion], feature_version: str) -> bool: pass def use(self, subproject: 'SubProject', location: T.Optional['mparser.BaseNode'] = None) -> None: @@ -642,15 +642,15 @@ def report(cls, subproject: str) -> None: if '\n' in warning_str: mlog.warning(warning_str) - def log_usage_warning(self, tv: str, location: T.Optional['mparser.BaseNode']) -> None: + def log_usage_warning(self, tv: T.Union[str, mesonlib.NoProjectVersion], location: T.Optional['mparser.BaseNode']) -> None: raise InterpreterException('log_usage_warning not implemented') @staticmethod - def get_warning_str_prefix(tv: str) -> str: + def get_warning_str_prefix(tv: T.Union[str, mesonlib.NoProjectVersion]) -> str: raise InterpreterException('get_warning_str_prefix not implemented') @staticmethod - def get_notice_str_prefix(tv: str) -> str: + def get_notice_str_prefix(tv: T.Union[str, mesonlib.NoProjectVersion]) -> str: raise InterpreterException('get_notice_str_prefix not implemented') def __call__(self, f: TV_func) -> TV_func: @@ -679,20 +679,32 @@ class FeatureNew(FeatureCheckBase): feature_registry = {} @staticmethod - def check_version(target_version: str, feature_version: str) -> bool: - return mesonlib.version_compare_condition_with_min(target_version, feature_version) + def check_version(target_version: T.Union[str, mesonlib.NoProjectVersion], feature_version: str) -> bool: + if isinstance(target_version, str): + return mesonlib.version_compare_condition_with_min(target_version, feature_version) + else: + # Warn for anything newer than the current semver base slot. + major = coredata.version.split('.', maxsplit=1)[0] + return mesonlib.version_compare(feature_version, f'<{major}.0') @staticmethod - def get_warning_str_prefix(tv: str) -> str: - return f'Project specifies a minimum meson_version \'{tv}\' but uses features which were added in newer versions:' + def get_warning_str_prefix(tv: T.Union[str, mesonlib.NoProjectVersion]) -> str: + if isinstance(tv, str): + return f'Project specifies a minimum meson_version \'{tv}\' but uses features which were added in newer versions:' + else: + return 'Project specifies no minimum version but uses features which were added in versions:' @staticmethod - def get_notice_str_prefix(tv: str) -> str: + def get_notice_str_prefix(tv: T.Union[str, mesonlib.NoProjectVersion]) -> str: return '' - def log_usage_warning(self, tv: str, location: T.Optional['mparser.BaseNode']) -> None: + def log_usage_warning(self, tv: T.Union[str, mesonlib.NoProjectVersion], location: T.Optional['mparser.BaseNode']) -> None: + if isinstance(tv, str): + prefix = f'Project targets {tv!r}' + else: + prefix = 'Project does not target a minimum version' args = [ - 'Project targets', f"'{tv}'", + prefix, 'but uses feature introduced in', f"'{self.feature_version}':", f'{self.feature_name}.', @@ -711,21 +723,29 @@ class FeatureDeprecated(FeatureCheckBase): emit_notice = True @staticmethod - def check_version(target_version: str, feature_version: str) -> bool: - # For deprecation checks we need to return the inverse of FeatureNew checks - return not mesonlib.version_compare_condition_with_min(target_version, feature_version) + def check_version(target_version: T.Union[str, mesonlib.NoProjectVersion], feature_version: str) -> bool: + if isinstance(target_version, str): + # For deprecation checks we need to return the inverse of FeatureNew checks + return not mesonlib.version_compare_condition_with_min(target_version, feature_version) + else: + # Always warn for functionality deprecated in the current semver slot (i.e. the current version). + return False @staticmethod - def get_warning_str_prefix(tv: str) -> str: + def get_warning_str_prefix(tv: T.Union[str, mesonlib.NoProjectVersion]) -> str: return 'Deprecated features used:' @staticmethod - def get_notice_str_prefix(tv: str) -> str: + def get_notice_str_prefix(tv: T.Union[str, mesonlib.NoProjectVersion]) -> str: return 'Future-deprecated features used:' - def log_usage_warning(self, tv: str, location: T.Optional['mparser.BaseNode']) -> None: + def log_usage_warning(self, tv: T.Union[str, mesonlib.NoProjectVersion], location: T.Optional['mparser.BaseNode']) -> None: + if isinstance(tv, str): + prefix = f'Project targets {tv!r}' + else: + prefix = 'Project does not target a minimum version' args = [ - 'Project targets', f"'{tv}'", + prefix, 'but uses feature deprecated since', f"'{self.feature_version}':", f'{self.feature_name}.', @@ -745,19 +765,19 @@ class FeatureBroken(FeatureCheckBase): unconditional = True @staticmethod - def check_version(target_version: str, feature_version: str) -> bool: + def check_version(target_version: T.Union[str, mesonlib.NoProjectVersion], feature_version: str) -> bool: # always warn for broken stuff return False @staticmethod - def get_warning_str_prefix(tv: str) -> str: + def get_warning_str_prefix(tv: T.Union[str, mesonlib.NoProjectVersion]) -> str: return 'Broken features used:' @staticmethod - def get_notice_str_prefix(tv: str) -> str: + def get_notice_str_prefix(tv: T.Union[str, mesonlib.NoProjectVersion]) -> str: return '' - def log_usage_warning(self, tv: str, location: T.Optional['mparser.BaseNode']) -> None: + def log_usage_warning(self, tv: T.Union[str, mesonlib.NoProjectVersion], location: T.Optional['mparser.BaseNode']) -> None: args = [ 'Project uses feature that was always broken,', 'and is now deprecated since', diff --git a/mesonbuild/utils/universal.py b/mesonbuild/utils/universal.py index 574bcb51f654..88d8e1f891c7 100644 --- a/mesonbuild/utils/universal.py +++ b/mesonbuild/utils/universal.py @@ -57,6 +57,7 @@ class _VerPickleLoadable(Protocol): __all__ = [ 'GIT', 'python_command', + 'NoProjectVersion', 'project_meson_versions', 'SecondLevelHolder', 'File', @@ -157,10 +158,13 @@ class _VerPickleLoadable(Protocol): ] +class NoProjectVersion: + pass + # TODO: this is such a hack, this really should be either in coredata or in the # interpreter # {subproject: project_meson_version} -project_meson_versions: T.DefaultDict[str, str] = collections.defaultdict(str) +project_meson_versions: T.Dict[str, T.Union[str, NoProjectVersion]] = {} from glob import glob