diff options
Diffstat (limited to '')
-rw-r--r-- | mesonbuild/modules/gnome.py | 2165 |
1 files changed, 2165 insertions, 0 deletions
diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py new file mode 100644 index 0000000..38a176d --- /dev/null +++ b/mesonbuild/modules/gnome.py @@ -0,0 +1,2165 @@ +# Copyright 2015-2016 The Meson development team + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +'''This module provides helper functions for Gnome/GLib related +functionality such as gobject-introspection, gresources and gtk-doc''' +from __future__ import annotations + +import copy +import itertools +import functools +import os +import subprocess +import textwrap +import typing as T + +from . import ExtensionModule, ModuleInfo +from . import ModuleReturnValue +from .. import build +from .. import interpreter +from .. import mesonlib +from .. import mlog +from ..build import CustomTarget, CustomTargetIndex, Executable, GeneratedList, InvalidArguments +from ..dependencies import Dependency, PkgConfigDependency, InternalDependency +from ..interpreter.type_checking import DEPENDS_KW, DEPEND_FILES_KW, INSTALL_DIR_KW, INSTALL_KW, NoneType, SOURCES_KW, in_set_validator +from ..interpreterbase import noPosargs, noKwargs, FeatureNew, FeatureDeprecated +from ..interpreterbase import typed_kwargs, KwargInfo, ContainerTypeInfo +from ..interpreterbase.decorators import typed_pos_args +from ..mesonlib import ( + MachineChoice, MesonException, OrderedSet, Popen_safe, join_args, +) +from ..programs import OverrideProgram +from ..scripts.gettext import read_linguas + +if T.TYPE_CHECKING: + from typing_extensions import Literal, TypedDict + + from . import ModuleState + from ..build import BuildTarget + from ..compilers import Compiler + from ..interpreter import Interpreter + from ..interpreterbase import TYPE_var, TYPE_kwargs + from ..mesonlib import FileOrString + from ..programs import ExternalProgram + + class PostInstall(TypedDict): + glib_compile_schemas: bool + gio_querymodules: T.List[str] + gtk_update_icon_cache: bool + update_desktop_database: bool + update_mime_database: bool + + class CompileSchemas(TypedDict): + + build_by_default: bool + depend_files: T.List[FileOrString] + + class Yelp(TypedDict): + + languages: T.List[str] + media: T.List[str] + sources: T.List[str] + symlink_media: bool + + class CompileResources(TypedDict): + + build_by_default: bool + c_name: T.Optional[str] + dependencies: T.List[T.Union[mesonlib.File, build.CustomTarget, build.CustomTargetIndex]] + export: bool + extra_args: T.List[str] + gresource_bundle: bool + install: bool + install_dir: T.Optional[str] + install_header: bool + source_dir: T.List[str] + + class GenerateGir(TypedDict): + + build_by_default: bool + dependencies: T.List[Dependency] + export_packages: T.List[str] + extra_args: T.List[str] + fatal_warnings: bool + header: T.List[str] + identifier_prefix: T.List[str] + include_directories: T.List[T.Union[build.IncludeDirs, str]] + includes: T.List[T.Union[str, GirTarget]] + install: bool + install_dir_gir: T.Optional[str] + install_dir_typelib: T.Optional[str] + link_with: T.List[T.Union[build.SharedLibrary, build.StaticLibrary]] + namespace: str + nsversion: str + sources: T.List[T.Union[FileOrString, build.GeneratedTypes]] + symbol_prefix: T.List[str] + + class GtkDoc(TypedDict): + + src_dir: T.List[T.Union[str, build.IncludeDirs]] + main_sgml: str + main_xml: str + module_version: str + namespace: str + mode: Literal['xml', 'smgl', 'auto', 'none'] + html_args: T.List[str] + scan_args: T.List[str] + scanobjs_args: T.List[str] + fixxref_args: T.List[str] + mkdb_args: T.List[str] + content_files: T.List[T.Union[build.GeneratedTypes, FileOrString]] + ignore_headers: T.List[str] + install_dir: T.List[str] + check: bool + install: bool + gobject_typesfile: T.List[FileOrString] + html_assets: T.List[FileOrString] + expand_content_files: T.List[FileOrString] + c_args: T.List[str] + include_directories: T.List[T.Union[str, build.IncludeDirs]] + dependencies: T.List[T.Union[Dependency, build.SharedLibrary, build.StaticLibrary]] + + class GdbusCodegen(TypedDict): + + sources: T.List[FileOrString] + extra_args: T.List[str] + interface_prefix: T.Optional[str] + namespace: T.Optional[str] + object_manager: bool + build_by_default: bool + annotations: T.List[T.List[str]] + install_header: bool + install_dir: T.Optional[str] + docbook: T.Optional[str] + autocleanup: Literal['all', 'none', 'objects', 'default'] + + class GenMarshal(TypedDict): + + build_always: T.Optional[str] + build_always_stale: T.Optional[bool] + build_by_default: T.Optional[bool] + depend_files: T.List[mesonlib.File] + extra_args: T.List[str] + install_dir: T.Optional[str] + install_header: bool + internal: bool + nostdinc: bool + prefix: T.Optional[str] + skip_source: bool + sources: T.List[FileOrString] + stdinc: bool + valist_marshallers: bool + + class GenerateVapi(TypedDict): + + sources: T.List[T.Union[str, GirTarget]] + install_dir: T.Optional[str] + install: bool + vapi_dirs: T.List[str] + metadata_dirs: T.List[str] + gir_dirs: T.List[str] + packages: T.List[T.Union[str, InternalDependency]] + + class _MkEnumsCommon(TypedDict): + + sources: T.List[T.Union[FileOrString, build.GeneratedTypes]] + install_header: bool + install_dir: T.Optional[str] + identifier_prefix: T.Optional[str] + symbol_prefix: T.Optional[str] + + class MkEnumsSimple(_MkEnumsCommon): + + header_prefix: str + decorator: str + function_prefix: str + body_prefix: str + + class MkEnums(_MkEnumsCommon): + + c_template: T.Optional[FileOrString] + h_template: T.Optional[FileOrString] + comments: T.Optional[str] + eprod: T.Optional[str] + fhead: T.Optional[str] + fprod: T.Optional[str] + ftail: T.Optional[str] + vhead: T.Optional[str] + vprod: T.Optional[str] + vtail: T.Optional[str] + depends: T.List[T.Union[BuildTarget, CustomTarget, CustomTargetIndex]] + + +# Differs from the CustomTarget version in that it straight defaults to True +_BUILD_BY_DEFAULT: KwargInfo[bool] = KwargInfo( + 'build_by_default', bool, default=True, +) + +_EXTRA_ARGS_KW: KwargInfo[T.List[str]] = KwargInfo( + 'extra_args', + ContainerTypeInfo(list, str), + default=[], + listify=True, +) + +_MK_ENUMS_COMMON_KWS: T.List[KwargInfo] = [ + INSTALL_KW.evolve(name='install_header'), + INSTALL_DIR_KW, + KwargInfo( + 'sources', + ContainerTypeInfo(list, (str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)), + listify=True, + required=True, + ), + KwargInfo('identifier_prefix', (str, NoneType)), + KwargInfo('symbol_prefix', (str, NoneType)), +] + +def annotations_validator(annotations: T.List[T.Union[str, T.List[str]]]) -> T.Optional[str]: + """Validate gdbus-codegen annotations argument""" + + badlist = 'must be made up of 3 strings for ELEMENT, KEY, and VALUE' + + if not annotations: + return None + elif all(isinstance(annot, str) for annot in annotations): + if len(annotations) == 3: + return None + else: + return badlist + elif not all(isinstance(annot, list) for annot in annotations): + for c, annot in enumerate(annotations): + if not isinstance(annot, list): + return f'element {c+1} must be a list' + else: + for c, annot in enumerate(annotations): + if len(annot) != 3 or not all(isinstance(i, str) for i in annot): + return f'element {c+1} {badlist}' + return None + +class GResourceTarget(build.CustomTarget): + pass + +class GResourceHeaderTarget(build.CustomTarget): + pass + +class GirTarget(build.CustomTarget): + pass + +class TypelibTarget(build.CustomTarget): + pass + +class VapiTarget(build.CustomTarget): + pass + +# gresource compilation is broken due to the way +# the resource compiler and Ninja clash about it +# +# https://github.com/ninja-build/ninja/issues/1184 +# https://bugzilla.gnome.org/show_bug.cgi?id=774368 +gresource_dep_needed_version = '>= 2.51.1' + +class GnomeModule(ExtensionModule): + + INFO = ModuleInfo('gnome') + + def __init__(self, interpreter: 'Interpreter') -> None: + super().__init__(interpreter) + self.gir_dep: T.Optional[Dependency] = None + self.giscanner: T.Optional[T.Union[ExternalProgram, build.Executable, OverrideProgram]] = None + self.gicompiler: T.Optional[T.Union[ExternalProgram, build.Executable, OverrideProgram]] = None + self.install_glib_compile_schemas = False + self.install_gio_querymodules: T.List[str] = [] + self.install_gtk_update_icon_cache = False + self.install_update_desktop_database = False + self.install_update_mime_database = False + self.devenv: T.Optional[build.EnvironmentVariables] = None + self.native_glib_version: T.Optional[str] = None + self.methods.update({ + 'post_install': self.post_install, + 'compile_resources': self.compile_resources, + 'generate_gir': self.generate_gir, + 'compile_schemas': self.compile_schemas, + 'yelp': self.yelp, + 'gtkdoc': self.gtkdoc, + 'gtkdoc_html_dir': self.gtkdoc_html_dir, + 'gdbus_codegen': self.gdbus_codegen, + 'mkenums': self.mkenums, + 'mkenums_simple': self.mkenums_simple, + 'genmarshal': self.genmarshal, + 'generate_vapi': self.generate_vapi, + }) + + def _get_native_glib_version(self, state: 'ModuleState') -> str: + if self.native_glib_version is None: + glib_dep = PkgConfigDependency('glib-2.0', state.environment, + {'native': True, 'required': False}) + if glib_dep.found(): + self.native_glib_version = glib_dep.get_version() + else: + mlog.warning('Could not detect glib version, assuming 2.54. ' + 'You may get build errors if your glib is older.') + self.native_glib_version = '2.54' + return self.native_glib_version + + @mesonlib.run_once + def __print_gresources_warning(self, state: 'ModuleState') -> None: + if not mesonlib.version_compare(self._get_native_glib_version(state), + gresource_dep_needed_version): + mlog.warning('GLib compiled dependencies do not work reliably with \n' + 'the current version of GLib. See the following upstream issue:', + mlog.bold('https://bugzilla.gnome.org/show_bug.cgi?id=774368')) + + @staticmethod + def _print_gdbus_warning() -> None: + mlog.warning('Code generated with gdbus_codegen() requires the root directory be added to\n' + ' include_directories of targets with GLib < 2.51.3:', + mlog.bold('https://github.com/mesonbuild/meson/issues/1387'), + once=True) + + @typed_kwargs( + 'gnome.post_install', + KwargInfo('glib_compile_schemas', bool, default=False), + KwargInfo('gio_querymodules', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo('gtk_update_icon_cache', bool, default=False), + KwargInfo('update_desktop_database', bool, default=False, since='0.59.0'), + KwargInfo('update_mime_database', bool, default=False, since='0.64.0'), + ) + @noPosargs + @FeatureNew('gnome.post_install', '0.57.0') + def post_install(self, state: 'ModuleState', args: T.List['TYPE_var'], kwargs: 'PostInstall') -> ModuleReturnValue: + rv: T.List['build.ExecutableSerialisation'] = [] + datadir_abs = os.path.join(state.environment.get_prefix(), state.environment.get_datadir()) + if kwargs['glib_compile_schemas'] and not self.install_glib_compile_schemas: + self.install_glib_compile_schemas = True + prog = state.find_tool('glib-compile-schemas', 'gio-2.0', 'glib_compile_schemas') + schemasdir = os.path.join(datadir_abs, 'glib-2.0', 'schemas') + script = state.backend.get_executable_serialisation([prog, schemasdir]) + script.skip_if_destdir = True + rv.append(script) + for d in kwargs['gio_querymodules']: + if d not in self.install_gio_querymodules: + self.install_gio_querymodules.append(d) + prog = state.find_tool('gio-querymodules', 'gio-2.0', 'gio_querymodules') + moduledir = os.path.join(state.environment.get_prefix(), d) + script = state.backend.get_executable_serialisation([prog, moduledir]) + script.skip_if_destdir = True + rv.append(script) + if kwargs['gtk_update_icon_cache'] and not self.install_gtk_update_icon_cache: + self.install_gtk_update_icon_cache = True + prog = state.find_program('gtk4-update-icon-cache', required=False) + found = isinstance(prog, build.Executable) or prog.found() + if not found: + prog = state.find_program('gtk-update-icon-cache') + icondir = os.path.join(datadir_abs, 'icons', 'hicolor') + script = state.backend.get_executable_serialisation([prog, '-q', '-t', '-f', icondir]) + script.skip_if_destdir = True + rv.append(script) + if kwargs['update_desktop_database'] and not self.install_update_desktop_database: + self.install_update_desktop_database = True + prog = state.find_program('update-desktop-database') + appdir = os.path.join(datadir_abs, 'applications') + script = state.backend.get_executable_serialisation([prog, '-q', appdir]) + script.skip_if_destdir = True + rv.append(script) + if kwargs['update_mime_database'] and not self.install_update_mime_database: + self.install_update_mime_database = True + prog = state.find_program('update-mime-database') + appdir = os.path.join(datadir_abs, 'mime') + script = state.backend.get_executable_serialisation([prog, appdir]) + script.skip_if_destdir = True + rv.append(script) + return ModuleReturnValue(None, rv) + + @typed_pos_args('gnome.compile_resources', str, (str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)) + @typed_kwargs( + 'gnome.compile_resources', + _BUILD_BY_DEFAULT, + _EXTRA_ARGS_KW, + INSTALL_KW, + INSTALL_KW.evolve(name='install_header', since='0.37.0'), + INSTALL_DIR_KW, + KwargInfo('c_name', (str, NoneType)), + KwargInfo('dependencies', ContainerTypeInfo(list, (mesonlib.File, build.CustomTarget, build.CustomTargetIndex)), default=[], listify=True), + KwargInfo('export', bool, default=False, since='0.37.0'), + KwargInfo('gresource_bundle', bool, default=False, since='0.37.0'), + KwargInfo('source_dir', ContainerTypeInfo(list, str), default=[], listify=True), + ) + def compile_resources(self, state: 'ModuleState', args: T.Tuple[str, 'FileOrString'], + kwargs: 'CompileResources') -> 'ModuleReturnValue': + self.__print_gresources_warning(state) + glib_version = self._get_native_glib_version(state) + + glib_compile_resources = state.find_program('glib-compile-resources') + cmd: T.List[T.Union[ExternalProgram, str]] = [glib_compile_resources, '@INPUT@'] + + source_dirs = kwargs['source_dir'] + dependencies = kwargs['dependencies'] + + target_name, input_file = args + + # Validate dependencies + subdirs: T.List[str] = [] + depends: T.List[T.Union[build.CustomTarget, build.CustomTargetIndex]] = [] + for dep in dependencies: + if isinstance(dep, mesonlib.File): + subdirs.append(dep.subdir) + else: + depends.append(dep) + subdirs.append(dep.get_subdir()) + if not mesonlib.version_compare(glib_version, gresource_dep_needed_version): + m = 'The "dependencies" argument of gnome.compile_resources() can not\n' \ + 'be used with the current version of glib-compile-resources due to\n' \ + '<https://bugzilla.gnome.org/show_bug.cgi?id=774368>' + raise MesonException(m) + + if not mesonlib.version_compare(glib_version, gresource_dep_needed_version): + # Resource xml files generated at build-time cannot be used with + # gnome.compile_resources() because we need to scan the xml for + # dependencies. Use configure_file() instead to generate it at + # configure-time + if isinstance(input_file, mesonlib.File): + # glib-compile-resources will be run inside the source dir, + # so we need either 'src_to_build' or the absolute path. + # Absolute path is the easiest choice. + if input_file.is_built: + ifile = os.path.join(state.environment.get_build_dir(), input_file.subdir, input_file.fname) + else: + ifile = os.path.join(input_file.subdir, input_file.fname) + + elif isinstance(input_file, (build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)): + raise MesonException('Resource xml files generated at build-time cannot be used with ' + 'gnome.compile_resources() in the current version of glib-compile-resources ' + 'because we need to scan the xml for dependencies due to ' + '<https://bugzilla.gnome.org/show_bug.cgi?id=774368>\nUse ' + 'configure_file() instead to generate it at configure-time.') + else: + ifile = os.path.join(state.subdir, input_file) + + depend_files, depends, subdirs = self._get_gresource_dependencies( + state, ifile, source_dirs, dependencies) + + # Make source dirs relative to build dir now + source_dirs = [os.path.join(state.build_to_src, state.subdir, d) for d in source_dirs] + # Ensure build directories of generated deps are included + source_dirs += subdirs + # Always include current directory, but after paths set by user + source_dirs.append(os.path.join(state.build_to_src, state.subdir)) + + for source_dir in OrderedSet(source_dirs): + cmd += ['--sourcedir', source_dir] + + if kwargs['c_name']: + cmd += ['--c-name', kwargs['c_name']] + if not kwargs['export']: + cmd += ['--internal'] + + cmd += ['--generate', '--target', '@OUTPUT@'] + cmd += kwargs['extra_args'] + + gresource = kwargs['gresource_bundle'] + if gresource: + output = f'{target_name}.gresource' + name = f'{target_name}_gresource' + else: + if 'c' in state.environment.coredata.compilers.host: + output = f'{target_name}.c' + name = f'{target_name}_c' + elif 'cpp' in state.environment.coredata.compilers.host: + output = f'{target_name}.cpp' + name = f'{target_name}_cpp' + else: + raise MesonException('Compiling GResources into code is only supported in C and C++ projects') + + if kwargs['install'] and not gresource: + raise MesonException('The install kwarg only applies to gresource bundles, see install_header') + + install_header = kwargs['install_header'] + if install_header and gresource: + raise MesonException('The install_header kwarg does not apply to gresource bundles') + if install_header and not kwargs['export']: + raise MesonException('GResource header is installed yet export is not enabled') + + depfile: T.Optional[str] = None + target_cmd: T.List[T.Union[ExternalProgram, str]] + if not mesonlib.version_compare(glib_version, gresource_dep_needed_version): + # This will eventually go out of sync if dependencies are added + target_cmd = cmd + else: + depfile = f'{output}.d' + depend_files = [] + target_cmd = copy.copy(cmd) + ['--dependency-file', '@DEPFILE@'] + target_c = GResourceTarget( + name, + state.subdir, + state.subproject, + state.environment, + target_cmd, + [input_file], + [output], + build_by_default=kwargs['build_by_default'], + depfile=depfile, + depend_files=depend_files, + extra_depends=depends, + install=kwargs['install'], + install_dir=[kwargs['install_dir']] if kwargs['install_dir'] else [], + install_tag=['runtime'], + ) + + if gresource: # Only one target for .gresource files + return ModuleReturnValue(target_c, [target_c]) + + install_dir = kwargs['install_dir'] or state.environment.coredata.get_option(mesonlib.OptionKey('includedir')) + assert isinstance(install_dir, str), 'for mypy' + target_h = GResourceHeaderTarget( + f'{target_name}_h', + state.subdir, + state.subproject, + state.environment, + cmd, + [input_file], + [f'{target_name}.h'], + build_by_default=kwargs['build_by_default'], + extra_depends=depends, + install=install_header, + install_dir=[install_dir], + install_tag=['devel'], + ) + rv = [target_c, target_h] + return ModuleReturnValue(rv, rv) + + @staticmethod + def _get_gresource_dependencies( + state: 'ModuleState', input_file: str, source_dirs: T.List[str], + dependencies: T.Sequence[T.Union[mesonlib.File, build.CustomTarget, build.CustomTargetIndex]] + ) -> T.Tuple[T.List[mesonlib.FileOrString], T.List[T.Union[build.CustomTarget, build.CustomTargetIndex]], T.List[str]]: + + cmd = ['glib-compile-resources', + input_file, + '--generate-dependencies'] + + # Prefer generated files over source files + cmd += ['--sourcedir', state.subdir] # Current build dir + for source_dir in source_dirs: + cmd += ['--sourcedir', os.path.join(state.subdir, source_dir)] + + try: + pc, stdout, stderr = Popen_safe(cmd, cwd=state.environment.get_source_dir()) + except (FileNotFoundError, PermissionError): + raise MesonException('Could not execute glib-compile-resources.') + if pc.returncode != 0: + m = f'glib-compile-resources failed to get dependencies for {cmd[1]}:\n{stderr}' + mlog.warning(m) + raise subprocess.CalledProcessError(pc.returncode, cmd) + + raw_dep_files: T.List[str] = stdout.split('\n')[:-1] + + depends: T.List[T.Union[build.CustomTarget, build.CustomTargetIndex]] = [] + subdirs: T.List[str] = [] + dep_files: T.List[mesonlib.FileOrString] = [] + for resfile in raw_dep_files.copy(): + resbasename = os.path.basename(resfile) + for dep in dependencies: + if isinstance(dep, mesonlib.File): + if dep.fname != resbasename: + continue + raw_dep_files.remove(resfile) + dep_files.append(dep) + subdirs.append(dep.subdir) + break + elif isinstance(dep, (build.CustomTarget, build.CustomTargetIndex)): + fname = None + outputs = {(o, os.path.basename(o)) for o in dep.get_outputs()} + for o, baseo in outputs: + if baseo == resbasename: + fname = o + break + if fname is not None: + raw_dep_files.remove(resfile) + depends.append(dep) + subdirs.append(dep.get_subdir()) + break + else: + # In generate-dependencies mode, glib-compile-resources doesn't raise + # an error for missing resources but instead prints whatever filename + # was listed in the input file. That's good because it means we can + # handle resource files that get generated as part of the build, as + # follows. + # + # If there are multiple generated resource files with the same basename + # then this code will get confused. + try: + f = mesonlib.File.from_source_file(state.environment.get_source_dir(), + ".", resfile) + except MesonException: + raise MesonException( + f'Resource "{resfile}" listed in "{input_file}" was not found. ' + 'If this is a generated file, pass the target that generates ' + 'it to gnome.compile_resources() using the "dependencies" ' + 'keyword argument.') + raw_dep_files.remove(resfile) + dep_files.append(f) + dep_files.extend(raw_dep_files) + return dep_files, depends, subdirs + + def _get_link_args(self, state: 'ModuleState', + lib: T.Union[build.SharedLibrary, build.StaticLibrary], + depends: T.Sequence[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString', build.StructuredSources]], + include_rpath: bool = False, + use_gir_args: bool = False + ) -> T.Tuple[T.List[str], T.List[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString', build.StructuredSources]]]: + link_command: T.List[str] = [] + new_depends = list(depends) + # Construct link args + if isinstance(lib, build.SharedLibrary): + libdir = os.path.join(state.environment.get_build_dir(), state.backend.get_target_dir(lib)) + link_command.append('-L' + libdir) + if include_rpath: + link_command.append('-Wl,-rpath,' + libdir) + new_depends.append(lib) + # Needed for the following binutils bug: + # https://github.com/mesonbuild/meson/issues/1911 + # However, g-ir-scanner does not understand -Wl,-rpath + # so we need to use -L instead + for d in state.backend.determine_rpath_dirs(lib): + d = os.path.join(state.environment.get_build_dir(), d) + link_command.append('-L' + d) + if include_rpath: + link_command.append('-Wl,-rpath,' + d) + if use_gir_args and self._gir_has_option('--extra-library'): + link_command.append('--extra-library=' + lib.name) + else: + link_command.append('-l' + lib.name) + return link_command, new_depends + + def _get_dependencies_flags_raw( + self, deps: T.Sequence[T.Union['Dependency', build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]], + state: 'ModuleState', + depends: T.Sequence[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString', build.StructuredSources]], + include_rpath: bool, + use_gir_args: bool, + ) -> T.Tuple[OrderedSet[str], OrderedSet[T.Union[str, T.Tuple[str, str]]], OrderedSet[T.Union[str, T.Tuple[str, str]]], OrderedSet[str], + T.List[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString', build.StructuredSources]]]: + cflags: OrderedSet[str] = OrderedSet() + # External linker flags that can't be de-duped reliably because they + # require two args in order, such as -framework AVFoundation will be stored as a tuple. + internal_ldflags: OrderedSet[T.Union[str, T.Tuple[str, str]]] = OrderedSet() + external_ldflags: OrderedSet[T.Union[str, T.Tuple[str, str]]] = OrderedSet() + gi_includes: OrderedSet[str] = OrderedSet() + deps = mesonlib.listify(deps) + depends = list(depends) + + for dep in deps: + if isinstance(dep, Dependency): + girdir = dep.get_variable(pkgconfig='girdir', internal='girdir', default_value='') + if girdir: + assert isinstance(girdir, str), 'for mypy' + gi_includes.update([girdir]) + if isinstance(dep, InternalDependency): + cflags.update(dep.get_compile_args()) + cflags.update(state.get_include_args(dep.include_directories)) + for lib in dep.libraries: + if isinstance(lib, build.SharedLibrary): + _ld, depends = self._get_link_args(state, lib, depends, include_rpath) + internal_ldflags.update(_ld) + libdepflags = self._get_dependencies_flags_raw(lib.get_external_deps(), state, depends, include_rpath, + use_gir_args) + cflags.update(libdepflags[0]) + internal_ldflags.update(libdepflags[1]) + external_ldflags.update(libdepflags[2]) + gi_includes.update(libdepflags[3]) + depends = libdepflags[4] + extdepflags = self._get_dependencies_flags_raw(dep.ext_deps, state, depends, include_rpath, + use_gir_args) + cflags.update(extdepflags[0]) + internal_ldflags.update(extdepflags[1]) + external_ldflags.update(extdepflags[2]) + gi_includes.update(extdepflags[3]) + depends = extdepflags[4] + for source in dep.sources: + if isinstance(source, GirTarget): + gi_includes.update([os.path.join(state.environment.get_build_dir(), + source.get_subdir())]) + # This should be any dependency other than an internal one. + elif isinstance(dep, Dependency): + cflags.update(dep.get_compile_args()) + ldflags = iter(dep.get_link_args(raw=True)) + for flag in ldflags: + if (os.path.isabs(flag) and + # For PkgConfigDependency only: + getattr(dep, 'is_libtool', False)): + lib_dir = os.path.dirname(flag) + external_ldflags.update([f'-L{lib_dir}']) + if include_rpath: + external_ldflags.update([f'-Wl,-rpath {lib_dir}']) + libname = os.path.basename(flag) + if libname.startswith("lib"): + libname = libname[3:] + libname = libname.split(".so")[0] + flag = f"-l{libname}" + # FIXME: Hack to avoid passing some compiler options in + if flag.startswith("-W"): + continue + # If it's a framework arg, slurp the framework name too + # to preserve the order of arguments + if flag == '-framework': + external_ldflags.update([(flag, next(ldflags))]) + else: + external_ldflags.update([flag]) + elif isinstance(dep, (build.StaticLibrary, build.SharedLibrary)): + cflags.update(state.get_include_args(dep.get_include_dirs())) + depends.append(dep) + else: + mlog.log(f'dependency {dep!r} not handled to build gir files') + continue + + if use_gir_args and self._gir_has_option('--extra-library'): + def fix_ldflags(ldflags: T.Iterable[T.Union[str, T.Tuple[str, str]]]) -> OrderedSet[T.Union[str, T.Tuple[str, str]]]: + fixed_ldflags: OrderedSet[T.Union[str, T.Tuple[str, str]]] = OrderedSet() + for ldflag in ldflags: + if isinstance(ldflag, str) and ldflag.startswith("-l"): + ldflag = ldflag.replace('-l', '--extra-library=', 1) + fixed_ldflags.add(ldflag) + return fixed_ldflags + internal_ldflags = fix_ldflags(internal_ldflags) + external_ldflags = fix_ldflags(external_ldflags) + return cflags, internal_ldflags, external_ldflags, gi_includes, depends + + def _get_dependencies_flags( + self, deps: T.Sequence[T.Union['Dependency', build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]], + state: 'ModuleState', + depends: T.Sequence[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString', build.StructuredSources]], + include_rpath: bool = False, + use_gir_args: bool = False, + ) -> T.Tuple[OrderedSet[str], T.List[str], T.List[str], OrderedSet[str], + T.List[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString', build.StructuredSources]]]: + + cflags, internal_ldflags_raw, external_ldflags_raw, gi_includes, depends = self._get_dependencies_flags_raw(deps, state, depends, include_rpath, use_gir_args) + internal_ldflags: T.List[str] = [] + external_ldflags: T.List[str] = [] + + # Extract non-deduplicable argument groups out of the tuples. + for ldflag in internal_ldflags_raw: + if isinstance(ldflag, str): + internal_ldflags.append(ldflag) + else: + internal_ldflags.extend(ldflag) + for ldflag in external_ldflags_raw: + if isinstance(ldflag, str): + external_ldflags.append(ldflag) + else: + external_ldflags.extend(ldflag) + + return cflags, internal_ldflags, external_ldflags, gi_includes, depends + + def _unwrap_gir_target(self, girtarget: T.Union[build.Executable, build.StaticLibrary, build.SharedLibrary], state: 'ModuleState' + ) -> T.Union[build.Executable, build.StaticLibrary, build.SharedLibrary]: + if not isinstance(girtarget, (build.Executable, build.SharedLibrary, + build.StaticLibrary)): + raise MesonException(f'Gir target must be an executable or library but is "{girtarget}" of type {type(girtarget).__name__}') + + STATIC_BUILD_REQUIRED_VERSION = ">=1.58.1" + if isinstance(girtarget, (build.StaticLibrary)) and \ + not mesonlib.version_compare( + self._get_gir_dep(state)[0].get_version(), + STATIC_BUILD_REQUIRED_VERSION): + raise MesonException('Static libraries can only be introspected with GObject-Introspection ' + STATIC_BUILD_REQUIRED_VERSION) + + return girtarget + + def _devenv_prepend(self, varname: str, value: str) -> None: + if self.devenv is None: + self.devenv = build.EnvironmentVariables() + self.devenv.prepend(varname, [value]) + + def get_devenv(self) -> T.Optional[build.EnvironmentVariables]: + return self.devenv + + def _get_gir_dep(self, state: 'ModuleState') -> T.Tuple[Dependency, T.Union[build.Executable, 'ExternalProgram', 'OverrideProgram'], + T.Union[build.Executable, 'ExternalProgram', 'OverrideProgram']]: + if not self.gir_dep: + self.gir_dep = state.dependency('gobject-introspection-1.0') + self.giscanner = state.find_tool('g-ir-scanner', 'gobject-introspection-1.0', 'g_ir_scanner') + self.gicompiler = state.find_tool('g-ir-compiler', 'gobject-introspection-1.0', 'g_ir_compiler') + return self.gir_dep, self.giscanner, self.gicompiler + + @functools.lru_cache(maxsize=None) + def _gir_has_option(self, option: str) -> bool: + exe = self.giscanner + if isinstance(exe, OverrideProgram): + # Handle overridden g-ir-scanner + assert option in {'--extra-library', '--sources-top-dirs'} + return True + p, o, _ = Popen_safe(exe.get_command() + ['--help'], stderr=subprocess.STDOUT) + return p.returncode == 0 and option in o + + # May mutate depends and gir_inc_dirs + @staticmethod + def _scan_include(state: 'ModuleState', includes: T.List[T.Union[str, GirTarget]] + ) -> T.Tuple[T.List[str], T.List[str], T.List[GirTarget]]: + ret: T.List[str] = [] + gir_inc_dirs: T.List[str] = [] + depends: T.List[GirTarget] = [] + + for inc in includes: + if isinstance(inc, str): + ret += [f'--include={inc}'] + elif isinstance(inc, GirTarget): + gir_inc_dirs .append(os.path.join(state.environment.get_build_dir(), inc.get_subdir())) + ret.append(f"--include-uninstalled={os.path.join(inc.get_subdir(), inc.get_basename())}") + depends.append(inc) + + return ret, gir_inc_dirs, depends + + @staticmethod + def _scan_langs(state: 'ModuleState', langs: T.Iterable[str]) -> T.List[str]: + ret: T.List[str] = [] + + for lang in langs: + link_args = state.environment.coredata.get_external_link_args(MachineChoice.HOST, lang) + for link_arg in link_args: + if link_arg.startswith('-L'): + ret.append(link_arg) + + return ret + + @staticmethod + def _scan_gir_targets(state: 'ModuleState', girtargets: T.Sequence[build.BuildTarget]) -> T.List[T.Union[str, build.Executable]]: + ret: T.List[T.Union[str, build.Executable]] = [] + + for girtarget in girtargets: + if isinstance(girtarget, build.Executable): + ret += ['--program', girtarget] + else: + # Because of https://gitlab.gnome.org/GNOME/gobject-introspection/merge_requests/72 + # we can't use the full path until this is merged. + libpath = os.path.join(girtarget.get_subdir(), girtarget.get_filename()) + # Must use absolute paths here because g-ir-scanner will not + # add them to the runtime path list if they're relative. This + # means we cannot use @BUILD_ROOT@ + build_root = state.environment.get_build_dir() + if isinstance(girtarget, build.SharedLibrary): + # need to put our output directory first as we need to use the + # generated libraries instead of any possibly installed system/prefix + # ones. + ret += ["-L{}/{}".format(build_root, os.path.dirname(libpath))] + libname = girtarget.get_basename() + else: + libname = os.path.join(f"{build_root}/{libpath}") + ret += ['--library', libname] + # Needed for the following binutils bug: + # https://github.com/mesonbuild/meson/issues/1911 + # However, g-ir-scanner does not understand -Wl,-rpath + # so we need to use -L instead + for d in state.backend.determine_rpath_dirs(girtarget): + d = os.path.join(state.environment.get_build_dir(), d) + ret.append('-L' + d) + + return ret + + @staticmethod + def _get_girtargets_langs_compilers(girtargets: T.Sequence[build.BuildTarget]) -> T.List[T.Tuple[str, 'Compiler']]: + ret: T.List[T.Tuple[str, 'Compiler']] = [] + for girtarget in girtargets: + for lang, compiler in girtarget.compilers.items(): + # XXX: Can you use g-i with any other language? + if lang in {'c', 'cpp', 'objc', 'objcpp', 'd'}: + ret.append((lang, compiler)) + break + + return ret + + @staticmethod + def _get_gir_targets_deps(girtargets: T.Sequence[build.BuildTarget] + ) -> T.List[T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex, Dependency]]: + ret: T.List[T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex, Dependency]] = [] + for girtarget in girtargets: + ret += girtarget.get_all_link_deps() + ret += girtarget.get_external_deps() + return ret + + @staticmethod + def _get_gir_targets_inc_dirs(girtargets: T.Sequence[build.BuildTarget]) -> OrderedSet[build.IncludeDirs]: + ret: OrderedSet = OrderedSet() + for girtarget in girtargets: + ret.update(girtarget.get_include_dirs()) + return ret + + @staticmethod + def _get_langs_compilers_flags(state: 'ModuleState', langs_compilers: T.List[T.Tuple[str, 'Compiler']] + ) -> T.Tuple[T.List[str], T.List[str], T.List[str]]: + cflags: T.List[str] = [] + internal_ldflags: T.List[str] = [] + external_ldflags: T.List[str] = [] + + for lang, compiler in langs_compilers: + if state.global_args.get(lang): + cflags += state.global_args[lang] + if state.project_args.get(lang): + cflags += state.project_args[lang] + if mesonlib.OptionKey('b_sanitize') in compiler.base_options: + sanitize = state.environment.coredata.options[mesonlib.OptionKey('b_sanitize')].value + cflags += compiler.sanitizer_compile_args(sanitize) + sanitize = sanitize.split(',') + # These must be first in ldflags + if 'address' in sanitize: + internal_ldflags += ['-lasan'] + if 'thread' in sanitize: + internal_ldflags += ['-ltsan'] + if 'undefined' in sanitize: + internal_ldflags += ['-lubsan'] + # FIXME: Linking directly to lib*san is not recommended but g-ir-scanner + # does not understand -f LDFLAGS. https://bugzilla.gnome.org/show_bug.cgi?id=783892 + # ldflags += compiler.sanitizer_link_args(sanitize) + + return cflags, internal_ldflags, external_ldflags + + @staticmethod + def _make_gir_filelist(state: 'ModuleState', srcdir: str, ns: str, + nsversion: str, girtargets: T.Sequence[build.BuildTarget], + libsources: T.Sequence[T.Union[ + str, mesonlib.File, build.GeneratedList, + build.CustomTarget, build.CustomTargetIndex]] + ) -> str: + gir_filelist_dir = state.backend.get_target_private_dir_abs(girtargets[0]) + if not os.path.isdir(gir_filelist_dir): + os.mkdir(gir_filelist_dir) + gir_filelist_filename = os.path.join(gir_filelist_dir, f'{ns}_{nsversion}_gir_filelist') + + with open(gir_filelist_filename, 'w', encoding='utf-8') as gir_filelist: + for s in libsources: + if isinstance(s, (build.CustomTarget, build.CustomTargetIndex)): + for custom_output in s.get_outputs(): + gir_filelist.write(os.path.join(state.environment.get_build_dir(), + state.backend.get_target_dir(s), + custom_output) + '\n') + elif isinstance(s, mesonlib.File): + gir_filelist.write(s.rel_to_builddir(state.build_to_src) + '\n') + elif isinstance(s, build.GeneratedList): + for gen_src in s.get_outputs(): + gir_filelist.write(os.path.join(srcdir, gen_src) + '\n') + else: + gir_filelist.write(os.path.join(srcdir, s) + '\n') + + return gir_filelist_filename + + @staticmethod + def _make_gir_target( + state: 'ModuleState', + girfile: str, + scan_command: T.Sequence[T.Union['FileOrString', Executable, ExternalProgram, OverrideProgram]], + generated_files: T.Sequence[T.Union[str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]], + depends: T.Sequence[T.Union['FileOrString', build.BuildTarget, 'build.GeneratedTypes', build.StructuredSources]], + kwargs: T.Dict[str, T.Any]) -> GirTarget: + install = kwargs['install_gir'] + if install is None: + install = kwargs['install'] + + install_dir = kwargs['install_dir_gir'] + if install_dir is None: + install_dir = os.path.join(state.environment.get_datadir(), 'gir-1.0') + elif install_dir is False: + install = False + + # g-ir-scanner uses pkg-config to find libraries such as glib. They could + # be built as subproject in which case we need to trick it to use + # -uninstalled.pc files Meson generated. It also must respect pkgconfig + # settings user could have set in machine file, like PKG_CONFIG_LIBDIR, + # SYSROOT, etc. + run_env = PkgConfigDependency.get_env(state.environment, MachineChoice.HOST, uninstalled=True) + + return GirTarget( + girfile, + state.subdir, + state.subproject, + state.environment, + scan_command, + generated_files, + [girfile], + build_by_default=kwargs['build_by_default'], + extra_depends=depends, + install=install, + install_dir=[install_dir], + install_tag=['devel'], + env=run_env, + ) + + @staticmethod + def _make_typelib_target(state: 'ModuleState', typelib_output: str, + typelib_cmd: T.Sequence[T.Union[str, build.Executable, ExternalProgram, build.CustomTarget]], + generated_files: T.Sequence[T.Union[str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]], + kwargs: T.Dict[str, T.Any]) -> TypelibTarget: + install = kwargs['install_typelib'] + if install is None: + install = kwargs['install'] + + install_dir = kwargs['install_dir_typelib'] + if install_dir is None: + install_dir = os.path.join(state.environment.get_libdir(), 'girepository-1.0') + elif install_dir is False: + install = False + + return TypelibTarget( + typelib_output, + state.subdir, + state.subproject, + state.environment, + typelib_cmd, + generated_files, + [typelib_output], + install=install, + install_dir=[install_dir], + install_tag=['typelib'], + build_by_default=kwargs['build_by_default'], + ) + + @staticmethod + def _gather_typelib_includes_and_update_depends( + state: 'ModuleState', + deps: T.Sequence[T.Union[Dependency, build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]], + depends: T.Sequence[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString', build.StructuredSources]] + ) -> T.Tuple[T.List[str], T.List[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString', build.StructuredSources]]]: + # Need to recursively add deps on GirTarget sources from our + # dependencies and also find the include directories needed for the + # typelib generation custom target below. + typelib_includes: T.List[str] = [] + new_depends = list(depends) + for dep in deps: + # Add a dependency on each GirTarget listed in dependencies and add + # the directory where it will be generated to the typelib includes + if isinstance(dep, InternalDependency): + for source in dep.sources: + if isinstance(source, GirTarget) and source not in depends: + new_depends.append(source) + subdir = os.path.join(state.environment.get_build_dir(), + source.get_subdir()) + if subdir not in typelib_includes: + typelib_includes.append(subdir) + # Do the same, but for dependencies of dependencies. These are + # stored in the list of generated sources for each link dep (from + # girtarget.get_all_link_deps() above). + # FIXME: Store this in the original form from declare_dependency() + # so it can be used here directly. + elif isinstance(dep, build.SharedLibrary): + for g_source in dep.generated: + if isinstance(g_source, GirTarget): + subdir = os.path.join(state.environment.get_build_dir(), + g_source.get_subdir()) + if subdir not in typelib_includes: + typelib_includes.append(subdir) + if isinstance(dep, Dependency): + girdir = dep.get_variable(pkgconfig='girdir', internal='girdir', default_value='') + assert isinstance(girdir, str), 'for mypy' + if girdir and girdir not in typelib_includes: + typelib_includes.append(girdir) + return typelib_includes, new_depends + + @staticmethod + def _get_external_args_for_langs(state: 'ModuleState', langs: T.List[str]) -> T.List[str]: + ret: T.List[str] = [] + for lang in langs: + ret += mesonlib.listify(state.environment.coredata.get_external_args(MachineChoice.HOST, lang)) + return ret + + @staticmethod + def _get_scanner_cflags(cflags: T.Iterable[str]) -> T.Iterable[str]: + 'g-ir-scanner only accepts -I/-D/-U; must ignore all other flags' + for f in cflags: + # _FORTIFY_SOURCE depends on / works together with -O, on the other hand this + # just invokes the preprocessor anyway + if f.startswith(('-D', '-U', '-I')) and not f.startswith('-D_FORTIFY_SOURCE'): + yield f + + @staticmethod + def _get_scanner_ldflags(ldflags: T.Iterable[str]) -> T.Iterable[str]: + 'g-ir-scanner only accepts -L/-l; must ignore -F and other linker flags' + for f in ldflags: + if f.startswith(('-L', '-l', '--extra-library')): + yield f + + @typed_pos_args('gnome.generate_gir', varargs=(build.Executable, build.SharedLibrary, build.StaticLibrary), min_varargs=1) + @typed_kwargs( + 'gnome.generate_gir', + INSTALL_KW, + _BUILD_BY_DEFAULT.evolve(since='0.40.0'), + _EXTRA_ARGS_KW, + KwargInfo('dependencies', ContainerTypeInfo(list, Dependency), default=[], listify=True), + KwargInfo('export_packages', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo('fatal_warnings', bool, default=False, since='0.55.0'), + KwargInfo('header', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo('identifier_prefix', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo('include_directories', ContainerTypeInfo(list, (str, build.IncludeDirs)), default=[], listify=True), + KwargInfo('includes', ContainerTypeInfo(list, (str, GirTarget)), default=[], listify=True), + KwargInfo('install_gir', (bool, NoneType), since='0.61.0'), + KwargInfo('install_dir_gir', (str, bool, NoneType), + deprecated_values={False: ('0.61.0', 'Use install_gir to disable installation')}, + validator=lambda x: 'as boolean can only be false' if x is True else None), + KwargInfo('install_typelib', (bool, NoneType), since='0.61.0'), + KwargInfo('install_dir_typelib', (str, bool, NoneType), + deprecated_values={False: ('0.61.0', 'Use install_typelib to disable installation')}, + validator=lambda x: 'as boolean can only be false' if x is True else None), + KwargInfo('link_with', ContainerTypeInfo(list, (build.SharedLibrary, build.StaticLibrary)), default=[], listify=True), + KwargInfo('namespace', str, required=True), + KwargInfo('nsversion', str, required=True), + KwargInfo('sources', ContainerTypeInfo(list, (str, mesonlib.File, build.GeneratedList, build.CustomTarget, build.CustomTargetIndex)), default=[], listify=True), + KwargInfo('symbol_prefix', ContainerTypeInfo(list, str), default=[], listify=True), + ) + def generate_gir(self, state: 'ModuleState', args: T.Tuple[T.List[T.Union[build.Executable, build.SharedLibrary, build.StaticLibrary]]], + kwargs: 'GenerateGir') -> ModuleReturnValue: + girtargets = [self._unwrap_gir_target(arg, state) for arg in args[0]] + if len(girtargets) > 1 and any(isinstance(el, build.Executable) for el in girtargets): + raise MesonException('generate_gir only accepts a single argument when one of the arguments is an executable') + + gir_dep, giscanner, gicompiler = self._get_gir_dep(state) + + ns = kwargs['namespace'] + nsversion = kwargs['nsversion'] + libsources = kwargs['sources'] + + girfile = f'{ns}-{nsversion}.gir' + srcdir = os.path.join(state.environment.get_source_dir(), state.subdir) + builddir = os.path.join(state.environment.get_build_dir(), state.subdir) + + depends: T.List[T.Union['FileOrString', 'build.GeneratedTypes', build.BuildTarget, build.StructuredSources]] = [] + depends.extend(gir_dep.sources) + depends.extend(girtargets) + + langs_compilers = self._get_girtargets_langs_compilers(girtargets) + cflags, internal_ldflags, external_ldflags = self._get_langs_compilers_flags(state, langs_compilers) + deps = self._get_gir_targets_deps(girtargets) + deps += kwargs['dependencies'] + deps += [gir_dep] + typelib_includes, depends = self._gather_typelib_includes_and_update_depends(state, deps, depends) + # ldflags will be misinterpreted by gir scanner (showing + # spurious dependencies) but building GStreamer fails if they + # are not used here. + dep_cflags, dep_internal_ldflags, dep_external_ldflags, gi_includes, depends = \ + self._get_dependencies_flags(deps, state, depends, use_gir_args=True) + scan_cflags = [] + scan_cflags += list(self._get_scanner_cflags(cflags)) + scan_cflags += list(self._get_scanner_cflags(dep_cflags)) + scan_cflags += list(self._get_scanner_cflags(self._get_external_args_for_langs(state, [lc[0] for lc in langs_compilers]))) + scan_internal_ldflags = [] + scan_internal_ldflags += list(self._get_scanner_ldflags(internal_ldflags)) + scan_internal_ldflags += list(self._get_scanner_ldflags(dep_internal_ldflags)) + scan_external_ldflags = [] + scan_external_ldflags += list(self._get_scanner_ldflags(external_ldflags)) + scan_external_ldflags += list(self._get_scanner_ldflags(dep_external_ldflags)) + girtargets_inc_dirs = self._get_gir_targets_inc_dirs(girtargets) + inc_dirs = kwargs['include_directories'] + + gir_inc_dirs: T.List[str] = [] + + scan_command: T.List[T.Union[str, build.Executable, 'ExternalProgram', 'OverrideProgram']] = [giscanner] + scan_command += ['--quiet'] + scan_command += ['--no-libtool'] + scan_command += ['--namespace=' + ns, '--nsversion=' + nsversion] + scan_command += ['--warn-all'] + scan_command += ['--output', '@OUTPUT@'] + scan_command += [f'--c-include={h}' for h in kwargs['header']] + scan_command += kwargs['extra_args'] + scan_command += ['-I' + srcdir, '-I' + builddir] + scan_command += state.get_include_args(girtargets_inc_dirs) + scan_command += ['--filelist=' + self._make_gir_filelist(state, srcdir, ns, nsversion, girtargets, libsources)] + for l in kwargs['link_with']: + _cflags, depends = self._get_link_args(state, l, depends, use_gir_args=True) + scan_command.extend(_cflags) + _cmd, _ginc, _deps = self._scan_include(state, kwargs['includes']) + scan_command.extend(_cmd) + gir_inc_dirs.extend(_ginc) + depends.extend(_deps) + + scan_command += [f'--symbol-prefix={p}' for p in kwargs['symbol_prefix']] + scan_command += [f'--identifier-prefix={p}' for p in kwargs['identifier_prefix']] + scan_command += [f'--pkg-export={p}' for p in kwargs['export_packages']] + scan_command += ['--cflags-begin'] + scan_command += scan_cflags + scan_command += ['--cflags-end'] + scan_command += state.get_include_args(inc_dirs) + scan_command += state.get_include_args(itertools.chain(gi_includes, gir_inc_dirs, inc_dirs), prefix='--add-include-path=') + scan_command += list(scan_internal_ldflags) + scan_command += self._scan_gir_targets(state, girtargets) + scan_command += self._scan_langs(state, [lc[0] for lc in langs_compilers]) + scan_command += list(scan_external_ldflags) + + if self._gir_has_option('--sources-top-dirs'): + scan_command += ['--sources-top-dirs', os.path.join(state.environment.get_source_dir(), state.root_subdir)] + scan_command += ['--sources-top-dirs', os.path.join(state.environment.get_build_dir(), state.root_subdir)] + + if '--warn-error' in scan_command: + FeatureDeprecated.single_use('gnome.generate_gir argument --warn-error', '0.55.0', + state.subproject, 'Use "fatal_warnings" keyword argument', state.current_node) + if kwargs['fatal_warnings']: + scan_command.append('--warn-error') + + generated_files = [f for f in libsources if isinstance(f, (GeneratedList, CustomTarget, CustomTargetIndex))] + + scan_target = self._make_gir_target( + state, girfile, scan_command, generated_files, depends, + # We have to cast here because mypy can't figure this out + T.cast('T.Dict[str, T.Any]', kwargs)) + + typelib_output = f'{ns}-{nsversion}.typelib' + typelib_cmd = [gicompiler, scan_target, '--output', '@OUTPUT@'] + typelib_cmd += state.get_include_args(gir_inc_dirs, prefix='--includedir=') + + for incdir in typelib_includes: + typelib_cmd += ["--includedir=" + incdir] + + typelib_target = self._make_typelib_target(state, typelib_output, typelib_cmd, generated_files, T.cast('T.Dict[str, T.Any]', kwargs)) + + self._devenv_prepend('GI_TYPELIB_PATH', os.path.join(state.environment.get_build_dir(), state.subdir)) + + rv = [scan_target, typelib_target] + + return ModuleReturnValue(rv, rv) + + @noPosargs + @typed_kwargs('gnome.compile_schemas', _BUILD_BY_DEFAULT.evolve(since='0.40.0'), DEPEND_FILES_KW) + def compile_schemas(self, state: 'ModuleState', args: T.List['TYPE_var'], kwargs: 'CompileSchemas') -> ModuleReturnValue: + srcdir = os.path.join(state.build_to_src, state.subdir) + outdir = state.subdir + + cmd: T.List[T.Union[ExternalProgram, str]] = [state.find_program('glib-compile-schemas'), '--targetdir', outdir, srcdir] + if state.subdir == '': + targetname = 'gsettings-compile' + else: + targetname = 'gsettings-compile-' + state.subdir.replace('/', '_') + target_g = build.CustomTarget( + targetname, + state.subdir, + state.subproject, + state.environment, + cmd, + [], + ['gschemas.compiled'], + build_by_default=kwargs['build_by_default'], + depend_files=kwargs['depend_files'], + ) + self._devenv_prepend('GSETTINGS_SCHEMA_DIR', os.path.join(state.environment.get_build_dir(), state.subdir)) + return ModuleReturnValue(target_g, [target_g]) + + @typed_pos_args('gnome.yelp', str, varargs=str) + @typed_kwargs( + 'gnome.yelp', + KwargInfo( + 'languages', ContainerTypeInfo(list, str), + listify=True, default=[], + deprecated='0.43.0', + deprecated_message='Use a LINGUAS file in the source directory instead', + ), + KwargInfo('media', ContainerTypeInfo(list, str), listify=True, default=[]), + KwargInfo('sources', ContainerTypeInfo(list, str), listify=True, default=[]), + KwargInfo('symlink_media', bool, default=True), + ) + def yelp(self, state: 'ModuleState', args: T.Tuple[str, T.List[str]], kwargs: 'Yelp') -> ModuleReturnValue: + project_id = args[0] + sources = kwargs['sources'] + if args[1]: + FeatureDeprecated.single_use('gnome.yelp more than one positional argument', '0.60.0', + state.subproject, 'use the "sources" keyword argument instead.', state.current_node) + if not sources: + sources = args[1] + if not sources: + raise MesonException('Yelp requires a list of sources') + elif args[1]: + mlog.warning('"gnome.yelp" ignores positional sources arguments when the "sources" keyword argument is set') + sources_files = [mesonlib.File.from_source_file(state.environment.source_dir, + os.path.join(state.subdir, 'C'), + s) for s in sources] + + langs = kwargs['languages'] + if not langs: + langs = read_linguas(os.path.join(state.environment.source_dir, state.subdir)) + + media = kwargs['media'] + symlinks = kwargs['symlink_media'] + targets: T.List[T.Union['build.Target', build.Data, build.SymlinkData]] = [] + potargets: T.List[build.RunTarget] = [] + + itstool = state.find_program('itstool') + msgmerge = state.find_program('msgmerge') + msgfmt = state.find_program('msgfmt') + + install_dir = os.path.join(state.environment.get_datadir(), 'help') + c_install_dir = os.path.join(install_dir, 'C', project_id) + c_data = build.Data(sources_files, c_install_dir, c_install_dir, + mesonlib.FileMode(), state.subproject, install_tag='doc') + targets.append(c_data) + + media_files: T.List[mesonlib.File] = [] + for m in media: + f = mesonlib.File.from_source_file(state.environment.source_dir, + os.path.join(state.subdir, 'C'), m) + media_files.append(f) + m_install_dir = os.path.join(c_install_dir, os.path.dirname(m)) + m_data = build.Data([f], m_install_dir, m_install_dir, + mesonlib.FileMode(), state.subproject, install_tag='doc') + targets.append(m_data) + + pot_file = os.path.join('@SOURCE_ROOT@', state.subdir, 'C', project_id + '.pot') + pot_sources = [os.path.join('@SOURCE_ROOT@', state.subdir, 'C', s) for s in sources] + pot_args: T.List[T.Union['ExternalProgram', str]] = [itstool, '-o', pot_file] + pot_args.extend(pot_sources) + pottarget = build.RunTarget(f'help-{project_id}-pot', pot_args, [], + os.path.join(state.subdir, 'C'), state.subproject, + state.environment) + targets.append(pottarget) + + for l in langs: + l_subdir = os.path.join(state.subdir, l) + l_install_dir = os.path.join(install_dir, l, project_id) + + for i, m in enumerate(media): + m_dir = os.path.dirname(m) + m_install_dir = os.path.join(l_install_dir, m_dir) + l_data: T.Union[build.Data, build.SymlinkData] + if symlinks: + link_target = os.path.join(os.path.relpath(c_install_dir, start=m_install_dir), m) + l_data = build.SymlinkData(link_target, os.path.basename(m), + m_install_dir, state.subproject, install_tag='doc') + else: + try: + m_file = mesonlib.File.from_source_file(state.environment.source_dir, l_subdir, m) + except MesonException: + m_file = media_files[i] + l_data = build.Data([m_file], m_install_dir, m_install_dir, + mesonlib.FileMode(), state.subproject, install_tag='doc') + targets.append(l_data) + + po_file = l + '.po' + po_args: T.List[T.Union['ExternalProgram', str]] = [ + msgmerge, '-q', '-o', + os.path.join('@SOURCE_ROOT@', l_subdir, po_file), + os.path.join('@SOURCE_ROOT@', l_subdir, po_file), pot_file] + potarget = build.RunTarget(f'help-{project_id}-{l}-update-po', + po_args, [pottarget], l_subdir, state.subproject, + state.environment) + targets.append(potarget) + potargets.append(potarget) + + gmo_file = project_id + '-' + l + '.gmo' + gmotarget = build.CustomTarget( + f'help-{project_id}-{l}-gmo', + l_subdir, + state.subproject, + state.environment, + [msgfmt, '@INPUT@', '-o', '@OUTPUT@'], + [po_file], + [gmo_file], + install_tag=['doc'], + ) + targets.append(gmotarget) + + mergetarget = build.CustomTarget( + f'help-{project_id}-{l}', + l_subdir, + state.subproject, + state.environment, + [itstool, '-m', os.path.join(l_subdir, gmo_file), '--lang', l, '-o', '@OUTDIR@', '@INPUT@'], + sources_files, + sources, + extra_depends=[gmotarget], + install=True, + install_dir=[l_install_dir], + install_tag=['doc'], + ) + targets.append(mergetarget) + + allpotarget = build.AliasTarget(f'help-{project_id}-update-po', potargets, + state.subdir, state.subproject, state.environment) + targets.append(allpotarget) + + return ModuleReturnValue(None, targets) + + @typed_pos_args('gnome.gtkdoc', str) + @typed_kwargs( + 'gnome.gtkdoc', + KwargInfo('c_args', ContainerTypeInfo(list, str), since='0.48.0', default=[], listify=True), + KwargInfo('check', bool, default=False, since='0.52.0'), + KwargInfo('content_files', ContainerTypeInfo(list, (str, mesonlib.File, build.GeneratedList, build.CustomTarget, build.CustomTargetIndex)), default=[], listify=True), + KwargInfo( + 'dependencies', + ContainerTypeInfo(list, (Dependency, build.SharedLibrary, build.StaticLibrary)), + listify=True, default=[]), + KwargInfo('expand_content_files', ContainerTypeInfo(list, (str, mesonlib.File)), default=[], listify=True), + KwargInfo('fixxref_args', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo('gobject_typesfile', ContainerTypeInfo(list, (str, mesonlib.File)), default=[], listify=True), + KwargInfo('html_args', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo('html_assets', ContainerTypeInfo(list, (str, mesonlib.File)), default=[], listify=True), + KwargInfo('ignore_headers', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo( + 'include_directories', + ContainerTypeInfo(list, (str, build.IncludeDirs)), + listify=True, default=[]), + KwargInfo('install', bool, default=True), + KwargInfo('install_dir', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo('main_sgml', (str, NoneType)), + KwargInfo('main_xml', (str, NoneType)), + KwargInfo('mkdb_args', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo( + 'mode', str, default='auto', since='0.37.0', + validator=in_set_validator({'xml', 'sgml', 'none', 'auto'})), + KwargInfo('module_version', str, default='', since='0.48.0'), + KwargInfo('namespace', str, default='', since='0.37.0'), + KwargInfo('scan_args', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo('scanobjs_args', ContainerTypeInfo(list, str), default=[], listify=True), + KwargInfo('src_dir', ContainerTypeInfo(list, (str, build.IncludeDirs)), listify=True, required=True), + ) + def gtkdoc(self, state: 'ModuleState', args: T.Tuple[str], kwargs: 'GtkDoc') -> ModuleReturnValue: + modulename = args[0] + main_file = kwargs['main_sgml'] + main_xml = kwargs['main_xml'] + if main_xml is not None: + if main_file is not None: + raise InvalidArguments('gnome.gtkdoc: main_xml and main_sgml are exclusive arguments') + main_file = main_xml + moduleversion = kwargs['module_version'] + targetname = modulename + ('-' + moduleversion if moduleversion else '') + '-doc' + command = state.environment.get_build_command() + + namespace = kwargs['namespace'] + + def abs_filenames(files: T.Iterable['FileOrString']) -> T.Iterator[str]: + for f in files: + if isinstance(f, mesonlib.File): + yield f.absolute_path(state.environment.get_source_dir(), state.environment.get_build_dir()) + else: + yield os.path.join(state.environment.get_source_dir(), state.subdir, f) + + src_dirs = kwargs['src_dir'] + header_dirs: T.List[str] = [] + for src_dir in src_dirs: + if isinstance(src_dir, build.IncludeDirs): + header_dirs.extend(src_dir.to_string_list(state.environment.get_source_dir(), + state.environment.get_build_dir())) + else: + header_dirs.append(src_dir) + + t_args: T.List[str] = [ + '--internal', 'gtkdoc', + '--sourcedir=' + state.environment.get_source_dir(), + '--builddir=' + state.environment.get_build_dir(), + '--subdir=' + state.subdir, + '--headerdirs=' + '@@'.join(header_dirs), + '--mainfile=' + main_file, + '--modulename=' + modulename, + '--moduleversion=' + moduleversion, + '--mode=' + kwargs['mode']] + for tool in ['scan', 'scangobj', 'mkdb', 'mkhtml', 'fixxref']: + program_name = 'gtkdoc-' + tool + program = state.find_program(program_name) + path = program.get_path() + t_args.append(f'--{program_name}={path}') + if namespace: + t_args.append('--namespace=' + namespace) + exe_wrapper = state.environment.get_exe_wrapper() + if exe_wrapper: + t_args.append('--run=' + ' '.join(exe_wrapper.get_command())) + t_args.append(f'--htmlargs={"@@".join(kwargs["html_args"])}') + t_args.append(f'--scanargs={"@@".join(kwargs["scan_args"])}') + t_args.append(f'--scanobjsargs={"@@".join(kwargs["scanobjs_args"])}') + t_args.append(f'--gobjects-types-file={"@@".join(abs_filenames(kwargs["gobject_typesfile"]))}') + t_args.append(f'--fixxrefargs={"@@".join(kwargs["fixxref_args"])}') + t_args.append(f'--mkdbargs={"@@".join(kwargs["mkdb_args"])}') + t_args.append(f'--html-assets={"@@".join(abs_filenames(kwargs["html_assets"]))}') + + depends: T.List['build.GeneratedTypes'] = [] + content_files = [] + for s in kwargs['content_files']: + if isinstance(s, (build.CustomTarget, build.CustomTargetIndex)): + depends.append(s) + for o in s.get_outputs(): + content_files.append(os.path.join(state.environment.get_build_dir(), + state.backend.get_target_dir(s), + o)) + elif isinstance(s, mesonlib.File): + content_files.append(s.absolute_path(state.environment.get_source_dir(), + state.environment.get_build_dir())) + elif isinstance(s, build.GeneratedList): + depends.append(s) + for gen_src in s.get_outputs(): + content_files.append(os.path.join(state.environment.get_source_dir(), + state.subdir, + gen_src)) + else: + content_files.append(os.path.join(state.environment.get_source_dir(), + state.subdir, + s)) + t_args += ['--content-files=' + '@@'.join(content_files)] + + t_args.append(f'--expand-content-files={"@@".join(abs_filenames(kwargs["expand_content_files"]))}') + t_args.append(f'--ignore-headers={"@@".join(kwargs["ignore_headers"])}') + t_args.append(f'--installdir={"@@".join(kwargs["install_dir"])}') + build_args, new_depends = self._get_build_args(kwargs['c_args'], kwargs['include_directories'], + kwargs['dependencies'], state, depends) + t_args.extend(build_args) + new_depends.extend(depends) + custom_target = build.CustomTarget( + targetname, + state.subdir, + state.subproject, + state.environment, + command + t_args, + [], + [f'{modulename}-decl.txt'], + build_always_stale=True, + extra_depends=new_depends, + ) + alias_target = build.AliasTarget(targetname, [custom_target], state.subdir, state.subproject, state.environment) + if kwargs['check']: + check_cmd = state.find_program('gtkdoc-check') + check_env = ['DOC_MODULE=' + modulename, + 'DOC_MAIN_SGML_FILE=' + main_file] + check_args = (targetname + '-check', check_cmd) + check_workdir = os.path.join(state.environment.get_build_dir(), state.subdir) + state.test(check_args, env=check_env, workdir=check_workdir, depends=[custom_target]) + res: T.List[T.Union[build.Target, build.ExecutableSerialisation]] = [custom_target, alias_target] + if kwargs['install']: + res.append(state.backend.get_executable_serialisation(command + t_args, tag='doc')) + return ModuleReturnValue(custom_target, res) + + def _get_build_args(self, c_args: T.List[str], inc_dirs: T.List[T.Union[str, build.IncludeDirs]], + deps: T.List[T.Union[Dependency, build.SharedLibrary, build.StaticLibrary]], + state: 'ModuleState', + depends: T.Sequence[T.Union[build.BuildTarget, 'build.GeneratedTypes']]) -> T.Tuple[ + T.List[str], T.List[T.Union[build.BuildTarget, 'build.GeneratedTypes', 'FileOrString', build.StructuredSources]]]: + args: T.List[str] = [] + cflags = c_args.copy() + deps_cflags, internal_ldflags, external_ldflags, _gi_includes, new_depends = \ + self._get_dependencies_flags(deps, state, depends, include_rpath=True) + + cflags.extend(deps_cflags) + cflags.extend(state.get_include_args(inc_dirs)) + ldflags: T.List[str] = [] + ldflags.extend(internal_ldflags) + ldflags.extend(external_ldflags) + + cflags.extend(state.environment.coredata.get_external_args(MachineChoice.HOST, 'c')) + ldflags.extend(state.environment.coredata.get_external_link_args(MachineChoice.HOST, 'c')) + compiler = state.environment.coredata.compilers[MachineChoice.HOST]['c'] + + compiler_flags = self._get_langs_compilers_flags(state, [('c', compiler)]) + cflags.extend(compiler_flags[0]) + ldflags.extend(compiler_flags[1]) + ldflags.extend(compiler_flags[2]) + if compiler: + args += ['--cc=%s' % join_args(compiler.get_exelist())] + args += ['--ld=%s' % join_args(compiler.get_linker_exelist())] + if cflags: + args += ['--cflags=%s' % join_args(cflags)] + if ldflags: + args += ['--ldflags=%s' % join_args(ldflags)] + + return args, new_depends + + @noKwargs + @typed_pos_args('gnome.gtkdoc_html_dir', str) + def gtkdoc_html_dir(self, state: 'ModuleState', args: T.Tuple[str], kwargs: 'TYPE_kwargs') -> str: + return os.path.join('share/gtk-doc/html', args[0]) + + @typed_pos_args('gnome.gdbus_codegen', str, optargs=[(str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList)]) + @typed_kwargs( + 'gnome.gdbus_codegen', + _BUILD_BY_DEFAULT.evolve(since='0.40.0'), + SOURCES_KW.evolve(since='0.46.0'), + KwargInfo('extra_args', ContainerTypeInfo(list, str), since='0.47.0', default=[], listify=True), + KwargInfo('interface_prefix', (str, NoneType)), + KwargInfo('namespace', (str, NoneType)), + KwargInfo('object_manager', bool, default=False), + KwargInfo( + 'annotations', ContainerTypeInfo(list, (list, str)), + default=[], + validator=annotations_validator, + convertor=lambda x: [x] if x and isinstance(x[0], str) else x, + ), + KwargInfo('install_header', bool, default=False, since='0.46.0'), + KwargInfo('docbook', (str, NoneType)), + KwargInfo( + 'autocleanup', str, default='default', since='0.47.0', + validator=in_set_validator({'all', 'none', 'objects'})), + INSTALL_DIR_KW.evolve(since='0.46.0') + ) + def gdbus_codegen(self, state: 'ModuleState', args: T.Tuple[str, T.Optional[T.Union['FileOrString', build.GeneratedTypes]]], + kwargs: 'GdbusCodegen') -> ModuleReturnValue: + namebase = args[0] + xml_files: T.List[T.Union['FileOrString', build.GeneratedTypes]] = [args[1]] if args[1] else [] + cmd: T.List[T.Union['ExternalProgram', str]] = [state.find_program('gdbus-codegen')] + cmd.extend(kwargs['extra_args']) + + # Autocleanup supported? + glib_version = self._get_native_glib_version(state) + if not mesonlib.version_compare(glib_version, '>= 2.49.1'): + # Warn if requested, silently disable if not + if kwargs['autocleanup'] != 'default': + mlog.warning(f'Glib version ({glib_version}) is too old to support the \'autocleanup\' ' + 'kwarg, need 2.49.1 or newer') + else: + # Handle legacy glib versions that don't have autocleanup + ac = kwargs['autocleanup'] + if ac == 'default': + ac = 'all' + cmd.extend(['--c-generate-autocleanup', ac]) + + if kwargs['interface_prefix'] is not None: + cmd.extend(['--interface-prefix', kwargs['interface_prefix']]) + if kwargs['namespace'] is not None: + cmd.extend(['--c-namespace', kwargs['namespace']]) + if kwargs['object_manager']: + cmd.extend(['--c-generate-object-manager']) + xml_files.extend(kwargs['sources']) + build_by_default = kwargs['build_by_default'] + + # Annotations are a bit ugly in that they are a list of lists of strings... + for annot in kwargs['annotations']: + cmd.append('--annotate') + cmd.extend(annot) + + targets = [] + install_header = kwargs['install_header'] + install_dir = kwargs['install_dir'] or state.environment.coredata.get_option(mesonlib.OptionKey('includedir')) + assert isinstance(install_dir, str), 'for mypy' + + output = namebase + '.c' + # Added in https://gitlab.gnome.org/GNOME/glib/commit/e4d68c7b3e8b01ab1a4231bf6da21d045cb5a816 (2.55.2) + # Fixed in https://gitlab.gnome.org/GNOME/glib/commit/cd1f82d8fc741a2203582c12cc21b4dacf7e1872 (2.56.2) + if mesonlib.version_compare(glib_version, '>= 2.56.2'): + c_cmd = cmd + ['--body', '--output', '@OUTPUT@', '@INPUT@'] + else: + if kwargs['docbook'] is not None: + docbook = kwargs['docbook'] + + cmd += ['--generate-docbook', docbook] + + # https://git.gnome.org/browse/glib/commit/?id=ee09bb704fe9ccb24d92dd86696a0e6bb8f0dc1a + if mesonlib.version_compare(glib_version, '>= 2.51.3'): + cmd += ['--output-directory', '@OUTDIR@', '--generate-c-code', namebase, '@INPUT@'] + else: + self._print_gdbus_warning() + cmd += ['--generate-c-code', '@OUTDIR@/' + namebase, '@INPUT@'] + c_cmd = cmd + + cfile_custom_target = build.CustomTarget( + output, + state.subdir, + state.subproject, + state.environment, + c_cmd, + xml_files, + [output], + build_by_default=build_by_default, + ) + targets.append(cfile_custom_target) + + output = namebase + '.h' + if mesonlib.version_compare(glib_version, '>= 2.56.2'): + hfile_cmd = cmd + ['--header', '--output', '@OUTPUT@', '@INPUT@'] + depends = [] + else: + hfile_cmd = cmd + depends = [cfile_custom_target] + + hfile_custom_target = build.CustomTarget( + output, + state.subdir, + state.subproject, + state.environment, + hfile_cmd, + xml_files, + [output], + build_by_default=build_by_default, + extra_depends=depends, + install=install_header, + install_dir=[install_dir], + install_tag=['devel'], + ) + targets.append(hfile_custom_target) + + if kwargs['docbook'] is not None: + docbook = kwargs['docbook'] + # The docbook output is always ${docbook}-${name_of_xml_file} + output = namebase + '-docbook' + outputs = [] + for f in xml_files: + outputs.append('{}-{}'.format(docbook, os.path.basename(str(f)))) + + if mesonlib.version_compare(glib_version, '>= 2.56.2'): + docbook_cmd = cmd + ['--output-directory', '@OUTDIR@', '--generate-docbook', docbook, '@INPUT@'] + depends = [] + else: + docbook_cmd = cmd + depends = [cfile_custom_target] + + docbook_custom_target = build.CustomTarget( + output, + state.subdir, + state.subproject, + state.environment, + docbook_cmd, + xml_files, + outputs, + build_by_default=build_by_default, + extra_depends=depends, + ) + targets.append(docbook_custom_target) + + return ModuleReturnValue(targets, targets) + + @typed_pos_args('gnome.mkenums', str) + @typed_kwargs( + 'gnome.mkenums', + *_MK_ENUMS_COMMON_KWS, + DEPENDS_KW, + KwargInfo('c_template', (str, mesonlib.File, NoneType)), + KwargInfo('h_template', (str, mesonlib.File, NoneType)), + KwargInfo('comments', (str, NoneType)), + KwargInfo('eprod', (str, NoneType)), + KwargInfo('fhead', (str, NoneType)), + KwargInfo('fprod', (str, NoneType)), + KwargInfo('ftail', (str, NoneType)), + KwargInfo('vhead', (str, NoneType)), + KwargInfo('vprod', (str, NoneType)), + KwargInfo('vtail', (str, NoneType)), + ) + def mkenums(self, state: 'ModuleState', args: T.Tuple[str], kwargs: 'MkEnums') -> ModuleReturnValue: + basename = args[0] + + c_template = kwargs['c_template'] + if isinstance(c_template, mesonlib.File): + c_template = c_template.absolute_path(state.environment.source_dir, state.environment.build_dir) + h_template = kwargs['h_template'] + if isinstance(h_template, mesonlib.File): + h_template = h_template.absolute_path(state.environment.source_dir, state.environment.build_dir) + + cmd: T.List[str] = [] + known_kwargs = ['comments', 'eprod', 'fhead', 'fprod', 'ftail', + 'identifier_prefix', 'symbol_prefix', + 'vhead', 'vprod', 'vtail'] + for arg in known_kwargs: + # mypy can't figure this out + if kwargs[arg]: # type: ignore + cmd += ['--' + arg.replace('_', '-'), kwargs[arg]] # type: ignore + + targets: T.List[CustomTarget] = [] + + h_target: T.Optional[CustomTarget] = None + if h_template is not None: + h_output = os.path.basename(os.path.splitext(h_template)[0]) + # We always set template as the first element in the source array + # so --template consumes it. + h_cmd = cmd + ['--template', '@INPUT@'] + h_sources: T.List[T.Union[FileOrString, 'build.GeneratedTypes']] = [h_template] + h_sources.extend(kwargs['sources']) + h_target = self._make_mkenum_impl( + state, h_sources, h_output, h_cmd, install=kwargs['install_header'], + install_dir=kwargs['install_dir']) + targets.append(h_target) + + if c_template is not None: + c_output = os.path.basename(os.path.splitext(c_template)[0]) + # We always set template as the first element in the source array + # so --template consumes it. + c_cmd = cmd + ['--template', '@INPUT@'] + c_sources: T.List[T.Union[FileOrString, 'build.GeneratedTypes']] = [c_template] + c_sources.extend(kwargs['sources']) + + depends = kwargs['depends'].copy() + if h_target is not None: + depends.append(h_target) + c_target = self._make_mkenum_impl( + state, c_sources, c_output, c_cmd, depends=depends) + targets.insert(0, c_target) + + if c_template is None and h_template is None: + generic_cmd = cmd + ['@INPUT@'] + target = self._make_mkenum_impl( + state, kwargs['sources'], basename, generic_cmd, + install=kwargs['install_header'], + install_dir=kwargs['install_dir']) + return ModuleReturnValue(target, [target]) + else: + return ModuleReturnValue(targets, targets) + + @FeatureNew('gnome.mkenums_simple', '0.42.0') + @typed_pos_args('gnome.mkenums_simple', str) + @typed_kwargs( + 'gnome.mkenums_simple', + *_MK_ENUMS_COMMON_KWS, + KwargInfo('header_prefix', str, default=''), + KwargInfo('function_prefix', str, default=''), + KwargInfo('body_prefix', str, default=''), + KwargInfo('decorator', str, default=''), + ) + def mkenums_simple(self, state: 'ModuleState', args: T.Tuple[str], kwargs: 'MkEnumsSimple') -> ModuleReturnValue: + hdr_filename = f'{args[0]}.h' + body_filename = f'{args[0]}.c' + + header_prefix = kwargs['header_prefix'] + decl_decorator = kwargs['decorator'] + func_prefix = kwargs['function_prefix'] + body_prefix = kwargs['body_prefix'] + + cmd: T.List[str] = [] + if kwargs['identifier_prefix']: + cmd.extend(['--identifier-prefix', kwargs['identifier_prefix']]) + if kwargs['symbol_prefix']: + cmd.extend(['--symbol-prefix', kwargs['symbol_prefix']]) + + c_cmd = cmd.copy() + # Maybe we should write our own template files into the build dir + # instead, but that seems like much more work, nice as it would be. + fhead = '' + if body_prefix != '': + fhead += '%s\n' % body_prefix + fhead += '#include "%s"\n' % hdr_filename + for hdr in kwargs['sources']: + fhead += '#include "{}"\n'.format(os.path.basename(str(hdr))) + fhead += textwrap.dedent( + ''' + #define C_ENUM(v) ((gint) v) + #define C_FLAGS(v) ((guint) v) + ''') + c_cmd.extend(['--fhead', fhead]) + + c_cmd.append('--fprod') + c_cmd.append(textwrap.dedent( + ''' + /* enumerations from "@basename@" */ + ''')) + + c_cmd.append('--vhead') + c_cmd.append(textwrap.dedent( + f''' + GType + {func_prefix}@enum_name@_get_type (void) + {{ + static gsize gtype_id = 0; + static const G@Type@Value values[] = {{''')) + + c_cmd.extend(['--vprod', ' { C_@TYPE@(@VALUENAME@), "@VALUENAME@", "@valuenick@" },']) + + c_cmd.append('--vtail') + c_cmd.append(textwrap.dedent( + ''' { 0, NULL, NULL } + }; + if (g_once_init_enter (>ype_id)) { + GType new_type = g_@type@_register_static (g_intern_static_string ("@EnumName@"), values); + g_once_init_leave (>ype_id, new_type); + } + return (GType) gtype_id; + }''')) + c_cmd.append('@INPUT@') + + c_file = self._make_mkenum_impl(state, kwargs['sources'], body_filename, c_cmd) + + # .h file generation + h_cmd = cmd.copy() + + h_cmd.append('--fhead') + h_cmd.append(textwrap.dedent( + f'''#pragma once + + #include <glib-object.h> + {header_prefix} + + G_BEGIN_DECLS + ''')) + + h_cmd.append('--fprod') + h_cmd.append(textwrap.dedent( + ''' + /* enumerations from "@basename@" */ + ''')) + + h_cmd.append('--vhead') + h_cmd.append(textwrap.dedent( + f''' + {decl_decorator} + GType {func_prefix}@enum_name@_get_type (void); + #define @ENUMPREFIX@_TYPE_@ENUMSHORT@ ({func_prefix}@enum_name@_get_type())''')) + + h_cmd.append('--ftail') + h_cmd.append(textwrap.dedent( + ''' + G_END_DECLS''')) + h_cmd.append('@INPUT@') + + h_file = self._make_mkenum_impl( + state, kwargs['sources'], hdr_filename, h_cmd, + install=kwargs['install_header'], + install_dir=kwargs['install_dir']) + + return ModuleReturnValue([c_file, h_file], [c_file, h_file]) + + @staticmethod + def _make_mkenum_impl( + state: 'ModuleState', + sources: T.Sequence[T.Union[str, mesonlib.File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]], + output: str, + cmd: T.List[str], + *, + install: bool = False, + install_dir: T.Optional[T.Sequence[T.Union[str, bool]]] = None, + depends: T.Optional[T.Sequence[T.Union[CustomTarget, CustomTargetIndex, BuildTarget]]] = None + ) -> build.CustomTarget: + real_cmd: T.List[T.Union[str, ExternalProgram]] = [state.find_program(['glib-mkenums', 'mkenums'])] + real_cmd.extend(cmd) + _install_dir = install_dir or state.environment.coredata.get_option(mesonlib.OptionKey('includedir')) + assert isinstance(_install_dir, str), 'for mypy' + + return build.CustomTarget( + output, + state.subdir, + state.subproject, + state.environment, + real_cmd, + sources, + [output], + capture=True, + install=install, + install_dir=[_install_dir], + install_tag=['devel'], + extra_depends=depends, + # https://github.com/mesonbuild/meson/issues/973 + absolute_paths=True, + ) + + @typed_pos_args('gnome.genmarshal', str) + @typed_kwargs( + 'gnome.genmarshal', + DEPEND_FILES_KW.evolve(since='0.61.0'), + DEPENDS_KW.evolve(since='0.61.0'), + INSTALL_KW.evolve(name='install_header'), + INSTALL_DIR_KW, + KwargInfo('extra_args', ContainerTypeInfo(list, str), listify=True, default=[]), + KwargInfo('internal', bool, default=False), + KwargInfo('nostdinc', bool, default=False), + KwargInfo('prefix', (str, NoneType)), + KwargInfo('skip_source', bool, default=False), + KwargInfo('sources', ContainerTypeInfo(list, (str, mesonlib.File), allow_empty=False), listify=True, required=True), + KwargInfo('stdinc', bool, default=False), + KwargInfo('valist_marshallers', bool, default=False), + ) + def genmarshal(self, state: 'ModuleState', args: T.Tuple[str], kwargs: 'GenMarshal') -> ModuleReturnValue: + output = args[0] + sources = kwargs['sources'] + + new_genmarshal = mesonlib.version_compare(self._get_native_glib_version(state), '>= 2.53.3') + + cmd: T.List[T.Union['ExternalProgram', str]] = [state.find_program('glib-genmarshal')] + if kwargs['prefix']: + cmd.extend(['--prefix', kwargs['prefix']]) + if kwargs['extra_args']: + if new_genmarshal: + cmd.extend(kwargs['extra_args']) + else: + mlog.warning('The current version of GLib does not support extra arguments \n' + 'for glib-genmarshal. You need at least GLib 2.53.3. See ', + mlog.bold('https://github.com/mesonbuild/meson/pull/2049')) + for k in ['internal', 'nostdinc', 'skip_source', 'stdinc', 'valist_marshallers']: + # Mypy can't figure out that this is correct + if kwargs[k]: # type: ignore + cmd.append(f'--{k.replace("_", "-")}') + + install_header = kwargs['install_header'] + capture = False + + # https://github.com/GNOME/glib/commit/0fbc98097fac4d3e647684f344e508abae109fdf + if mesonlib.version_compare(self._get_native_glib_version(state), '>= 2.51.0'): + cmd += ['--output', '@OUTPUT@'] + else: + capture = True + + header_file = output + '.h' + h_cmd = cmd + ['--header', '@INPUT@'] + if new_genmarshal: + h_cmd += ['--pragma-once'] + header = build.CustomTarget( + output + '_h', + state.subdir, + state.subproject, + state.environment, + h_cmd, + sources, + [header_file], + install=install_header, + install_dir=[kwargs['install_dir']] if kwargs['install_dir'] else [], + install_tag=['devel'], + capture=capture, + depend_files=kwargs['depend_files'], + ) + + c_cmd = cmd + ['--body', '@INPUT@'] + extra_deps: T.List[build.CustomTarget] = [] + if mesonlib.version_compare(self._get_native_glib_version(state), '>= 2.53.4'): + # Silence any warnings about missing prototypes + c_cmd += ['--include-header', header_file] + extra_deps.append(header) + body = build.CustomTarget( + output + '_c', + state.subdir, + state.subproject, + state.environment, + c_cmd, + sources, + [f'{output}.c'], + capture=capture, + depend_files=kwargs['depend_files'], + extra_depends=extra_deps, + ) + + rv = [body, header] + return ModuleReturnValue(rv, rv) + + def _extract_vapi_packages(self, state: 'ModuleState', packages: T.List[T.Union[InternalDependency, str]], + ) -> T.Tuple[T.List[str], T.List[VapiTarget], T.List[str], T.List[str], T.List[str]]: + ''' + Packages are special because we need to: + - Get a list of packages for the .deps file + - Get a list of depends for any VapiTargets + - Get package name from VapiTargets + - Add include dirs for any VapiTargets + ''' + if not packages: + return [], [], [], [], [] + vapi_depends: T.List[VapiTarget] = [] + vapi_packages: T.List[str] = [] + vapi_includes: T.List[str] = [] + vapi_args: T.List[str] = [] + remaining_args = [] + for arg in packages: + if isinstance(arg, InternalDependency): + targets = [t for t in arg.sources if isinstance(t, VapiTarget)] + for target in targets: + srcdir = os.path.join(state.environment.get_source_dir(), + target.get_subdir()) + outdir = os.path.join(state.environment.get_build_dir(), + target.get_subdir()) + outfile = target.get_outputs()[0][:-5] # Strip .vapi + vapi_args.append('--vapidir=' + outdir) + vapi_args.append('--girdir=' + outdir) + vapi_args.append('--pkg=' + outfile) + vapi_depends.append(target) + vapi_packages.append(outfile) + vapi_includes.append(srcdir) + else: + assert isinstance(arg, str), 'for mypy' + vapi_args.append(f'--pkg={arg}') + vapi_packages.append(arg) + remaining_args.append(arg) + + # TODO: this is supposed to take IncludeDirs, but it never worked + return vapi_args, vapi_depends, vapi_packages, vapi_includes, remaining_args + + def _generate_deps(self, state: 'ModuleState', library: str, packages: T.List[str], install_dir: str) -> build.Data: + outdir = state.environment.scratch_dir + fname = os.path.join(outdir, library + '.deps') + with open(fname, 'w', encoding='utf-8') as ofile: + for package in packages: + ofile.write(package + '\n') + return build.Data([mesonlib.File(True, outdir, fname)], install_dir, install_dir, mesonlib.FileMode(), state.subproject) + + def _get_vapi_link_with(self, target: build.CustomTarget) -> T.List[build.LibTypes]: + link_with: T.List[build.LibTypes] = [] + for dep in target.get_target_dependencies(): + if isinstance(dep, build.SharedLibrary): + link_with.append(dep) + elif isinstance(dep, GirTarget): + link_with += self._get_vapi_link_with(dep) + return link_with + + @typed_pos_args('gnome.generate_vapi', str) + @typed_kwargs( + 'gnome.generate_vapi', + INSTALL_KW, + INSTALL_DIR_KW, + KwargInfo( + 'sources', + ContainerTypeInfo(list, (str, GirTarget), allow_empty=False), + listify=True, + required=True, + ), + KwargInfo('vapi_dirs', ContainerTypeInfo(list, str), listify=True, default=[]), + KwargInfo('metadata_dirs', ContainerTypeInfo(list, str), listify=True, default=[]), + KwargInfo('gir_dirs', ContainerTypeInfo(list, str), listify=True, default=[]), + KwargInfo('packages', ContainerTypeInfo(list, (str, InternalDependency)), listify=True, default=[]), + ) + def generate_vapi(self, state: 'ModuleState', args: T.Tuple[str], kwargs: 'GenerateVapi') -> ModuleReturnValue: + created_values: T.List[T.Union[Dependency, build.Data]] = [] + library = args[0] + build_dir = os.path.join(state.environment.get_build_dir(), state.subdir) + source_dir = os.path.join(state.environment.get_source_dir(), state.subdir) + pkg_cmd, vapi_depends, vapi_packages, vapi_includes, packages = self._extract_vapi_packages(state, kwargs['packages']) + cmd: T.List[T.Union[str, 'ExternalProgram']] + cmd = [state.find_program('vapigen'), '--quiet', f'--library={library}', f'--directory={build_dir}'] + cmd.extend([f'--vapidir={d}' for d in kwargs['vapi_dirs']]) + cmd.extend([f'--metadatadir={d}' for d in kwargs['metadata_dirs']]) + cmd.extend([f'--girdir={d}' for d in kwargs['gir_dirs']]) + cmd += pkg_cmd + cmd += ['--metadatadir=' + source_dir] + + inputs = kwargs['sources'] + + link_with: T.List[build.LibTypes] = [] + for i in inputs: + if isinstance(i, str): + cmd.append(os.path.join(source_dir, i)) + elif isinstance(i, GirTarget): + link_with += self._get_vapi_link_with(i) + subdir = os.path.join(state.environment.get_build_dir(), + i.get_subdir()) + gir_file = os.path.join(subdir, i.get_outputs()[0]) + cmd.append(gir_file) + + vapi_output = library + '.vapi' + datadir = state.environment.coredata.get_option(mesonlib.OptionKey('datadir')) + assert isinstance(datadir, str), 'for mypy' + install_dir = kwargs['install_dir'] or os.path.join(datadir, 'vala', 'vapi') + + if kwargs['install']: + # We shouldn't need this locally but we install it + deps_target = self._generate_deps(state, library, vapi_packages, install_dir) + created_values.append(deps_target) + vapi_target = VapiTarget( + vapi_output, + state.subdir, + state.subproject, + state.environment, + command=cmd, + sources=inputs, + outputs=[vapi_output], + extra_depends=vapi_depends, + install=kwargs['install'], + install_dir=[install_dir], + install_tag=['devel'], + ) + + # So to try our best to get this to just work we need: + # - link with with the correct library + # - include the vapi and dependent vapi files in sources + # - add relevant directories to include dirs + incs = [build.IncludeDirs(state.subdir, ['.'] + vapi_includes, False)] + sources = [vapi_target] + vapi_depends + rv = InternalDependency(None, incs, [], [], link_with, [], sources, [], {}, [], []) + created_values.append(rv) + return ModuleReturnValue(rv, created_values) + +def initialize(interp: 'Interpreter') -> GnomeModule: + mod = GnomeModule(interp) + mod.interpreter.append_holder_map(GResourceTarget, interpreter.CustomTargetHolder) + mod.interpreter.append_holder_map(GResourceHeaderTarget, interpreter.CustomTargetHolder) + mod.interpreter.append_holder_map(GirTarget, interpreter.CustomTargetHolder) + mod.interpreter.append_holder_map(TypelibTarget, interpreter.CustomTargetHolder) + mod.interpreter.append_holder_map(VapiTarget, interpreter.CustomTargetHolder) + return mod |