Merge pull request #7123 from dcbaker/use-feature-deprecated

Use FeatureDeprecated (and other cleanups)
This commit is contained in:
Jussi Pakkanen 2020-05-22 17:08:44 +03:00 committed by GitHub
commit 2b1ca518cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 75 additions and 65 deletions

View File

@ -778,7 +778,7 @@ class BuildTarget(Target):
if isinstance(src, str):
src = File(False, self.subdir, src)
elif isinstance(src, File):
FeatureNew('File argument for extract_objects', '0.50.0').use(self.subproject)
FeatureNew.single_use('File argument for extract_objects', '0.50.0', self.subproject)
else:
raise MesonException('Object extraction arguments must be strings or Files.')
# FIXME: It could be a generated source
@ -2162,7 +2162,7 @@ class CustomTarget(Target):
'when installing a target')
if isinstance(kwargs['install_dir'], list):
FeatureNew('multiple install_dir for custom_target', '0.40.0').use(self.subproject)
FeatureNew.single_use('multiple install_dir for custom_target', '0.40.0', self.subproject)
# If an item in this list is False, the output corresponding to
# the list index of that item will not be installed
self.install_dir = typeslistify(kwargs['install_dir'], (str, bool))
@ -2174,7 +2174,6 @@ class CustomTarget(Target):
if 'build_always' in kwargs and 'build_always_stale' in kwargs:
raise InvalidArguments('build_always and build_always_stale are mutually exclusive. Combine build_by_default and build_always_stale.')
elif 'build_always' in kwargs:
mlog.deprecation('build_always is deprecated. Combine build_by_default and build_always_stale instead.')
if 'build_by_default' not in kwargs:
self.build_by_default = kwargs['build_always']
self.build_always_stale = kwargs['build_always']

View File

@ -29,7 +29,7 @@ from .interpreterbase import InterpreterBase
from .interpreterbase import check_stringlist, flatten, noPosargs, noKwargs, stringArgs, permittedKwargs, noArgsFlattening
from .interpreterbase import InterpreterException, InvalidArguments, InvalidCode, SubdirDoneRequest
from .interpreterbase import InterpreterObject, MutableInterpreterObject, Disabler, disablerIfNotFound
from .interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs
from .interpreterbase import FeatureNew, FeatureDeprecated, FeatureNewKwargs, FeatureDeprecatedKwargs
from .interpreterbase import ObjectHolder
from .modules import ModuleReturnValue
from .cmake import CMakeInterpreter
@ -527,8 +527,9 @@ class ExternalProgramHolder(InterpreterObject, ObjectHolder):
@noPosargs
@permittedKwargs({})
@FeatureDeprecated('ExternalProgram.path', '0.55.0',
'use ExternalProgram.full_path() instead')
def path_method(self, args, kwargs):
mlog.deprecation('path() method is deprecated and replaced by full_path()')
return self._full_path()
@noPosargs
@ -1957,8 +1958,10 @@ class MesonMain(InterpreterObject):
'Arguments to {} must be strings, Files, CustomTargets, '
'Indexes of CustomTargets, or ConfigureFiles'.format(name))
if new:
FeatureNew('Calling "{}" with File, CustomTaget, Index of CustomTarget, ConfigureFile, Executable, or ExternalProgram'.format(name), '0.55.0').use(
self.interpreter.subproject)
FeatureNew.single_use(
'Calling "{}" with File, CustomTaget, Index of CustomTarget, '
'ConfigureFile, Executable, or ExternalProgram'.format(name),
'0.55.0', self.interpreter.subproject)
return script_args
@permittedKwargs(set())
@ -1982,7 +1985,8 @@ class MesonMain(InterpreterObject):
if len(args) < 1:
raise InterpreterException('add_dist_script takes one or more arguments')
if len(args) > 1:
FeatureNew('Calling "add_dist_script" with multiple arguments', '0.49.0').use(self.interpreter.subproject)
FeatureNew.single_use('Calling "add_dist_script" with multiple arguments',
'0.49.0', self.interpreter.subproject)
if self.interpreter.subproject != '':
raise InterpreterException('add_dist_script may not be used in a subproject.')
script_args = self._process_script_args('add_dist_script', args[1:], allow_built=True)
@ -2605,7 +2609,7 @@ external dependencies (including libraries) must go to "dependencies".''')
@noKwargs
def func_assert(self, node, args, kwargs):
if len(args) == 1:
FeatureNew('assert function without message argument', '0.53.0').use(self.subproject)
FeatureNew.single_use('assert function without message argument', '0.53.0', self.subproject)
value = args[0]
message = None
elif len(args) == 2:
@ -2941,7 +2945,7 @@ external dependencies (including libraries) must go to "dependencies".''')
if len(args) > 1:
raise InterpreterException('configuration_data takes only one optional positional arguments')
elif len(args) == 1:
FeatureNew('configuration_data dictionary', '0.49.0').use(self.subproject)
FeatureNew.single_use('configuration_data dictionary', '0.49.0', self.subproject)
initial_values = args[0]
if not isinstance(initial_values, dict):
raise InterpreterException('configuration_data first argument must be a dictionary')
@ -2981,11 +2985,14 @@ external dependencies (including libraries) must go to "dependencies".''')
if ':' in proj_name:
raise InvalidArguments("Project name {!r} must not contain ':'".format(proj_name))
# This needs to be evaluated as early as possible, as meson uses this
# for things like deprecation testing.
if 'meson_version' in kwargs:
cv = coredata.version
pv = kwargs['meson_version']
if not mesonlib.version_compare(cv, pv):
raise InterpreterException('Meson version is %s but project requires %s' % (cv, pv))
mesonlib.project_meson_versions[self.subproject] = kwargs['meson_version']
if os.path.exists(self.option_file):
oi = optinterpreter.OptionInterpreter(self.subproject)
@ -3032,10 +3039,6 @@ external dependencies (including libraries) must go to "dependencies".''')
self.build.subproject_dir = self.subproject_dir
mesonlib.project_meson_versions[self.subproject] = ''
if 'meson_version' in kwargs:
mesonlib.project_meson_versions[self.subproject] = kwargs['meson_version']
self.build.projects[self.subproject] = proj_name
mlog.log('Project name:', mlog.bold(proj_name))
mlog.log('Project version:', mlog.bold(self.project_version))
@ -3082,7 +3085,7 @@ external dependencies (including libraries) must go to "dependencies".''')
@noKwargs
def func_message(self, node, args, kwargs):
if len(args) > 1:
FeatureNew('message with more than one argument', '0.54.0').use(self.subproject)
FeatureNew.single_use('message with more than one argument', '0.54.0', self.subproject)
args_str = [self.get_message_string_arg(i) for i in args]
self.message_impl(args_str)
@ -3144,7 +3147,7 @@ external dependencies (including libraries) must go to "dependencies".''')
@noKwargs
def func_warning(self, node, args, kwargs):
if len(args) > 1:
FeatureNew('warning with more than one argument', '0.54.0').use(self.subproject)
FeatureNew.single_use('warning with more than one argument', '0.54.0', self.subproject)
args_str = [self.get_message_string_arg(i) for i in args]
mlog.warning(*args_str, location=node)
@ -3468,15 +3471,15 @@ external dependencies (including libraries) must go to "dependencies".''')
def _handle_featurenew_dependencies(self, name):
'Do a feature check on dependencies used by this subproject'
if name == 'mpi':
FeatureNew('MPI Dependency', '0.42.0').use(self.subproject)
FeatureNew.single_use('MPI Dependency', '0.42.0', self.subproject)
elif name == 'pcap':
FeatureNew('Pcap Dependency', '0.42.0').use(self.subproject)
FeatureNew.single_use('Pcap Dependency', '0.42.0', self.subproject)
elif name == 'vulkan':
FeatureNew('Vulkan Dependency', '0.42.0').use(self.subproject)
FeatureNew.single_use('Vulkan Dependency', '0.42.0', self.subproject)
elif name == 'libwmf':
FeatureNew('LibWMF Dependency', '0.44.0').use(self.subproject)
FeatureNew.single_use('LibWMF Dependency', '0.44.0', self.subproject)
elif name == 'openmp':
FeatureNew('OpenMP Dependency', '0.46.0').use(self.subproject)
FeatureNew.single_use('OpenMP Dependency', '0.46.0', self.subproject)
@FeatureNewKwargs('dependency', '0.54.0', ['components'])
@FeatureNewKwargs('dependency', '0.52.0', ['include_type'])
@ -3598,7 +3601,7 @@ external dependencies (including libraries) must go to "dependencies".''')
def get_subproject_infos(self, kwargs):
fbinfo = mesonlib.stringlistify(kwargs['fallback'])
if len(fbinfo) == 1:
FeatureNew('Fallback without variable name', '0.53.0').use(self.subproject)
FeatureNew.single_use('Fallback without variable name', '0.53.0', self.subproject)
return fbinfo[0], None
elif len(fbinfo) != 2:
raise InterpreterException('Fallback info must have one or two items.')
@ -3686,11 +3689,13 @@ external dependencies (including libraries) must go to "dependencies".''')
raise InterpreterException('Unknown target_type.')
@permittedKwargs(permitted_kwargs['vcs_tag'])
@FeatureDeprecatedKwargs('custom_target', '0.47.0', ['build_always'],
'combine build_by_default and build_always_stale instead.')
def func_vcs_tag(self, node, args, kwargs):
if 'input' not in kwargs or 'output' not in kwargs:
raise InterpreterException('Keyword arguments input and output must exist')
if 'fallback' not in kwargs:
FeatureNew('Optional fallback in vcs_tag', '0.41.0').use(self.subproject)
FeatureNew.single_use('Optional fallback in vcs_tag', '0.41.0', self.subproject)
fallback = kwargs.pop('fallback', self.project_version)
if not isinstance(fallback, str):
raise InterpreterException('Keyword argument fallback must be a string.')
@ -3743,7 +3748,7 @@ external dependencies (including libraries) must go to "dependencies".''')
if len(args) != 1:
raise InterpreterException('custom_target: Only one positional argument is allowed, and it must be a string name')
if 'depfile' in kwargs and ('@BASENAME@' in kwargs['depfile'] or '@PLAINNAME@' in kwargs['depfile']):
FeatureNew('substitutions in custom_target depfile', '0.47.0').use(self.subproject)
FeatureNew.single_use('substitutions in custom_target depfile', '0.47.0', self.subproject)
return self._func_custom_target_impl(node, args, kwargs)
def _func_custom_target_impl(self, node, args, kwargs):
@ -3832,7 +3837,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
@permittedKwargs(permitted_kwargs['test'])
def func_test(self, node, args, kwargs):
if kwargs.get('protocol') == 'gtest':
FeatureNew('"gtest" protocol for tests', '0.55.0').use(self.subproject)
FeatureNew.single_use('"gtest" protocol for tests', '0.55.0', self.subproject)
self.add_test(node, args, kwargs, True)
def unpack_env_kwarg(self, kwargs) -> build.EnvironmentVariables:
@ -3840,7 +3845,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
if isinstance(envlist, EnvironmentVariablesHolder):
env = envlist.held_object
elif isinstance(envlist, dict):
FeatureNew('environment dictionary', '0.52.0').use(self.subproject)
FeatureNew.single_use('environment dictionary', '0.52.0', self.subproject)
env = EnvironmentVariablesHolder(envlist)
env = env.held_object
else:
@ -4156,7 +4161,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
if 'configuration' in kwargs:
conf = kwargs['configuration']
if isinstance(conf, dict):
FeatureNew('configure_file.configuration dictionary', '0.49.0').use(self.subproject)
FeatureNew.single_use('configure_file.configuration dictionary', '0.49.0', self.subproject)
conf = ConfigurationDataHolder(self.subproject, conf)
elif not isinstance(conf, ConfigurationDataHolder):
raise InterpreterException('Argument "configuration" is not of type configuration_data')
@ -4186,7 +4191,7 @@ This will become a hard error in the future.''' % kwargs['input'], location=self
conf.mark_used()
elif 'command' in kwargs:
if len(inputs) > 1:
FeatureNew('multiple inputs in configure_file()', '0.52.0').use(self.subproject)
FeatureNew.single_use('multiple inputs in configure_file()', '0.52.0', self.subproject)
# We use absolute paths for input and output here because the cwd
# that the command is run from is 'unspecified', so it could change.
# Currently it's builddir/subdir for in_builddir else srcdir/subdir.
@ -4434,7 +4439,7 @@ different subdirectory.
if len(args) > 1:
raise InterpreterException('environment takes only one optional positional arguments')
elif len(args) == 1:
FeatureNew('environment positional arguments', '0.52.0').use(self.subproject)
FeatureNew.single_use('environment positional arguments', '0.52.0', self.subproject)
initial_values = args[0]
if not isinstance(initial_values, dict) and not isinstance(initial_values, list):
raise InterpreterException('environment first argument must be a dictionary or a list')

View File

@ -286,6 +286,13 @@ class FeatureCheckBase(metaclass=abc.ABCMeta):
return f(*wrapped_args, **wrapped_kwargs)
return wrapped
@classmethod
def single_use(cls, feature_name: str, version: str, subproject: str,
extra_message: T.Optional[str] = None) -> None:
"""Oneline version that instantiates and calls use()."""
cls(feature_name, version, extra_message).use(subproject)
class FeatureNew(FeatureCheckBase):
"""Checks for new features"""
@ -342,17 +349,23 @@ class FeatureDeprecated(FeatureCheckBase):
mlog.warning(*args)
class FeatureCheckKwargsBase:
def __init__(self, feature_name: str, feature_version: str, kwargs: T.List[str]):
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):
@wraps(f)
def wrapped(*wrapped_args, **wrapped_kwargs):
# Which FeatureCheck class to invoke
FeatureCheckClass = self.feature_check_class
kwargs, subproject = _get_callee_args(wrapped_args, want_subproject=True)[3:5]
if subproject is None:
raise AssertionError('{!r}'.format(wrapped_args))
@ -360,7 +373,8 @@ class FeatureCheckKwargsBase:
if arg not in kwargs:
continue
name = arg + ' arg in ' + self.feature_name
FeatureCheckClass(name, self.feature_version).use(subproject)
self.feature_check_class.single_use(
name, self.feature_version, subproject, self.extra_message)
return f(*wrapped_args, **wrapped_kwargs)
return wrapped
@ -570,7 +584,7 @@ class InterpreterBase:
self.argument_depth += 1
for key, value in kwargs.items():
if not isinstance(key, mparser.StringNode):
FeatureNew('Dictionary entry using non literal key', '0.53.0').use(self.subproject)
FeatureNew.single_use('Dictionary entry using non literal key', '0.53.0', self.subproject)
assert isinstance(key, mparser.BaseNode) # All keys must be nodes due to resolve_key_nodes=False
str_key = self.evaluate_statement(key)
if not isinstance(str_key, str):

View File

@ -39,8 +39,10 @@ _U = T.TypeVar('_U')
have_fcntl = False
have_msvcrt = False
# TODO: this is such a hack, this really should be either in coredata or in the
# interpreter
# {subproject: project_meson_version}
project_meson_versions = {} # type: T.Dict[str, str]
project_meson_versions = collections.defaultdict(str) # type: T.DefaultDict[str, str]
try:
import fcntl

View File

@ -33,7 +33,7 @@ from ..mesonlib import (
join_args, unholder,
)
from ..dependencies import Dependency, PkgConfigDependency, InternalDependency, ExternalProgram
from ..interpreterbase import noKwargs, permittedKwargs, FeatureNew, FeatureNewKwargs
from ..interpreterbase import noKwargs, permittedKwargs, FeatureNew, FeatureNewKwargs, FeatureDeprecatedKwargs
# gresource compilation is broken due to the way
# the resource compiler and Ninja clash about it
@ -834,6 +834,8 @@ class GnomeModule(ExtensionModule):
return ModuleReturnValue(target_g, [target_g])
@permittedKwargs({'sources', 'media', 'symlink_media', 'languages'})
@FeatureDeprecatedKwargs('gnome.yelp', '0.43.0', ['languages'],
'Use a LINGUAS file in the source directory instead')
def yelp(self, state, args, kwargs):
if len(args) < 1:
raise MesonException('Yelp requires a project id')
@ -848,11 +850,6 @@ class GnomeModule(ExtensionModule):
source_str = '@@'.join(sources)
langs = mesonlib.stringlistify(kwargs.pop('languages', []))
if langs:
mlog.deprecation('''The "languages" argument of gnome.yelp() is deprecated.
Use a LINGUAS file in the sources directory instead.
This will become a hard error in the future.''')
media = mesonlib.stringlistify(kwargs.pop('media', []))
symlinks = kwargs.pop('symlink_media', True)

View File

@ -76,7 +76,7 @@ class DependenciesHelper:
processed_reqs = []
for obj in mesonlib.unholder(mesonlib.listify(reqs)):
if not isinstance(obj, str):
FeatureNew('pkgconfig.generate requirement from non-string object', '0.46.0').use(self.state.subproject)
FeatureNew.single_use('pkgconfig.generate requirement from non-string object', '0.46.0', self.state.subproject)
if hasattr(obj, 'generated_pc'):
self._check_generated_pc_deprecation(obj)
processed_reqs.append(obj.generated_pc)
@ -394,8 +394,6 @@ class PkgConfigModule(ExtensionModule):
'install_dir', 'extra_cflags', 'variables', 'url', 'd_module_versions',
'dataonly', 'conflicts'})
def generate(self, state, args, kwargs):
if 'variables' in kwargs:
FeatureNew('custom pkgconfig variables', '0.41.0').use(state.subproject)
default_version = state.project_version['version']
default_install_dir = None
default_description = None
@ -403,9 +401,9 @@ class PkgConfigModule(ExtensionModule):
mainlib = None
default_subdirs = ['.']
if not args and 'version' not in kwargs:
FeatureNew('pkgconfig.generate implicit version keyword', '0.46.0').use(state.subproject)
FeatureNew.single_use('pkgconfig.generate implicit version keyword', '0.46.0', state.subproject)
elif len(args) == 1:
FeatureNew('pkgconfig.generate optional positional argument', '0.46.0').use(state.subproject)
FeatureNew.single_use('pkgconfig.generate optional positional argument', '0.46.0', state.subproject)
mainlib = getattr(args[0], 'held_object', args[0])
if not isinstance(mainlib, (build.StaticLibrary, build.SharedLibrary)):
raise mesonlib.MesonException('Pkgconfig_gen first positional argument must be a library object')

View File

@ -16,10 +16,11 @@ import re
import functools
import typing as T
from . import mparser
from . import compilers
from . import coredata
from . import mesonlib
from . import compilers
from . import mparser
from .interpreterbase import FeatureNew
forbidden_option_names = set(coredata.builtin_options.keys())
forbidden_prefixes = [lang + '_' for lang in compilers.all_languages] + ['b_', 'backend_']
@ -200,11 +201,8 @@ class OptionInterpreter:
raise OptionException('Only calls to option() are allowed in option files.')
(posargs, kwargs) = self.reduce_arguments(node.args)
# FIXME: Cannot use FeatureNew while parsing options because we parse
# it before reading options in project(). See func_project() in
# interpreter.py
#if 'yield' in kwargs:
# FeatureNew('option yield', '0.45.0').use(self.subproject)
if 'yield' in kwargs:
FeatureNew.single_use('option yield', '0.45.0', self.subproject)
if 'type' not in kwargs:
raise OptionException('Option call missing mandatory "type" keyword argument')

View File

@ -718,25 +718,22 @@ class InternalTests(unittest.TestCase):
self.assertEqual([1, 2, 3], extract(kwargs, 'sources'))
def test_pkgconfig_module(self):
class Mock:
pass
dummystate = Mock()
dummystate = mock.Mock()
dummystate.subproject = 'dummy'
mock = Mock()
mock.pcdep = Mock()
mock.pcdep.name = "some_name"
mock.version_reqs = []
_mock = mock.Mock(spec=mesonbuild.dependencies.ExternalDependency)
_mock.pcdep = mock.Mock()
_mock.pcdep.name = "some_name"
_mock.version_reqs = []
_mock = mock.Mock(held_object=_mock)
# pkgconfig dependency as lib
deps = mesonbuild.modules.pkgconfig.DependenciesHelper(dummystate, "thislib")
deps.add_pub_libs([mock])
deps.add_pub_libs([_mock])
self.assertEqual(deps.format_reqs(deps.pub_reqs), "some_name")
# pkgconfig dependency as requires
deps = mesonbuild.modules.pkgconfig.DependenciesHelper(dummystate, "thislib")
deps.add_pub_reqs([mock])
deps.add_pub_reqs([_mock])
self.assertEqual(deps.format_reqs(deps.pub_reqs), "some_name")
def _test_all_naming(self, cc, env, patterns, platform):