# Copyright 2013-2021 The Meson development team
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from . . import mesonlib , mlog
from . disabler import Disabler
from . exceptions import InterpreterException , InvalidArguments
from . _unholder import _unholder
from dataclasses import dataclass
from functools import wraps
import abc
import itertools
import copy
import typing as T
if T . TYPE_CHECKING :
from typing_extensions import Protocol
from . . import mparser
from . baseobjects import InterpreterObject , SubProject , TV_func , TYPE_var , TYPE_kwargs
from . operator import MesonOperator
_TV_IntegerObject = T . TypeVar ( ' _TV_IntegerObject ' , bound = InterpreterObject , contravariant = True )
_TV_ARG1 = T . TypeVar ( ' _TV_ARG1 ' , bound = TYPE_var , contravariant = True )
class FN_Operator ( Protocol [ _TV_IntegerObject , _TV_ARG1 ] ) :
def __call__ ( s , self : _TV_IntegerObject , other : _TV_ARG1 ) - > TYPE_var : . . .
_TV_FN_Operator = T . TypeVar ( ' _TV_FN_Operator ' , bound = FN_Operator )
def get_callee_args ( wrapped_args : T . Sequence [ T . Any ] ) - > T . Tuple [ ' mparser.BaseNode ' , T . List [ ' TYPE_var ' ] , ' TYPE_kwargs ' , ' SubProject ' ] :
# First argument could be InterpreterBase, InterpreterObject or ModuleObject.
# In the case of a ModuleObject it is the 2nd argument (ModuleState) that
# contains the needed information.
s = wrapped_args [ 0 ]
if not hasattr ( s , ' current_node ' ) :
s = wrapped_args [ 1 ]
node = s . current_node
subproject = s . subproject
args = kwargs = None
if len ( wrapped_args ) > = 3 :
args = wrapped_args [ - 2 ]
kwargs = wrapped_args [ - 1 ]
return node , args , kwargs , subproject
def noPosargs ( f : TV_func ) - > TV_func :
@wraps ( f )
def wrapped ( * wrapped_args : T . Any , * * wrapped_kwargs : T . Any ) - > T . Any :
args = get_callee_args ( wrapped_args ) [ 1 ]
if args :
raise InvalidArguments ( ' Function does not take positional arguments. ' )
return f ( * wrapped_args , * * wrapped_kwargs )
return T . cast ( ' TV_func ' , wrapped )
def noKwargs ( f : TV_func ) - > TV_func :
@wraps ( f )
def wrapped ( * wrapped_args : T . Any , * * wrapped_kwargs : T . Any ) - > T . Any :
kwargs = get_callee_args ( wrapped_args ) [ 2 ]
if kwargs :
raise InvalidArguments ( ' Function does not take keyword arguments. ' )
return f ( * wrapped_args , * * wrapped_kwargs )
return T . cast ( ' TV_func ' , wrapped )
def stringArgs ( f : TV_func ) - > TV_func :
@wraps ( f )
def wrapped ( * wrapped_args : T . Any , * * wrapped_kwargs : T . Any ) - > T . Any :
args = get_callee_args ( wrapped_args ) [ 1 ]
if not isinstance ( args , list ) :
mlog . debug ( ' Not a list: ' , str ( args ) )
raise InvalidArguments ( ' Argument not a list. ' )
if not all ( isinstance ( s , str ) for s in args ) :
mlog . debug ( ' Element not a string: ' , str ( args ) )
raise InvalidArguments ( ' Arguments must be strings. ' )
return f ( * wrapped_args , * * wrapped_kwargs )
return T . cast ( ' TV_func ' , wrapped )
def noArgsFlattening ( f : TV_func ) - > TV_func :
setattr ( f , ' no-args-flattening ' , True ) # noqa: B010
return f
def noSecondLevelHolderResolving ( f : TV_func ) - > TV_func :
setattr ( f , ' no-second-level-holder-flattening ' , True ) # noqa: B010
return f
def unholder_return ( f : TV_func ) - > T . Callable [ . . . , TYPE_var ] :
@wraps ( f )
def wrapped ( * wrapped_args : T . Any , * * wrapped_kwargs : T . Any ) - > T . Any :
res = f ( * wrapped_args , * * wrapped_kwargs )
return _unholder ( res )
return T . cast ( ' T.Callable[..., TYPE_var] ' , wrapped )
def disablerIfNotFound ( f : TV_func ) - > TV_func :
@wraps ( f )
def wrapped ( * wrapped_args : T . Any , * * wrapped_kwargs : T . Any ) - > T . Any :
kwargs = get_callee_args ( wrapped_args ) [ 2 ]
disabler = kwargs . pop ( ' disabler ' , False )
ret = f ( * wrapped_args , * * wrapped_kwargs )
if disabler and not ret . found ( ) :
return Disabler ( )
return ret
return T . cast ( ' TV_func ' , wrapped )
@dataclass ( repr = False , eq = False )
class permittedKwargs :
permitted : T . Set [ str ]
def __call__ ( self , f : TV_func ) - > TV_func :
@wraps ( f )
def wrapped ( * wrapped_args : T . Any , * * wrapped_kwargs : T . Any ) - > T . Any :
kwargs = get_callee_args ( wrapped_args ) [ 2 ]
unknowns = set ( kwargs ) . difference ( self . permitted )
if unknowns :
ustr = ' , ' . join ( [ f ' " { u } " ' for u in sorted ( unknowns ) ] )
raise InvalidArguments ( f ' Got unknown keyword arguments { ustr } ' )
return f ( * wrapped_args , * * wrapped_kwargs )
return T . cast ( ' TV_func ' , wrapped )
def typed_operator ( operator : MesonOperator ,
types : T . Union [ T . Type , T . Tuple [ T . Type , . . . ] ] ) - > T . Callable [ [ ' _TV_FN_Operator ' ] , ' _TV_FN_Operator ' ] :
""" Decorator that does type checking for operator calls.
The principle here is similar to typed_pos_args , however much simpler
since only one other object ever is passed
"""
def inner ( f : ' _TV_FN_Operator ' ) - > ' _TV_FN_Operator ' :
@wraps ( f )
def wrapper ( self : ' InterpreterObject ' , other : TYPE_var ) - > TYPE_var :
if not isinstance ( other , types ) :
raise InvalidArguments ( f ' The ` { operator . value } ` of { self . display_name ( ) } does not accept objects of type { type ( other ) . __name__ } ( { other } ) ' )
return f ( self , other )
return T . cast ( ' _TV_FN_Operator ' , wrapper )
return inner
def unary_operator ( operator : MesonOperator ) - > T . Callable [ [ ' _TV_FN_Operator ' ] , ' _TV_FN_Operator ' ] :
""" Decorator that does type checking for unary operator calls.
This decorator is for unary operators that do not take any other objects .
It should be impossible for a user to accidentally break this . Triggering
this check always indicates a bug in the Meson interpreter .
"""
def inner ( f : ' _TV_FN_Operator ' ) - > ' _TV_FN_Operator ' :
@wraps ( f )
def wrapper ( self : ' InterpreterObject ' , other : TYPE_var ) - > TYPE_var :
if other is not None :
raise mesonlib . MesonBugException ( f ' The unary operator ` { operator . value } ` of { self . display_name ( ) } was passed the object { other } of type { type ( other ) . __name__ } ' )
return f ( self , other )
return T . cast ( ' _TV_FN_Operator ' , wrapper )
return inner
def typed_pos_args ( name : str , * types : T . Union [ T . Type , T . Tuple [ T . Type , . . . ] ] ,
varargs : T . Optional [ T . Union [ T . Type , T . Tuple [ T . Type , . . . ] ] ] = None ,
optargs : T . Optional [ T . List [ T . Union [ T . Type , T . Tuple [ T . Type , . . . ] ] ] ] = None ,
min_varargs : int = 0 , max_varargs : int = 0 ) - > T . Callable [ . . . , T . Any ] :
""" Decorator that types type checking of positional arguments.
This supports two different models of optional arguments , the first is the
variadic argument model . Variadic arguments are a possibly bounded ,
possibly unbounded number of arguments of the same type ( unions are
supported ) . The second is the standard default value model , in this case
a number of optional arguments may be provided , but they are still
ordered , and they may have different types .
This function does not support mixing variadic and default arguments .
: name : The name of the decorated function ( as displayed in error messages )
: varargs : They type ( s ) of any variadic arguments the function takes . If
None the function takes no variadic args
: min_varargs : the minimum number of variadic arguments taken
: max_varargs : the maximum number of variadic arguments taken . 0 means unlimited
: optargs : The types of any optional arguments parameters taken . If None
then no optional parameters are taken .
Some examples of usage blow :
>> > @typed_pos_args ( ' mod.func ' , str , ( str , int ) )
. . . def func ( self , state : ModuleState , args : T . Tuple [ str , T . Union [ str , int ] ] , kwargs : T . Dict [ str , T . Any ] ) - > T . Any :
. . . pass
>> > @typed_pos_args ( ' method ' , str , varargs = str )
. . . def method ( self , node : BaseNode , args : T . Tuple [ str , T . List [ str ] ] , kwargs : T . Dict [ str , T . Any ] ) - > T . Any :
. . . pass
>> > @typed_pos_args ( ' method ' , varargs = str , min_varargs = 1 )
. . . def method ( self , node : BaseNode , args : T . Tuple [ T . List [ str ] ] , kwargs : T . Dict [ str , T . Any ] ) - > T . Any :
. . . pass
>> > @typed_pos_args ( ' method ' , str , optargs = [ ( str , int ) , str ] )
. . . def method ( self , node : BaseNode , args : T . Tuple [ str , T . Optional [ T . Union [ str , int ] ] , T . Optional [ str ] ] , kwargs : T . Dict [ str , T . Any ] ) - > T . Any :
. . . pass
When should you chose ` typed_pos_args ( ' name ' , varargs = str ,
min_varargs = 1 ) ` vs ` typed_pos_args ( ' name ' , str , varargs = str ) ` ?
The answer has to do with the semantics of the function , if all of the
inputs are the same type ( such as with ` files ( ) ` ) then the former is
correct , all of the arguments are string names of files . If the first
argument is something else the it should be separated .
"""
def inner ( f : TV_func ) - > TV_func :
@wraps ( f )
def wrapper ( * wrapped_args : T . Any , * * wrapped_kwargs : T . Any ) - > T . Any :
args = get_callee_args ( wrapped_args ) [ 1 ]
# These are implementation programming errors, end users should never see them.
assert isinstance ( args , list ) , args
assert max_varargs > = 0 , ' max_varags cannot be negative '
assert min_varargs > = 0 , ' min_varags cannot be negative '
assert optargs is None or varargs is None , \
' varargs and optargs not supported together as this would be ambiguous '
num_args = len ( args )
num_types = len ( types )
a_types = types
if varargs :
min_args = num_types + min_varargs
max_args = num_types + max_varargs
if max_varargs == 0 and num_args < min_args :
raise InvalidArguments ( f ' { name } takes at least { min_args } arguments, but got { num_args } . ' )
elif max_varargs != 0 and ( num_args < min_args or num_args > max_args ) :
raise InvalidArguments ( f ' { name } takes between { min_args } and { max_args } arguments, but got { num_args } . ' )
elif optargs :
if num_args < num_types :
raise InvalidArguments ( f ' { name } takes at least { num_types } arguments, but got { num_args } . ' )
elif num_args > num_types + len ( optargs ) :
raise InvalidArguments ( f ' { name } takes at most { num_types + len ( optargs ) } arguments, but got { num_args } . ' )
# Add the number of positional arguments required
if num_args > num_types :
diff = num_args - num_types
a_types = tuple ( list ( types ) + list ( optargs [ : diff ] ) )
elif num_args != num_types :
raise InvalidArguments ( f ' { name } takes exactly { num_types } arguments, but got { num_args } . ' )
for i , ( arg , type_ ) in enumerate ( itertools . zip_longest ( args , a_types , fillvalue = varargs ) , start = 1 ) :
if not isinstance ( arg , type_ ) :
if isinstance ( type_ , tuple ) :
shouldbe = ' one of: {} ' . format ( " , " . join ( f ' " { t . __name__ } " ' for t in type_ ) )
else :
shouldbe = f ' " { type_ . __name__ } " '
raise InvalidArguments ( f ' { name } argument { i } was of type " { type ( arg ) . __name__ } " but should have been { shouldbe } ' )
# Ensure that we're actually passing a tuple.
# Depending on what kind of function we're calling the length of
# wrapped_args can vary.
nargs = list ( wrapped_args )
i = nargs . index ( args )
if varargs :
# if we have varargs we need to split them into a separate
# tuple, as python's typing doesn't understand tuples with
# fixed elements and variadic elements, only one or the other.
# so in that case we need T.Tuple[int, str, float, T.Tuple[str, ...]]
pos = args [ : len ( types ) ]
var = list ( args [ len ( types ) : ] )
pos . append ( var )
nargs [ i ] = tuple ( pos )
elif optargs :
if num_args < num_types + len ( optargs ) :
diff = num_types + len ( optargs ) - num_args
nargs [ i ] = tuple ( list ( args ) + [ None ] * diff )
else :
nargs [ i ] = tuple ( args )
else :
nargs [ i ] = tuple ( args )
return f ( * nargs , * * wrapped_kwargs )
return T . cast ( ' TV_func ' , wrapper )
return inner
class ContainerTypeInfo :
""" Container information for keyword arguments.
For keyword arguments that are containers ( list or dict ) , this class encodes
that information .
: param container : the type of container
: param contains : the types the container holds
: param pairs : if the container is supposed to be of even length .
This is mainly used for interfaces that predate the addition of dictionaries , and use
` [ key , value , key2 , value2 ] ` format .
: param allow_empty : Whether this container is allowed to be empty
There are some cases where containers not only must be passed , but must
not be empty , and other cases where an empty container is allowed .
"""
def __init__ ( self , container : T . Type , contains : T . Union [ T . Type , T . Tuple [ T . Type , . . . ] ] , * ,
pairs : bool = False , allow_empty : bool = True ) :
self . container = container
self . contains = contains
self . pairs = pairs
self . allow_empty = allow_empty
def check ( self , value : T . Any ) - > bool :
""" Check that a value is valid.
: param value : A value to check
: return : True if it is valid , False otherwise
"""
if not isinstance ( value , self . container ) :
return False
iter_ = iter ( value . values ( ) ) if isinstance ( value , dict ) else iter ( value )
if any ( not isinstance ( i , self . contains ) for i in iter_ ) :
return False
if self . pairs and len ( value ) % 2 != 0 :
return False
if not value and not self . allow_empty :
return False
return True
def check_any ( self , value : T . Any ) - > bool :
""" Check a value should emit new/deprecated feature.
: param value : A value to check
: return : True if any of the items in value matches , False otherwise
"""
if not isinstance ( value , self . container ) :
return False
iter_ = iter ( value . values ( ) ) if isinstance ( value , dict ) else iter ( value )
return any ( isinstance ( i , self . contains ) for i in iter_ )
def description ( self ) - > str :
""" Human readable description of this container type.
: return : string to be printed
"""
container = ' dict ' if self . container is dict else ' array '
if isinstance ( self . contains , tuple ) :
contains = ' | ' . join ( [ t . __name__ for t in self . contains ] )
else :
contains = self . contains . __name__
s = f ' { container } [ { contains } ] '
if self . pairs :
s + = ' that has even size '
if not self . allow_empty :
s + = ' that cannot be empty '
return s
_T = T . TypeVar ( ' _T ' )
class _NULL_T :
""" Special null type for evolution, this is an implementation detail. """
_NULL = _NULL_T ( )
class KwargInfo ( T . Generic [ _T ] ) :
""" A description of a keyword argument to a meson function
This is used to describe a value to the : func : typed_kwargs function .
: param name : the name of the parameter
: param types : A type or tuple of types that are allowed , or a : class : ContainerType
: param required : Whether this is a required keyword argument . defaults to False
: param listify : If true , then the argument will be listified before being
checked . This is useful for cases where the Meson DSL allows a scalar or
a container , but internally we only want to work with containers
: param default : A default value to use if this isn ' t set. defaults to None,
this may be safely set to a mutable type , as long as that type does not
itself contain mutable types , typed_kwargs will copy the default
: param since : Meson version in which this argument has been added . defaults to None
: param since_message : An extra message to pass to FeatureNew when since is triggered
: param deprecated : Meson version in which this argument has been deprecated . defaults to None
: param deprecated_message : An extra message to pass to FeatureDeprecated
when since is triggered
: param validator : A callable that does additional validation . This is mainly
intended for cases where a string is expected , but only a few specific
values are accepted . Must return None if the input is valid , or a
message if the input is invalid
: param convertor : A callable that converts the raw input value into a
different type . This is intended for cases such as the meson DSL using a
string , but the implementation using an Enum . This should not do
validation , just conversion .
: param deprecated_values : a dictionary mapping a value to the version of
meson it was deprecated in . The Value may be any valid value for this
argument .
: param since_values : a dictionary mapping a value to the version of meson it was
added in .
: param not_set_warning : A warning message that is logged if the kwarg is not
set by the user .
"""
def __init__ ( self , name : str ,
types : T . Union [ T . Type [ _T ] , T . Tuple [ T . Union [ T . Type [ _T ] , ContainerTypeInfo ] , . . . ] , ContainerTypeInfo ] ,
* , required : bool = False , listify : bool = False ,
default : T . Optional [ _T ] = None ,
since : T . Optional [ str ] = None ,
since_message : T . Optional [ str ] = None ,
since_values : T . Optional [ T . Dict [ T . Union [ _T , ContainerTypeInfo , type ] , T . Union [ str , T . Tuple [ str , str ] ] ] ] = None ,
deprecated : T . Optional [ str ] = None ,
deprecated_message : T . Optional [ str ] = None ,
deprecated_values : T . Optional [ T . Dict [ T . Union [ _T , ContainerTypeInfo , type ] , T . Union [ str , T . Tuple [ str , str ] ] ] ] = None ,
validator : T . Optional [ T . Callable [ [ T . Any ] , T . Optional [ str ] ] ] = None ,
convertor : T . Optional [ T . Callable [ [ _T ] , object ] ] = None ,
not_set_warning : T . Optional [ str ] = None ) :
self . name = name
self . types = types
self . required = required
self . listify = listify
self . default = default
self . since = since
self . since_message = since_message
self . since_values = since_values
self . deprecated = deprecated
self . deprecated_message = deprecated_message
self . deprecated_values = deprecated_values
self . validator = validator
self . convertor = convertor
self . not_set_warning = not_set_warning
def evolve ( self , * ,
name : T . Union [ str , _NULL_T ] = _NULL ,
required : T . Union [ bool , _NULL_T ] = _NULL ,
listify : T . Union [ bool , _NULL_T ] = _NULL ,
default : T . Union [ _T , None , _NULL_T ] = _NULL ,
since : T . Union [ str , None , _NULL_T ] = _NULL ,
since_message : T . Union [ str , None , _NULL_T ] = _NULL ,
since_values : T . Union [ T . Dict [ T . Union [ _T , ContainerTypeInfo , type ] , T . Union [ str , T . Tuple [ str , str ] ] ] , None , _NULL_T ] = _NULL ,
deprecated : T . Union [ str , None , _NULL_T ] = _NULL ,
deprecated_message : T . Union [ str , None , _NULL_T ] = _NULL ,
deprecated_values : T . Union [ T . Dict [ T . Union [ _T , ContainerTypeInfo , type ] , T . Union [ str , T . Tuple [ str , str ] ] ] , None , _NULL_T ] = _NULL ,
validator : T . Union [ T . Callable [ [ _T ] , T . Optional [ str ] ] , None , _NULL_T ] = _NULL ,
convertor : T . Union [ T . Callable [ [ _T ] , TYPE_var ] , None , _NULL_T ] = _NULL ) - > ' KwargInfo ' :
""" Create a shallow copy of this KwargInfo, with modifications.
This allows us to create a new copy of a KwargInfo with modifications .
This allows us to use a shared kwarg that implements complex logic , but
has slight differences in usage , such as being added to different
functions in different versions of Meson .
The use the _NULL special value here allows us to pass None , which has
meaning in many of these cases . _NULL itself is never stored , always
being replaced by either the copy in self , or the provided new version .
"""
return type ( self ) (
name if not isinstance ( name , _NULL_T ) else self . name ,
self . types ,
listify = listify if not isinstance ( listify , _NULL_T ) else self . listify ,
required = required if not isinstance ( required , _NULL_T ) else self . required ,
default = default if not isinstance ( default , _NULL_T ) else self . default ,
since = since if not isinstance ( since , _NULL_T ) else self . since ,
since_message = since_message if not isinstance ( since_message , _NULL_T ) else self . since_message ,
since_values = since_values if not isinstance ( since_values , _NULL_T ) else self . since_values ,
deprecated = deprecated if not isinstance ( deprecated , _NULL_T ) else self . deprecated ,
deprecated_message = deprecated_message if not isinstance ( deprecated_message , _NULL_T ) else self . deprecated_message ,
deprecated_values = deprecated_values if not isinstance ( deprecated_values , _NULL_T ) else self . deprecated_values ,
validator = validator if not isinstance ( validator , _NULL_T ) else self . validator ,
convertor = convertor if not isinstance ( convertor , _NULL_T ) else self . convertor ,
)
def typed_kwargs ( name : str , * types : KwargInfo , allow_unknown : bool = False ) - > T . Callable [ . . . , T . Any ] :
""" Decorator for type checking keyword arguments.
Used to wrap a meson DSL implementation function , where it checks various
things about keyword arguments , including the type , and various other
information . For non - required values it sets the value to a default , which
means the value will always be provided .
If type is a : class : ContainerTypeInfo , then the default value will be
passed as an argument to the container initializer , making a shallow copy
: param name : the name of the function , including the object it ' s attached to
( if applicable )
: param * types : KwargInfo entries for each keyword argument .
"""
def inner ( f : TV_func ) - > TV_func :
def types_description ( types_tuple : T . Tuple [ T . Union [ T . Type , ContainerTypeInfo ] , . . . ] ) - > str :
candidates = [ ]
for t in types_tuple :
if isinstance ( t , ContainerTypeInfo ) :
candidates . append ( t . description ( ) )
else :
candidates . append ( t . __name__ )
shouldbe = ' one of: ' if len ( candidates ) > 1 else ' '
shouldbe + = ' , ' . join ( candidates )
return shouldbe
def raw_description ( t : object ) - > str :
""" describe a raw type (ie, one that is not a ContainerTypeInfo). """
if isinstance ( t , list ) :
if t :
return f " array[ { ' | ' . join ( sorted ( mesonlib . OrderedSet ( type ( v ) . __name__ for v in t ) ) ) } ] "
return ' array[] '
elif isinstance ( t , dict ) :
if t :
return f " dict[ { ' | ' . join ( sorted ( mesonlib . OrderedSet ( type ( v ) . __name__ for v in t . values ( ) ) ) ) } ] "
return ' dict[] '
return type ( t ) . __name__
def check_value_type ( types_tuple : T . Tuple [ T . Union [ T . Type , ContainerTypeInfo ] , . . . ] ,
value : T . Any ) - > bool :
for t in types_tuple :
if isinstance ( t , ContainerTypeInfo ) :
if t . check ( value ) :
return True
elif isinstance ( value , t ) :
return True
return False
@wraps ( f )
def wrapper ( * wrapped_args : T . Any , * * wrapped_kwargs : T . Any ) - > T . Any :
def emit_feature_change ( values : T . Dict [ _T , T . Union [ str , T . Tuple [ str , str ] ] ] , feature : T . Union [ T . Type [ ' FeatureDeprecated ' ] , T . Type [ ' FeatureNew ' ] ] ) - > None :
for n , version in values . items ( ) :
if isinstance ( version , tuple ) :
version , msg = version
else :
msg = None
warning : T . Optional [ str ] = None
if isinstance ( n , ContainerTypeInfo ) :
if n . check_any ( value ) :
warning = f ' of type { n . description ( ) } '
elif isinstance ( n , type ) :
if isinstance ( value , n ) :
warning = f ' of type { n . __name__ } '
elif isinstance ( value , list ) :
if n in value :
warning = f ' value " { n } " in list '
elif isinstance ( value , dict ) :
if n in value . keys ( ) :
warning = f ' value " { n } " in dict keys '
elif n == value :
warning = f ' value " { n } " '
if warning :
feature . single_use ( f ' " { name } " keyword argument " { info . name } " { warning } ' , version , subproject , msg , location = node )
node , _ , _kwargs , subproject = get_callee_args ( wrapped_args )
# Cast here, as the convertor function may place something other than a TYPE_var in the kwargs
kwargs = T . cast ( ' T.Dict[str, object] ' , _kwargs )
if not allow_unknown :
all_names = { t . name for t in types }
unknowns = set ( kwargs ) . difference ( all_names )
if unknowns :
ustr = ' , ' . join ( [ f ' " { u } " ' for u in sorted ( unknowns ) ] )
raise InvalidArguments ( f ' { name } got unknown keyword arguments { ustr } ' )
for info in types :
types_tuple = info . types if isinstance ( info . types , tuple ) else ( info . types , )
value = kwargs . get ( info . name )
if value is not None :
if info . since :
feature_name = info . name + ' arg in ' + name
FeatureNew . single_use ( feature_name , info . since , subproject , info . since_message , location = node )
if info . deprecated :
feature_name = info . name + ' arg in ' + name
FeatureDeprecated . single_use ( feature_name , info . deprecated , subproject , info . deprecated_message , location = node )
if info . listify :
kwargs [ info . name ] = value = mesonlib . listify ( value )
if not check_value_type ( types_tuple , value ) :
shouldbe = types_description ( types_tuple )
raise InvalidArguments ( f ' { name } keyword argument { info . name !r} was of type { raw_description ( value ) } but should have been { shouldbe } ' )
if info . validator is not None :
msg = info . validator ( value )
if msg is not None :
raise InvalidArguments ( f ' { name } keyword argument " { info . name } " { msg } ' )
if info . deprecated_values is not None :
emit_feature_change ( info . deprecated_values , FeatureDeprecated )
if info . since_values is not None :
emit_feature_change ( info . since_values , FeatureNew )
elif info . required :
raise InvalidArguments ( f ' { name } is missing required keyword argument " { info . name } " ' )
else :
# set the value to the default, this ensuring all kwargs are present
# This both simplifies the typing checking and the usage
assert check_value_type ( types_tuple , info . default ) , f ' In function { name } default value of { info . name } is not a valid type, got { type ( info . default ) } expected { types_description ( types_tuple ) } '
# Create a shallow copy of the container. This allows mutable
# types to be used safely as default values
kwargs [ info . name ] = copy . copy ( info . default )
if info . not_set_warning :
mlog . warning ( info . not_set_warning )
if info . convertor :
kwargs [ info . name ] = info . convertor ( kwargs [ info . name ] )
return f ( * wrapped_args , * * wrapped_kwargs )
return T . cast ( ' TV_func ' , wrapper )
return inner
# 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 "
feature_registry : T . ClassVar [ T . Dict [ str , T . Dict [ str , T . Set [ T . Tuple [ str , T . Optional [ ' mparser.BaseNode ' ] ] ] ] ] ]
emit_notice = False
unconditional = False
def __init__ ( self , feature_name : str , feature_version : str , extra_message : str = ' ' ) :
self . feature_name = feature_name # type: str
self . feature_version = feature_version # type: str
self . extra_message = extra_message # type: str
@staticmethod
def get_target_version ( subproject : str ) - > str :
# 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 :
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 :
return
# Target version is new enough, don't warn
if self . check_version ( tv , self . feature_version ) and not self . emit_notice :
return
# Feature is too new for target version or we want to emit notices, register it
if subproject not in self . feature_registry :
self . feature_registry [ subproject ] = { self . feature_version : set ( ) }
register = self . feature_registry [ subproject ]
if self . feature_version not in register :
register [ self . feature_version ] = set ( )
feature_key = ( self . feature_name , location )
if feature_key in register [ self . feature_version ] :
# Don't warn about the same feature multiple times
# FIXME: This is needed to prevent duplicate warnings, but also
# means we won't warn about a feature used in multiple places.
return
register [ self . feature_version ] . add ( feature_key )
# Target version is new enough, don't warn even if it is registered for notice
if self . check_version ( tv , self . feature_version ) :
return
self . log_usage_warning ( tv , location )
@classmethod
def report ( cls , subproject : str ) - > None :
if subproject not in cls . feature_registry :
return
warning_str = cls . get_warning_str_prefix ( cls . get_target_version ( subproject ) )
notice_str = cls . get_notice_str_prefix ( cls . get_target_version ( subproject ) )
fv = cls . feature_registry [ subproject ]
tv = cls . get_target_version ( subproject )
for version in sorted ( fv . keys ( ) ) :
message = ' , ' . join ( sorted ( { f " ' { i [ 0 ] } ' " for i in fv [ version ] } ) )
if cls . check_version ( tv , version ) :
notice_str + = ' \n * {} : {{ {} }} ' . format ( version , message )
else :
warning_str + = ' \n * {} : {{ {} }} ' . format ( version , message )
if ' \n ' in notice_str :
mlog . notice ( notice_str , fatal = False )
if ' \n ' in warning_str :
mlog . warning ( warning_str )
def log_usage_warning ( self , tv : str , location : T . Optional [ ' mparser.BaseNode ' ] ) - > None :
raise InterpreterException ( ' log_usage_warning not implemented ' )
@staticmethod
def get_warning_str_prefix ( tv : str ) - > str :
raise InterpreterException ( ' get_warning_str_prefix not implemented ' )
@staticmethod
def get_notice_str_prefix ( tv : str ) - > str :
raise InterpreterException ( ' get_notice_str_prefix not implemented ' )
def __call__ ( self , f : TV_func ) - > TV_func :
@wraps ( f )
def wrapped ( * wrapped_args : T . Any , * * wrapped_kwargs : T . Any ) - > T . Any :
node , _ , _ , subproject = get_callee_args ( wrapped_args )
if subproject is None :
raise AssertionError ( f ' { wrapped_args !r} ' )
self . use ( subproject , node )
return f ( * wrapped_args , * * wrapped_kwargs )
return T . cast ( ' TV_func ' , wrapped )
@classmethod
def single_use ( cls , feature_name : str , version : str , subproject : ' SubProject ' ,
extra_message : str = ' ' , location : T . Optional [ ' mparser.BaseNode ' ] = None ) - > None :
""" Oneline version that instantiates and calls use(). """
cls ( feature_name , version , extra_message ) . use ( subproject , location )
class FeatureNew ( FeatureCheckBase ) :
""" Checks for new features """
# Class variable, shared across all instances
#
# Format: {subproject: {feature_version: set(feature_names)}}
feature_registry = { }
@staticmethod
def check_version ( target_version : str , feature_version : str ) - > bool :
return mesonlib . version_compare_condition_with_min ( target_version , feature_version )
@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: '
@staticmethod
def get_notice_str_prefix ( tv : str ) - > str :
return ' '
def log_usage_warning ( self , tv : str , location : T . Optional [ ' mparser.BaseNode ' ] ) - > None :
args = [
' Project targets ' , f " ' { tv } ' " ,
' but uses feature introduced in ' ,
f " ' { self . feature_version } ' : " ,
f ' { self . feature_name } . ' ,
]
if self . extra_message :
args . append ( self . extra_message )
mlog . warning ( * args , location = location )
class FeatureDeprecated ( FeatureCheckBase ) :
""" Checks for deprecated features """
# Class variable, shared across all instances
#
# Format: {subproject: {feature_version: set(feature_names)}}
feature_registry = { }
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 )
@staticmethod
def get_warning_str_prefix ( tv : str ) - > str :
return ' Deprecated features used: '
@staticmethod
def get_notice_str_prefix ( tv : str ) - > str :
return ' Future-deprecated features used: '
def log_usage_warning ( self , tv : str , location : T . Optional [ ' mparser.BaseNode ' ] ) - > None :
args = [
' Project targets ' , f " ' { tv } ' " ,
' but uses feature deprecated since ' ,
f " ' { self . feature_version } ' : " ,
f ' { self . feature_name } . ' ,
]
if self . extra_message :
args . append ( self . extra_message )
mlog . warning ( * args , location = location )
class FeatureBroken ( FeatureCheckBase ) :
""" Checks for broken features """
# Class variable, shared across all instances
#
# Format: {subproject: {feature_version: set(feature_names)}}
feature_registry = { }
unconditional = True
@staticmethod
def check_version ( target_version : str , feature_version : str ) - > bool :
# always warn for broken stuff
return False
@staticmethod
def get_warning_str_prefix ( tv : str ) - > str :
return ' Broken features used: '
@staticmethod
def get_notice_str_prefix ( tv : str ) - > str :
return ' '
def log_usage_warning ( self , tv : str , location : T . Optional [ ' mparser.BaseNode ' ] ) - > None :
args = [
' Project uses feature that was always broken, ' ,
' and is now deprecated since ' ,
f " ' { self . feature_version } ' : " ,
f ' { self . feature_name } . ' ,
]
if self . extra_message :
args . append ( self . extra_message )
mlog . deprecation ( * args , location = location )
# This cannot be a dataclass due to https://github.com/python/mypy/issues/5374
class FeatureCheckKwargsBase ( metaclass = abc . ABCMeta ) :
@property
@abc . abstractmethod
def feature_check_class ( self ) - > T . Type [ FeatureCheckBase ] :
pass
def __init__ ( self , feature_name : str , feature_version : str ,
kwargs : T . List [ str ] , extra_message : T . Optional [ str ] = None ) :
self . feature_name = feature_name
self . feature_version = feature_version
self . kwargs = kwargs
self . extra_message = extra_message
def __call__ ( self , f : TV_func ) - > TV_func :
@wraps ( f )
def wrapped ( * wrapped_args : T . Any , * * wrapped_kwargs : T . Any ) - > T . Any :
node , _ , kwargs , subproject = get_callee_args ( wrapped_args )
if subproject is None :
raise AssertionError ( f ' { wrapped_args !r} ' )
for arg in self . kwargs :
if arg not in kwargs :
continue
name = arg + ' arg in ' + self . feature_name
self . feature_check_class . single_use (
name , self . feature_version , subproject , self . extra_message , node )
return f ( * wrapped_args , * * wrapped_kwargs )
return T . cast ( ' TV_func ' , wrapped )
class FeatureNewKwargs ( FeatureCheckKwargsBase ) :
feature_check_class = FeatureNew
class FeatureDeprecatedKwargs ( FeatureCheckKwargsBase ) :
feature_check_class = FeatureDeprecated