diff --git a/docs/markdown/snippets/embed_directories.md b/docs/markdown/snippets/embed_directories.md new file mode 100644 index 000000000000..5c59eb955c1f --- /dev/null +++ b/docs/markdown/snippets/embed_directories.md @@ -0,0 +1,4 @@ +## BuildTarget now has an `embed_directories` keyword argument + +This provides an include_directories like path for the c23 and c++26 `#embed` +preprocessor directive. diff --git a/docs/yaml/functions/_build_target_base.yaml b/docs/yaml/functions/_build_target_base.yaml index b8612569d04b..b63c064ef2d3 100644 --- a/docs/yaml/functions/_build_target_base.yaml +++ b/docs/yaml/functions/_build_target_base.yaml @@ -84,6 +84,14 @@ kwargs: These may only be static sources. + embed_directories: + type: array[inc | str] + since: 1.13.0 + description: | + one or more objects created with the [[include_directories]] function, or + strings, which will be implicitly convert to [[@inc]] objects, that is + used for the #embed preprocessor directive. + gui_app: type: bool deprecated: 0.56.0 diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 520f6cf04ec9..f6b55fd632e9 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -3181,6 +3181,12 @@ def _generate_single_compile_target_args(self, target: build.BuildTarget, compil commands += bargs for d in i.extra_build_dirs: commands += compiler.get_include_args(d, i.is_system) + + # Add any embed search dir arguments + for i in target.embed_dirs: + for incdir in i.rel_string_list(self.build_to_src, self.build_dir): + commands.extend(compiler.get_embed_args(incdir)) + # Add per-target compile args, f.ex, `c_args : ['-DFOO']`. We set these # near the end since these are supposed to override everything else. commands += self.escape_extra_args(target.get_extra_args(compiler.get_language())) diff --git a/mesonbuild/build.py b/mesonbuild/build.py index 177705038739..093d6a1085c3 100644 --- a/mesonbuild/build.py +++ b/mesonbuild/build.py @@ -90,6 +90,7 @@ class BuildTargetKeywordArguments(TypedDict, total=False): d_unittest: bool dependencies: T.List[dependencies.Dependency] depend_files: T.List[File] + embed_directories: list[IncludeDirs] extra_files: T.List[File] gnu_symbol_visibility: Literal['default', 'internal', 'hidden', 'protected', 'inlineshidden', ''] implicit_include_directories: bool @@ -841,6 +842,8 @@ def __init__( self.structured_sources = structured_sources self.external_deps: T.List[dependencies.Dependency] = [] self.include_dirs: T.List['IncludeDirs'] = [] + # Use .get for embed_dirs because this is inherited in Java, which doesn't have embed_directories + self.embed_dirs = kwargs.get('embed_directories', []).copy() self.link_language: T.Optional[Language] = kwargs.get('link_language') self.link_targets: T.List[LinkableTargetTypes] = [] self.link_whole_targets: T.List[StaticTargetTypes] = [] @@ -1565,6 +1568,7 @@ def add_deps(self, deps: T.List[dependencies.Dependency]) -> None: self.process_sourcelist(dep.sources) self.extra_files.extend(f for f in dep.extra_files if f not in self.extra_files) self.add_include_dirs(dep.get_include_dirs()) + self.embed_dirs.extend(dep.embed_directories) self.objects.extend(dep.objects) self.link_targets.extend(dep.libraries) self.link_whole_targets.extend(dep.whole_libraries) diff --git a/mesonbuild/compilers/c.py b/mesonbuild/compilers/c.py index 2d88082beb80..19a32d9e4297 100644 --- a/mesonbuild/compilers/c.py +++ b/mesonbuild/compilers/c.py @@ -279,6 +279,10 @@ def get_option_link_args(self, target: 'BuildTarget', subproject: T.Optional[str def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: return ['-fpch-preprocess', '-include', os.path.basename(header)] + def get_embed_args(self, path: str) -> list[str]: + # Requires C23 or C++26 standard and GCC 15 + return [f'--embed-dir={path}'] + class PGICCompiler(PGICompiler, CCompiler): def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 9afed45e0b05..571eb48de515 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -1717,3 +1717,7 @@ def get_target_libdir(self) -> str: def get_show_dep_args(self) -> T.List[str]: """Arguments for printing depfile information in MSVC compatible format""" return [] + + def get_embed_args(self, path: str) -> list[str]: + """Format arguments for C and C++ #embed statements.""" + raise EnvironmentException(f'{self.get_id()} does not support embed search paths') diff --git a/mesonbuild/compilers/cpp.py b/mesonbuild/compilers/cpp.py index 7927d4a80568..397ac9cc5ee5 100644 --- a/mesonbuild/compilers/cpp.py +++ b/mesonbuild/compilers/cpp.py @@ -550,6 +550,10 @@ def get_pch_use_args(self, pch_dir: str, header: str) -> T.List[str]: def get_cpp_modules_args(self) -> T.List[str]: return ['-fmodules', '-fmodules-ts'] + def get_embed_args(self, path: str) -> list[str]: + # Requires C23 or C++26 standard and GCC 15 + return [f'--embed-dir={path}'] + class PGICPPCompiler(PGICompiler, CPPCompiler): def __init__(self, ccache: T.List[str], exelist: T.List[str], version: str, for_machine: MachineChoice, diff --git a/mesonbuild/compilers/mixins/clang.py b/mesonbuild/compilers/mixins/clang.py index 07a45a75bc26..0c973fd815a5 100644 --- a/mesonbuild/compilers/mixins/clang.py +++ b/mesonbuild/compilers/mixins/clang.py @@ -254,6 +254,11 @@ def get_lto_link_args(self, *, target: T.Optional[BuildTarget] = None, threads: args.append(f'-flto-jobs={threads}') return args + def get_embed_args(self, path: str) -> list[str]: + # Requires C23 or C++26 standard and Clang 19 + # Is not currently supported by AppleClang + return [f'--embed-dir={path}'] + class ClangCStds(CompilerMixinBase): diff --git a/mesonbuild/dependencies/base.py b/mesonbuild/dependencies/base.py index 5118c5cfab7e..1c41b14ad242 100644 --- a/mesonbuild/dependencies/base.py +++ b/mesonbuild/dependencies/base.py @@ -307,6 +307,7 @@ class InternalDependency(Dependency): type_name = DependencyTypeName('internal') def __init__(self, version: str, incdirs: T.Optional[T.List['IncludeDirs']] = None, + embed_dirs: list[IncludeDirs] | None = None, compile_args: T.Optional[T.List[str]] = None, link_args: T.Optional[T.List[str]] = None, libraries: T.Optional[T.List[LinkableTargetTypes]] = None, @@ -322,6 +323,7 @@ def __init__(self, version: str, incdirs: T.Optional[T.List['IncludeDirs']] = No self.version = version self.is_found = True self.include_directories = incdirs or [] + self.embed_directories = embed_dirs or [] self.compile_args = compile_args or [] self.link_args = link_args or [] self.libraries = libraries or [] @@ -372,11 +374,12 @@ def get_partial_dependency(self, *, compile_args: bool = False, final_sources = self.sources.copy() if sources else [] final_extra_files = self.extra_files.copy() if extra_files else [] final_includes = self.get_include_dirs().copy() if includes else [] + final_embed = self.embed_directories.copy() if includes else [] final_deps = [d.get_partial_dependency( compile_args=compile_args, link_args=link_args, links=links, includes=includes, sources=sources) for d in self.ext_deps] return type(self)( - self.version, final_includes, final_compile_args, + self.version, final_includes, final_embed, final_compile_args, final_link_args, final_libraries, final_whole_libraries, final_sources, final_extra_files, final_deps, self.variables, [], [], [], self.name) diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index e4459f3cc1a9..e8784c0526e5 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -74,6 +74,7 @@ ENV_KW, ENV_METHOD_KW, ENV_SEPARATOR_KW, + EMBED_DIRECTORIES, INCLUDE_DIRECTORIES, INSTALL_KW, INSTALL_DIR_KW, @@ -727,6 +728,7 @@ def func_files(self, node: mparser.FunctionNode, args: T.Tuple[T.List[str]], kwa KwargInfo('compile_args', ContainerTypeInfo(list, str), listify=True, default=[]), INCLUDE_DIRECTORIES.evolve(name='d_import_dirs', since='0.62.0'), D_MODULE_VERSIONS_KW.evolve(since='0.62.0'), + EMBED_DIRECTORIES, LINK_ARGS_KW, DEPENDENCIES_KW, INCLUDE_DIRECTORIES.evolve(since_values={ContainerTypeInfo(list, str): '0.50.0'}), @@ -742,6 +744,7 @@ def func_declare_dependency(self, node: mparser.BaseNode, args: T.List[TYPE_var] kwargs: kwtypes.FuncDeclareDependency) -> dependencies.Dependency: deps = kwargs['dependencies'] incs = self.extract_incdirs(kwargs['include_directories']) + embed_directories = self.extract_incdirs(kwargs['embed_directories']) libs = kwargs['link_with'] libs_whole = kwargs['link_whole'] objects = kwargs['objects'] @@ -768,7 +771,7 @@ def func_declare_dependency(self, node: mparser.BaseNode, args: T.List[TYPE_var] and os.path.isdir(v): variables[k] = P_OBJ.DependencyVariableString(v) - dep = dependencies.InternalDependency(version, incs, compile_args, + dep = dependencies.InternalDependency(version, incs, embed_directories, compile_args, link_args, libs, libs_whole, sources, extra_files, deps, variables, d_module_versions, d_import_dirs, objects) @@ -3690,6 +3693,7 @@ def __convert_build_target_base_kwargs(self, kwargs: kwtypes.BuildTarget, final: # Convert into IncludeDirs objects final['include_directories'] = self.extract_incdirs(kwargs['include_directories']) + final['embed_directories'] = self.extract_incdirs(kwargs['embed_directories']) final['d_import_dirs'] = self.extract_incdirs(kwargs['d_import_dirs'], True) # Convert language arguments diff --git a/mesonbuild/interpreter/kwargs.py b/mesonbuild/interpreter/kwargs.py index 37ac74f713d9..003aad6ae2bb 100644 --- a/mesonbuild/interpreter/kwargs.py +++ b/mesonbuild/interpreter/kwargs.py @@ -403,6 +403,7 @@ class BuildTarget(BaseBuildTarget): d_import_dirs: T.List[T.Union[str, build.IncludeDirs]] d_module_versions: T.List[T.Union[str, int]] d_unittest: bool + embed_directories: list[str | build.IncludeDirs] install_dir: T.List[T.Union[str, bool]] install_vala_header: T.Union[str, bool, None] install_vala_vapi: T.Union[str, bool, None] @@ -540,6 +541,7 @@ class FuncDeclareDependency(TypedDict): d_import_dirs: T.List[T.Union[build.IncludeDirs, str]] d_module_versions: T.List[T.Union[str, int]] dependencies: T.List[Dependency] + embed_directories: list[str | build.IncludeDirs] extra_files: T.List[FileOrString] include_directories: T.List[T.Union[build.IncludeDirs, str]] link_args: T.List[str] diff --git a/mesonbuild/interpreter/type_checking.py b/mesonbuild/interpreter/type_checking.py index d5e7137010b7..bfc462a2a195 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -425,6 +425,11 @@ def _local_program_convertor(raw: T.List[T.Union[str, File, BuildTarget, Generat default=[], ) +EMBED_DIRECTORIES = INCLUDE_DIRECTORIES.evolve( + name='embed_directories', + since='1.13.0', +) + def _default_options_convertor(raw: T.Union[str, T.List[str], T.Dict[str, ElementaryOptionValues]]) -> T.Dict[OptionKey, ElementaryOptionValues]: d = _override_options_convertor(raw) return {OptionKey.from_string(k): v for k, v in d.items()} @@ -800,6 +805,7 @@ def _pch_convertor(args: T.List[str]) -> T.Optional[T.Tuple[str, T.Optional[str] *_LANGUAGE_KWS, BT_SOURCES_KW, INCLUDE_DIRECTORIES.evolve(name='d_import_dirs'), + EMBED_DIRECTORIES, LINK_ARGS_KW, LINK_WHOLE_KW.evolve( since='0.40.0', diff --git a/mesonbuild/modules/external_project.py b/mesonbuild/modules/external_project.py index 078dc7ba8112..fe463ac5c1d4 100644 --- a/mesonbuild/modules/external_project.py +++ b/mesonbuild/modules/external_project.py @@ -287,7 +287,7 @@ def dependency_method(self, state: 'ModuleState', args: T.Tuple[str], kwargs: 'D compile_args = [f'-I{abs_includedir}'] link_args = [f'-L{abs_libdir}', f'-l{libname}'] sources = self.target - dep = InternalDependency(version, [], compile_args, link_args, [], + dep = InternalDependency(version, [], [], compile_args, link_args, [], [], [sources], [], [], {}, [], [], []) return dep diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index da1b247e0333..0e428249be27 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -2333,7 +2333,7 @@ def generate_vapi(self, state: 'ModuleState', args: T.Tuple[str], kwargs: 'Gener incs = [build.IncludeDirs(state.subdir, ['.'] + vapi_includes, False, state.current_build_project)] sources = [vapi_target] + vapi_depends - rv = InternalDependency(None, incs, [], [], link_with, [], sources, [], [], {}, [], [], []) + rv = InternalDependency(None, incs, [], [], [], link_with, [], sources, [], [], {}, [], [], []) created_values.append(rv) return ModuleReturnValue(rv, created_values) diff --git a/test cases/common/302 embed dirs/c/main.c b/test cases/common/302 embed dirs/c/main.c new file mode 100644 index 000000000000..32506fca7441 --- /dev/null +++ b/test cases/common/302 embed dirs/c/main.c @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright © 2026 Intel Corporation + */ + +#include + +const char msg[] = +{ +#embed "data.txt" +}; + +const char expected[] = { + 'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!', '\n', +}; + + +int main(void) { + const int num_found = sizeof(msg) / sizeof(msg[0]); + const int num_expected = sizeof(expected) / sizeof(expected[0]); + + if (num_found != num_expected) { + fprintf(stderr, "The number of found arguments (%d) does not match the expected(%d)\n", num_found, num_expected); + return 1; + } + + for (unsigned i = 0; i < num_found; ++i) { + if (msg[i] != expected[i]) { + fprintf(stderr, "The items at index %d does not match: found: %c, expected: %c\n", i, msg[i], expected[i]); + return 1; + } + } + + return 0; +} diff --git a/test cases/common/302 embed dirs/c/meson.build b/test cases/common/302 embed dirs/c/meson.build new file mode 100644 index 000000000000..0a9a83d97042 --- /dev/null +++ b/test cases/common/302 embed dirs/c/meson.build @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright © 2026 Intel Corporation + +if not add_languages('c', required : false, native : false) + debug('No C language support') + subdir_done() +endif + +cc = meson.get_compiler('c') +if cc.get_id() == 'gcc' + if cc.version().version_compare('< 15') + debug('Skipping C test due to GCC being older than 15, and lacking support for #embed') + subdir_done() + endif +elif cc.get_id() == 'clang' + if cc.version().version_compare('< 19') + debug('Skipping C test due to Clang being older than 19, and lacking support for #embed') + subdir_done() + endif +else + debug('Skipping C test due to compiler @0@, which is not known to support #embed'.format(cc.get_id())) + subdir_done() +endif + +exe = executable( + 'c_prog', + 'main.c', + embed_directories : embed_dir, +) + +test('C embed', exe) + +exe = executable( + 'c_with_dep', + 'main.c', + dependencies : dep, +) + +test('From dependency object', exe) diff --git a/test cases/common/302 embed dirs/cpp/main.cpp b/test cases/common/302 embed dirs/cpp/main.cpp new file mode 100644 index 000000000000..e0bcaf0f38dd --- /dev/null +++ b/test cases/common/302 embed dirs/cpp/main.cpp @@ -0,0 +1,35 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright © 2026 Intel Corporation + */ + +#include + +constexpr char msg[] = +{ +#embed "data.txt" +}; + +constexpr char expected[] = { + 'H', 'e', 'l', 'l', 'o', ',', ' ', 'W', 'o', 'r', 'l', 'd', '!', '\n', +}; + + +int main(void) { + constexpr int num_found = sizeof(msg) / sizeof(msg[0]); + constexpr int num_expected = sizeof(expected) / sizeof(expected[0]); + + if (num_found != num_expected) { + std::cerr << "The number of found arguments (" << num_found << ") does not match the expected (" << num_expected << ")\n"; + return 1; + } + + for (unsigned i = 0; i < num_found; ++i) { + if (msg[i] != expected[i]) { + std::cerr << "The items at index " << i << "does not match: found: " << msg[i] << "expected: " << expected[i] << "\n"; + return 1; + } + } + + return 0; +} diff --git a/test cases/common/302 embed dirs/cpp/meson.build b/test cases/common/302 embed dirs/cpp/meson.build new file mode 100644 index 000000000000..3202d316720f --- /dev/null +++ b/test cases/common/302 embed dirs/cpp/meson.build @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright © 2026 Intel Corporation + +if not add_languages('cpp', required : false, native : false) + debug('No C++ support') + subdir_done() +endif + +cpp = meson.get_compiler('cpp') +if cpp.get_id() == 'gcc' + if cpp.version().version_compare('< 15') + debug('Skipping C++ test due to G++ being older than 15, and lacking support for #embed') + subdir_done() + endif +elif cpp.get_id() == 'clang' + if cpp.version().version_compare('< 19') + debug('Skipping C++ test due to Clang++ being older than 19, and lacking support for #embed') + subdir_done() + endif +else + debug('Skipping C++ test due to compiler @0@, which is not known to support #embed'.format(cpp.get_id())) + subdir_done() +endif + +exe = executable( + 'cpp_prog', + 'main.cpp', + embed_directories : '../embed', +) + +test('C++ embed', exe) diff --git a/test cases/common/302 embed dirs/embed/data.txt b/test cases/common/302 embed dirs/embed/data.txt new file mode 100644 index 000000000000..8ab686eafeb1 --- /dev/null +++ b/test cases/common/302 embed dirs/embed/data.txt @@ -0,0 +1 @@ +Hello, World! diff --git a/test cases/common/302 embed dirs/meson.build b/test cases/common/302 embed dirs/meson.build new file mode 100644 index 000000000000..0f903cd28cb8 --- /dev/null +++ b/test cases/common/302 embed dirs/meson.build @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright © 2026 Intel Corporation + +project( + 'embed dirs', + meson_version : '>= 1.13.0', + default_options : {'c_std' : 'c23', 'cpp_std' : 'c++26'}, +) + +embed_dir = include_directories('embed') + +dep = declare_dependency( + embed_directories : ['embed'], +) + +subdir('c') +subdir('cpp') diff --git a/test cases/common/98 subproject subdir/subprojects/subsubsub-1.0/.meson-subproject-wrap-hash.txt b/test cases/common/98 subproject subdir/subprojects/subsubsub-1.0/.meson-subproject-wrap-hash.txt deleted file mode 100644 index 40138659fd02..000000000000 --- a/test cases/common/98 subproject subdir/subprojects/subsubsub-1.0/.meson-subproject-wrap-hash.txt +++ /dev/null @@ -1 +0,0 @@ -0fd8007dd44a1a5eb5c01af4c138f0993c6cb44da194b36db04484212eff591b diff --git a/test cases/common/98 subproject subdir/subprojects/subsubsub-1.0/meson.build b/test cases/common/98 subproject subdir/subprojects/subsubsub-1.0/meson.build deleted file mode 100644 index 530852c0d3cc..000000000000 --- a/test cases/common/98 subproject subdir/subprojects/subsubsub-1.0/meson.build +++ /dev/null @@ -1,3 +0,0 @@ -project('subsubsub') - -meson.override_dependency('subsubsub', declare_dependency()) diff --git a/test cases/common/98 subproject subdir/subprojects/subsubsub.wrap b/test cases/common/98 subproject subdir/subprojects/subsubsub.wrap deleted file mode 100644 index 5fa019a72cc8..000000000000 --- a/test cases/common/98 subproject subdir/subprojects/subsubsub.wrap +++ /dev/null @@ -1,2 +0,0 @@ -[wrap-redirect] -filename = sub_implicit/subprojects/subsub/subprojects/subsubsub.wrap