From 28fcb7d7ba302c6e786642171b51f243bce3b44b Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 15 Jun 2026 11:55:56 +0200 Subject: [PATCH 1/4] vs2010backend: merge conditionals for build.GeneratedList Signed-off-by: Paolo Bonzini --- mesonbuild/backend/vs2010backend.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 139e31a4c436..449e9f903e09 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -170,10 +170,6 @@ def generate_genlist_for_target( generator_output_files: T.List[str], custom_target_include_dirs: T.List[str], custom_target_output_files: T.List[str]) -> None: - if isinstance(genlist, build.GeneratedList): - for x in genlist.depends: - self.generate_genlist_for_target(x, target, parent_node, [], [], []) - target_private_dir = self.relpath(self.get_target_private_dir(target), self.get_target_dir(target)) down = self.target_to_build_root(target) if isinstance(genlist, (build.CustomTarget, build.CustomTargetIndex)): for i in genlist.get_outputs(): @@ -184,6 +180,9 @@ def generate_genlist_for_target( if idir not in custom_target_include_dirs: custom_target_include_dirs.append(idir) else: + target_private_dir = self.relpath(self.get_target_private_dir(target), self.get_target_dir(target)) + for x in genlist.depends: + self.generate_genlist_for_target(x, target, parent_node, [], [], []) generator = genlist.get_generator() exe = generator.get_exe() infilelist = genlist.get_inputs() From 7dbff4fa9a74c92ff90f24bdb6756199536493d7 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Mon, 15 Jun 2026 11:58:18 +0200 Subject: [PATCH 2/4] vs2010backend: remove unnecessary recursion The only effect of generate_genlist_for_target on non-GeneratedLists is to add elements to the parameters, but those are ignored in this specific recursive call. Signed-off-by: Paolo Bonzini --- mesonbuild/backend/vs2010backend.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 449e9f903e09..86ae917cc2cc 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -182,7 +182,8 @@ def generate_genlist_for_target( else: target_private_dir = self.relpath(self.get_target_private_dir(target), self.get_target_dir(target)) for x in genlist.depends: - self.generate_genlist_for_target(x, target, parent_node, [], [], []) + if isinstance(x, build.GeneratedList): + self.generate_genlist_for_target(x, target, parent_node, [], [], []) generator = genlist.get_generator() exe = generator.get_exe() infilelist = genlist.get_inputs() From d7f0cba38ada02b650463613ba90654c60a9c669 Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 12 Jun 2026 12:38:08 +0200 Subject: [PATCH 3/4] vs2010backend: split generate_genlist_for_target in two generate_genlist_for_target works on all generated sources, and has a silly do-nothing recursion for custom targets when operating on genlist.depends. Clarify that the genlist.depends recursion only needs to do something if it finds another generator. The old code would need changes if BuildTargets were added to genlist.depends; the new one does not. Signed-off-by: Paolo Bonzini --- mesonbuild/backend/vs2010backend.py | 136 +++++++++++++++------------- 1 file changed, 73 insertions(+), 63 deletions(-) diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 86ae917cc2cc..e531350a8dbf 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -163,75 +163,85 @@ def detect_toolset(self) -> None: def get_target_private_dir(self, target: build.AnyTargetType) -> str: return os.path.join(self.get_target_dir(target), target.get_id()) - def generate_genlist_for_target( + def generate_gensrc_for_target( self, genlist: build.GeneratedTypes, target: build.BuildTarget | build.CustomTarget, parent_node: ET.Element, generator_output_files: T.List[str], custom_target_include_dirs: T.List[str], custom_target_output_files: T.List[str]) -> None: + if isinstance(genlist, build.GeneratedList): + self.generate_genlist_for_target(genlist, target, parent_node, generator_output_files) + return + down = self.target_to_build_root(target) - if isinstance(genlist, (build.CustomTarget, build.CustomTargetIndex)): - for i in genlist.get_outputs(): - # Path to the generated source from the current vcxproj dir via the build root - ipath = os.path.join(down, self.get_target_dir(genlist), i) - custom_target_output_files.append(ipath) - idir = self.relpath(self.get_target_dir(genlist), self.get_target_dir(target)) - if idir not in custom_target_include_dirs: - custom_target_include_dirs.append(idir) - else: - target_private_dir = self.relpath(self.get_target_private_dir(target), self.get_target_dir(target)) - for x in genlist.depends: - if isinstance(x, build.GeneratedList): - self.generate_genlist_for_target(x, target, parent_node, [], [], []) - generator = genlist.get_generator() - exe = generator.get_exe() - infilelist = genlist.get_inputs() - outfilelist = genlist.get_outputs() - source_dir = os.path.join(down, self.build_to_src, genlist.subdir) - idgroup = ET.SubElement(parent_node, 'ItemGroup') - samelen = len(infilelist) == len(outfilelist) - for i, curfile in enumerate(infilelist): - if samelen: - sole_output = os.path.join(target_private_dir, outfilelist[i]) - else: - sole_output = '' - infilename = os.path.join(down, curfile.rel_to_builddir(self.build_to_src, self.get_target_private_dir(target))) - deps = self.get_target_depend_files(genlist, True) - deps += self.get_paths_for_dep_outputs(target, genlist.extra_depends) - base_args = generator.get_arglist(infilename) - outfiles_rel = genlist.get_outputs_for(curfile) - outfiles = [os.path.join(target_private_dir, of) for of in outfiles_rel] - generator_output_files += outfiles - args = [x.replace("@INPUT@", infilename).replace('@OUTPUT@', sole_output) - for x in base_args] - args = self.replace_outputs(args, target_private_dir, outfiles_rel) - args = [x.replace("@SOURCE_DIR@", self.environment.get_source_dir()) - .replace("@BUILD_DIR@", target_private_dir) - for x in args] - args = [x.replace("@CURRENT_SOURCE_DIR@", source_dir) for x in args] - args = [x.replace("@SOURCE_ROOT@", self.environment.get_source_dir()) - .replace("@BUILD_ROOT@", self.environment.get_build_dir()) - for x in args] - args = [x.replace('\\', '/') for x in args] - # Always use a wrapper because MSBuild eats random characters when - # there are many arguments. - tdir_abs = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) - cmd, _ = self.as_meson_exe_cmdline( - exe, - self.replace_extra_args(args, genlist), - workdir=tdir_abs, - capture=outfiles[0] if generator.capture else None, - force_serialize=True, - env=genlist.env - ) - deps = cmd[-1:] + deps - abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) - os.makedirs(abs_pdir, exist_ok=True) - cbs = ET.SubElement(idgroup, 'CustomBuild', Include=infilename) - ET.SubElement(cbs, 'Command').text = ' '.join(self.quote_arguments(cmd)) - ET.SubElement(cbs, 'Outputs').text = ';'.join(outfiles) - ET.SubElement(cbs, 'AdditionalInputs').text = ';'.join(deps) + for i in genlist.get_outputs(): + # Path to the generated source from the current vcxproj dir via the build root + ipath = os.path.join(down, self.get_target_dir(genlist), i) + custom_target_output_files.append(ipath) + idir = self.relpath(self.get_target_dir(genlist), self.get_target_dir(target)) + if idir not in custom_target_include_dirs: + custom_target_include_dirs.append(idir) + + def generate_genlist_for_target( + self, genlist: build.GeneratedList, + target: build.BuildTarget | build.CustomTarget, + parent_node: ET.Element, + generator_output_files: T.List[str]) -> None: + for x in genlist.depends: + if isinstance(x, build.GeneratedList): + self.generate_genlist_for_target(x, target, parent_node, []) + + target_private_dir = self.relpath(self.get_target_private_dir(target), self.get_target_dir(target)) + down = self.target_to_build_root(target) + generator = genlist.get_generator() + exe = generator.get_exe() + infilelist = genlist.get_inputs() + outfilelist = genlist.get_outputs() + source_dir = os.path.join(down, self.build_to_src, genlist.subdir) + idgroup = ET.SubElement(parent_node, 'ItemGroup') + samelen = len(infilelist) == len(outfilelist) + for i, curfile in enumerate(infilelist): + if samelen: + sole_output = os.path.join(target_private_dir, outfilelist[i]) + else: + sole_output = '' + infilename = os.path.join(down, curfile.rel_to_builddir(self.build_to_src, self.get_target_private_dir(target))) + deps = self.get_target_depend_files(genlist, True) + deps += self.get_paths_for_dep_outputs(target, genlist.extra_depends) + base_args = generator.get_arglist(infilename) + outfiles_rel = genlist.get_outputs_for(curfile) + outfiles = [os.path.join(target_private_dir, of) for of in outfiles_rel] + generator_output_files += outfiles + args = [x.replace("@INPUT@", infilename).replace('@OUTPUT@', sole_output) + for x in base_args] + args = self.replace_outputs(args, target_private_dir, outfiles_rel) + args = [x.replace("@SOURCE_DIR@", self.environment.get_source_dir()) + .replace("@BUILD_DIR@", target_private_dir) + for x in args] + args = [x.replace("@CURRENT_SOURCE_DIR@", source_dir) for x in args] + args = [x.replace("@SOURCE_ROOT@", self.environment.get_source_dir()) + .replace("@BUILD_ROOT@", self.environment.get_build_dir()) + for x in args] + args = [x.replace('\\', '/') for x in args] + # Always use a wrapper because MSBuild eats random characters when + # there are many arguments. + tdir_abs = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) + cmd, _ = self.as_meson_exe_cmdline( + exe, + self.replace_extra_args(args, genlist), + workdir=tdir_abs, + capture=outfiles[0] if generator.capture else None, + force_serialize=True, + env=genlist.env + ) + deps = cmd[-1:] + deps + abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target)) + os.makedirs(abs_pdir, exist_ok=True) + cbs = ET.SubElement(idgroup, 'CustomBuild', Include=infilename) + ET.SubElement(cbs, 'Command').text = ' '.join(self.quote_arguments(cmd)) + ET.SubElement(cbs, 'Outputs').text = ';'.join(outfiles) + ET.SubElement(cbs, 'AdditionalInputs').text = ';'.join(deps) def generate_custom_generator_commands( self, target: build.BuildTarget | build.CustomTarget, parent_node: ET.Element @@ -240,7 +250,7 @@ def generate_custom_generator_commands( custom_target_include_dirs: list[str] = [] custom_target_output_files: list[str] = [] for genlist in target.get_generated_sources(): - self.generate_genlist_for_target(genlist, target, parent_node, generator_output_files, custom_target_include_dirs, custom_target_output_files) + self.generate_gensrc_for_target(genlist, target, parent_node, generator_output_files, custom_target_include_dirs, custom_target_output_files) return generator_output_files, custom_target_output_files, custom_target_include_dirs def generate(self, From b02868e3445e6a05ebb43ffa8dd93b1ffcf4e67e Mon Sep 17 00:00:00 2001 From: Paolo Bonzini Date: Fri, 12 Jun 2026 12:44:41 +0200 Subject: [PATCH 4/4] Allow buildtargets in generator's process function Co-authored-by: Jens Wolvers <13272708+JWolvers@users.noreply.github.com> --- docs/markdown/snippets/permit_generator_any_target.md | 3 +++ mesonbuild/build.py | 6 +++--- mesonbuild/interpreter/interpreter.py | 5 +++++ mesonbuild/interpreter/interpreterobjects.py | 9 +++++++-- test cases/common/105 generatorcustom/embedded.c | 3 +++ test cases/common/105 generatorcustom/gen.py | 5 +++-- test cases/common/105 generatorcustom/main.c | 2 +- test cases/common/105 generatorcustom/meson.build | 4 +++- 8 files changed, 28 insertions(+), 9 deletions(-) create mode 100644 docs/markdown/snippets/permit_generator_any_target.md create mode 100644 test cases/common/105 generatorcustom/embedded.c diff --git a/docs/markdown/snippets/permit_generator_any_target.md b/docs/markdown/snippets/permit_generator_any_target.md new file mode 100644 index 000000000000..cf3e1db10981 --- /dev/null +++ b/docs/markdown/snippets/permit_generator_any_target.md @@ -0,0 +1,3 @@ +## Allow using generator with any [[@build_tgt]] object + +Passing any build target to `generator.process()` is now allowed. diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 1d2745102767..872a21ae64d4 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -2005,7 +2005,7 @@ def get_arglist(self, inname: str) -> T.List[str]: basename = os.path.splitext(plainname)[0] return [x.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) for x in self.arglist] - def process_files(self, files: T.Iterable[TargetSources], + def process_files(self, files: T.Iterable[TargetSources | BuildTarget], subdir: str = '', preserve_path_from: T.Optional[str] = None, extra_args: T.Optional[T.List[str]] = None, @@ -2020,7 +2020,7 @@ def process_files(self, files: T.Iterable[TargetSources], extra_depends=list(extra_depends) if extra_depends is not None else []) for e in files: - if isinstance(e, (CustomTarget, CustomTargetIndex)): + if isinstance(e, (BuildTarget, CustomTarget, CustomTargetIndex)): output.depends.add(e) fs = [File.from_built_file(e.get_builddir(), f) for f in e.get_outputs()] elif isinstance(e, GeneratedList): @@ -2059,7 +2059,7 @@ class GeneratedList(HoldableObject): def __post_init__(self) -> None: self.name = self.generator.exe - self.depends: T.Set[GeneratedTypes] = set() + self.depends: T.Set[BuildTarget | GeneratedTypes] = set() self.infilelist: T.List[FileMaybeInTargetPrivateDir] = [] self.outfilelist: T.List[str] = [] self.outmap: T.Dict[FileMaybeInTargetPrivateDir, T.List[str]] = {} diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index 9c7dd243582e..89f9dd65fbe1 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -3256,6 +3256,9 @@ def source_strings_to_files(self, sources: list[kwtypes.BuildTargetObjects], # def source_strings_to_files(self, sources: list[T.Union[str, build.TargetSources, build.BuildTargetTypes, build.BothLibraries, build.ExtractedObjects]], # type: ignore[overload-overlap] ) -> list[T.Union[build.TargetSources, build.BuildTargetTypes, build.BothLibraries, build.ExtractedObjects]]: ... + @T.overload + def source_strings_to_files(self, sources: list[T.Union[str, build.BuildTarget, build.TargetSources]]) -> list[build.BuildTarget | build.TargetSources]: ... # type: ignore[overload-overlap] + @T.overload def source_strings_to_files(self, sources: list[T.Union[str, build.TargetSources, build.StructuredSources]], ) -> list[T.Union[build.TargetSources, build.StructuredSources]]: ... # noqa: F811 @@ -3274,6 +3277,7 @@ def source_strings_to_files(self, list[mesonlib.File | str | build.BuildTargetTypes], list[str | build.TargetSources], list[str | build.TargetSources | build.StructuredSources], + list[str | build.TargetSources | build.BuildTarget], list[kwtypes.CustomTargetInputs], list[mesonlib.File | str | build.BuildTargetTypes | build.BothLibraries | build.ExtractedObjects | build.GeneratedTypes], list[kwtypes.BuildTargetObjects], @@ -3285,6 +3289,7 @@ def source_strings_to_files(self, list[mesonlib.File | build.CustomTarget | build.CustomTargetIndex], list[build.TargetSources], list[build.TargetSources | build.StructuredSources], + list[build.TargetSources | build.BuildTarget], list[mesonlib.File | build.BuildTargetTypes | build.BothLibraries | build.ExtractedObjects | build.GeneratedTypes], list[build.ObjectTypes | build.GeneratedTypes], list[CustomTargetSources], diff --git a/mesonbuild/interpreter/interpreterobjects.py b/mesonbuild/interpreter/interpreterobjects.py index 6e9a25dbf8b0..8f7975d0c306 100644 --- a/mesonbuild/interpreter/interpreterobjects.py +++ b/mesonbuild/interpreter/interpreterobjects.py @@ -1228,7 +1228,7 @@ class GeneratorHolder(ObjectHolder[build.Generator]): def __init__(self, gen: build.Generator, interpreter: 'Interpreter'): super().__init__(gen, interpreter) - @typed_pos_args('generator.process', min_varargs=1, varargs=(str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) + @typed_pos_args('generator.process', min_varargs=1, varargs=(str, mesonlib.File, build.BuildTarget, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) @typed_kwargs( 'generator.process', KwargInfo('preserve_path_from', (str, NoneType), since='0.45.0'), @@ -1238,7 +1238,7 @@ def __init__(self, gen: build.Generator, interpreter: 'Interpreter'): ) @InterpreterObject.method('process') def process_method(self, - args: T.Tuple[T.List[str | build.TargetSources]], + args: T.Tuple[T.List[str | build.BuildTarget | build.TargetSources]], kwargs: 'kwargs.GeneratorProcess') -> build.GeneratedList: preserve_path_from = kwargs['preserve_path_from'] if preserve_path_from is not None: @@ -1252,6 +1252,11 @@ def process_method(self, 'Calling generator.process with CustomTarget or Index of CustomTarget.', '0.57.0', self.interpreter.subproject) + if any(isinstance(a, build.BuildTarget) for a in args[0]): + FeatureNew.single_use( + 'Calling generator.process with BuildTarget.', + '1.12.0', self.interpreter.subproject) + sources = self.interpreter.source_strings_to_files(args[0]) gl = self.held_object.process_files(sources, self.interpreter.subdir, preserve_path_from, extra_args=kwargs['extra_args'], env=kwargs['env'], diff --git a/test cases/common/105 generatorcustom/embedded.c b/test cases/common/105 generatorcustom/embedded.c new file mode 100644 index 000000000000..a51103c3c68e --- /dev/null +++ b/test cases/common/105 generatorcustom/embedded.c @@ -0,0 +1,3 @@ +int main(void) { + return 0; +} \ No newline at end of file diff --git a/test cases/common/105 generatorcustom/gen.py b/test cases/common/105 generatorcustom/gen.py index 8c3d6e4e0a47..524bf27d5f57 100755 --- a/test cases/common/105 generatorcustom/gen.py +++ b/test cases/common/105 generatorcustom/gen.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 +from pathlib import Path import sys ifile = sys.argv[1] ofile = sys.argv[2] -with open(ifile) as f: - resname = f.readline().strip() + +resname = Path(ifile).stem templ = 'const char %s[] = "%s";\n' with open(ofile, 'w', encoding='utf-8') as f: diff --git a/test cases/common/105 generatorcustom/main.c b/test cases/common/105 generatorcustom/main.c index 153dc12cba1b..e5a86b430c37 100644 --- a/test cases/common/105 generatorcustom/main.c +++ b/test cases/common/105 generatorcustom/main.c @@ -3,6 +3,6 @@ #include "alltogether.h" int main(void) { - printf("%s - %s - %s - %s\n", res1, res2, res3, res4); + printf("%s - %s - %s - %s - %s\n", res1, res2, res3, res4, embedded); return 0; } diff --git a/test cases/common/105 generatorcustom/meson.build b/test cases/common/105 generatorcustom/meson.build index dab55de4b4f7..40b74443845b 100644 --- a/test cases/common/105 generatorcustom/meson.build +++ b/test cases/common/105 generatorcustom/meson.build @@ -16,7 +16,9 @@ res4 = custom_target('gen-res4', output : 'res4.txt', command : [gen_resx, '@OUTPUT@', '4']) -hs = gen.process('res1.txt', 'res2.txt', res3, res4[0]) +embedded = executable('embedded', 'embedded.c') + +hs = gen.process('res1.txt', 'res2.txt', res3, res4[0], embedded) allinone = custom_target('alltogether', input : hs,