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
pull/13617/head
Eli Schwartz 1 year ago committed by Eli Schwartz
parent 1794a1e63c
commit 29797f92f8
No known key found for this signature in database
GPG Key ID: CEB167EFB5722BD6
  1. 29
      docs/markdown/snippets/always_report_deprecations.md
  2. 2
      mesonbuild/interpreter/interpreter.py
  3. 68
      mesonbuild/interpreterbase/decorators.py
  4. 6
      mesonbuild/utils/universal.py

@ -0,0 +1,29 @@
## Default to printing deprecations when no minimum version is specified.
For a long time, the [[project]] function has supported specifying the minimum
`meson_version:` needed by a project. When this is used, deprecated features
from before that version produce warnings, as do features which aren't
available in all supported versions.
When no minimum version was specified, meson didn't warn you even about
deprecated functionality that might go away in an upcoming semver major release
of meson.
Now, meson will treat an unspecified minimum version following semver:
- For new features introduced in the current meson semver major cycle
(currently: all features added since 1.0) a warning is printed. Features that
have been available since the initial 1.0 release are assumed to be widely
available.
- For features that have been deprecated by any version of meson, a warning is
printed. Since no minimum version was specified, it is assumed that the
project wishes to follow the latest and greatest functionality.
These warnings will overlap for functionality that was both deprecated and
replaced with an alternative in the current release cycle. The combination
means that projects without a minimum version specified are assumed to want
broad compatibility with the current release cycle (1.x).
Projects that specify a minimum `meson_version:` will continue to only receive
actionable warnings based on their current minimum version.

@ -1178,6 +1178,8 @@ class Interpreter(InterpreterBase, HoldableObject):
# for things like deprecation testing. # for things like deprecation testing.
if kwargs['meson_version']: if kwargs['meson_version']:
self.handle_meson_version(kwargs['meson_version'], node) 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 # 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 # it is being used with an old version. I have added check that if both

@ -3,7 +3,7 @@
from __future__ import annotations from __future__ import annotations
from .. import mesonlib, mlog from .. import coredata, mesonlib, mlog
from .disabler import Disabler from .disabler import Disabler
from .exceptions import InterpreterException, InvalidArguments from .exceptions import InterpreterException, InvalidArguments
from ._unholder import _unholder from ._unholder import _unholder
@ -585,7 +585,7 @@ class FeatureCheckBase(metaclass=abc.ABCMeta):
self.extra_message = extra_message self.extra_message = extra_message
@staticmethod @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 # Don't do any checks if project() has not been parsed yet
if subproject not in mesonlib.project_meson_versions: if subproject not in mesonlib.project_meson_versions:
return '' return ''
@ -593,7 +593,7 @@ class FeatureCheckBase(metaclass=abc.ABCMeta):
@staticmethod @staticmethod
@abc.abstractmethod @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 pass
def use(self, subproject: 'SubProject', location: T.Optional['mparser.BaseNode'] = None) -> None: def use(self, subproject: 'SubProject', location: T.Optional['mparser.BaseNode'] = None) -> None:
@ -642,15 +642,15 @@ class FeatureCheckBase(metaclass=abc.ABCMeta):
if '\n' in warning_str: if '\n' in warning_str:
mlog.warning(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') raise InterpreterException('log_usage_warning not implemented')
@staticmethod @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') raise InterpreterException('get_warning_str_prefix not implemented')
@staticmethod @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') raise InterpreterException('get_notice_str_prefix not implemented')
def __call__(self, f: TV_func) -> TV_func: def __call__(self, f: TV_func) -> TV_func:
@ -679,20 +679,32 @@ class FeatureNew(FeatureCheckBase):
feature_registry = {} feature_registry = {}
@staticmethod @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:
return mesonlib.version_compare_condition_with_min(target_version, feature_version) 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 @staticmethod
def get_warning_str_prefix(tv: str) -> str: def get_warning_str_prefix(tv: T.Union[str, mesonlib.NoProjectVersion]) -> str:
return f'Project specifies a minimum meson_version \'{tv}\' but uses features which were added in newer versions:' 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 @staticmethod
def get_notice_str_prefix(tv: str) -> str: def get_notice_str_prefix(tv: T.Union[str, mesonlib.NoProjectVersion]) -> str:
return '' 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 = [ args = [
'Project targets', f"'{tv}'", prefix,
'but uses feature introduced in', 'but uses feature introduced in',
f"'{self.feature_version}':", f"'{self.feature_version}':",
f'{self.feature_name}.', f'{self.feature_name}.',
@ -711,21 +723,29 @@ class FeatureDeprecated(FeatureCheckBase):
emit_notice = True emit_notice = True
@staticmethod @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:
# For deprecation checks we need to return the inverse of FeatureNew checks if isinstance(target_version, str):
return not mesonlib.version_compare_condition_with_min(target_version, feature_version) # 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 @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:' return 'Deprecated features used:'
@staticmethod @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:' 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 = [ args = [
'Project targets', f"'{tv}'", prefix,
'but uses feature deprecated since', 'but uses feature deprecated since',
f"'{self.feature_version}':", f"'{self.feature_version}':",
f'{self.feature_name}.', f'{self.feature_name}.',
@ -745,19 +765,19 @@ class FeatureBroken(FeatureCheckBase):
unconditional = True unconditional = True
@staticmethod @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 # always warn for broken stuff
return False return False
@staticmethod @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:' return 'Broken features used:'
@staticmethod @staticmethod
def get_notice_str_prefix(tv: str) -> str: def get_notice_str_prefix(tv: T.Union[str, mesonlib.NoProjectVersion]) -> str:
return '' 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 = [ args = [
'Project uses feature that was always broken,', 'Project uses feature that was always broken,',
'and is now deprecated since', 'and is now deprecated since',

@ -57,6 +57,7 @@ _U = T.TypeVar('_U')
__all__ = [ __all__ = [
'GIT', 'GIT',
'python_command', 'python_command',
'NoProjectVersion',
'project_meson_versions', 'project_meson_versions',
'SecondLevelHolder', 'SecondLevelHolder',
'File', 'File',
@ -157,10 +158,13 @@ __all__ = [
] ]
class NoProjectVersion:
pass
# TODO: this is such a hack, this really should be either in coredata or in the # TODO: this is such a hack, this really should be either in coredata or in the
# interpreter # interpreter
# {subproject: project_meson_version} # {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 from glob import glob

Loading…
Cancel
Save