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.
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

@ -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 @@ class FeatureCheckBase(metaclass=abc.ABCMeta):
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 @@ class FeatureCheckBase(metaclass=abc.ABCMeta):
@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 @@ class FeatureCheckBase(metaclass=abc.ABCMeta):
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',

@ -57,6 +57,7 @@ _U = T.TypeVar('_U')
__all__ = [
'GIT',
'python_command',
'NoProjectVersion',
'project_meson_versions',
'SecondLevelHolder',
'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
# 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

Loading…
Cancel
Save