Repository: mesonbuild/meson

Issue:
[1.8] Cannot set per-project warning_level&werror from the parent project's default_options
Overriding the warning level and -Werror in select subprojects from the parent `project(default_options: [...])` broke in Meson 1.8.

```meson
project('test', 'c',
  version: 'unknown',
  meson_version: '>= 1.7',
  default_options: [
    'warning_level=2',
    'werror=true',
    'subprj:my_option=disabled',
    'subprj:warning_level=0',
    'subprj:werror=false',
  ])
```

Meson 1.7.2: no warnings nor errors are generated.

Meson 1.8:
```
[1/2] Compiling C object subprojects/subprj/libfoo.so.p/foo.c.o
FAILED: subprojects/subprj/libfoo.so.p/foo.c.o 
cc -Isubprojects/subprj/libfoo.so.p -Isubprojects/subprj -I../subprojects/subprj -fdiagnostics-color=always -D_FILE_OFFSET_BITS=64 -Wall -Winvalid-pch -Wextra -Werror -O0 -g -fPIC -MD -MQ subprojects/subprj/libfoo.so.p/foo.c.o -MF subprojects/subprj/libfoo.so.p/foo.c.o.d -o subprojects/subprj/libfoo.so.p/foo.c.o -c ../subprojects/subprj/foo.c
../subprojects/subprj/foo.c: In function ‘main’:
../subprojects/subprj/foo.c:2:7: error: unused variable ‘unused’ [-Werror=unused-variable]
    2 |   int unused = 42;
      |       ^~~~~~
cc1: all warnings being treated as errors
```

I've setup a minimal project that duplicates this at https://github.com/cfillion/test_meson_1.8.

Overriding via the command line instead of within meson.build works as expected (`meson setup build -Dsubprj:warning_level=0`. Same with `werror=false`.
Issue with default options for dependencies with 1.8.0
**Describe the bug**
With the new 1.8.0 version of meson a type error occurs with the default options for dependencies (prior versions still work). The issue is reproducible for several projects via the github CI independent of the operating system or compilers: 
[grimme-lab/mctc-lib](https://github.com/thfroitzheim/mctc-lib/actions/runs/14749385776/job/41403047038)
[tblite/tblite](https://github.com/tblite/tblite/actions/runs/14729880367/job/41341306363)

Here is one sample error message: 
```
  meson setup _build --buildtype=debug --prefix=$PWD/_dist --libdir=lib --warnlevel=0 -Db_coverage=false 
  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
  env:
    BUILD_DIR: _build
    PIP_PACKAGES: meson cmake ninja gcovr
    LINUX_INTEL_COMPONENTS: intel-oneapi-compiler-fortran-2021.2.0 intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic-2021.2.0 intel-oneapi-mkl-2021.2.0 intel-oneapi-mkl-devel-2021.2.0
    FC: gfortran
    CC: gcc
    GCC_V: 10
    PYTHON_V: 3.9
    OMP_NUM_THREADS: 1,2,1
    pythonLocation: /opt/hostedtoolcache/Python/3.9.22/x64
    PKG_CONFIG_PATH: /opt/hostedtoolcache/Python/3.9.22/x64/lib/pkgconfig
    Python_ROOT_DIR: /opt/hostedtoolcache/Python/3.9.22/x64
    Python2_ROOT_DIR: /opt/hostedtoolcache/Python/3.9.22/x64
    Python3_ROOT_DIR: /opt/hostedtoolcache/Python/3.9.22/x64
    LD_LIBRARY_PATH: /opt/hostedtoolcache/Python/3.9.22/x64/lib
    COVERAGE: false
    MESON_ARGS: 
  
Traceback (most recent call last):
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/mesonmain.py", line 193, in run
    return options.run_func(options)
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/msetup.py", line 391, in run
    app.generate()
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/msetup.py", line 189, in generate
    return self._generate(env, capture, vslite_ctx)
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/msetup.py", line 250, in _generate
    intr.run()
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreter/interpreter.py", line 3044, in run
    super().run()
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreterbase/interpreterbase.py", line 178, in run
    self.evaluate_codeblock(self.ast, start=1)
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreterbase/interpreterbase.py", line 203, in evaluate_codeblock
    raise e
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreterbase/interpreterbase.py", line 195, in evaluate_codeblock
    self.evaluate_statement(cur)
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreterbase/interpreterbase.py", line 209, in evaluate_statement
    return self.function_call(cur)
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreterbase/interpreterbase.py", line 536, in function_call
    res = func(node, func_args, kwargs)
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreterbase/decorators.py", line 237, in wrapper
    return f(*nargs, **wrapped_kwargs)
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreterbase/decorators.py", line 556, in wrapper
    return f(*wrapped_args, **wrapped_kwargs)
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreter/interpreter.py", line 2458, in func_subdir
    if not self._evaluate_subdir(self.environment.get_source_dir(), subdir):
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreterbase/interpreterbase.py", line 742, in _evaluate_subdir
    self.evaluate_codeblock(codeblock)
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreterbase/interpreterbase.py", line 203, in evaluate_codeblock
    raise e
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreterbase/interpreterbase.py", line 195, in evaluate_codeblock
    self.evaluate_statement(cur)
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreterbase/interpreterbase.py", line 213, in evaluate_statement
    self.assignment(cur)
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreterbase/interpreterbase.py", line 650, in assignment
    value = self.evaluate_statement(node.value)
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreterbase/interpreterbase.py", line 209, in evaluate_statement
    return self.function_call(cur)
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreterbase/interpreterbase.py", line 536, in function_call
    res = func(node, func_args, kwargs)
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreterbase/decorators.py", line 806, in wrapped
    return f(*wrapped_args, **wrapped_kwargs)
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreterbase/decorators.py", line 806, in wrapped
    return f(*wrapped_args, **wrapped_kwargs)
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreterbase/decorators.py", line 806, in wrapped
    return f(*wrapped_args, **wrapped_kwargs)
  [Previous line repeated 4 more times]
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreterbase/decorators.py", line 85, in wrapped
    ret = f(*wrapped_args, **wrapped_kwargs)
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreterbase/decorators.py", line 103, in wrapped
    return f(*wrapped_args, **wrapped_kwargs)
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreterbase/decorators.py", line 237, in wrapper
    return f(*nargs, **wrapped_kwargs)
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreterbase/decorators.py", line 556, in wrapper
    return f(*wrapped_args, **wrapped_kwargs)
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreter/interpreter.py", line 1795, in func_dependency
    d = df.lookup(kwargs)
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreter/dependencyfallbacks.py", line 365, in lookup
    dep = func(kwargs, func_args, func_kwargs)
  File "/opt/hostedtoolcache/Python/3.9.22/x64/lib/python3.9/site-packages/mesonbuild/interpreter/dependencyfallbacks.py", line 132, in _do_subproject
    default_options['default_library'] = default_library
TypeError: list indices must be integers or slices, not str
The Meson build system
Version: 1.8.0
Source dir: /home/runner/work/mctc-lib/mctc-lib
Build dir: /home/runner/work/mctc-lib/mctc-lib/_build
Build type: native build
Project name: mctc-lib
Project version: 0.4.1
Fortran compiler for the host machine: gfortran (gcc 10.5.0 "GNU Fortran (Ubuntu 10.5.0-4ubuntu2) 10.5.0")
Fortran linker for the host machine: gfortran ld.bfd 2.42
Host machine cpu family: x86_64
Host machine cpu: x86_64
Found pkg-config: YES (/usr/bin/pkg-config) 1.8.1
Found CMake: /opt/hostedtoolcache/Python/3.9.22/x64/bin/cmake (4.0.0)
Run-time dependency json-fortran found: NO (tried pkgconfig and cmake)
Looking for a fallback subproject for the dependency json-fortran
Building fallback subproject with default_library=static
config/meson.build:55:18: ERROR: Unhandled python exception
    This is a Meson bug and should be reported!
```
As far as I can tell, the issue arises from dependency declarations such as this:
```
jsonfortran_dep = dependency(
  'json-fortran',
  required: get_option('json'),
  fallback: ['json-fortran-8.2.5','jsonfortran_dep'],
  default_options: [
    'default_library=static',
  ],
  static: get_option('default_library') != 'dynamic',
)
lib_deps += jsonfortran_dep
```
 
**To Reproduce**
For example with grimme-lab/mctc-lib
```
git clone git@github.com:grimme-lab/mctc-lib.git
meson setup _build
```

**Expected behavior**
Meson should still find the dependencies. 

**system parameters**
* Is this a [cross build](https://mesonbuild.com/Cross-compilation.html) or just a plain native build (for the same computer)? No
* what operating system (e.g. MacOS Catalina, Windows 10, CentOS 8.0, Ubuntu 18.04, etc.) Ubuntu 24.04.2 or macOS 14.7.5
* what Python version are you using e.g. 3.9
* what `meson --version` 1.8.0
* what `ninja --version` if it's a Ninja build 1.11.1.4



Discussion:


Here is a minimal reproducer: [gh-14532.zip](https://github.com/user-attachments/files/20013158/gh-14532.zip)



Patch:
diff --git a/mesonbuild/interpreter/dependencyfallbacks.py b/mesonbuild/interpreter/dependencyfallbacks.py
index 0ebfe3bd578f..53eeb2911cba 100644
--- a/mesonbuild/interpreter/dependencyfallbacks.py
+++ b/mesonbuild/interpreter/dependencyfallbacks.py
@@ -4,14 +4,12 @@
 
 from __future__ import annotations
 
-import copy
-
 from .interpreterobjects import extract_required_kwarg
 from .. import mlog
 from .. import dependencies
 from .. import build
 from ..wrap import WrapMode
-from ..mesonlib import extract_as_list, stringlistify, version_compare_many, listify
+from ..mesonlib import extract_as_list, stringlistify, version_compare_many
 from ..options import OptionKey
 from ..dependencies import Dependency, DependencyException, NotFoundDependency
 from ..interpreterbase import (MesonInterpreterObject, FeatureNew,
@@ -124,21 +122,17 @@ def _do_subproject(self, kwargs: TYPE_nkwargs, func_args: TYPE_nvar, func_kwargs
         # dependency('foo', static: true) should implicitly add
         # default_options: ['default_library=static']
         static = kwargs.get('static')
-        default_options = func_kwargs.get('default_options', {})
-        if static is not None and 'default_library' not in default_options:
+        extra_default_options = {}
+        if static is not None:
             default_library = 'static' if static else 'shared'
             mlog.log(f'Building fallback subproject with default_library={default_library}')
-            default_options = copy.copy(default_options)
-            default_options['default_library'] = default_library
-            func_kwargs['default_options'] = default_options
+            extra_default_options['default_library'] = default_library
 
         # Configure the subproject
         subp_name = self.subproject_name
         varname = self.subproject_varname
         func_kwargs.setdefault('version', [])
-        if 'default_options' in kwargs and isinstance(kwargs['default_options'], str):
-            func_kwargs['default_options'] = listify(kwargs['default_options'])
-        self.interpreter.do_subproject(subp_name, func_kwargs)
+        self.interpreter.do_subproject(subp_name, func_kwargs, extra_default_options=extra_default_options)
         return self._get_subproject_dep(subp_name, varname, kwargs)
 
     def _get_subproject(self, subp_name: str) -> T.Optional[SubprojectHolder]:
diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py
index abdc8899d8f6..8fb660d1f078 100644
--- a/mesonbuild/interpreter/interpreter.py
+++ b/mesonbuild/interpreter/interpreter.py
@@ -296,9 +296,8 @@ def __init__(
         self.configure_file_outputs: T.Dict[str, int] = {}
         # Passed from the outside, only used in subprojects.
         if default_project_options:
-            self.default_project_options = default_project_options if isinstance(default_project_options, str) else default_project_options.copy()
-            if isinstance(default_project_options, dict):
-                pass
+            assert isinstance(default_project_options, dict)
+            self.default_project_options = default_project_options
         else:
             self.default_project_options = {}
         self.project_default_options: T.List[str] = []
@@ -868,7 +867,8 @@ def disabled_subproject(self, subp_name: str, disabled_feature: T.Optional[str]
         self.subprojects[subp_name] = sub
         return sub
 
-    def do_subproject(self, subp_name: str, kwargs: kwtypes.DoSubproject, force_method: T.Optional[wrap.Method] = None) -> SubprojectHolder:
+    def do_subproject(self, subp_name: str, kwargs: kwtypes.DoSubproject, force_method: T.Optional[wrap.Method] = None,
+                      extra_default_options: T.Optional[T.Dict[str, options.ElementaryOptionValues]] = None) -> SubprojectHolder:
         if subp_name == 'sub_static':
             pass
         disabled, required, feature = extract_required_kwarg(kwargs, self.subproject)
@@ -878,6 +878,12 @@ def do_subproject(self, subp_name: str, kwargs: kwtypes.DoSubproject, force_meth
             return self.disabled_subproject(subp_name, disabled_feature=feature)
 
         default_options = kwargs['default_options']
+        if isinstance(default_options, str):
+            default_options = [default_options]
+        if isinstance(default_options, list):
+            default_options = dict((x.split('=', 1) for x in default_options))
+        if extra_default_options:
+            default_options = {**extra_default_options, **default_options}
 
         if subp_name == '':
             raise InterpreterException('Subproject name must not be empty.')
@@ -952,7 +958,7 @@ def do_subproject(self, subp_name: str, kwargs: kwtypes.DoSubproject, force_meth
             raise e
 
     def _do_subproject_meson(self, subp_name: str, subdir: str,
-                             default_options: T.List[str],
+                             default_options: T.Dict[str, options.ElementaryOptionValues],
                              kwargs: kwtypes.DoSubproject,
                              ast: T.Optional[mparser.CodeBlockNode] = None,
                              build_def_files: T.Optional[T.List[str]] = None,
@@ -1012,7 +1018,7 @@ def _do_subproject_meson(self, subp_name: str, subdir: str,
         return self.subprojects[subp_name]
 
     def _do_subproject_cmake(self, subp_name: str, subdir: str,
-                             default_options: T.List[str],
+                             default_options: T.Dict[str, options.ElementaryOptionValues],
                              kwargs: kwtypes.DoSubproject) -> SubprojectHolder:
         from ..cmake import CMakeInterpreter
         with mlog.nested(subp_name):
@@ -1039,7 +1045,7 @@ def _do_subproject_cmake(self, subp_name: str, subdir: str,
         return result
 
     def _do_subproject_cargo(self, subp_name: str, subdir: str,
-                             default_options: T.List[str],
+                             default_options: T.Dict[str, options.ElementaryOptionValues],
                              kwargs: kwtypes.DoSubproject) -> SubprojectHolder:
         from .. import cargo
         FeatureNew.single_use('Cargo subproject', '1.3.0', self.subproject, location=self.current_node)
diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py
index fb34bbbce9c8..d741aabc5583 100644
--- a/mesonbuild/interpreter/kwargs.py
+++ b/mesonbuild/interpreter/kwargs.py
@@ -321,7 +321,7 @@ class Subproject(ExtractRequired):
 
 class DoSubproject(ExtractRequired):
 
-    default_options: T.List[str]
+    default_options: T.Union[T.List[str], T.Dict[str, options.ElementaryOptionValues], str]
     version: T.List[str]
     cmake_options: T.List[str]
     options: T.Optional[CMakeSubprojectOptions]
diff --git a/mesonbuild/options.py b/mesonbuild/options.py
index 3b7d8b2c0825..f897916c7efb 100644
--- a/mesonbuild/options.py
+++ b/mesonbuild/options.py
@@ -1338,7 +1338,8 @@ def initialize_from_top_level_project_call(self,
             if not self.is_cross and key.is_for_build():
                 continue
             if key.subproject:
-                self.pending_options[key] = valstr
+                augstr = str(key)
+                self.augments[augstr] = valstr
             elif key in self.options:
                 self.set_option(key, valstr, first_invocation)
             else:
@@ -1362,7 +1363,8 @@ def initialize_from_top_level_project_call(self,
             if not self.is_cross and key.is_for_build():
                 continue
             if key.subproject:
-                self.pending_options[key] = valstr
+                augstr = str(key)
+                self.augments[augstr] = valstr
             elif key in self.options:
                 self.set_option(key, valstr, True)
             else:
@@ -1392,36 +1394,36 @@ def validate_cmd_line_options(self, cmd_line_options: OptionStringLikeDict) -> N
             keys = ', '.join(unknown_options)
             raise MesonException(f'Unknown options: {keys}')
 
-    def hacky_mchackface_back_to_list(self, optdict: T.Dict[str, str]) -> T.List[str]:
+    def hacky_mchackface_back_to_list(self, optdict: T.Union[T.List[str], OptionStringLikeDict]) -> T.List[str]:
         if isinstance(optdict, dict):
             return [f'{k}={v}' for k, v in optdict.items()]
         return optdict
 
     def initialize_from_subproject_call(self,
                                         subproject: str,
-                                        spcall_default_options: T.Union[T.List[str], OptionStringLikeDict],
-                                        project_default_options: T.Union[T.List[str], OptionStringLikeDict],
-                                        cmd_line_options: T.Union[T.List[str], OptionStringLikeDict]) -> None:
+                                        spcall_default_options_in: OptionStringLikeDict,
+                                        project_default_options_in: T.Union[T.List[str], OptionStringLikeDict],
+                                        cmd_line_options: OptionStringLikeDict) -> None:
         is_first_invocation = True
-        spcall_default_options = self.hacky_mchackface_back_to_list(spcall_default_options) # type: ignore [arg-type]
-        project_default_options = self.hacky_mchackface_back_to_list(project_default_options) # type: ignore [arg-type]
-        if isinstance(spcall_default_options, str):
-            spcall_default_options = [spcall_default_options]
+        spcall_default_options = self.hacky_mchackface_back_to_list(spcall_default_options_in)
+        project_default_options = self.hacky_mchackface_back_to_list(project_default_options_in)
         for o in itertools.chain(project_default_options, spcall_default_options):
             keystr, valstr = o.split('=', 1)
             key = OptionKey.from_string(keystr)
-            assert key.subproject is None
-            key = key.evolve(subproject=subproject)
+            if key.subproject is None:
+                key = key.evolve(subproject=subproject)
+            elif key.subproject == subproject:
+                without_subp = key.evolve(subproject=None)
+                raise MesonException(f'subproject name not needed in default_options; use "{without_subp}" instead of "{key}"')
             # If the key points to a project option, set the value from that.
             # Otherwise set an augment.
             if key in self.project_options:
                 self.set_option(key, valstr, is_first_invocation)
             else:
                 self.pending_options.pop(key, None)
-                aug_str = f'{subproject}:{keystr}'
+                aug_str = str(key)
                 self.augments[aug_str] = valstr
         # Check for pending options
-        assert isinstance(cmd_line_options, dict)
         for key, valstr in cmd_line_options.items(): # type: ignore [assignment]
             if not isinstance(key, OptionKey):
                 key = OptionKey.from_string(key)


Test patch:
diff --git a/test cases/common/281 subproj options/meson.build b/test cases/common/281 subproj options/meson.build
index 55fb109a3eec..d450004900a8 100644
--- a/test cases/common/281 subproj options/meson.build
+++ b/test cases/common/281 subproj options/meson.build
@@ -1,4 +1,3 @@
-project('pkg_opt_test')
+project('pkg_opt_test', default_options: ['werror=false', 'sub:from_toplevel=true', 'sub:werror=true'])
 
-subproject('sub')
-subproject('sub2')
+subproject('sub', default_options: ['sub2:from_subp=true'])
diff --git a/test cases/common/281 subproj options/subprojects/sub/meson.build b/test cases/common/281 subproj options/subprojects/sub/meson.build
index 82cd38665e38..6cc4906cc71c 100644
--- a/test cases/common/281 subproj options/subprojects/sub/meson.build
+++ b/test cases/common/281 subproj options/subprojects/sub/meson.build
@@ -1,8 +1,12 @@
 project('subproject', 'c')
 assert(get_option('bar') == true)
+assert(get_option('werror') == true)
+assert(get_option('from_toplevel') == true)
 
 # b_lto is only initialized if used, see test "common/40 options"
 cc = meson.get_compiler('c')
 if cc.get_id() in ['gcc', 'clang', 'clang-cl']
   assert(get_option('b_lto') == true)
 endif
+
+subproject('sub2')
diff --git a/test cases/common/281 subproj options/subprojects/sub/meson_options.txt b/test cases/common/281 subproj options/subprojects/sub/meson_options.txt
index 129a7d4a07a4..7f94d02cc60a 100644
--- a/test cases/common/281 subproj options/subprojects/sub/meson_options.txt
+++ b/test cases/common/281 subproj options/subprojects/sub/meson_options.txt
@@ -1,1 +1,2 @@
 option('bar', type: 'boolean', value: false)
+option('from_toplevel', type: 'boolean', value: false)
diff --git a/test cases/common/281 subproj options/subprojects/sub2/meson.build b/test cases/common/281 subproj options/subprojects/sub2/meson.build
index 3b0ed92c3f94..65f3e5af0501 100644
--- a/test cases/common/281 subproj options/subprojects/sub2/meson.build
+++ b/test cases/common/281 subproj options/subprojects/sub2/meson.build
@@ -1,5 +1,7 @@
 project('subproject', 'c')
 
+assert(get_option('from_subp') == true)
+
 # b_lto is only initialized if used, see test "common/40 options"
 cc = meson.get_compiler('c')
 if cc.get_id() in ['gcc', 'clang', 'clang-cl']
diff --git a/test cases/common/281 subproj options/subprojects/sub2/meson_options.txt b/test cases/common/281 subproj options/subprojects/sub2/meson_options.txt
new file mode 100644
index 000000000000..d645182a2c45
--- /dev/null
+++ b/test cases/common/281 subproj options/subprojects/sub2/meson_options.txt
@@ -0,0 +1,1 @@
+option('from_subp', type: 'boolean', value: false)
diff --git a/test cases/common/98 subproject subdir/meson.build b/test cases/common/98 subproject subdir/meson.build
index d2bafedf5119..5d92772c1354 100644
--- a/test cases/common/98 subproject subdir/meson.build
+++ b/test cases/common/98 subproject subdir/meson.build
@@ -83,7 +83,7 @@ d = dependency('subsubsub')
 assert(d.found(), 'Should be able to fallback to sub-sub-subproject')
 
 # Verify that `static: true` implies 'default_library=static'.
-d = dependency('sub_static', static: true)
+d = dependency('sub_static', static: true, default_options: ['bar=true'])
 assert(d.found())
 # Verify that when not specifying static kwarg we can still get fallback dep.
 d = dependency('sub_static')
diff --git a/test cases/common/98 subproject subdir/subprojects/sub_static/meson.build b/test cases/common/98 subproject subdir/subprojects/sub_static/meson.build
index 6c00623a1d22..8de7cb40674a 100644
--- a/test cases/common/98 subproject subdir/subprojects/sub_static/meson.build
+++ b/test cases/common/98 subproject subdir/subprojects/sub_static/meson.build
@@ -1,6 +1,7 @@
 project('sub_static')
 
 assert(get_option('default_library') == 'static')
+assert(get_option('bar') == true)
 meson.override_dependency('sub_static', declare_dependency())
 meson.override_dependency('sub_static2', declare_dependency(), static: true)
 meson.override_dependency('sub_static3', declare_dependency(variables: {'static': 'true'}), static: true)
diff --git a/test cases/common/98 subproject subdir/subprojects/sub_static/meson_options.txt b/test cases/common/98 subproject subdir/subprojects/sub_static/meson_options.txt
new file mode 100644
index 000000000000..129a7d4a07a4
--- /dev/null
+++ b/test cases/common/98 subproject subdir/subprojects/sub_static/meson_options.txt
@@ -0,0 +1,1 @@
+option('bar', type: 'boolean', value: false)