mintro: include per-subproject options in buildoptions

The configured-builddir path called the option listing without the set
of subprojects, so the per-subproject replication of non-yielding
builtin options only happened for source-only introspection and no
sub: rows were emitted at all. Augments (per-subproject values such as
-Dsub:c_args=...) were additionally never consulted, so even the
source-only rows reported the parent project's value.

Pass coredata.initialized_subprojects on the configured path, add
per-subproject rows for base and compiler options that have an augment
set, and resolve replicated keys through the option store so augments
and yielding are reflected in the reported value. Project options keep
their stored value as before.

Fixes #15993.
This commit is contained in:
Maxandre Ogeret authored and Paolo Bonzini committed 2026-09-23 19:31:56 +02:00
1 parent 0102669f6c
commit e6c4e8d040
7 files changed
+59 -11

No files matched your search

@@ -0,0 +1,8 @@
## Per-subproject options are included in buildoptions introspection
`meson introspect --buildoptions` and `meson-info/intro-buildoptions.json`
now contain rows for per-subproject options in configured build
directories: non-yielding builtin options get one row per subproject
(e.g. `sub:warning_level`), and options with a per-subproject value set
(e.g. via `-Dsub:c_args=...`) are reported with the value in effect for
that subproject.
+26 -8
View File
@@ -208,7 +208,7 @@ def list_targets(coredata: cdata.CoreData, builddata: build.Build, backend: back
return tlist
def list_buildoptions(coredata: cdata.CoreData, builddata: build.Build, backend: backends.Backend) -> T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]]:
return _list_buildoptions(coredata)
return _list_buildoptions(coredata, sorted(coredata.initialized_subprojects - {''}))
def list_buildoptions_from_source(intr: IntrospectionInterpreter) -> T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]]:
subprojects = [i['name'] for i in intr.project_data['subprojects']]
@@ -216,7 +216,20 @@ def list_buildoptions_from_source(intr: IntrospectionInterpreter) -> T.List[T.Di
def _list_buildoptions(coredata: cdata.CoreData, subprojects: T.Optional[T.List[str]] = None) -> T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]]:
optlist: T.List[T.Dict[str, T.Union[str, bool, int, T.List[str]]]] = []
subprojects = subprojects or []
subprojects = sorted(subprojects or [])
def add_subproject_keys(opts: options.MutableKeyedOptionDictType,
replicate_non_yielding: bool = False) -> options.MutableKeyedOptionDictType:
# Add a row per subproject for options that can differ from the
# top-level value there: non-yielding ones (if requested) and any
# with a per-subproject value (augment) actually set.
ret = dict(opts)
for k, v in opts.items():
for s in subprojects:
sk = k.evolve(subproject=s)
if (replicate_non_yielding and not v.yielding) or sk in coredata.optstore.augments:
ret[sk] = v
return ret
dir_option_names = set(options.BUILTIN_DIR_OPTIONS)
test_option_names = {OptionKey('errorlogs'),
@@ -232,13 +245,18 @@ def _list_buildoptions(coredata: cdata.CoreData, subprojects: T.Optional[T.List[
test_options[k] = v
elif coredata.optstore.is_builtin_option(k):
core_options[k] = v
if not v.yielding:
for s in subprojects:
core_options[k.evolve(subproject=s)] = v
core_options = add_subproject_keys(core_options, replicate_non_yielding=True)
def add_keys(opts: T.Union[options.MutableKeyedOptionDictType, options.OptionStore], section: str) -> None:
for key, opt in sorted(opts.items()):
optdict = {'name': str(key), 'value': opt.value, 'section': section,
# Replicated per-subproject keys resolve through augments and
# yielding to report the value in effect for that subproject.
# Project options keep their stored value as before.
if key.subproject and not coredata.optstore.is_project_option(key):
value = coredata.optstore.get_value_for(key)
else:
value = opt.value
optdict = {'name': str(key), 'value': value, 'section': section,
'machine': key.machine.get_lower_case_name() if coredata.optstore.is_per_machine_option(key) else 'any'}
if isinstance(opt, options.UserStringOption):
typestr = 'string'
@@ -269,9 +287,9 @@ def _list_buildoptions(coredata: cdata.CoreData, subprojects: T.Optional[T.List[
add_keys(core_options, 'core')
add_keys({k: v for k, v in coredata.optstore.items() if coredata.optstore.is_backend_option(k)}, 'backend')
add_keys({k: v for k, v in coredata.optstore.items() if coredata.optstore.is_base_option(k)}, 'base')
add_keys(add_subproject_keys({k: v for k, v in coredata.optstore.items() if coredata.optstore.is_base_option(k)}), 'base')
add_keys(
{k: v for k, v in sorted(coredata.optstore.items(), key=lambda i: i[0].machine) if coredata.optstore.is_compiler_option(k)},
add_subproject_keys({k: v for k, v in sorted(coredata.optstore.items(), key=lambda i: i[0].machine) if coredata.optstore.is_compiler_option(k)}),
'compiler',
)
add_keys(dir_options, 'directory')
@@ -0,0 +1 @@
0fd8007dd44a1a5eb5c01af4c138f0993c6cb44da194b36db04484212eff591b
@@ -0,0 +1,3 @@
project('subsubsub')
meson.override_dependency('subsubsub', declare_dependency())
@@ -0,0 +1,2 @@
[wrap-redirect]
filename = sub_implicit/subprojects/subsub/subprojects/subsubsub.wrap
+18
View File
@@ -3781,6 +3781,24 @@ class AllPlatformTests(BasePlatformTests):
self.assertIn('c_args', optnames)
self.assertNotIn('build.c_args', optnames)
def test_introspect_buildoptions_subproject_augments(self):
testdir = os.path.join(self.unit_test_dir, '47 reconfigure')
self.init(testdir, extra_args=['-Dsub1:c_args=-DAUG', '-Dsub1:werror=true'])
res = self.introspect('--buildoptions')
opts = {o['name']: o for o in res}
self.assertEqual(opts['sub1:c_args']['value'], ['-DAUG'])
self.assertEqual(opts['sub1:werror']['value'], True)
# Non-yielding builtins get a row per subproject.
self.assertIn('sub1:warning_level', opts)
# The generated intro file matches the introspect command.
infofile = os.path.join(self.builddir, 'meson-info', 'intro-buildoptions.json')
with open(infofile, encoding='utf-8') as fp:
self.assertListEqual(json.load(fp), res)
# Source-only introspection takes the same path and must not crash.
testfile = os.path.join(testdir, 'meson.build')
res_nb = self.introspect_directory(testfile, ['--buildoptions'] + self.meson_args)
self.assertIn('sub1:warning_level', {o['name'] for o in res_nb})
def test_introspect_json_flat(self):
testdir = os.path.join(self.unit_test_dir, '56 introspection')
self.init(testdir, extra_args=['-Dlayout=flat'])
+1 -3
View File
@@ -583,9 +583,7 @@ class NativeFileTests(BasePlatformTests):
elif each['name'] == 'sub:default_library':
self.assertEqual(each['value'], 'static')
found += 1
# FIXME: check that the subproject option has beeb added
# into augments.
self.assertEqual(found, 2, 'Did not find all two sections')
self.assertEqual(found, 3, 'Did not find all three sections')
def test_builtin_options_machinefile_overrides_subproject(self):
# The buildfile says subproject(... default_library: static), the machinefile overrides it