Add required kwarg to compiler.{compiles,links,run}

This is a similar commit to the one that added required to all the
compiler.has* functions.
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 0000000..693313c
--- /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 `required` 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 c86ef7f..cd988a6 100644
--- a/docs/yaml/objects/compiler.yaml
+++ b/docs/yaml/objects/compiler.yaml
@@ -121,6 +121,16 @@
 - 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 fb43035..50a850a 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 @@
         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 @@
 
 # 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 @@
             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 @@
     @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 @@
             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 @@
     @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 @@
                 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 0000000..d255a36
--- /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 0000000..d0f5b7f
--- /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 0000000..53175af
--- /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 0000000..8479e67
--- /dev/null
+++ b/test cases/common/275 required keyword in compiles functions/valid.c
@@ -0,0 +1,5 @@
+int
+main(void)
+{
+    return 0;
+}