Fix meson dist --include-subprojects with redirected wraps

When redirected wraps were used, mdist was searching for the source
directory in the subprojects directory of the root project. However, the
source for redirected wraps is actually located in some child
subprojects directory. Subsequently, this resulted in
a FileNotFoundError. By skipping redirected wraps as we collect
subprojects, we can effectively prevent the issue from occuring because
the authoritative subproject will be picked up and included in the dist.

Fixes: https://github.com/mesonbuild/meson/issues/12489
Signed-off-by: Tristan Partin <tristan@partin.io>
This commit is contained in:
Tristan Partin authored and Paolo Bonzini committed 2026-08-11 16:58:48 +02:00
1 parent 097b497d24
commit 66ab168e6f
3 files changed
+91 -4

No files matched your search

+5 -2
View File
@@ -399,8 +399,11 @@ def run(options: argparse.Namespace) -> int:
if options.include_subprojects:
resolver = wrap.Resolver(src_root, b.subproject_dir, silent=True)
for sub in set(itertools.chain(b.projects.host, b.projects.build)):
if sub:
directory = resolver.get_directory(sub)
if not sub:
continue
directory = resolver.get_directory(sub)
if directory is not None:
subprojects[sub] = os.path.join(b.subproject_dir, directory)
extra_meson_args.append('-Dwrap_mode=nodownload')
+4 -2
View File
@@ -520,9 +520,11 @@ class Resolver:
self.cargolocks.update(other_resolver.cargolocks)
self.loaded_dirs.add(subdir)
def get_directory(self, packagename: str) -> str:
def get_directory(self, packagename: str) -> T.Optional[str]:
wrap = self.wraps.get(packagename)
return wrap.directory if wrap else packagename
if wrap:
return None if wrap.redirected else wrap.directory
return packagename
def find_dep_provider(self, packagename: str) -> T.Tuple[T.Optional[str], T.Optional[str]]:
# Python's ini parser converts all key values to lowercase.
+82
View File
@@ -1675,6 +1675,88 @@ class AllPlatformTests(BasePlatformTests):
# fails sometimes.
pass
@skipIfNoExecutable('git')
def test_dist_nested_promoted_subproject(self):
'''
https://github.com/mesonbuild/meson/issues/12489
A subproject (e.g. glib) can itself depend on a nested subproject
(e.g. gvdb) whose sources are bundled inside the outer subproject's
own "subprojects" directory rather than the main project's. Meson
promotes the wrap file for such a nested subproject to the main
project's subprojects directory (writing a [wrap-redirect] wrap
there), but the actual source directory stays nested. `meson dist
--include-subprojects` used to look for the nested subproject
directly under the main project's subprojects directory (following
the promoted wrap file naively) and crashed with a FileNotFoundError
because that directory did not exist there.
'''
if self.backend is not Backend.ninja:
raise SkipTest('Dist is only supported with Ninja')
with tempfile.TemporaryDirectory() as project_dir:
with open(os.path.join(project_dir, 'meson.build'), 'w', encoding='utf-8') as f:
f.write(textwrap.dedent('''\
project('gtk', version : '1.0')
subproject('glib')
'''))
glib_dir = os.path.join(project_dir, 'subprojects', 'glib')
gvdb_dir = os.path.join(glib_dir, 'subprojects', 'gvdb')
os.makedirs(gvdb_dir)
# glib is a normal subproject with its own wrap file
with open(os.path.join(project_dir, 'subprojects', 'glib.wrap'), 'w', encoding='utf-8') as f:
f.write(textwrap.dedent('''\
[wrap-file]
directory = glib
'''))
# Only the main project and glib's wrap file are tracked by git;
# glib itself is *not* a git repository, mimicking a subproject
# fetched/extracted from a tarball
git_init(project_dir)
with open(os.path.join(glib_dir, 'meson.build'), 'w', encoding='utf-8') as f:
f.write(textwrap.dedent('''\
project('glib', version : '1.0')
subproject('gvdb')
'''))
# gvdb is bundled inside glib's own subprojects directory, with
# its sources already extracted there
with open(os.path.join(glib_dir, 'subprojects', 'gvdb.wrap'), 'w', encoding='utf-8') as f:
f.write(textwrap.dedent('''\
[wrap-file]
directory = gvdb
'''))
with open(os.path.join(gvdb_dir, 'meson.build'), 'w', encoding='utf-8') as f:
f.write(textwrap.dedent('''\
project('gvdb', version : '1.0')
'''))
self.init(project_dir)
# Configuring writes the promoted [wrap-redirect] wrap file
redirect_wrap = os.path.join(project_dir, 'subprojects', 'gvdb.wrap')
self.assertPathExists(redirect_wrap)
with open(redirect_wrap, encoding='utf-8') as f:
self.assertIn('[wrap-redirect]', f.read())
self._run(self.meson_command + ['dist', '--include-subprojects', '--no-tests',
'--formats', 'zip'],
workdir=self.builddir)
zip_distfile = os.path.join(self.distdir, 'gtk-1.0.zip')
self.assertPathExists(zip_distfile)
z = zipfile.ZipFile(zip_distfile)
names = set(z.namelist())
# gvdb must end up nested inside glib, where it really lives...
self.assertIn('gtk-1.0/subprojects/glib/subprojects/gvdb/meson.build', names)
# ... and must not be duplicated (or looked up) directly under the
# main project's subprojects directory.
self.assertNotIn('gtk-1.0/subprojects/gvdb/meson.build', names)
self.assertNotIn('gtk-1.0/subprojects/gvdb.wrap', names)
def create_dummy_subproject(self, project_dir, name):
path = os.path.join(project_dir, 'subprojects', name)
os.makedirs(path)