Skip to content

Commit

Permalink
Feature checks: fall back to reporting insufficiently portable features
Browse files Browse the repository at this point in the history
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
  • Loading branch information
eli-schwartz committed Aug 29, 2024
1 parent 3c0de47 commit d723594
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 25 deletions.
2 changes: 2 additions & 0 deletions mesonbuild/interpreter/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
68 changes: 44 additions & 24 deletions mesonbuild/interpreterbase/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -585,15 +585,15 @@ 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 ''
return mesonlib.project_meson_versions[subproject]

@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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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}.',
Expand All @@ -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}.',
Expand All @@ -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',
Expand Down
6 changes: 5 additions & 1 deletion mesonbuild/utils/universal.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class _VerPickleLoadable(Protocol):
__all__ = [
'GIT',
'python_command',
'NoProjectVersion',
'project_meson_versions',
'SecondLevelHolder',
'File',
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit d723594

Please sign in to comment.