Repository: mesonbuild/meson
Issue category: Bug Report

Issue:
pkgconfig.relocatable in subproject default_options fails in parent project setup
**Describe the bug**
When setting up a parent project that includes a subproject with `pkgconfig.relocatable=true` in its `default_options`, meson setup fails in the parent project with the error:
```
subprojects/mysubprj/meson.build:1:0: ERROR: In subproject mysubprj: Unknown options: "mysubprj:pkgconfig.relocatable"
```

However, running meson setup directly within the subproject works without issue.
This change in behavior started with the following commit:
```
commit 6e200222957063819a00e3bf767ce28b7489c31f
Author: Jussi Pakkanen <jpakkane@gmail.com>
Date:   Sun Jul 14 19:33:41 2024 +0300

    Remove module type from OptionKey.
```

**To Reproduce**
In the parent project `meson.build`:
```
project('test', version : '1.0')
subproject('mysubprj')
```

In the subproject `meson.build`:
```
project('mysubprj', version : '1.0', default_options: ['pkgconfig.relocatable=true'])
```

Command-line steps:
```sh
cd test
meson setup build
# Error as shown above
cd test/subprojects/mysubprj
meson setup build
# Success
```

**Expected behavior**
The meson setup command in the parent project should successfully handle `pkgconfig.relocatable=true` in subproject `default_options` as it did in previous versions.

**system parameters**
* Is this a [cross build](https://mesonbuild.com/Cross-compilation.html) or just a plain native build (for the same computer)?
native
* what operating system (e.g. MacOS Catalina, Windows 10, CentOS 8.0, Ubuntu 18.04, etc.)
Ubuntu 24.04
* what Python version are you using e.g. 3.8.0
3.12.3
* what `meson --version`
1.6.0



Hints:
Can confirm when using [librsvg](https://gitlab.gnome.org/gnome/librsvg) as a wrap from within the GStreamer repository.

Patch:
diff --git a/mesonbuild/coredata.py b/mesonbuild/coredata.py
index 98656b27d9b0..c3e435271952 100644
--- a/mesonbuild/coredata.py
+++ b/mesonbuild/coredata.py
@@ -658,9 +658,20 @@ def set_options(self, opts_to_set: T.Dict[OptionKey, T.Any], subproject: str = '
             elif k.machine != MachineChoice.BUILD and not self.optstore.is_compiler_option(k):
                 unknown_options.append(k)
         if unknown_options:
-            unknown_options_str = ', '.join(sorted(str(s) for s in unknown_options))
-            sub = f'In subproject {subproject}: ' if subproject else ''
-            raise MesonException(f'{sub}Unknown options: "{unknown_options_str}"')
+            if subproject:
+                # The subproject may have top-level options that should be used
+                # when it is not a subproject. Ignore those for now. With option
+                # refactor they will get per-subproject values.
+                really_unknown = []
+                for uo in unknown_options:
+                    topkey = uo.evolve(subproject='')
+                    if topkey not in self.optstore:
+                        really_unknown.append(uo)
+                unknown_options = really_unknown
+            if unknown_options:
+                unknown_options_str = ', '.join(sorted(str(s) for s in unknown_options))
+                sub = f'In subproject {subproject}: ' if subproject else ''
+                raise MesonException(f'{sub}Unknown options: "{unknown_options_str}"')
 
         if not self.is_cross_build():
             dirty |= self.copy_build_options_from_regular_ones()


Test patch:
diff --git a/test cases/unit/123 pkgsubproj/meson.build b/test cases/unit/123 pkgsubproj/meson.build
new file mode 100644
index 000000000000..b4cf89fa0b6d
--- /dev/null
+++ b/test cases/unit/123 pkgsubproj/meson.build
@@ -0,0 +1,3 @@
+project('pkg_opt_test')
+
+subproject('sub')
diff --git a/test cases/unit/123 pkgsubproj/subprojects/sub/meson.build b/test cases/unit/123 pkgsubproj/subprojects/sub/meson.build
new file mode 100644
index 000000000000..99622b681cdd
--- /dev/null
+++ b/test cases/unit/123 pkgsubproj/subprojects/sub/meson.build
@@ -0,0 +1,1 @@
+project('subproject', default_options: 'pkgconfig.relocatable=true')
diff --git a/unittests/linuxliketests.py b/unittests/linuxliketests.py
index 1e9e38d1b323..c8d5a538df17 100644
--- a/unittests/linuxliketests.py
+++ b/unittests/linuxliketests.py
@@ -1863,3 +1863,7 @@ def test_complex_link_cases(self):
         self.assertIn('build t9-e1: c_LINKER t9-e1.p/main.c.o | libt9-s1.a libt9-s2.a libt9-s3.a\n', content)
         self.assertIn('build t12-e1: c_LINKER t12-e1.p/main.c.o | libt12-s1.a libt12-s2.a libt12-s3.a\n', content)
         self.assertIn('build t13-e1: c_LINKER t13-e1.p/main.c.o | libt12-s1.a libt13-s3.a\n', content)
+
+    def test_top_options_in_sp(self):
+        testdir = os.path.join(self.unit_test_dir, '123 pkgsubproj')
+        self.init(testdir)