diff --git a/docs/markdown/snippets/requires_kwarg_on_more_compiler_methods.md b/docs/markdown/snippets/requires_kwarg_on_more_compiler_methods.md new file mode 100644 index 000000000000..65edaa98b5e3 --- /dev/null +++ b/docs/markdown/snippets/requires_kwarg_on_more_compiler_methods.md @@ -0,0 +1,21 @@ +## Required kwarg on more `compiler` methods + +The following `compiler` methods now support the `requires` keyword argument: + +- `compiler.compiles()` +- `compiler.links()` +- `compiler.runs()` + +```meson +cc.compiles(valid, name: 'valid', required : true) +cc.links(valid, name: 'valid', required : true) +cc.run(valid, name: 'valid', required : true) + +assert(not cc.compiles(valid, name: 'valid', required : opt)) +assert(not cc.links(valid, name: 'valid', required : opt)) +res = cc.run(valid, name: 'valid', required : opt) +assert(res.compiled()) +assert(res.returncode() == 0) +assert(res.stdout() == '') +assert(res.stderr() == '') +``` diff --git a/docs/yaml/objects/compiler.yaml b/docs/yaml/objects/compiler.yaml index c86ef7f4ccb0..cd988a6834cd 100644 --- a/docs/yaml/objects/compiler.yaml +++ b/docs/yaml/objects/compiler.yaml @@ -121,6 +121,16 @@ methods: - name: _compiles returns: void description: You have found a bug if you can see this! + kwargs: + required: + type: bool | feature + default: false + since: 1.5.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. kwargs_inherit: - compiler._args - compiler._include_directories diff --git a/mesonbuild/interpreter/compiler.py b/mesonbuild/interpreter/compiler.py index fb43035a565f..50a850af4482 100644 --- a/mesonbuild/interpreter/compiler.py +++ b/mesonbuild/interpreter/compiler.py @@ -15,7 +15,7 @@ from .. import dependencies from .. import mesonlib from .. import mlog -from ..compilers import SUFFIX_TO_LANG +from ..compilers import SUFFIX_TO_LANG, RunResult from ..compilers.compilers import CompileCheckMode from ..interpreterbase import (ObjectHolder, noPosargs, noKwargs, FeatureNew, FeatureNewKwargs, disablerIfNotFound, @@ -27,7 +27,7 @@ if T.TYPE_CHECKING: from ..interpreter import Interpreter - from ..compilers import Compiler, RunResult + from ..compilers import Compiler from ..interpreterbase import TYPE_var, TYPE_kwargs from .kwargs import ExtractRequired, ExtractSearchDirs from .interpreter import SourceOutputs @@ -50,7 +50,7 @@ class BaseCompileKW(TypedDict): include_directories: T.List[build.IncludeDirs] args: T.List[str] - class CompileKW(BaseCompileKW): + class CompileKW(BaseCompileKW, ExtractRequired): name: str dependencies: T.List[dependencies.Dependency] @@ -178,7 +178,8 @@ def stderr_method(self, args: T.List['TYPE_var'], kwargs: 'TYPE_kwargs') -> str: # Common methods of compiles, links, runs, and similar _COMPILES_KWS: T.List[KwargInfo] = [_NAME_KW, _ARGS_KW, _DEPENDENCIES_KW, _INCLUDE_DIRS_KW, _NO_BUILTIN_ARGS_KW, - _WERROR_KW] + _WERROR_KW, + REQUIRED_KW.evolve(since='1.5.0', default=False)] _HEADER_KWS: T.List[KwargInfo] = [REQUIRED_KW.evolve(since='0.50.0', default=False), *_COMMON_KWS] _HAS_REQUIRED_KW = REQUIRED_KW.evolve(since='1.3.0', default=False) @@ -306,15 +307,25 @@ def run_method(self, args: T.Tuple['mesonlib.FileOrString'], kwargs: 'CompileKW' FeatureNew.single_use(f'compiler.run for {self.compiler.get_display_language()} language', '1.5.0', self.subproject, location=self.current_node) code = args[0] + testname = kwargs['name'] + + disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False) + if disabled: + if testname: + mlog.log('Checking if', mlog.bold(testname, True), 'runs:', 'skipped: feature', mlog.bold(feature), 'disabled') + return RunResult(compiled=True, returncode=0, stdout='', stderr='', cached=False) + if isinstance(code, mesonlib.File): self.interpreter.add_build_def_file(code) code = mesonlib.File.from_absolute_file( code.rel_to_builddir(self.environment.source_dir)) - testname = kwargs['name'] extra_args = functools.partial(self._determine_args, kwargs) deps, msg = self._determine_dependencies(kwargs['dependencies'], compile_only=False, endl=None) result = self.compiler.run(code, self.environment, extra_args=extra_args, dependencies=deps) + if required and result.returncode != 0: + raise InterpreterException(f'Could not run {testname if testname else "code"}') + if testname: if not result.compiled: h = mlog.red('DID NOT COMPILE') @@ -510,6 +521,14 @@ def has_define_method(self, args: T.Tuple[str], kwargs: 'CommonKW') -> bool: @typed_kwargs('compiler.compiles', *_COMPILES_KWS) def compiles_method(self, args: T.Tuple['mesonlib.FileOrString'], kwargs: 'CompileKW') -> bool: code = args[0] + testname = kwargs['name'] + + disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False) + if disabled: + if testname: + mlog.log('Checking if', mlog.bold(testname, True), 'compiles:', 'skipped: feature', mlog.bold(feature), 'disabled') + return False + if isinstance(code, mesonlib.File): if code.is_built: FeatureNew.single_use('compiler.compiles with file created at setup time', '1.2.0', self.subproject, @@ -517,12 +536,14 @@ def compiles_method(self, args: T.Tuple['mesonlib.FileOrString'], kwargs: 'Compi self.interpreter.add_build_def_file(code) code = mesonlib.File.from_absolute_file( code.absolute_path(self.environment.source_dir, self.environment.build_dir)) - testname = kwargs['name'] extra_args = functools.partial(self._determine_args, kwargs) deps, msg = self._determine_dependencies(kwargs['dependencies'], endl=None) result, cached = self.compiler.compiles(code, self.environment, extra_args=extra_args, dependencies=deps) + if required and not result: + raise InterpreterException(f'Could not compile {testname}') + if testname: if result: h = mlog.green('YES') @@ -536,6 +557,14 @@ def compiles_method(self, args: T.Tuple['mesonlib.FileOrString'], kwargs: 'Compi @typed_kwargs('compiler.links', *_COMPILES_KWS) def links_method(self, args: T.Tuple['mesonlib.FileOrString'], kwargs: 'CompileKW') -> bool: code = args[0] + testname = kwargs['name'] + + disabled, required, feature = extract_required_kwarg(kwargs, self.subproject, default=False) + if disabled: + if testname: + mlog.log('Checking if', mlog.bold(testname, True), 'links:', 'skipped: feature', mlog.bold(feature), 'disabled') + return False + compiler = None if isinstance(code, mesonlib.File): if code.is_built: @@ -556,19 +585,21 @@ def links_method(self, args: T.Tuple['mesonlib.FileOrString'], kwargs: 'CompileK else: compiler = clist[SUFFIX_TO_LANG[suffix]] - testname = kwargs['name'] extra_args = functools.partial(self._determine_args, kwargs) deps, msg = self._determine_dependencies(kwargs['dependencies'], compile_only=False) result, cached = self.compiler.links(code, self.environment, compiler=compiler, extra_args=extra_args, dependencies=deps) - cached_msg = mlog.blue('(cached)') if cached else '' + if required and not result: + raise InterpreterException(f'Could not link {testname if testname else "code"}') + if testname: if result: h = mlog.green('YES') else: h = mlog.red('NO') + cached_msg = mlog.blue('(cached)') if cached else '' mlog.log('Checking if', mlog.bold(testname, True), msg, 'links:', h, cached_msg) return result diff --git a/test cases/common/275 required keyword in compiles functions/invalid.c b/test cases/common/275 required keyword in compiles functions/invalid.c new file mode 100644 index 000000000000..d255a36aa376 --- /dev/null +++ b/test cases/common/275 required keyword in compiles functions/invalid.c @@ -0,0 +1,7 @@ +// The error in this file is an homage to the xz incident :) +// +int +main(void) +{ +. return 0; +} diff --git a/test cases/common/275 required keyword in compiles functions/meson.build b/test cases/common/275 required keyword in compiles functions/meson.build new file mode 100644 index 000000000000..d0f5b7f4a9b4 --- /dev/null +++ b/test cases/common/275 required keyword in compiles functions/meson.build @@ -0,0 +1,41 @@ +project('required keyword in compiles functions', 'c') + +cc = meson.get_compiler('c') +opt = get_option('opt') + +valid = files('valid.c') +invalid = files('invalid.c') + +cc.compiles(valid, name: 'valid', required : true) +cc.links(valid, name: 'valid', required : true) +if meson.can_run_host_binaries() + cc.run(valid, name: 'valid', required : true) +endif + +assert(not cc.compiles(valid, name: 'valid', required : opt)) +assert(not cc.links(valid, name: 'valid', required : opt)) +if meson.can_run_host_binaries() + res = cc.run(valid, name: 'valid', required : opt) + assert(res.compiled()) + assert(res.returncode() == 0) + assert(res.stdout() == '') + assert(res.stderr() == '') +endif + +testcase expect_error('''compiler.compiles keyword argument 'required' was of type str but should have been one of: bool, UserFeatureOption''') + cc.compiles(valid, name: 'valid', required : 'not a bool') +endtestcase + +testcase expect_error('''Could not compile invalid''') + cc.compiles(invalid, name: 'invalid', required : true) +endtestcase + +testcase expect_error('''Could not link invalid''') + cc.links(invalid, name: 'invalid', required : true) +endtestcase + +if meson.can_run_host_binaries() + testcase expect_error('''Could not run invalid''') + cc.run(invalid, name: 'invalid', required : true) + endtestcase +endif diff --git a/test cases/common/275 required keyword in compiles functions/meson_options.txt b/test cases/common/275 required keyword in compiles functions/meson_options.txt new file mode 100644 index 000000000000..53175afec478 --- /dev/null +++ b/test cases/common/275 required keyword in compiles functions/meson_options.txt @@ -0,0 +1 @@ +option('opt', type: 'feature', value: 'disabled') diff --git a/test cases/common/275 required keyword in compiles functions/valid.c b/test cases/common/275 required keyword in compiles functions/valid.c new file mode 100644 index 000000000000..8479e67d1090 --- /dev/null +++ b/test cases/common/275 required keyword in compiles functions/valid.c @@ -0,0 +1,5 @@ +int +main(void) +{ + return 0; +}