diff options
| author | Xavier Claessens <xavier.claessens@collabora.com> | 2023-03-02 11:59:28 -0500 |
|---|---|---|
| committer | Xavier Claessens <xclaesse@gmail.com> | 2023-08-05 07:14:08 -0400 |
| commit | e01d53b816c9fba25a068039e62d8ac9e5e2a971 (patch) | |
| tree | 7905b8adc6c81750daa160cccbe7738e636e59a5 | |
| parent | 50baf3c626267252a2d943a49d8e7c0402e23218 (diff) | |
| download | meson-e01d53b816c9fba25a068039e62d8ac9e5e2a971.tar.gz | |
compiler: Add required keyword to has_* methods
add the "required" keyword to the functions
has_function
has_type
has_member
has_members
has_argument
has_multi_arguments
has_link_argument
has_multi_link_argument
has_function_attribute
Co-authored-by: Milan Hauth <milahu@gmail.com>
5 files changed, 222 insertions, 48 deletions
diff --git a/docs/markdown/snippets/required_keyword_for_has_functions.md b/docs/markdown/snippets/required_keyword_for_has_functions.md new file mode 100644 index 000000000..0752ac7fe --- /dev/null +++ b/docs/markdown/snippets/required_keyword_for_has_functions.md @@ -0,0 +1,19 @@ +## All compiler `has_*` methods support the `required` keyword + +Now instead of + +```meson +assert(cc.has_function('some_function')) +assert(cc.has_type('some_type')) +assert(cc.has_member('struct some_type', 'x')) +assert(cc.has_members('struct some_type', ['x', 'y'])) +``` + +we can use + +```meson +cc.has_function('some_function', required: true) +cc.has_type('some_type', required: true) +cc.has_member('struct some_type', 'x', required: true) +cc.has_members('struct some_type', ['x', 'y'], required: true) +``` diff --git a/docs/yaml/objects/compiler.yaml b/docs/yaml/objects/compiler.yaml index 4bf69c3b1..14b61a876 100644 --- a/docs/yaml/objects/compiler.yaml +++ b/docs/yaml/objects/compiler.yaml @@ -143,6 +143,19 @@ methods: When set to a [`feature`](Build-options.md#features) option, the feature will control if it is searched and whether to fail if not found. +- name: _required + returns: void + description: You have found a bug if you can see this! + kwargs: + required: + type: bool | feature + default: false + since: 1.1.0 + description: + When set to `true`, Meson will halt if the check fails. + + When set to a [`feature`](Build-options.md#features) option, the feature + will control if it is searched and whether to fail if not found. # Star of the actual functions - name: version @@ -196,7 +209,9 @@ methods: - name: has_member returns: bool description: Returns true if the type has the specified member. - kwargs_inherit: compiler._common + kwargs_inherit: + - compiler._common + - compiler._required posargs: typename: type: str @@ -208,7 +223,9 @@ methods: - name: has_members returns: bool description: Returns `true` if the type has *all* the specified members. - kwargs_inherit: compiler._common + kwargs_inherit: + - compiler._common + - compiler._required posargs: typename: type: str @@ -225,7 +242,9 @@ methods: Returns true if the given function is provided by the standard library or a library passed in with the `args` keyword. - kwargs_inherit: compiler._common + kwargs_inherit: + - compiler._common + - compiler._required posargs: funcname: type: str @@ -234,7 +253,9 @@ methods: - name: has_type returns: bool description: Returns `true` if the specified token is a type. - kwargs_inherit: compiler._common + kwargs_inherit: + - compiler._common + - compiler._required posargs: typename: type: str @@ -457,6 +478,8 @@ methods: argument: type: str description: The argument to check. + kwargs_inherit: + - compiler._required - name: has_multi_arguments since: 0.37.0 @@ -469,6 +492,8 @@ methods: name: arg type: str description: The arguments to check. + kwargs_inherit: + - compiler._required - name: get_supported_arguments returns: list[str] @@ -515,6 +540,8 @@ methods: argument: type: str description: The argument to check. + kwargs_inherit: + - compiler._required - name: has_multi_link_arguments since: 0.46.0 @@ -527,6 +554,8 @@ methods: name: arg type: str description: The link arguments to check. + kwargs_inherit: + - compiler._required - name: get_supported_link_arguments returns: list[str] @@ -556,10 +585,6 @@ methods: Given a list of strings, returns the first argument that passes the [[compiler.has_link_argument]] test or an empty array if none pass. - - - - - name: has_function_attribute returns: bool since: 0.48.0 @@ -573,6 +598,8 @@ methods: name: type: str description: The attribute name to check. + kwargs_inherit: + - compiler._required - name: get_supported_function_attributes returns: list[str] diff --git a/mesonbuild/interpreter/compiler.py b/mesonbuild/interpreter/compiler.py index 52737c4e0..434587427 100644 --- a/mesonbuild/interpreter/compiler.py +++ b/mesonbuild/interpreter/compiler.py @@ -31,6 +31,7 @@ if T.TYPE_CHECKING: from ..interpreterbase import TYPE_var, TYPE_kwargs from .kwargs import ExtractRequired, ExtractSearchDirs from .interpreter.interpreter import SourceOutputs + from ..mlog import TV_LoggableList from typing_extensions import TypedDict, Literal @@ -69,6 +70,12 @@ if T.TYPE_CHECKING: class HeaderKW(CommonKW, ExtractRequired): pass + class HasKW(CommonKW, ExtractRequired): + pass + + class HasArgumentKW(ExtractRequired): + pass + class FindLibraryKW(ExtractRequired, ExtractSearchDirs): disabler: bool @@ -165,6 +172,7 @@ _COMMON_KWS: T.List[KwargInfo] = [_ARGS_KW, _DEPENDENCIES_KW, _INCLUDE_DIRS_KW, _COMPILES_KWS: T.List[KwargInfo] = [_NAME_KW, _ARGS_KW, _DEPENDENCIES_KW, _INCLUDE_DIRS_KW, _NO_BUILTIN_ARGS_KW] _HEADER_KWS: T.List[KwargInfo] = [REQUIRED_KW.evolve(since='0.50.0', default=False), *_COMMON_KWS] +_HAS_REQUIRED_KW = REQUIRED_KW.evolve(since='1.1.0', default=False) class CompilerHolder(ObjectHolder['Compiler']): preprocess_uid: T.Dict[str, itertools.count] = collections.defaultdict(itertools.count) @@ -325,9 +333,13 @@ class CompilerHolder(ObjectHolder['Compiler']): return self.compiler.symbols_have_underscore_prefix(self.environment) @typed_pos_args('compiler.has_member', str, str) - @typed_kwargs('compiler.has_member', *_COMMON_KWS) - def has_member_method(self, args: T.Tuple[str, str], kwargs: 'CommonKW') -> bool: + @typed_kwargs('compiler.has_member', _HAS_REQUIRED_KW, *_COMMON_KWS) + def has_member_method(self, args: T.Tuple[str, str], kwargs: 'HasKW') -> bool: typename, membername = args + disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False) + if disabled: + mlog.log('Type', mlog.bold(typename, True), 'has member', mlog.bold(membername, True), 'skipped: feature', mlog.bold(feature), 'disabled') + return False extra_args = functools.partial(self._determine_args, kwargs['no_builtin_args'], kwargs['include_directories'], kwargs['args']) deps, msg = self._determine_dependencies(kwargs['dependencies']) had, cached = self.compiler.has_members(typename, [membername], kwargs['prefix'], @@ -335,7 +347,9 @@ class CompilerHolder(ObjectHolder['Compiler']): extra_args=extra_args, dependencies=deps) cached_msg = mlog.blue('(cached)') if cached else '' - if had: + if required and not had: + raise InterpreterException(f'{self.compiler.get_display_language()} member {membername!r} of type {typename!r} not usable') + elif had: hadtxt = mlog.green('YES') else: hadtxt = mlog.red('NO') @@ -344,9 +358,14 @@ class CompilerHolder(ObjectHolder['Compiler']): return had @typed_pos_args('compiler.has_members', str, varargs=str, min_varargs=1) - @typed_kwargs('compiler.has_members', *_COMMON_KWS) - def has_members_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'CommonKW') -> bool: + @typed_kwargs('compiler.has_members', _HAS_REQUIRED_KW, *_COMMON_KWS) + def has_members_method(self, args: T.Tuple[str, T.List[str]], kwargs: 'HasKW') -> bool: typename, membernames = args + members = mlog.bold(', '.join([f'"{m}"' for m in membernames])) + disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False) + if disabled: + mlog.log('Type', mlog.bold(typename, True), 'has members', members, 'skipped: feature', mlog.bold(feature), 'disabled') + return False extra_args = functools.partial(self._determine_args, kwargs['no_builtin_args'], kwargs['include_directories'], kwargs['args']) deps, msg = self._determine_dependencies(kwargs['dependencies']) had, cached = self.compiler.has_members(typename, membernames, kwargs['prefix'], @@ -354,26 +373,34 @@ class CompilerHolder(ObjectHolder['Compiler']): extra_args=extra_args, dependencies=deps) cached_msg = mlog.blue('(cached)') if cached else '' - if had: + if required and not had: + # print members as array: ['member1', 'member2'] + raise InterpreterException(f'{self.compiler.get_display_language()} members {membernames!r} of type {typename!r} not usable') + elif had: hadtxt = mlog.green('YES') else: hadtxt = mlog.red('NO') - members = mlog.bold(', '.join([f'"{m}"' for m in membernames])) mlog.log('Checking whether type', mlog.bold(typename, True), 'has members', members, msg, hadtxt, cached_msg) return had @typed_pos_args('compiler.has_function', str) - @typed_kwargs('compiler.has_function', *_COMMON_KWS) - def has_function_method(self, args: T.Tuple[str], kwargs: 'CommonKW') -> bool: + @typed_kwargs('compiler.has_function', _HAS_REQUIRED_KW, *_COMMON_KWS) + def has_function_method(self, args: T.Tuple[str], kwargs: 'HasKW') -> bool: funcname = args[0] + disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False) + if disabled: + mlog.log('Has function', mlog.bold(funcname, True), 'skipped: feature', mlog.bold(feature), 'disabled') + return False extra_args = self._determine_args(kwargs['no_builtin_args'], kwargs['include_directories'], kwargs['args']) deps, msg = self._determine_dependencies(kwargs['dependencies'], compile_only=False) had, cached = self.compiler.has_function(funcname, kwargs['prefix'], self.environment, extra_args=extra_args, dependencies=deps) cached_msg = mlog.blue('(cached)') if cached else '' - if had: + if required and not had: + raise InterpreterException(f'{self.compiler.get_display_language()} function {funcname!r} not usable') + elif had: hadtxt = mlog.green('YES') else: hadtxt = mlog.red('NO') @@ -381,15 +408,21 @@ class CompilerHolder(ObjectHolder['Compiler']): return had @typed_pos_args('compiler.has_type', str) - @typed_kwargs('compiler.has_type', *_COMMON_KWS) - def has_type_method(self, args: T.Tuple[str], kwargs: 'CommonKW') -> bool: + @typed_kwargs('compiler.has_type', _HAS_REQUIRED_KW, *_COMMON_KWS) + def has_type_method(self, args: T.Tuple[str], kwargs: 'HasKW') -> bool: typename = args[0] + disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False) + if disabled: + mlog.log('Has type', mlog.bold(typename, True), 'skipped: feature', mlog.bold(feature), 'disabled') + return False extra_args = functools.partial(self._determine_args, kwargs['no_builtin_args'], kwargs['include_directories'], kwargs['args']) deps, msg = self._determine_dependencies(kwargs['dependencies']) had, cached = self.compiler.has_type(typename, kwargs['prefix'], self.environment, extra_args=extra_args, dependencies=deps) cached_msg = mlog.blue('(cached)') if cached else '' - if had: + if required and not had: + raise InterpreterException(f'{self.compiler.get_display_language()} type {typename!r} not usable') + elif had: hadtxt = mlog.green('YES') else: hadtxt = mlog.red('NO') @@ -646,33 +679,46 @@ class CompilerHolder(ObjectHolder['Compiler']): return lib def _has_argument_impl(self, arguments: T.Union[str, T.List[str]], - mode: _TestMode = _TestMode.COMPILER) -> bool: + mode: _TestMode = _TestMode.COMPILER, + kwargs: T.Optional['ExtractRequired'] = None) -> bool: """Shared implementation for methods checking compiler and linker arguments.""" # This simplifies the callers if isinstance(arguments, str): arguments = [arguments] - test = self.compiler.has_multi_link_arguments if mode is _TestMode.LINKER else self.compiler.has_multi_arguments - result, cached = test(arguments, self.environment) - cached_msg = mlog.blue('(cached)') if cached else '' - mlog.log( + logargs: TV_LoggableList = [ 'Compiler for', self.compiler.get_display_language(), 'supports{}'.format(' link' if mode is _TestMode.LINKER else ''), 'arguments {}:'.format(' '.join(arguments)), + ] + kwargs = kwargs or {'required': False} + disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False) + if disabled: + logargs += ['skipped: feature', mlog.bold(feature), 'disabled'] + mlog.log(*logargs) + return False + test = self.compiler.has_multi_link_arguments if mode is _TestMode.LINKER else self.compiler.has_multi_arguments + result, cached = test(arguments, self.environment) + if required and not result: + logargs += ['not usable'] + raise InterpreterException(*logargs) + logargs += [ mlog.green('YES') if result else mlog.red('NO'), - cached_msg) + mlog.blue('(cached)') if cached else '', + ] + mlog.log(*logargs) return result - @noKwargs @typed_pos_args('compiler.has_argument', str) - def has_argument_method(self, args: T.Tuple[str], kwargs: 'TYPE_kwargs') -> bool: - return self._has_argument_impl([args[0]]) + @typed_kwargs('compiler.has_argument', _HAS_REQUIRED_KW) + def has_argument_method(self, args: T.Tuple[str], kwargs: 'HasArgumentKW') -> bool: + return self._has_argument_impl([args[0]], kwargs=kwargs) - @noKwargs @typed_pos_args('compiler.has_multi_arguments', varargs=str) + @typed_kwargs('compiler.has_multi_arguments', _HAS_REQUIRED_KW) @FeatureNew('compiler.has_multi_arguments', '0.37.0') - def has_multi_arguments_method(self, args: T.Tuple[T.List[str]], kwargs: 'TYPE_kwargs') -> bool: - return self._has_argument_impl(args[0]) + def has_multi_arguments_method(self, args: T.Tuple[T.List[str]], kwargs: 'HasArgumentKW') -> bool: + return self._has_argument_impl(args[0], kwargs=kwargs) @FeatureNew('compiler.get_supported_arguments', '0.43.0') @typed_pos_args('compiler.get_supported_arguments', varargs=str) @@ -707,16 +753,16 @@ class CompilerHolder(ObjectHolder['Compiler']): return [] @FeatureNew('compiler.has_link_argument', '0.46.0') - @noKwargs @typed_pos_args('compiler.has_link_argument', str) - def has_link_argument_method(self, args: T.Tuple[str], kwargs: 'TYPE_kwargs') -> bool: - return self._has_argument_impl([args[0]], mode=_TestMode.LINKER) + @typed_kwargs('compiler.has_link_argument', _HAS_REQUIRED_KW) + def has_link_argument_method(self, args: T.Tuple[str], kwargs: 'HasArgumentKW') -> bool: + return self._has_argument_impl([args[0]], mode=_TestMode.LINKER, kwargs=kwargs) @FeatureNew('compiler.has_multi_link_argument', '0.46.0') - @noKwargs @typed_pos_args('compiler.has_multi_link_argument', varargs=str) - def has_multi_link_arguments_method(self, args: T.Tuple[T.List[str]], kwargs: 'TYPE_kwargs') -> bool: - return self._has_argument_impl(args[0], mode=_TestMode.LINKER) + @typed_kwargs('compiler.has_multi_link_argument', _HAS_REQUIRED_KW) + def has_multi_link_arguments_method(self, args: T.Tuple[T.List[str]], kwargs: 'HasArgumentKW') -> bool: + return self._has_argument_impl(args[0], mode=_TestMode.LINKER, kwargs=kwargs) @FeatureNew('compiler.get_supported_link_arguments', '0.46.0') @noKwargs @@ -739,19 +785,33 @@ class CompilerHolder(ObjectHolder['Compiler']): mlog.log('First supported link argument:', mlog.red('None')) return [] - def _has_function_attribute_impl(self, attr: str) -> bool: + def _has_function_attribute_impl(self, attr: str, kwargs: T.Optional['ExtractRequired'] = None) -> bool: """Common helper for function attribute testing.""" - result, cached = self.compiler.has_func_attribute(attr, self.environment) - cached_msg = mlog.blue('(cached)') if cached else '' - h = mlog.green('YES') if result else mlog.red('NO') - mlog.log(f'Compiler for {self.compiler.get_display_language()} supports function attribute {attr}:', h, cached_msg) - return result + logargs: TV_LoggableList = [ + f'Compiler for {self.compiler.get_display_language()} supports function attribute {attr}:', + ] + kwargs = kwargs or {'required': False} + disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False) + if disabled: + logargs += ['skipped: feature', mlog.bold(feature), 'disabled'] + mlog.log(*logargs) + return False + had, cached = self.compiler.has_func_attribute(attr, self.environment) + if required and not had: + logargs += ['not usable'] + raise InterpreterException(*logargs) + logargs += [ + mlog.green('YES') if had else mlog.red('NO'), + mlog.blue('(cached)') if cached else '' + ] + mlog.log(*logargs) + return had @FeatureNew('compiler.has_function_attribute', '0.48.0') - @noKwargs @typed_pos_args('compiler.has_function_attribute', str) - def has_func_attribute_method(self, args: T.Tuple[str], kwargs: 'TYPE_kwargs') -> bool: - return self._has_function_attribute_impl(args[0]) + @typed_kwargs('compiler.has_function_attribute', _HAS_REQUIRED_KW) + def has_func_attribute_method(self, args: T.Tuple[str], kwargs: 'HasArgumentKW') -> bool: + return self._has_function_attribute_impl(args[0], kwargs) @FeatureNew('compiler.get_supported_function_attributes', '0.48.0') @noKwargs diff --git a/test cases/common/262 required keyword in has functions/meson.build b/test cases/common/262 required keyword in has functions/meson.build new file mode 100644 index 000000000..afd07eab1 --- /dev/null +++ b/test cases/common/262 required keyword in has functions/meson.build @@ -0,0 +1,67 @@ +project('required keyword in has functions', 'c') + +cc = meson.get_compiler('c') +opt = get_option('opt') + +cc.has_function('printf', prefix : '#include<stdio.h>', required : true) +cc.has_type('time_t', prefix : '#include<time.h>', required : true) +cc.has_member('struct tm', 'tm_sec', prefix : '#include<time.h>', required : true) +cc.has_members('struct tm', ['tm_sec', 'tm_min'], prefix : '#include<time.h>', required : true) +cc.has_header('time.h', required : true) +cc.has_header_symbol('time.h', 'time', required : true) + +assert(not cc.has_function('printf', prefix : '#include<stdio.h>', required : opt)) +assert(not cc.has_type('time_t', prefix : '#include<time.h>', required : opt)) +assert(not cc.has_member('struct tm', 'tm_sec', prefix : '#include<time.h>', required : opt)) +assert(not cc.has_members('struct tm', ['tm_sec', 'tm_min'], prefix : '#include<time.h>', required : opt)) +assert(not cc.has_header('time.h', required : opt)) +assert(not cc.has_header_symbol('time.h', 'time', required : opt)) + +# compiler.has_argument +if cc.get_id() == 'msvc' + is_arg = '/O2' +else + is_arg = '-O2' +endif +cc.has_argument(is_arg, required: true) +assert(not cc.has_argument(is_arg, required: opt)) + +# compiler.has_multi_arguments +if cc.get_id() == 'gcc' + pre_arg = '-Wformat' + arg = '-Werror=format-security' + cc.has_multi_arguments([pre_arg, arg], required: true) + assert(not cc.has_multi_arguments(pre_arg, arg, required: opt)) +endif + +# compiler.has_link_argument +if cc.get_argument_syntax() == 'msvc' + is_arg = '/OPT:REF' +else + is_arg = '-Wl,-L/tmp' +endif +cc.has_link_argument(is_arg, required: true) +assert(not cc.has_link_argument(is_arg, required: opt)) + +# compiler.has_function_attribute +if not ['pgi', 'msvc', 'clang-cl', 'intel-cl'].contains(cc.get_id()) + a = 'aligned' + cc.has_function_attribute(a, required: true) + assert(not cc.has_function_attribute(a, required: opt)) +endif + +testcase expect_error('''compiler.has_function keyword argument 'required' was of type str but should have been one of: bool, UserFeatureOption''') + cc.has_function('printf', required : 'not a bool') +endtestcase + +testcase expect_error('''C function 'asdfkawlegsdiovapfjhkr' not usable''') + cc.has_function('asdfkawlegsdiovapfjhkr', required : true) +endtestcase + +testcase expect_error('''C header 'asdfkawlegsdiovapfjhkr.h' not found''') + cc.has_header('asdfkawlegsdiovapfjhkr.h', required : true) +endtestcase + +testcase expect_error('''C symbol time_not_found not found in header time.h''') + cc.has_header_symbol('time.h', 'time_not_found', required : true) +endtestcase diff --git a/test cases/common/262 required keyword in has functions/meson_options.txt b/test cases/common/262 required keyword in has functions/meson_options.txt new file mode 100644 index 000000000..53175afec --- /dev/null +++ b/test cases/common/262 required keyword in has functions/meson_options.txt @@ -0,0 +1 @@ +option('opt', type: 'feature', value: 'disabled') |
