diff --git a/docs/markdown/snippets/embed_directories.md b/docs/markdown/snippets/embed_directories.md new file mode 100644 index 000000000000..a099cf4e975f --- /dev/null +++ b/docs/markdown/snippets/embed_directories.md @@ -0,0 +1,3 @@ +## BuildTarget now has an `embed_directories` keyword argument + +This provides an include_directories like path for `--embed`. diff --git a/docs/yaml/functions/_build_target_base.yaml b/docs/yaml/functions/_build_target_base.yaml index c2c4552e9fa1..c81dfafbc07d 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. + gui_app: type: bool deprecated: 0.56.0 diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index d0ff82375062..0cfb59169995 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -3176,6 +3176,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 d9fa10414f6d..0510d35ea276 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 @@ -819,6 +820,7 @@ def __init__( self.structured_sources = structured_sources self.external_deps: T.List[dependencies.Dependency] = [] self.include_dirs: T.List['IncludeDirs'] = [] + self.embed_dirs = kwargs['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] = [] @@ -1543,6 +1545,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.include_directories, dep.get_include_type()) + 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 1ba9d91d67a8..7519367e6d8d 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -1704,3 +1704,7 @@ def get_crt_static(self) -> bool: def get_target_libdir(self) -> str: """Where is the libdir for the current machine located""" raise EnvironmentException(f'{self.get_id()} does not support Rust target libdir') + + 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 41915038a2b8..e2f6356dc624 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 69d29b70f1bc..0caa2fab98f1 100644 --- a/mesonbuild/compilers/mixins/clang.py +++ b/mesonbuild/compilers/mixins/clang.py @@ -249,6 +249,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 99cdfa2667d5..04c2dc4cddbb 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 [] @@ -370,11 +372,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.include_directories.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 f4cb0b6531f5..da70fd523ce3 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, @@ -725,6 +726,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'}), @@ -740,6 +742,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'] @@ -766,7 +769,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) @@ -3693,6 +3696,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 5d9e3f1a1ac1..2a0fd480da5b 100644 --- a/mesonbuild/interpreter/type_checking.py +++ b/mesonbuild/interpreter/type_checking.py @@ -419,6 +419,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()} @@ -577,7 +582,7 @@ def suite_convertor(suite: T.List[str]) -> T.List[str]: default=[], ) -_LANGUAGE_KWS: T.List[KwargInfo[T.List[str]]] = [ +_LANGUAGE_KWS: T.List[KwargInfo[T.List[str | File]]] = [ _BASE_LANG_KW.evolve(name=f'{lang}_args') for lang in compilers.all_languages - {'rust', 'vala', 'java'} ] @@ -587,7 +592,7 @@ def suite_convertor(suite: T.List[str]) -> T.List[str]: _LANGUAGE_KWS.append(_BASE_LANG_KW.evolve(name='rust_args', since='0.41.0')) # We need this deprecated values more than the non-deprecated values. So we'll evolve them out elsewhere. -_JAVA_LANG_KW: KwargInfo[T.List[str]] = _BASE_LANG_KW.evolve( +_JAVA_LANG_KW: KwargInfo[T.List[str | File]] = _BASE_LANG_KW.evolve( name='java_args', deprecated='1.3.0', deprecated_message='This does not, and never has, done anything. It should be removed' @@ -781,6 +786,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( as_default=[('', ('1.11.0', "Replace an empty string with an empty array: `link_whole : ''` -> `link_whole : []`"))], @@ -1010,7 +1016,7 @@ def _shortname_validator(shortname: T.Optional[str]) -> T.Optional[str]: for a in _LANGUAGE_KWS], ] -_SHARED_STATIC_ARGS: T.List[KwargInfo[T.List[str]]] = [ +_SHARED_STATIC_ARGS: T.List[KwargInfo[T.List[str | File]]] = [ *[l.evolve(name=l.name.replace('_', '_static_'), since='1.3.0') for l in _LANGUAGE_KWS], *[l.evolve(name=l.name.replace('_', '_shared_'), since='1.3.0') diff --git a/mesonbuild/interpreterbase/decorators.py b/mesonbuild/interpreterbase/decorators.py index ad5f17e263c6..bfd7cc24afcd 100644 --- a/mesonbuild/interpreterbase/decorators.py +++ b/mesonbuild/interpreterbase/decorators.py @@ -441,7 +441,7 @@ def evolve(self, *, convertor: T.Union[T.Callable[[_T], object], None, _NULL_T] = _NULL, extra_types: T.Union[T.Mapping[T.Type, T.Callable[[object], str]], None, _NULL_T] = _NULL, as_default: T.Union[T.List[T.Tuple[object, T.Union[str, T.Tuple[str, str]]]], None, _NULL_T] = _NULL - ) -> 'KwargInfo': + ) -> 'KwargInfo[_T]': """Create a shallow copy of this KwargInfo, with modifications. This allows us to create a new copy of a KwargInfo with modifications. diff --git a/mesonbuild/modules/external_project.py b/mesonbuild/modules/external_project.py index a800f961d57d..a45b2c3459b8 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 3776d3927f30..304407d4e190 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -2324,7 +2324,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/300 embed dirs/embed/data.txt b/test cases/common/300 embed dirs/embed/data.txt new file mode 100644 index 000000000000..8ab686eafeb1 --- /dev/null +++ b/test cases/common/300 embed dirs/embed/data.txt @@ -0,0 +1 @@ +Hello, World! diff --git a/test cases/common/300 embed dirs/main.c b/test cases/common/300 embed dirs/main.c new file mode 100644 index 000000000000..32506fca7441 --- /dev/null +++ b/test cases/common/300 embed dirs/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/300 embed dirs/main.cpp b/test cases/common/300 embed dirs/main.cpp new file mode 100644 index 000000000000..e0bcaf0f38dd --- /dev/null +++ b/test cases/common/300 embed dirs/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/300 embed dirs/meson.build b/test cases/common/300 embed dirs/meson.build new file mode 100644 index 000000000000..826c194fa039 --- /dev/null +++ b/test cases/common/300 embed dirs/meson.build @@ -0,0 +1,40 @@ +# 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'}, +) + +dep = declare_dependency( + embed_directories : ['embed'], +) + +if add_languages('c', required : false, native : false) + exe = executable( + 'c_prog', + 'main.c', + embed_directories : ['embed'], + ) + + test('C embed', exe) + + exe = executable( + 'c_with_dep', + 'main.c', + dependencies : dep, + ) + + test('From dependency object', exe) +endif + +if add_languages('cpp', required : false, native : false) + exe = executable( + 'cpp_prog', + 'main.cpp', + embed_directories : include_directories('embed'), + ) + + test('C++ embed', exe) +endif