Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mesonbuild/interpreter/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ def process_new_values(self, invalues: T.List[T.Union[TYPE_var, ExecutableSerial
def handle_meson_version(self, pv: str, location: mparser.BaseNode) -> None:
if not mesonlib.version_compare(coredata.stable_version, pv):
raise InterpreterException.from_node(f'Meson version is {coredata.version} but project requires {pv}', node=location)
mesonlib.project_meson_versions[self.subproject] = pv
mesonlib.project_meson_versions[self.subproject] = mesonlib.version_check_to_range([pv])

def handle_meson_version_from_ast(self) -> None:
if not self.ast.lines:
Expand Down
15 changes: 7 additions & 8 deletions mesonbuild/interpreter/primitives/string.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import typing as T

from ... import mlog
from ...mesonlib import version_compare_many, underscorify
from ...mesonlib import version_check_to_range, version_compare_many, underscorify
from ...interpreterbase import (
InterpreterObject,
MesonOperator,
Expand Down Expand Up @@ -204,20 +204,19 @@ class MesonVersionStringHolder(StringHolder):
@InterpreterObject.method('version_compare')
@typed_pos_args('str.version_compare', varargs=str, min_varargs=1)
def version_compare_method(self, args: T.Tuple[T.List[str]], kwargs: TYPE_kwargs) -> bool:
unsupported = []
unsupported = False
for constraint in args[0]:
if not constraint.strip().startswith('>'):
unsupported.append('non-upper-bounds (> or >=) constraints')
if constraint.strip().startswith('!'):
unsupported = True
if len(args[0]) > 1:
FeatureNew.single_use('meson.version().version_compare() with multiple arguments', '1.10.0',
self.subproject, 'From 1.8.0 - 1.9.* it failed to match str.version_compare',
location=self.current_node)
unsupported.append('multiple arguments')
else:
self.interpreter.tmp_meson_version = args[0][0]
if unsupported:
mlog.debug('meson.version().version_compare() with', ' or '.join(unsupported),
mlog.debug('meson.version().version_compare() with != constraints',
'does not support overriding minimum meson_version checks.')
else:
self.interpreter.tmp_meson_version = version_check_to_range(args[0])

return version_compare_many(self.held_object, args[0])[0]

Expand Down
54 changes: 28 additions & 26 deletions mesonbuild/interpreterbase/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,8 @@ def emit_feature_change(values: T.Dict[_T, T.Union[str, T.Tuple[str, str]]], fea
return inner


MesonVersionTarget = T.Optional[T.Union[mesonlib.Range[mesonlib.Version], mesonlib.NoProjectVersion]]

# This cannot be a dataclass due to https://github.com/python/mypy/issues/5374
class FeatureCheckBase(metaclass=abc.ABCMeta):
"Base class for feature version checks"
Expand All @@ -617,21 +619,21 @@ def __init__(self, feature_name: str, feature_version: str, extra_message: str =
self.extra_message = extra_message

@staticmethod
def get_target_version(subproject: str) -> T.Union[str, mesonlib.NoProjectVersion]:
def get_target_version(subproject: str) -> MesonVersionTarget:
# Don't do any checks if project() has not been parsed yet
if subproject not in mesonlib.project_meson_versions:
return ''
return None
return mesonlib.project_meson_versions[subproject]

@staticmethod
@abc.abstractmethod
def check_version(target_version: T.Union[str, mesonlib.NoProjectVersion], feature_version: str) -> bool:
def check_version(target_version: MesonVersionTarget, feature_version: str) -> bool:
pass

def use(self, subproject: 'SubProject', location: T.Optional['mparser.BaseNode'] = None) -> None:
tv = self.get_target_version(subproject)
# No target version
if tv == '' and not self.unconditional:
if tv is None and not self.unconditional:
return
# Target version is new enough, don't warn
if self.check_version(tv, self.feature_version) and not self.emit_notice:
Expand Down Expand Up @@ -674,15 +676,15 @@ def report(cls, subproject: str) -> None:
if '\n' in warning_str:
mlog.warning(warning_str)

def log_usage_warning(self, tv: T.Union[str, mesonlib.NoProjectVersion], location: T.Optional['mparser.BaseNode']) -> None:
def log_usage_warning(self, tv: MesonVersionTarget, location: T.Optional['mparser.BaseNode']) -> None:
raise InterpreterException('log_usage_warning not implemented')

@staticmethod
def get_warning_str_prefix(tv: T.Union[str, mesonlib.NoProjectVersion]) -> str:
def get_warning_str_prefix(tv: MesonVersionTarget) -> str:
raise InterpreterException('get_warning_str_prefix not implemented')

@staticmethod
def get_notice_str_prefix(tv: T.Union[str, mesonlib.NoProjectVersion]) -> str:
def get_notice_str_prefix(tv: MesonVersionTarget) -> str:
raise InterpreterException('get_notice_str_prefix not implemented')

def __call__(self, f: TV_func) -> TV_func:
Expand Down Expand Up @@ -711,28 +713,28 @@ class FeatureNew(FeatureCheckBase):
feature_registry = {}

@staticmethod
def check_version(target_version: T.Union[str, mesonlib.NoProjectVersion], feature_version: str) -> bool:
if isinstance(target_version, str):
def check_version(target_version: MesonVersionTarget, feature_version: str) -> bool:
if isinstance(target_version, mesonlib.Range):
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: T.Union[str, mesonlib.NoProjectVersion]) -> str:
if isinstance(tv, str):
def get_warning_str_prefix(tv: MesonVersionTarget) -> str:
if isinstance(tv, mesonlib.Range):
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: T.Union[str, mesonlib.NoProjectVersion]) -> str:
def get_notice_str_prefix(tv: MesonVersionTarget) -> str:
return ''

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}'
def log_usage_warning(self, tv: MesonVersionTarget, location: T.Optional['mparser.BaseNode']) -> None:
if isinstance(tv, mesonlib.Range):
prefix = f"Project targets '{tv}'"
else:
prefix = 'Project does not target a minimum version'
args = [
Expand All @@ -755,25 +757,25 @@ class FeatureDeprecated(FeatureCheckBase):
emit_notice = True

@staticmethod
def check_version(target_version: T.Union[str, mesonlib.NoProjectVersion], feature_version: str) -> bool:
if isinstance(target_version, str):
def check_version(target_version: MesonVersionTarget, feature_version: str) -> bool:
if isinstance(target_version, mesonlib.Range):
# 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: T.Union[str, mesonlib.NoProjectVersion]) -> str:
def get_warning_str_prefix(tv: MesonVersionTarget) -> str:
return 'Deprecated features used:'

@staticmethod
def get_notice_str_prefix(tv: T.Union[str, mesonlib.NoProjectVersion]) -> str:
def get_notice_str_prefix(tv: MesonVersionTarget) -> str:
return 'Future-deprecated features used:'

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}'
def log_usage_warning(self, tv: MesonVersionTarget, location: T.Optional['mparser.BaseNode']) -> None:
if isinstance(tv, mesonlib.Range):
prefix = f"Project targets '{tv}'"
else:
prefix = 'Project does not target a minimum version'
args = [
Expand All @@ -797,19 +799,19 @@ class FeatureBroken(FeatureCheckBase):
unconditional = True

@staticmethod
def check_version(target_version: T.Union[str, mesonlib.NoProjectVersion], feature_version: str) -> bool:
def check_version(target_version: MesonVersionTarget, feature_version: str) -> bool:
# always warn for broken stuff
return False

@staticmethod
def get_warning_str_prefix(tv: T.Union[str, mesonlib.NoProjectVersion]) -> str:
def get_warning_str_prefix(tv: MesonVersionTarget) -> str:
return 'Broken features used:'

@staticmethod
def get_notice_str_prefix(tv: T.Union[str, mesonlib.NoProjectVersion]) -> str:
def get_notice_str_prefix(tv: MesonVersionTarget) -> str:
return ''

def log_usage_warning(self, tv: T.Union[str, mesonlib.NoProjectVersion], location: T.Optional['mparser.BaseNode']) -> None:
def log_usage_warning(self, tv: MesonVersionTarget, location: T.Optional['mparser.BaseNode']) -> None:
args = [
'Project uses feature that was always broken,',
'and is now deprecated since',
Expand Down
22 changes: 13 additions & 9 deletions mesonbuild/interpreterbase/interpreterbase.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def __init__(self, source_root: str, subdir: str, subproject: SubProject, subpro
# meson.version().compare_version(version_string)
# If it was part of a if-clause, it is used to temporally override the
# current meson version target within that if-block.
self.tmp_meson_version: T.Optional[str] = None
self.tmp_meson_version: T.Optional[mesonlib.Range[mesonlib.Version]] = None

def handle_meson_version_from_ast(self, strict: bool = True) -> None:
# do nothing in an AST interpreter
Expand Down Expand Up @@ -310,15 +310,19 @@ def evaluate_if(self, node: mparser.IfClauseNode) -> T.Optional[Disabler]:
res = result.operator_call(MesonOperator.BOOL, None)
if not isinstance(res, bool):
raise InvalidCode(f'If clause {result!r} does not evaluate to true or false.')
if res:
prev_meson_version = mesonlib.project_meson_versions[self.subproject]
if self.tmp_meson_version:
mesonlib.project_meson_versions[self.subproject] = self.tmp_meson_version
try:
prev_meson_version = mesonlib.project_meson_versions[self.subproject]
if self.tmp_meson_version and isinstance(prev_meson_version, mesonlib.Range):
always = prev_meson_version.always(self.tmp_meson_version)
if always is not None:
mlog.warning(f"Conditional on version {self.tmp_meson_version} always evaluates to {str(always).lower()}",
location=self.current_node)
mesonlib.project_meson_versions[self.subproject] = prev_meson_version.intersect(self.tmp_meson_version)
try:
if res:
self.evaluate_codeblock(i.block)
finally:
mesonlib.project_meson_versions[self.subproject] = prev_meson_version
return None
return None
finally:
mesonlib.project_meson_versions[self.subproject] = prev_meson_version
if not isinstance(node.elseblock, mparser.EmptyNode):
self.evaluate_codeblock(node.elseblock.block)
return None
Expand Down
Loading
Loading