diff options
Diffstat (limited to '')
-rw-r--r-- | mesonbuild/build.py | 2905 |
1 files changed, 2905 insertions, 0 deletions
diff --git a/mesonbuild/build.py b/mesonbuild/build.py new file mode 100644 index 0000000..a37ee92 --- /dev/null +++ b/mesonbuild/build.py @@ -0,0 +1,2905 @@ +# Copyright 2012-2017 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. + +from __future__ import annotations +from collections import defaultdict, OrderedDict +from dataclasses import dataclass, field +from functools import lru_cache +import copy +import hashlib +import itertools, pathlib +import os +import pickle +import re +import textwrap +import typing as T + + +from . import environment +from . import dependencies +from . import mlog +from . import programs +from .mesonlib import ( + HoldableObject, SecondLevelHolder, + File, MesonException, MachineChoice, PerMachine, OrderedSet, listify, + extract_as_list, typeslistify, stringlistify, classify_unity_sources, + get_filenames_templates_dict, substitute_values, has_path_sep, + OptionKey, PerMachineDefaultable, OptionOverrideProxy, + MesonBugException, EnvironmentVariables, pickle_load, +) +from .compilers import ( + is_object, clink_langs, sort_clink, all_languages, + is_known_suffix, detect_static_linker +) +from .interpreterbase import FeatureNew, FeatureDeprecated + +if T.TYPE_CHECKING: + from typing_extensions import Literal + from ._typing import ImmutableListProtocol + from .backend.backends import Backend, ExecutableSerialisation + from .compilers import Compiler + from .interpreter.interpreter import Test, SourceOutputs, Interpreter + from .interpreterbase import SubProject + from .linkers import StaticLinker + from .mesonlib import FileMode, FileOrString + from .modules import ModuleState + from .mparser import BaseNode + from .wrap import WrapMode + + GeneratedTypes = T.Union['CustomTarget', 'CustomTargetIndex', 'GeneratedList'] + LibTypes = T.Union['SharedLibrary', 'StaticLibrary', 'CustomTarget', 'CustomTargetIndex'] + BuildTargetTypes = T.Union['BuildTarget', 'CustomTarget', 'CustomTargetIndex'] + +pch_kwargs = {'c_pch', 'cpp_pch'} + +lang_arg_kwargs = {f'{lang}_args' for lang in all_languages} +lang_arg_kwargs |= { + 'd_import_dirs', + 'd_unittest', + 'd_module_versions', + 'd_debug', +} + +vala_kwargs = {'vala_header', 'vala_gir', 'vala_vapi'} +rust_kwargs = {'rust_crate_type'} +cs_kwargs = {'resources', 'cs_args'} + +buildtarget_kwargs = { + 'build_by_default', + 'build_rpath', + 'dependencies', + 'extra_files', + 'gui_app', + 'link_with', + 'link_whole', + 'link_args', + 'link_depends', + 'implicit_include_directories', + 'include_directories', + 'install', + 'install_rpath', + 'install_dir', + 'install_mode', + 'install_tag', + 'name_prefix', + 'name_suffix', + 'native', + 'objects', + 'override_options', + 'sources', + 'gnu_symbol_visibility', + 'link_language', + 'win_subsystem', +} + +known_build_target_kwargs = ( + buildtarget_kwargs | + lang_arg_kwargs | + pch_kwargs | + vala_kwargs | + rust_kwargs | + cs_kwargs) + +known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic', 'pie'} +known_shlib_kwargs = known_build_target_kwargs | {'version', 'soversion', 'vs_module_defs', 'darwin_versions'} +known_shmod_kwargs = known_build_target_kwargs | {'vs_module_defs'} +known_stlib_kwargs = known_build_target_kwargs | {'pic', 'prelink'} +known_jar_kwargs = known_exe_kwargs | {'main_class', 'java_resources'} + +def _process_install_tag(install_tag: T.Optional[T.List[T.Optional[str]]], + num_outputs: int) -> T.List[T.Optional[str]]: + _install_tag: T.List[T.Optional[str]] + if not install_tag: + _install_tag = [None] * num_outputs + elif len(install_tag) == 1: + _install_tag = install_tag * num_outputs + else: + _install_tag = install_tag + return _install_tag + + +@lru_cache(maxsize=None) +def get_target_macos_dylib_install_name(ld) -> str: + name = ['@rpath/', ld.prefix, ld.name] + if ld.soversion is not None: + name.append('.' + ld.soversion) + name.append('.dylib') + return ''.join(name) + +class InvalidArguments(MesonException): + pass + +@dataclass(eq=False) +class DependencyOverride(HoldableObject): + dep: dependencies.Dependency + node: 'BaseNode' + explicit: bool = True + +@dataclass(eq=False) +class Headers(HoldableObject): + sources: T.List[File] + install_subdir: T.Optional[str] + custom_install_dir: T.Optional[str] + custom_install_mode: 'FileMode' + subproject: str + + # TODO: we really don't need any of these methods, but they're preserved to + # keep APIs relying on them working. + + def set_install_subdir(self, subdir: str) -> None: + self.install_subdir = subdir + + def get_install_subdir(self) -> T.Optional[str]: + return self.install_subdir + + def get_sources(self) -> T.List[File]: + return self.sources + + def get_custom_install_dir(self) -> T.Optional[str]: + return self.custom_install_dir + + def get_custom_install_mode(self) -> 'FileMode': + return self.custom_install_mode + + +@dataclass(eq=False) +class Man(HoldableObject): + sources: T.List[File] + custom_install_dir: T.Optional[str] + custom_install_mode: 'FileMode' + subproject: str + locale: T.Optional[str] + + def get_custom_install_dir(self) -> T.Optional[str]: + return self.custom_install_dir + + def get_custom_install_mode(self) -> 'FileMode': + return self.custom_install_mode + + def get_sources(self) -> T.List['File']: + return self.sources + + +@dataclass(eq=False) +class EmptyDir(HoldableObject): + path: str + install_mode: 'FileMode' + subproject: str + install_tag: T.Optional[str] = None + + +@dataclass(eq=False) +class InstallDir(HoldableObject): + source_subdir: str + installable_subdir: str + install_dir: str + install_dir_name: str + install_mode: 'FileMode' + exclude: T.Tuple[T.Set[str], T.Set[str]] + strip_directory: bool + subproject: str + from_source_dir: bool = True + install_tag: T.Optional[str] = None + +@dataclass(eq=False) +class DepManifest: + version: str + license: T.List[str] + + def to_json(self) -> T.Dict[str, T.Union[str, T.List[str]]]: + return { + 'version': self.version, + 'license': self.license, + } + + +# literally everything isn't dataclass stuff +class Build: + """A class that holds the status of one build including + all dependencies and so on. + """ + + def __init__(self, environment: environment.Environment): + self.project_name = 'name of master project' + self.project_version = None + self.environment = environment + self.projects = {} + self.targets: 'T.OrderedDict[str, T.Union[CustomTarget, BuildTarget]]' = OrderedDict() + self.global_args: PerMachine[T.Dict[str, T.List[str]]] = PerMachine({}, {}) + self.global_link_args: PerMachine[T.Dict[str, T.List[str]]] = PerMachine({}, {}) + self.projects_args: PerMachine[T.Dict[str, T.Dict[str, T.List[str]]]] = PerMachine({}, {}) + self.projects_link_args: PerMachine[T.Dict[str, T.Dict[str, T.List[str]]]] = PerMachine({}, {}) + self.tests: T.List['Test'] = [] + self.benchmarks: T.List['Test'] = [] + self.headers: T.List[Headers] = [] + self.man: T.List[Man] = [] + self.emptydir: T.List[EmptyDir] = [] + self.data: T.List[Data] = [] + self.symlinks: T.List[SymlinkData] = [] + self.static_linker: PerMachine[StaticLinker] = PerMachine(None, None) + self.subprojects = {} + self.subproject_dir = '' + self.install_scripts: T.List['ExecutableSerialisation'] = [] + self.postconf_scripts: T.List['ExecutableSerialisation'] = [] + self.dist_scripts: T.List['ExecutableSerialisation'] = [] + self.install_dirs: T.List[InstallDir] = [] + self.dep_manifest_name: T.Optional[str] = None + self.dep_manifest: T.Dict[str, DepManifest] = {} + self.stdlibs = PerMachine({}, {}) + self.test_setups: T.Dict[str, TestSetup] = {} + self.test_setup_default_name = None + self.find_overrides: T.Dict[str, T.Union['Executable', programs.ExternalProgram, programs.OverrideProgram]] = {} + self.searched_programs: T.Set[str] = set() # The list of all programs that have been searched for. + + # If we are doing a cross build we need two caches, if we're doing a + # build == host compilation the both caches should point to the same place. + self.dependency_overrides: PerMachine[T.Dict[T.Tuple, DependencyOverride]] = PerMachineDefaultable.default( + environment.is_cross_build(), {}, {}) + self.devenv: T.List[EnvironmentVariables] = [] + self.modules: T.List[str] = [] + self.need_vsenv = False + + def get_build_targets(self): + build_targets = OrderedDict() + for name, t in self.targets.items(): + if isinstance(t, BuildTarget): + build_targets[name] = t + return build_targets + + def get_custom_targets(self): + custom_targets = OrderedDict() + for name, t in self.targets.items(): + if isinstance(t, CustomTarget): + custom_targets[name] = t + return custom_targets + + def copy(self) -> Build: + other = Build(self.environment) + for k, v in self.__dict__.items(): + if isinstance(v, (list, dict, set, OrderedDict)): + other.__dict__[k] = v.copy() + else: + other.__dict__[k] = v + return other + + def merge(self, other: Build) -> None: + for k, v in other.__dict__.items(): + self.__dict__[k] = v + + def ensure_static_linker(self, compiler: Compiler) -> None: + if self.static_linker[compiler.for_machine] is None and compiler.needs_static_linker(): + self.static_linker[compiler.for_machine] = detect_static_linker(self.environment, compiler) + + def get_project(self): + return self.projects[''] + + def get_subproject_dir(self): + return self.subproject_dir + + def get_targets(self) -> 'T.OrderedDict[str, T.Union[CustomTarget, BuildTarget]]': + return self.targets + + def get_tests(self) -> T.List['Test']: + return self.tests + + def get_benchmarks(self) -> T.List['Test']: + return self.benchmarks + + def get_headers(self) -> T.List['Headers']: + return self.headers + + def get_man(self) -> T.List['Man']: + return self.man + + def get_data(self) -> T.List['Data']: + return self.data + + def get_symlinks(self) -> T.List['SymlinkData']: + return self.symlinks + + def get_emptydir(self) -> T.List['EmptyDir']: + return self.emptydir + + def get_install_subdirs(self) -> T.List['InstallDir']: + return self.install_dirs + + def get_global_args(self, compiler: 'Compiler', for_machine: 'MachineChoice') -> T.List[str]: + d = self.global_args[for_machine] + return d.get(compiler.get_language(), []) + + def get_project_args(self, compiler: 'Compiler', project: str, for_machine: 'MachineChoice') -> T.List[str]: + d = self.projects_args[for_machine] + args = d.get(project) + if not args: + return [] + return args.get(compiler.get_language(), []) + + def get_global_link_args(self, compiler: 'Compiler', for_machine: 'MachineChoice') -> T.List[str]: + d = self.global_link_args[for_machine] + return d.get(compiler.get_language(), []) + + def get_project_link_args(self, compiler: 'Compiler', project: str, for_machine: 'MachineChoice') -> T.List[str]: + d = self.projects_link_args[for_machine] + + link_args = d.get(project) + if not link_args: + return [] + + return link_args.get(compiler.get_language(), []) + +@dataclass(eq=False) +class IncludeDirs(HoldableObject): + + """Internal representation of an include_directories call.""" + + curdir: str + incdirs: T.List[str] + is_system: bool + # Interpreter has validated that all given directories + # actually exist. + extra_build_dirs: T.List[str] = field(default_factory=list) + + def __repr__(self) -> str: + r = '<{} {}/{}>' + return r.format(self.__class__.__name__, self.curdir, self.incdirs) + + def get_curdir(self) -> str: + return self.curdir + + def get_incdirs(self) -> T.List[str]: + return self.incdirs + + def get_extra_build_dirs(self) -> T.List[str]: + return self.extra_build_dirs + + def to_string_list(self, sourcedir: str, builddir: T.Optional[str] = None) -> T.List[str]: + """Convert IncludeDirs object to a list of strings. + + :param sourcedir: The absolute source directory + :param builddir: The absolute build directory, option, build dir will not + be added if this is unset + :returns: A list of strings (without compiler argument) + """ + strlist: T.List[str] = [] + for idir in self.incdirs: + strlist.append(os.path.join(sourcedir, self.curdir, idir)) + if builddir: + strlist.append(os.path.join(builddir, self.curdir, idir)) + return strlist + +@dataclass(eq=False) +class ExtractedObjects(HoldableObject): + ''' + Holds a list of sources for which the objects must be extracted + ''' + target: 'BuildTarget' + srclist: T.List[File] = field(default_factory=list) + genlist: T.List['GeneratedTypes'] = field(default_factory=list) + objlist: T.List[T.Union[str, 'File', 'ExtractedObjects']] = field(default_factory=list) + recursive: bool = True + + def __post_init__(self) -> None: + if self.target.is_unity: + self.check_unity_compatible() + + def __repr__(self) -> str: + r = '<{0} {1!r}: {2}>' + return r.format(self.__class__.__name__, self.target.name, self.srclist) + + @staticmethod + def get_sources(sources: T.Sequence['FileOrString'], generated_sources: T.Sequence['GeneratedTypes']) -> T.List['FileOrString']: + # Merge sources and generated sources + sources = list(sources) + for gensrc in generated_sources: + for s in gensrc.get_outputs(): + # We cannot know the path where this source will be generated, + # but all we need here is the file extension to determine the + # compiler. + sources.append(s) + + # Filter out headers and all non-source files + return [s for s in sources if environment.is_source(s)] + + def classify_all_sources(self, sources: T.List[FileOrString], generated_sources: T.Sequence['GeneratedTypes']) -> T.Dict['Compiler', T.List['FileOrString']]: + sources_ = self.get_sources(sources, generated_sources) + return classify_unity_sources(self.target.compilers.values(), sources_) + + def check_unity_compatible(self) -> None: + # Figure out if the extracted object list is compatible with a Unity + # build. When we're doing a Unified build, we go through the sources, + # and create a single source file from each subset of the sources that + # can be compiled with a specific compiler. Then we create one object + # from each unified source file. So for each compiler we can either + # extra all its sources or none. + cmpsrcs = self.classify_all_sources(self.target.sources, self.target.generated) + extracted_cmpsrcs = self.classify_all_sources(self.srclist, self.genlist) + + for comp, srcs in extracted_cmpsrcs.items(): + if set(srcs) != set(cmpsrcs[comp]): + raise MesonException('Single object files can not be extracted ' + 'in Unity builds. You can only extract all ' + 'the object files for each compiler at once.') + + +@dataclass(eq=False, order=False) +class StructuredSources(HoldableObject): + + """A container for sources in languages that use filesystem hierarchy. + + Languages like Rust and Cython rely on the layout of files in the filesystem + as part of the compiler implementation. This structure allows us to + represent the required filesystem layout. + """ + + sources: T.DefaultDict[str, T.List[T.Union[File, CustomTarget, CustomTargetIndex, GeneratedList]]] = field( + default_factory=lambda: defaultdict(list)) + + def __add__(self, other: StructuredSources) -> StructuredSources: + sources = self.sources.copy() + for k, v in other.sources.items(): + sources[k].extend(v) + return StructuredSources(sources) + + def __bool__(self) -> bool: + return bool(self.sources) + + def first_file(self) -> T.Union[File, CustomTarget, CustomTargetIndex, GeneratedList]: + """Get the first source in the root + + :return: The first source in the root + """ + return self.sources[''][0] + + def as_list(self) -> T.List[T.Union[File, CustomTarget, CustomTargetIndex, GeneratedList]]: + return list(itertools.chain.from_iterable(self.sources.values())) + + def needs_copy(self) -> bool: + """Do we need to create a structure in the build directory. + + This allows us to avoid making copies if the structures exists in the + source dir. Which could happen in situations where a generated source + only exists in some configurations + """ + for files in self.sources.values(): + for f in files: + if isinstance(f, File): + if f.is_built: + return True + else: + return True + return False + + +@dataclass(eq=False) +class Target(HoldableObject): + + # TODO: should Target be an abc.ABCMeta? + + name: str + subdir: str + subproject: 'SubProject' + build_by_default: bool + for_machine: MachineChoice + environment: environment.Environment + + def __post_init__(self) -> None: + if has_path_sep(self.name): + # Fix failing test 53 when this becomes an error. + mlog.warning(textwrap.dedent(f'''\ + Target "{self.name}" has a path separator in its name. + This is not supported, it can cause unexpected failures and will become + a hard error in the future. + ''')) + self.install = False + self.build_always_stale = False + self.options = OptionOverrideProxy({}, self.environment.coredata.options, self.subproject) + self.extra_files = [] # type: T.List[File] + if not hasattr(self, 'typename'): + raise RuntimeError(f'Target type is not set for target class "{type(self).__name__}". This is a bug') + + # dataclass comparators? + def __lt__(self, other: object) -> bool: + if not isinstance(other, Target): + return NotImplemented + return self.get_id() < other.get_id() + + def __le__(self, other: object) -> bool: + if not isinstance(other, Target): + return NotImplemented + return self.get_id() <= other.get_id() + + def __gt__(self, other: object) -> bool: + if not isinstance(other, Target): + return NotImplemented + return self.get_id() > other.get_id() + + def __ge__(self, other: object) -> bool: + if not isinstance(other, Target): + return NotImplemented + return self.get_id() >= other.get_id() + + def get_default_install_dir(self) -> T.Tuple[str, str]: + raise NotImplementedError + + def get_custom_install_dir(self) -> T.List[T.Union[str, Literal[False]]]: + raise NotImplementedError + + def get_install_dir(self) -> T.Tuple[T.List[T.Union[str, Literal[False]]], str, Literal[False]]: + # Find the installation directory. + default_install_dir, default_install_dir_name = self.get_default_install_dir() + outdirs = self.get_custom_install_dir() + if outdirs and outdirs[0] != default_install_dir and outdirs[0] is not True: + # Either the value is set to a non-default value, or is set to + # False (which means we want this specific output out of many + # outputs to not be installed). + custom_install_dir = True + install_dir_names = [getattr(i, 'optname', None) for i in outdirs] + else: + custom_install_dir = False + # if outdirs is empty we need to set to something, otherwise we set + # only the first value to the default. + if outdirs: + outdirs[0] = default_install_dir + else: + outdirs = [default_install_dir] + install_dir_names = [default_install_dir_name] * len(outdirs) + + return outdirs, install_dir_names, custom_install_dir + + def get_basename(self) -> str: + return self.name + + def get_subdir(self) -> str: + return self.subdir + + def get_typename(self) -> str: + return self.typename + + @staticmethod + def _get_id_hash(target_id): + # We don't really need cryptographic security here. + # Small-digest hash function with unlikely collision is good enough. + h = hashlib.sha256() + h.update(target_id.encode(encoding='utf-8', errors='replace')) + # This ID should be case-insensitive and should work in Visual Studio, + # e.g. it should not start with leading '-'. + return h.hexdigest()[:7] + + @staticmethod + def construct_id_from_path(subdir: str, name: str, type_suffix: str) -> str: + """Construct target ID from subdir, name and type suffix. + + This helper function is made public mostly for tests.""" + # This ID must also be a valid file name on all OSs. + # It should also avoid shell metacharacters for obvious + # reasons. '@' is not used as often as '_' in source code names. + # In case of collisions consider using checksums. + # FIXME replace with assert when slash in names is prohibited + name_part = name.replace('/', '@').replace('\\', '@') + assert not has_path_sep(type_suffix) + my_id = name_part + type_suffix + if subdir: + subdir_part = Target._get_id_hash(subdir) + # preserve myid for better debuggability + return subdir_part + '@@' + my_id + return my_id + + def get_id(self) -> str: + return self.construct_id_from_path( + self.subdir, self.name, self.type_suffix()) + + def process_kwargs_base(self, kwargs: T.Dict[str, T.Any]) -> None: + if 'build_by_default' in kwargs: + self.build_by_default = kwargs['build_by_default'] + if not isinstance(self.build_by_default, bool): + raise InvalidArguments('build_by_default must be a boolean value.') + elif kwargs.get('install', False): + # For backward compatibility, if build_by_default is not explicitly + # set, use the value of 'install' if it's enabled. + self.build_by_default = True + + self.set_option_overrides(self.parse_overrides(kwargs)) + + def set_option_overrides(self, option_overrides: T.Dict[OptionKey, str]) -> None: + self.options.overrides = {} + for k, v in option_overrides.items(): + if k.lang: + self.options.overrides[k.evolve(machine=self.for_machine)] = v + else: + self.options.overrides[k] = v + + def get_options(self) -> OptionOverrideProxy: + return self.options + + def get_option(self, key: 'OptionKey') -> T.Union[str, int, bool, 'WrapMode']: + # We don't actually have wrapmode here to do an assert, so just do a + # cast, we know what's in coredata anyway. + # TODO: if it's possible to annotate get_option or validate_option_value + # in the future we might be able to remove the cast here + return T.cast('T.Union[str, int, bool, WrapMode]', self.options[key].value) + + @staticmethod + def parse_overrides(kwargs: T.Dict[str, T.Any]) -> T.Dict[OptionKey, str]: + opts = kwargs.get('override_options', []) + + # In this case we have an already parsed and ready to go dictionary + # provided by typed_kwargs + if isinstance(opts, dict): + return T.cast('T.Dict[OptionKey, str]', opts) + + result: T.Dict[OptionKey, str] = {} + overrides = stringlistify(opts) + for o in overrides: + if '=' not in o: + raise InvalidArguments('Overrides must be of form "key=value"') + k, v = o.split('=', 1) + key = OptionKey.from_string(k.strip()) + v = v.strip() + result[key] = v + return result + + def is_linkable_target(self) -> bool: + return False + + def get_outputs(self) -> T.List[str]: + return [] + + def should_install(self) -> bool: + return False + +class BuildTarget(Target): + known_kwargs = known_build_target_kwargs + + install_dir: T.List[T.Union[str, Literal[False]]] + + def __init__(self, name: str, subdir: str, subproject: SubProject, for_machine: MachineChoice, + sources: T.List['SourceOutputs'], structured_sources: T.Optional[StructuredSources], + objects, environment: environment.Environment, compilers: T.Dict[str, 'Compiler'], kwargs): + super().__init__(name, subdir, subproject, True, for_machine, environment) + self.all_compilers = compilers + self.compilers = OrderedDict() # type: OrderedDict[str, Compiler] + self.objects: T.List[T.Union[str, 'File', 'ExtractedObjects']] = [] + self.structured_sources = structured_sources + self.external_deps: T.List[dependencies.Dependency] = [] + self.include_dirs: T.List['IncludeDirs'] = [] + self.link_language = kwargs.get('link_language') + self.link_targets: T.List[LibTypes] = [] + self.link_whole_targets: T.List[T.Union[StaticLibrary, CustomTarget, CustomTargetIndex]] = [] + self.link_depends = [] + self.added_deps = set() + self.name_prefix_set = False + self.name_suffix_set = False + self.filename = 'no_name' + # The list of all files outputted by this target. Useful in cases such + # as Vala which generates .vapi and .h besides the compiled output. + self.outputs = [self.filename] + self.need_install = False + self.pch: T.Dict[str, T.List[str]] = {} + self.extra_args: T.Dict[str, T.List['FileOrString']] = {} + self.sources: T.List[File] = [] + self.generated: T.List['GeneratedTypes'] = [] + self.d_features = defaultdict(list) + self.pic = False + self.pie = False + # Track build_rpath entries so we can remove them at install time + self.rpath_dirs_to_remove: T.Set[bytes] = set() + self.process_sourcelist(sources) + # Objects can be: + # 1. Pre-existing objects provided by the user with the `objects:` kwarg + # 2. Compiled objects created by and extracted from another target + self.process_objectlist(objects) + self.process_kwargs(kwargs) + self.check_unknown_kwargs(kwargs) + if not any([self.sources, self.generated, self.objects, self.link_whole_targets, self.structured_sources]): + mlog.warning(f'Build target {name} has no sources. ' + 'This was never supposed to be allowed but did because of a bug, ' + 'support will be removed in a future release of Meson') + self.validate_install() + self.check_module_linking() + + def post_init(self) -> None: + ''' Initialisations and checks requiring the final list of compilers to be known + ''' + self.validate_sources() + if self.structured_sources and any([self.sources, self.generated]): + raise MesonException('cannot mix structured sources and unstructured sources') + if self.structured_sources and 'rust' not in self.compilers: + raise MesonException('structured sources are only supported in Rust targets') + + def __repr__(self): + repr_str = "<{0} {1}: {2}>" + return repr_str.format(self.__class__.__name__, self.get_id(), self.filename) + + def __str__(self): + return f"{self.name}" + + @property + def is_unity(self) -> bool: + unity_opt = self.get_option(OptionKey('unity')) + return unity_opt == 'on' or (unity_opt == 'subprojects' and self.subproject != '') + + def validate_install(self): + if self.for_machine is MachineChoice.BUILD and self.need_install: + if self.environment.is_cross_build(): + raise InvalidArguments('Tried to install a target for the build machine in a cross build.') + else: + mlog.warning('Installing target build for the build machine. This will fail in a cross build.') + + def check_unknown_kwargs(self, kwargs): + # Override this method in derived classes that have more + # keywords. + self.check_unknown_kwargs_int(kwargs, self.known_kwargs) + + def check_unknown_kwargs_int(self, kwargs, known_kwargs): + unknowns = [] + for k in kwargs: + if k not in known_kwargs: + unknowns.append(k) + if len(unknowns) > 0: + mlog.warning('Unknown keyword argument(s) in target {}: {}.'.format(self.name, ', '.join(unknowns))) + + def process_objectlist(self, objects): + assert isinstance(objects, list) + for s in objects: + if isinstance(s, (str, File, ExtractedObjects)): + self.objects.append(s) + elif isinstance(s, (GeneratedList, CustomTarget)): + msg = 'Generated files are not allowed in the \'objects\' kwarg ' + \ + f'for target {self.name!r}.\nIt is meant only for ' + \ + 'pre-built object files that are shipped with the\nsource ' + \ + 'tree. Try adding it in the list of sources.' + raise InvalidArguments(msg) + else: + raise InvalidArguments(f'Bad object of type {type(s).__name__!r} in target {self.name!r}.') + + def process_sourcelist(self, sources: T.List['SourceOutputs']) -> None: + """Split sources into generated and static sources. + + Sources can be: + 1. Pre-existing source files in the source tree (static) + 2. Pre-existing sources generated by configure_file in the build tree. + (static as they are only regenerated if meson itself is regenerated) + 3. Sources files generated by another target or a Generator (generated) + """ + added_sources: T.Set[File] = set() # If the same source is defined multiple times, use it only once. + for s in sources: + if isinstance(s, File): + if s not in added_sources: + self.sources.append(s) + added_sources.add(s) + elif isinstance(s, (CustomTarget, CustomTargetIndex, GeneratedList)): + self.generated.append(s) + + @staticmethod + def can_compile_remove_sources(compiler: 'Compiler', sources: T.List['FileOrString']) -> bool: + removed = False + for s in sources[:]: + if compiler.can_compile(s): + sources.remove(s) + removed = True + return removed + + def process_compilers_late(self, extra_languages: T.List[str]): + """Processes additional compilers after kwargs have been evaluated. + + This can add extra compilers that might be required by keyword + arguments, such as link_with or dependencies. It will also try to guess + which compiler to use if one hasn't been selected already. + """ + for lang in extra_languages: + self.compilers[lang] = self.all_compilers[lang] + + # did user override clink_langs for this target? + link_langs = [self.link_language] if self.link_language else clink_langs + + # If this library is linked against another library we need to consider + # the languages of those libraries as well. + if self.link_targets or self.link_whole_targets: + for t in itertools.chain(self.link_targets, self.link_whole_targets): + if isinstance(t, (CustomTarget, CustomTargetIndex)): + continue # We can't know anything about these. + for name, compiler in t.compilers.items(): + if name in link_langs and name not in self.compilers: + self.compilers[name] = compiler + + if not self.compilers: + # No source files or parent targets, target consists of only object + # files of unknown origin. Just add the first clink compiler + # that we have and hope that it can link these objects + for lang in link_langs: + if lang in self.all_compilers: + self.compilers[lang] = self.all_compilers[lang] + break + + # Now that we have the final list of compilers we can sort it according + # to clink_langs and do sanity checks. + self.compilers = OrderedDict(sorted(self.compilers.items(), + key=lambda t: sort_clink(t[0]))) + self.post_init() + + def process_compilers(self) -> T.List[str]: + ''' + Populate self.compilers, which is the list of compilers that this + target will use for compiling all its sources. + We also add compilers that were used by extracted objects to simplify + dynamic linker determination. + Returns a list of missing languages that we can add implicitly, such as + C/C++ compiler for cython. + ''' + missing_languages: T.List[str] = [] + if not any([self.sources, self.generated, self.objects, self.structured_sources]): + return missing_languages + # Pre-existing sources + sources: T.List['FileOrString'] = list(self.sources) + generated = self.generated.copy() + + if self.structured_sources: + for v in self.structured_sources.sources.values(): + for src in v: + if isinstance(src, (str, File)): + sources.append(src) + else: + generated.append(src) + + # All generated sources + for gensrc in generated: + for s in gensrc.get_outputs(): + # Generated objects can't be compiled, so don't use them for + # compiler detection. If our target only has generated objects, + # we will fall back to using the first c-like compiler we find, + # which is what we need. + if not is_object(s): + sources.append(s) + for d in self.external_deps: + for s in d.sources: + if isinstance(s, (str, File)): + sources.append(s) + + # Sources that were used to create our extracted objects + for o in self.objects: + if not isinstance(o, ExtractedObjects): + continue + compsrcs = o.classify_all_sources(o.srclist, []) + for comp in compsrcs: + # Don't add Vala sources since that will pull in the Vala + # compiler even though we will never use it since we are + # dealing with compiled C code. + if comp.language == 'vala': + continue + if comp.language not in self.compilers: + self.compilers[comp.language] = comp + if sources: + # For each source, try to add one compiler that can compile it. + # + # If it has a suffix that belongs to a known language, we must have + # a compiler for that language. + # + # Otherwise, it's ok if no compilers can compile it, because users + # are expected to be able to add arbitrary non-source files to the + # sources list + for s in sources: + for lang, compiler in self.all_compilers.items(): + if compiler.can_compile(s): + if lang not in self.compilers: + self.compilers[lang] = compiler + break + else: + if is_known_suffix(s): + path = pathlib.Path(str(s)).as_posix() + m = f'No {self.for_machine.get_lower_case_name()} machine compiler for {path!r}' + raise MesonException(m) + + # If all our sources are Vala, our target also needs the C compiler but + # it won't get added above. + if 'vala' in self.compilers and 'c' not in self.compilers: + self.compilers['c'] = self.all_compilers['c'] + if 'cython' in self.compilers: + key = OptionKey('language', machine=self.for_machine, lang='cython') + value = self.get_option(key) + + try: + self.compilers[value] = self.all_compilers[value] + except KeyError: + missing_languages.append(value) + + return missing_languages + + def validate_sources(self): + if len(self.compilers) > 1 and any(lang in self.compilers for lang in ['cs', 'java']): + langs = ', '.join(self.compilers.keys()) + raise InvalidArguments(f'Cannot mix those languages into a target: {langs}') + + def process_link_depends(self, sources): + """Process the link_depends keyword argument. + + This is designed to handle strings, Files, and the output of Custom + Targets. Notably it doesn't handle generator() returned objects, since + adding them as a link depends would inherently cause them to be + generated twice, since the output needs to be passed to the ld_args and + link_depends. + """ + sources = listify(sources) + for s in sources: + if isinstance(s, File): + self.link_depends.append(s) + elif isinstance(s, str): + self.link_depends.append( + File.from_source_file(self.environment.source_dir, self.subdir, s)) + elif hasattr(s, 'get_outputs'): + self.link_depends.append(s) + else: + raise InvalidArguments( + 'Link_depends arguments must be strings, Files, ' + 'or a Custom Target, or lists thereof.') + + def get_original_kwargs(self): + return self.kwargs + + def copy_kwargs(self, kwargs): + self.kwargs = copy.copy(kwargs) + for k, v in self.kwargs.items(): + if isinstance(v, list): + self.kwargs[k] = listify(v, flatten=True) + for t in ['dependencies', 'link_with', 'include_directories', 'sources']: + if t in self.kwargs: + self.kwargs[t] = listify(self.kwargs[t], flatten=True) + + def extract_objects(self, srclist: T.List[T.Union['FileOrString', 'GeneratedTypes']]) -> ExtractedObjects: + sources_set = set(self.sources) + generated_set = set(self.generated) + + obj_src: T.List['File'] = [] + obj_gen: T.List['GeneratedTypes'] = [] + for src in srclist: + if isinstance(src, (str, File)): + if isinstance(src, str): + src = File(False, self.subdir, src) + else: + FeatureNew.single_use('File argument for extract_objects', '0.50.0', self.subproject) + if src not in sources_set: + raise MesonException(f'Tried to extract unknown source {src}.') + obj_src.append(src) + elif isinstance(src, (CustomTarget, CustomTargetIndex, GeneratedList)): + FeatureNew.single_use('Generated sources for extract_objects', '0.61.0', self.subproject) + target = src.target if isinstance(src, CustomTargetIndex) else src + if src not in generated_set and target not in generated_set: + raise MesonException(f'Tried to extract unknown source {target.get_basename()}.') + obj_gen.append(src) + else: + raise MesonException(f'Object extraction arguments must be strings, Files or targets (got {type(src).__name__}).') + return ExtractedObjects(self, obj_src, obj_gen) + + def extract_all_objects(self, recursive: bool = True) -> ExtractedObjects: + return ExtractedObjects(self, self.sources, self.generated, self.objects, + recursive) + + def get_all_link_deps(self) -> ImmutableListProtocol[BuildTargetTypes]: + return self.get_transitive_link_deps() + + @lru_cache(maxsize=None) + def get_transitive_link_deps(self) -> ImmutableListProtocol[BuildTargetTypes]: + result: T.List[Target] = [] + for i in self.link_targets: + result += i.get_all_link_deps() + return result + + def get_link_deps_mapping(self, prefix: str) -> T.Mapping[str, str]: + return self.get_transitive_link_deps_mapping(prefix) + + @lru_cache(maxsize=None) + def get_transitive_link_deps_mapping(self, prefix: str) -> T.Mapping[str, str]: + result: T.Dict[str, str] = {} + for i in self.link_targets: + mapping = i.get_link_deps_mapping(prefix) + #we are merging two dictionaries, while keeping the earlier one dominant + result_tmp = mapping.copy() + result_tmp.update(result) + result = result_tmp + return result + + @lru_cache(maxsize=None) + def get_link_dep_subdirs(self) -> T.AbstractSet[str]: + result: OrderedSet[str] = OrderedSet() + for i in self.link_targets: + if not isinstance(i, StaticLibrary): + result.add(i.get_subdir()) + result.update(i.get_link_dep_subdirs()) + return result + + def get_default_install_dir(self) -> T.Tuple[str, str]: + return self.environment.get_libdir(), '{libdir}' + + def get_custom_install_dir(self) -> T.List[T.Union[str, Literal[False]]]: + return self.install_dir + + def get_custom_install_mode(self) -> T.Optional['FileMode']: + return self.install_mode + + def process_kwargs(self, kwargs): + self.process_kwargs_base(kwargs) + self.copy_kwargs(kwargs) + kwargs.get('modules', []) + self.need_install = kwargs.get('install', self.need_install) + llist = extract_as_list(kwargs, 'link_with') + for linktarget in llist: + if isinstance(linktarget, dependencies.ExternalLibrary): + raise MesonException(textwrap.dedent('''\ + An external library was used in link_with keyword argument, which + is reserved for libraries built as part of this project. External + libraries must be passed using the dependencies keyword argument + instead, because they are conceptually "external dependencies", + just like those detected with the dependency() function. + ''')) + self.link(linktarget) + lwhole = extract_as_list(kwargs, 'link_whole') + for linktarget in lwhole: + self.link_whole(linktarget) + + for lang in all_languages: + lang_args = extract_as_list(kwargs, f'{lang}_args') + self.add_compiler_args(lang, lang_args) + + self.add_pch('c', extract_as_list(kwargs, 'c_pch')) + self.add_pch('cpp', extract_as_list(kwargs, 'cpp_pch')) + + if not isinstance(self, Executable) or kwargs.get('export_dynamic', False): + self.vala_header = kwargs.get('vala_header', self.name + '.h') + self.vala_vapi = kwargs.get('vala_vapi', self.name + '.vapi') + self.vala_gir = kwargs.get('vala_gir', None) + + dfeatures = defaultdict(list) + dfeature_unittest = kwargs.get('d_unittest', False) + if dfeature_unittest: + dfeatures['unittest'] = dfeature_unittest + dfeature_versions = kwargs.get('d_module_versions', []) + if dfeature_versions: + dfeatures['versions'] = dfeature_versions + dfeature_debug = kwargs.get('d_debug', []) + if dfeature_debug: + dfeatures['debug'] = dfeature_debug + if 'd_import_dirs' in kwargs: + dfeature_import_dirs = extract_as_list(kwargs, 'd_import_dirs') + for d in dfeature_import_dirs: + if not isinstance(d, IncludeDirs): + raise InvalidArguments('Arguments to d_import_dirs must be include_directories.') + dfeatures['import_dirs'] = dfeature_import_dirs + if dfeatures: + self.d_features = dfeatures + + self.link_args = extract_as_list(kwargs, 'link_args') + for i in self.link_args: + if not isinstance(i, str): + raise InvalidArguments('Link_args arguments must be strings.') + for l in self.link_args: + if '-Wl,-rpath' in l or l.startswith('-rpath'): + mlog.warning(textwrap.dedent('''\ + Please do not define rpath with a linker argument, use install_rpath + or build_rpath properties instead. + This will become a hard error in a future Meson release. + ''')) + self.process_link_depends(kwargs.get('link_depends', [])) + # Target-specific include dirs must be added BEFORE include dirs from + # internal deps (added inside self.add_deps()) to override them. + inclist = extract_as_list(kwargs, 'include_directories') + self.add_include_dirs(inclist) + # Add dependencies (which also have include_directories) + deplist = extract_as_list(kwargs, 'dependencies') + self.add_deps(deplist) + # If an item in this list is False, the output corresponding to + # the list index of that item will not be installed + self.install_dir = typeslistify(kwargs.get('install_dir', []), + (str, bool)) + self.install_mode = kwargs.get('install_mode', None) + self.install_tag = stringlistify(kwargs.get('install_tag', [None])) + main_class = kwargs.get('main_class', '') + if not isinstance(main_class, str): + raise InvalidArguments('Main class must be a string') + self.main_class = main_class + if isinstance(self, Executable): + # This kwarg is deprecated. The value of "none" means that the kwarg + # was not specified and win_subsystem should be used instead. + self.gui_app = None + if 'gui_app' in kwargs: + if 'win_subsystem' in kwargs: + raise InvalidArguments('Can specify only gui_app or win_subsystem for a target, not both.') + self.gui_app = kwargs['gui_app'] + if not isinstance(self.gui_app, bool): + raise InvalidArguments('Argument gui_app must be boolean.') + self.win_subsystem = self.validate_win_subsystem(kwargs.get('win_subsystem', 'console')) + elif 'gui_app' in kwargs: + raise InvalidArguments('Argument gui_app can only be used on executables.') + elif 'win_subsystem' in kwargs: + raise InvalidArguments('Argument win_subsystem can only be used on executables.') + extra_files = extract_as_list(kwargs, 'extra_files') + for i in extra_files: + assert isinstance(i, File) + trial = os.path.join(self.environment.get_source_dir(), i.subdir, i.fname) + if not os.path.isfile(trial): + raise InvalidArguments(f'Tried to add non-existing extra file {i}.') + self.extra_files = extra_files + self.install_rpath: str = kwargs.get('install_rpath', '') + if not isinstance(self.install_rpath, str): + raise InvalidArguments('Install_rpath is not a string.') + self.build_rpath = kwargs.get('build_rpath', '') + if not isinstance(self.build_rpath, str): + raise InvalidArguments('Build_rpath is not a string.') + resources = extract_as_list(kwargs, 'resources') + for r in resources: + if not isinstance(r, str): + raise InvalidArguments('Resource argument is not a string.') + trial = os.path.join(self.environment.get_source_dir(), self.subdir, r) + if not os.path.isfile(trial): + raise InvalidArguments(f'Tried to add non-existing resource {r}.') + self.resources = resources + if 'name_prefix' in kwargs: + name_prefix = kwargs['name_prefix'] + if isinstance(name_prefix, list): + if name_prefix: + raise InvalidArguments('name_prefix array must be empty to signify default.') + else: + if not isinstance(name_prefix, str): + raise InvalidArguments('name_prefix must be a string.') + self.prefix = name_prefix + self.name_prefix_set = True + if 'name_suffix' in kwargs: + name_suffix = kwargs['name_suffix'] + if isinstance(name_suffix, list): + if name_suffix: + raise InvalidArguments('name_suffix array must be empty to signify default.') + else: + if not isinstance(name_suffix, str): + raise InvalidArguments('name_suffix must be a string.') + if name_suffix == '': + raise InvalidArguments('name_suffix should not be an empty string. ' + 'If you want meson to use the default behaviour ' + 'for each platform pass `[]` (empty array)') + self.suffix = name_suffix + self.name_suffix_set = True + if isinstance(self, StaticLibrary): + # You can't disable PIC on OS X. The compiler ignores -fno-PIC. + # PIC is always on for Windows (all code is position-independent + # since library loading is done differently) + m = self.environment.machines[self.for_machine] + if m.is_darwin() or m.is_windows(): + self.pic = True + else: + self.pic = self._extract_pic_pie(kwargs, 'pic', 'b_staticpic') + if isinstance(self, Executable) or (isinstance(self, StaticLibrary) and not self.pic): + # Executables must be PIE on Android + if self.environment.machines[self.for_machine].is_android(): + self.pie = True + else: + self.pie = self._extract_pic_pie(kwargs, 'pie', 'b_pie') + self.implicit_include_directories = kwargs.get('implicit_include_directories', True) + if not isinstance(self.implicit_include_directories, bool): + raise InvalidArguments('Implicit_include_directories must be a boolean.') + self.gnu_symbol_visibility = kwargs.get('gnu_symbol_visibility', '') + if not isinstance(self.gnu_symbol_visibility, str): + raise InvalidArguments('GNU symbol visibility must be a string.') + if self.gnu_symbol_visibility != '': + permitted = ['default', 'internal', 'hidden', 'protected', 'inlineshidden'] + if self.gnu_symbol_visibility not in permitted: + raise InvalidArguments('GNU symbol visibility arg {} not one of: {}'.format(self.gnu_symbol_visibility, ', '.join(permitted))) + + def validate_win_subsystem(self, value: str) -> str: + value = value.lower() + if re.fullmatch(r'(boot_application|console|efi_application|efi_boot_service_driver|efi_rom|efi_runtime_driver|native|posix|windows)(,\d+(\.\d+)?)?', value) is None: + raise InvalidArguments(f'Invalid value for win_subsystem: {value}.') + return value + + def _extract_pic_pie(self, kwargs, arg: str, option: str): + # Check if we have -fPIC, -fpic, -fPIE, or -fpie in cflags + all_flags = self.extra_args['c'] + self.extra_args['cpp'] + if '-f' + arg.lower() in all_flags or '-f' + arg.upper() in all_flags: + mlog.warning(f"Use the '{arg}' kwarg instead of passing '-f{arg}' manually to {self.name!r}") + return True + + k = OptionKey(option) + if arg in kwargs: + val = kwargs[arg] + elif k in self.environment.coredata.options: + val = self.environment.coredata.options[k].value + else: + val = False + + if not isinstance(val, bool): + raise InvalidArguments(f'Argument {arg} to {self.name!r} must be boolean') + return val + + def get_filename(self) -> str: + return self.filename + + def get_outputs(self) -> T.List[str]: + return self.outputs + + def get_extra_args(self, language): + return self.extra_args.get(language, []) + + def get_dependencies(self, exclude=None): + transitive_deps = [] + if exclude is None: + exclude = [] + for t in itertools.chain(self.link_targets, self.link_whole_targets): + if t in transitive_deps or t in exclude: + continue + transitive_deps.append(t) + if isinstance(t, StaticLibrary): + transitive_deps += t.get_dependencies(transitive_deps + exclude) + return transitive_deps + + def get_source_subdir(self): + return self.subdir + + def get_sources(self): + return self.sources + + def get_objects(self) -> T.List[T.Union[str, 'File', 'ExtractedObjects']]: + return self.objects + + def get_generated_sources(self) -> T.List['GeneratedTypes']: + return self.generated + + def should_install(self) -> bool: + return self.need_install + + def has_pch(self) -> bool: + return bool(self.pch) + + def get_pch(self, language: str) -> T.List[str]: + return self.pch.get(language, []) + + def get_include_dirs(self) -> T.List['IncludeDirs']: + return self.include_dirs + + def add_deps(self, deps): + deps = listify(deps) + for dep in deps: + if dep in self.added_deps: + continue + + if isinstance(dep, dependencies.InternalDependency): + # Those parts that are internal. + self.process_sourcelist(dep.sources) + self.add_include_dirs(dep.include_directories, dep.get_include_type()) + for l in dep.libraries: + self.link(l) + for l in dep.whole_libraries: + self.link_whole(l) + if dep.get_compile_args() or dep.get_link_args(): + # Those parts that are external. + extpart = dependencies.InternalDependency('undefined', + [], + dep.get_compile_args(), + dep.get_link_args(), + [], [], [], [], {}, [], []) + self.external_deps.append(extpart) + # Deps of deps. + self.add_deps(dep.ext_deps) + elif isinstance(dep, dependencies.Dependency): + if dep not in self.external_deps: + self.external_deps.append(dep) + self.process_sourcelist(dep.get_sources()) + self.add_deps(dep.ext_deps) + elif isinstance(dep, BuildTarget): + raise InvalidArguments('''Tried to use a build target as a dependency. +You probably should put it in link_with instead.''') + else: + # This is a bit of a hack. We do not want Build to know anything + # about the interpreter so we can't import it and use isinstance. + # This should be reliable enough. + if hasattr(dep, 'held_object'): + # FIXME: subproject is not a real ObjectHolder so we have to do this by hand + dep = dep.held_object + if hasattr(dep, 'project_args_frozen') or hasattr(dep, 'global_args_frozen'): + raise InvalidArguments('Tried to use subproject object as a dependency.\n' + 'You probably wanted to use a dependency declared in it instead.\n' + 'Access it by calling get_variable() on the subproject object.') + raise InvalidArguments(f'Argument is of an unacceptable type {type(dep).__name__!r}.\nMust be ' + 'either an external dependency (returned by find_library() or ' + 'dependency()) or an internal dependency (returned by ' + 'declare_dependency()).') + + dep_d_features = dep.d_features + + for feature in ('versions', 'import_dirs'): + if feature in dep_d_features: + self.d_features[feature].extend(dep_d_features[feature]) + + self.added_deps.add(dep) + + def get_external_deps(self) -> T.List[dependencies.Dependency]: + return self.external_deps + + def is_internal(self) -> bool: + return False + + def link(self, target): + for t in listify(target): + if isinstance(self, StaticLibrary) and self.need_install: + if isinstance(t, (CustomTarget, CustomTargetIndex)): + if not t.should_install(): + mlog.warning(f'Try to link an installed static library target {self.name} with a' + 'custom target that is not installed, this might cause problems' + 'when you try to use this static library') + elif t.is_internal() and not t.uses_rust(): + # When we're a static library and we link_with to an + # internal/convenience library, promote to link_whole. + # + # There are cases we cannot do this, however. In Rust, for + # example, this can't be done with Rust ABI libraries, though + # it could be done with C ABI libraries, though there are + # several meson issues that need to be fixed: + # https://github.com/mesonbuild/meson/issues/10722 + # https://github.com/mesonbuild/meson/issues/10723 + # https://github.com/mesonbuild/meson/issues/10724 + return self.link_whole(t) + if not isinstance(t, (Target, CustomTargetIndex)): + raise InvalidArguments(f'{t!r} is not a target.') + if not t.is_linkable_target(): + raise InvalidArguments(f"Link target '{t!s}' is not linkable.") + if isinstance(self, SharedLibrary) and isinstance(t, StaticLibrary) and not t.pic: + msg = f"Can't link non-PIC static library {t.name!r} into shared library {self.name!r}. " + msg += "Use the 'pic' option to static_library to build with PIC." + raise InvalidArguments(msg) + if self.for_machine is not t.for_machine: + msg = f'Tried to mix libraries for machines {self.for_machine} and {t.for_machine} in target {self.name!r}' + if self.environment.is_cross_build(): + raise InvalidArguments(msg + ' This is not possible in a cross build.') + else: + mlog.warning(msg + ' This will fail in cross build.') + self.link_targets.append(t) + + def link_whole(self, target): + for t in listify(target): + if isinstance(t, (CustomTarget, CustomTargetIndex)): + if not t.is_linkable_target(): + raise InvalidArguments(f'Custom target {t!r} is not linkable.') + if t.links_dynamically(): + raise InvalidArguments('Can only link_whole custom targets that are static archives.') + if isinstance(self, StaticLibrary): + # FIXME: We could extract the .a archive to get object files + raise InvalidArguments('Cannot link_whole a custom target into a static library') + elif not isinstance(t, StaticLibrary): + raise InvalidArguments(f'{t!r} is not a static library.') + elif isinstance(self, SharedLibrary) and not t.pic: + msg = f"Can't link non-PIC static library {t.name!r} into shared library {self.name!r}. " + msg += "Use the 'pic' option to static_library to build with PIC." + raise InvalidArguments(msg) + if self.for_machine is not t.for_machine: + msg = f'Tried to mix libraries for machines {self.for_machine} and {t.for_machine} in target {self.name!r}' + if self.environment.is_cross_build(): + raise InvalidArguments(msg + ' This is not possible in a cross build.') + else: + mlog.warning(msg + ' This will fail in cross build.') + if isinstance(self, StaticLibrary): + # When we're a static library and we link_whole: to another static + # library, we need to add that target's objects to ourselves. + self.objects += t.extract_all_objects_recurse() + self.link_whole_targets.append(t) + + def extract_all_objects_recurse(self) -> T.List[T.Union[str, 'ExtractedObjects']]: + objs = [self.extract_all_objects()] + for t in self.link_targets: + if t.is_internal(): + objs += t.extract_all_objects_recurse() + return objs + + def add_pch(self, language: str, pchlist: T.List[str]) -> None: + if not pchlist: + return + elif len(pchlist) == 1: + if not environment.is_header(pchlist[0]): + raise InvalidArguments(f'PCH argument {pchlist[0]} is not a header.') + elif len(pchlist) == 2: + if environment.is_header(pchlist[0]): + if not environment.is_source(pchlist[1]): + raise InvalidArguments('PCH definition must contain one header and at most one source.') + elif environment.is_source(pchlist[0]): + if not environment.is_header(pchlist[1]): + raise InvalidArguments('PCH definition must contain one header and at most one source.') + pchlist = [pchlist[1], pchlist[0]] + else: + raise InvalidArguments(f'PCH argument {pchlist[0]} is of unknown type.') + + if os.path.dirname(pchlist[0]) != os.path.dirname(pchlist[1]): + raise InvalidArguments('PCH files must be stored in the same folder.') + + FeatureDeprecated.single_use('PCH source files', '0.50.0', self.subproject, + 'Only a single header file should be used.') + elif len(pchlist) > 2: + raise InvalidArguments('PCH definition may have a maximum of 2 files.') + for f in pchlist: + if not isinstance(f, str): + raise MesonException('PCH arguments must be strings.') + if not os.path.isfile(os.path.join(self.environment.source_dir, self.subdir, f)): + raise MesonException(f'File {f} does not exist.') + self.pch[language] = pchlist + + def add_include_dirs(self, args: T.Sequence['IncludeDirs'], set_is_system: T.Optional[str] = None) -> None: + ids: T.List['IncludeDirs'] = [] + for a in args: + if not isinstance(a, IncludeDirs): + raise InvalidArguments('Include directory to be added is not an include directory object.') + ids.append(a) + if set_is_system is None: + set_is_system = 'preserve' + if set_is_system != 'preserve': + is_system = set_is_system == 'system' + ids = [IncludeDirs(x.get_curdir(), x.get_incdirs(), is_system, x.get_extra_build_dirs()) for x in ids] + self.include_dirs += ids + + def add_compiler_args(self, language: str, args: T.List['FileOrString']) -> None: + args = listify(args) + for a in args: + if not isinstance(a, (str, File)): + raise InvalidArguments('A non-string passed to compiler args.') + if language in self.extra_args: + self.extra_args[language] += args + else: + self.extra_args[language] = args + + def get_aliases(self) -> T.List[T.Tuple[str, str, str]]: + return [] + + def get_langs_used_by_deps(self) -> T.List[str]: + ''' + Sometimes you want to link to a C++ library that exports C API, which + means the linker must link in the C++ stdlib, and we must use a C++ + compiler for linking. The same is also applicable for objc/objc++, etc, + so we can keep using clink_langs for the priority order. + + See: https://github.com/mesonbuild/meson/issues/1653 + ''' + langs = [] # type: T.List[str] + + # Check if any of the external libraries were written in this language + for dep in self.external_deps: + if dep.language is None: + continue + if dep.language not in langs: + langs.append(dep.language) + # Check if any of the internal libraries this target links to were + # written in this language + for link_target in itertools.chain(self.link_targets, self.link_whole_targets): + if isinstance(link_target, (CustomTarget, CustomTargetIndex)): + continue + for language in link_target.compilers: + if language not in langs: + langs.append(language) + + return langs + + def get_prelinker(self): + if self.link_language: + comp = self.all_compilers[self.link_language] + return comp + for l in clink_langs: + if l in self.compilers: + try: + prelinker = self.all_compilers[l] + except KeyError: + raise MesonException( + f'Could not get a prelinker linker for build target {self.name!r}. ' + f'Requires a compiler for language "{l}", but that is not ' + 'a project language.') + return prelinker + raise MesonException(f'Could not determine prelinker for {self.name!r}.') + + def get_clink_dynamic_linker_and_stdlibs(self) -> T.Tuple['Compiler', T.List[str]]: + ''' + We use the order of languages in `clink_langs` to determine which + linker to use in case the target has sources compiled with multiple + compilers. All languages other than those in this list have their own + linker. + Note that Vala outputs C code, so Vala sources can use any linker + that can link compiled C. We don't actually need to add an exception + for Vala here because of that. + ''' + # If the user set the link_language, just return that. + if self.link_language: + comp = self.all_compilers[self.link_language] + return comp, comp.language_stdlib_only_link_flags(self.environment) + + # Since dependencies could come from subprojects, they could have + # languages we don't have in self.all_compilers. Use the global list of + # all compilers here. + all_compilers = self.environment.coredata.compilers[self.for_machine] + + # Languages used by dependencies + dep_langs = self.get_langs_used_by_deps() + + # This set contains all the languages a linker can link natively + # without extra flags. For instance, nvcc (cuda) can link C++ + # without injecting -lc++/-lstdc++, see + # https://github.com/mesonbuild/meson/issues/10570 + MASK_LANGS = frozenset([ + # (language, linker) + ('cpp', 'cuda'), + ]) + # Pick a compiler based on the language priority-order + for l in clink_langs: + if l in self.compilers or l in dep_langs: + try: + linker = all_compilers[l] + except KeyError: + raise MesonException( + f'Could not get a dynamic linker for build target {self.name!r}. ' + f'Requires a linker for language "{l}", but that is not ' + 'a project language.') + stdlib_args: T.List[str] = [] + for dl in itertools.chain(self.compilers, dep_langs): + if dl != linker.language and (dl, linker.language) not in MASK_LANGS: + stdlib_args += all_compilers[dl].language_stdlib_only_link_flags(self.environment) + # Type of var 'linker' is Compiler. + # Pretty hard to fix because the return value is passed everywhere + return linker, stdlib_args + + # None of our compilers can do clink, this happens for example if the + # target only has ASM sources. Pick the first capable compiler. + for l in clink_langs: + try: + comp = self.all_compilers[l] + return comp, comp.language_stdlib_only_link_flags(self.environment) + except KeyError: + pass + + raise AssertionError(f'Could not get a dynamic linker for build target {self.name!r}') + + def uses_rust(self) -> bool: + return 'rust' in self.compilers + + def uses_fortran(self) -> bool: + return 'fortran' in self.compilers + + def get_using_msvc(self) -> bool: + ''' + Check if the dynamic linker is MSVC. Used by Executable, StaticLibrary, + and SharedLibrary for deciding when to use MSVC-specific file naming + and debug filenames. + + If at least some code is built with MSVC and the final library is + linked with MSVC, we can be sure that some debug info will be + generated. We only check the dynamic linker here because the static + linker is guaranteed to be of the same type. + + Interesting cases: + 1. The Vala compiler outputs C code to be compiled by whatever + C compiler we're using, so all objects will still be created by the + MSVC compiler. + 2. If the target contains only objects, process_compilers guesses and + picks the first compiler that smells right. + ''' + # Rustc can use msvc style linkers + if self.uses_rust(): + compiler = self.all_compilers['rust'] + else: + compiler, _ = self.get_clink_dynamic_linker_and_stdlibs() + # Mixing many languages with MSVC is not supported yet so ignore stdlibs. + return compiler and compiler.get_linker_id() in {'link', 'lld-link', 'xilink', 'optlink'} + + def check_module_linking(self): + ''' + Warn if shared modules are linked with target: (link_with) #2865 + ''' + for link_target in self.link_targets: + if isinstance(link_target, SharedModule) and not link_target.force_soname: + if self.environment.machines[self.for_machine].is_darwin(): + raise MesonException( + f'target {self.name} links against shared module {link_target.name}. This is not permitted on OSX') + elif self.environment.machines[self.for_machine].is_android() and isinstance(self, SharedModule): + # Android requires shared modules that use symbols from other shared modules to + # be linked before they can be dlopen()ed in the correct order. Not doing so + # leads to a missing symbol error: https://github.com/android/ndk/issues/201 + link_target.force_soname = True + else: + mlog.deprecation(f'target {self.name} links against shared module {link_target.name}, which is incorrect.' + '\n ' + f'This will be an error in the future, so please use shared_library() for {link_target.name} instead.' + '\n ' + f'If shared_module() was used for {link_target.name} because it has references to undefined symbols,' + '\n ' + 'use shared_libary() with `override_options: [\'b_lundef=false\']` instead.') + link_target.force_soname = True + +class Generator(HoldableObject): + def __init__(self, exe: T.Union['Executable', programs.ExternalProgram], + arguments: T.List[str], + output: T.List[str], + # how2dataclass + *, + depfile: T.Optional[str] = None, + capture: bool = False, + depends: T.Optional[T.List[T.Union[BuildTarget, 'CustomTarget']]] = None, + name: str = 'Generator'): + self.exe = exe + self.depfile = depfile + self.capture = capture + self.depends: T.List[T.Union[BuildTarget, 'CustomTarget']] = depends or [] + self.arglist = arguments + self.outputs = output + self.name = name + + def __repr__(self) -> str: + repr_str = "<{0}: {1}>" + return repr_str.format(self.__class__.__name__, self.exe) + + def get_exe(self) -> T.Union['Executable', programs.ExternalProgram]: + return self.exe + + def get_base_outnames(self, inname: str) -> T.List[str]: + plainname = os.path.basename(inname) + basename = os.path.splitext(plainname)[0] + bases = [x.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) for x in self.outputs] + return bases + + def get_dep_outname(self, inname: str) -> T.List[str]: + if self.depfile is None: + raise InvalidArguments('Tried to get dep name for rule that does not have dependency file defined.') + plainname = os.path.basename(inname) + basename = os.path.splitext(plainname)[0] + return self.depfile.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) + + def get_arglist(self, inname: str) -> T.List[str]: + plainname = os.path.basename(inname) + basename = os.path.splitext(plainname)[0] + return [x.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) for x in self.arglist] + + @staticmethod + def is_parent_path(parent: str, trial: str) -> bool: + relpath = pathlib.PurePath(trial).relative_to(parent) + return relpath.parts[0] != '..' # For subdirs we can only go "down". + + def process_files(self, files: T.Iterable[T.Union[str, File, 'CustomTarget', 'CustomTargetIndex', 'GeneratedList']], + state: T.Union['Interpreter', 'ModuleState'], + preserve_path_from: T.Optional[str] = None, + extra_args: T.Optional[T.List[str]] = None) -> 'GeneratedList': + output = GeneratedList(self, state.subdir, preserve_path_from, extra_args=extra_args if extra_args is not None else []) + + for e in files: + if isinstance(e, CustomTarget): + output.depends.add(e) + if isinstance(e, CustomTargetIndex): + output.depends.add(e.target) + + if isinstance(e, (CustomTarget, CustomTargetIndex, GeneratedList)): + output.depends.add(e) + fs = [File.from_built_file(state.subdir, f) for f in e.get_outputs()] + elif isinstance(e, str): + fs = [File.from_source_file(state.environment.source_dir, state.subdir, e)] + else: + fs = [e] + + for f in fs: + if preserve_path_from: + abs_f = f.absolute_path(state.environment.source_dir, state.environment.build_dir) + if not self.is_parent_path(preserve_path_from, abs_f): + raise InvalidArguments('generator.process: When using preserve_path_from, all input files must be in a subdirectory of the given dir.') + output.add_file(f, state) + return output + + +@dataclass(eq=False) +class GeneratedList(HoldableObject): + + """The output of generator.process.""" + + generator: Generator + subdir: str + preserve_path_from: T.Optional[str] + extra_args: T.List[str] + + def __post_init__(self) -> None: + self.name = self.generator.exe + self.depends: T.Set[GeneratedTypes] = set() + self.infilelist: T.List['File'] = [] + self.outfilelist: T.List[str] = [] + self.outmap: T.Dict[File, T.List[str]] = {} + self.extra_depends = [] # XXX: Doesn't seem to be used? + self.depend_files: T.List[File] = [] + + if self.extra_args is None: + self.extra_args: T.List[str] = [] + + if isinstance(self.generator.exe, programs.ExternalProgram): + if not self.generator.exe.found(): + raise InvalidArguments('Tried to use not-found external program as generator') + path = self.generator.exe.get_path() + if os.path.isabs(path): + # Can only add a dependency on an external program which we + # know the absolute path of + self.depend_files.append(File.from_absolute_file(path)) + + def add_preserved_path_segment(self, infile: File, outfiles: T.List[str], state: T.Union['Interpreter', 'ModuleState']) -> T.List[str]: + result: T.List[str] = [] + in_abs = infile.absolute_path(state.environment.source_dir, state.environment.build_dir) + assert os.path.isabs(self.preserve_path_from) + rel = os.path.relpath(in_abs, self.preserve_path_from) + path_segment = os.path.dirname(rel) + for of in outfiles: + result.append(os.path.join(path_segment, of)) + return result + + def add_file(self, newfile: File, state: T.Union['Interpreter', 'ModuleState']) -> None: + self.infilelist.append(newfile) + outfiles = self.generator.get_base_outnames(newfile.fname) + if self.preserve_path_from: + outfiles = self.add_preserved_path_segment(newfile, outfiles, state) + self.outfilelist += outfiles + self.outmap[newfile] = outfiles + + def get_inputs(self) -> T.List['File']: + return self.infilelist + + def get_outputs(self) -> T.List[str]: + return self.outfilelist + + def get_outputs_for(self, filename: 'File') -> T.List[str]: + return self.outmap[filename] + + def get_generator(self) -> 'Generator': + return self.generator + + def get_extra_args(self) -> T.List[str]: + return self.extra_args + + def get_subdir(self) -> str: + return self.subdir + + +class Executable(BuildTarget): + known_kwargs = known_exe_kwargs + + typename = 'executable' + + def __init__(self, name: str, subdir: str, subproject: str, for_machine: MachineChoice, + sources: T.List[SourceOutputs], structured_sources: T.Optional['StructuredSources'], + objects, environment: environment.Environment, compilers: T.Dict[str, 'Compiler'], + kwargs): + key = OptionKey('b_pie') + if 'pie' not in kwargs and key in environment.coredata.options: + kwargs['pie'] = environment.coredata.options[key].value + super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects, + environment, compilers, kwargs) + # Check for export_dynamic + self.export_dynamic = kwargs.get('export_dynamic', False) + if not isinstance(self.export_dynamic, bool): + raise InvalidArguments('"export_dynamic" keyword argument must be a boolean') + self.implib = kwargs.get('implib') + if not isinstance(self.implib, (bool, str, type(None))): + raise InvalidArguments('"export_dynamic" keyword argument must be a boolean or string') + if self.implib: + self.export_dynamic = True + if self.export_dynamic and self.implib is False: + raise InvalidArguments('"implib" keyword argument must not be false for if "export_dynamic" is true') + # Only linkwithable if using export_dynamic + self.is_linkwithable = self.export_dynamic + # Remember that this exe was returned by `find_program()` through an override + self.was_returned_by_find_program = False + + def post_init(self) -> None: + super().post_init() + machine = self.environment.machines[self.for_machine] + # Unless overridden, executables have no suffix or prefix. Except on + # Windows and with C#/Mono executables where the suffix is 'exe' + if not hasattr(self, 'prefix'): + self.prefix = '' + if not hasattr(self, 'suffix'): + # Executable for Windows or C#/Mono + if machine.is_windows() or machine.is_cygwin() or 'cs' in self.compilers: + self.suffix = 'exe' + elif machine.system.startswith('wasm') or machine.system == 'emscripten': + self.suffix = 'js' + elif ('c' in self.compilers and self.compilers['c'].get_id().startswith('armclang') or + 'cpp' in self.compilers and self.compilers['cpp'].get_id().startswith('armclang')): + self.suffix = 'axf' + elif ('c' in self.compilers and self.compilers['c'].get_id().startswith('ccrx') or + 'cpp' in self.compilers and self.compilers['cpp'].get_id().startswith('ccrx')): + self.suffix = 'abs' + elif ('c' in self.compilers and self.compilers['c'].get_id().startswith('xc16')): + self.suffix = 'elf' + elif ('c' in self.compilers and self.compilers['c'].get_id() in {'ti', 'c2000'} or + 'cpp' in self.compilers and self.compilers['cpp'].get_id() in {'ti', 'c2000'}): + self.suffix = 'out' + else: + self.suffix = machine.get_exe_suffix() + self.filename = self.name + if self.suffix: + self.filename += '.' + self.suffix + self.outputs = [self.filename] + + # The import library this target will generate + self.import_filename = None + # The import library that Visual Studio would generate (and accept) + self.vs_import_filename = None + # The import library that GCC would generate (and prefer) + self.gcc_import_filename = None + # The debugging information file this target will generate + self.debug_filename = None + + # If using export_dynamic, set the import library name + if self.export_dynamic: + implib_basename = self.name + '.exe' + if isinstance(self.implib, str): + implib_basename = self.implib + if machine.is_windows() or machine.is_cygwin(): + self.vs_import_filename = f'{implib_basename}.lib' + self.gcc_import_filename = f'lib{implib_basename}.a' + if self.get_using_msvc(): + self.import_filename = self.vs_import_filename + else: + self.import_filename = self.gcc_import_filename + + if machine.is_windows() and ('cs' in self.compilers or + self.uses_rust() or + self.get_using_msvc()): + self.debug_filename = self.name + '.pdb' + + def get_default_install_dir(self) -> T.Tuple[str, str]: + return self.environment.get_bindir(), '{bindir}' + + def description(self): + '''Human friendly description of the executable''' + return self.name + + def type_suffix(self): + return "@exe" + + def get_import_filename(self) -> T.Optional[str]: + """ + The name of the import library that will be outputted by the compiler + + Returns None if there is no import library required for this platform + """ + return self.import_filename + + def get_import_filenameslist(self): + if self.import_filename: + return [self.vs_import_filename, self.gcc_import_filename] + return [] + + def get_debug_filename(self) -> T.Optional[str]: + """ + The name of debuginfo file that will be created by the compiler + + Returns None if the build won't create any debuginfo file + """ + return self.debug_filename + + def is_linkable_target(self): + return self.is_linkwithable + + def get_command(self) -> 'ImmutableListProtocol[str]': + """Provides compatibility with ExternalProgram. + + Since you can override ExternalProgram instances with Executables. + """ + return self.outputs + +class StaticLibrary(BuildTarget): + known_kwargs = known_stlib_kwargs + + typename = 'static library' + + def __init__(self, name: str, subdir: str, subproject: str, for_machine: MachineChoice, + sources: T.List[SourceOutputs], structured_sources: T.Optional['StructuredSources'], + objects, environment: environment.Environment, compilers: T.Dict[str, 'Compiler'], + kwargs): + self.prelink = kwargs.get('prelink', False) + if not isinstance(self.prelink, bool): + raise InvalidArguments('Prelink keyword argument must be a boolean.') + super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects, + environment, compilers, kwargs) + + def post_init(self) -> None: + super().post_init() + if 'cs' in self.compilers: + raise InvalidArguments('Static libraries not supported for C#.') + if 'rust' in self.compilers: + # If no crate type is specified, or it's the generic lib type, use rlib + if not hasattr(self, 'rust_crate_type') or self.rust_crate_type == 'lib': + mlog.debug('Defaulting Rust static library target crate type to rlib') + self.rust_crate_type = 'rlib' + # Don't let configuration proceed with a non-static crate type + elif self.rust_crate_type not in ['rlib', 'staticlib']: + raise InvalidArguments(f'Crate type "{self.rust_crate_type}" invalid for static libraries; must be "rlib" or "staticlib"') + # By default a static library is named libfoo.a even on Windows because + # MSVC does not have a consistent convention for what static libraries + # are called. The MSVC CRT uses libfoo.lib syntax but nothing else uses + # it and GCC only looks for static libraries called foo.lib and + # libfoo.a. However, we cannot use foo.lib because that's the same as + # the import library. Using libfoo.a is ok because people using MSVC + # always pass the library filename while linking anyway. + if not hasattr(self, 'prefix'): + self.prefix = 'lib' + if not hasattr(self, 'suffix'): + if 'rust' in self.compilers: + if not hasattr(self, 'rust_crate_type') or self.rust_crate_type == 'rlib': + # default Rust static library suffix + self.suffix = 'rlib' + elif self.rust_crate_type == 'staticlib': + self.suffix = 'a' + else: + self.suffix = 'a' + self.filename = self.prefix + self.name + '.' + self.suffix + self.outputs = [self.filename] + + def get_link_deps_mapping(self, prefix: str) -> T.Mapping[str, str]: + return {} + + def get_default_install_dir(self) -> T.Tuple[str, str]: + return self.environment.get_static_lib_dir(), '{libdir_static}' + + def type_suffix(self): + return "@sta" + + def process_kwargs(self, kwargs): + super().process_kwargs(kwargs) + if 'rust_crate_type' in kwargs: + rust_crate_type = kwargs['rust_crate_type'] + if isinstance(rust_crate_type, str): + self.rust_crate_type = rust_crate_type + else: + raise InvalidArguments(f'Invalid rust_crate_type "{rust_crate_type}": must be a string.') + + def is_linkable_target(self): + return True + + def is_internal(self) -> bool: + return not self.need_install + +class SharedLibrary(BuildTarget): + known_kwargs = known_shlib_kwargs + + typename = 'shared library' + + def __init__(self, name: str, subdir: str, subproject: str, for_machine: MachineChoice, + sources: T.List[SourceOutputs], structured_sources: T.Optional['StructuredSources'], + objects, environment: environment.Environment, compilers: T.Dict[str, 'Compiler'], + kwargs): + self.soversion = None + self.ltversion = None + # Max length 2, first element is compatibility_version, second is current_version + self.darwin_versions = [] + self.vs_module_defs = None + # The import library this target will generate + self.import_filename = None + # The import library that Visual Studio would generate (and accept) + self.vs_import_filename = None + # The import library that GCC would generate (and prefer) + self.gcc_import_filename = None + # The debugging information file this target will generate + self.debug_filename = None + # Use by the pkgconfig module + self.shared_library_only = False + super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects, + environment, compilers, kwargs) + + def post_init(self) -> None: + super().post_init() + if 'rust' in self.compilers: + # If no crate type is specified, or it's the generic lib type, use dylib + if not hasattr(self, 'rust_crate_type') or self.rust_crate_type == 'lib': + mlog.debug('Defaulting Rust dynamic library target crate type to "dylib"') + self.rust_crate_type = 'dylib' + # Don't let configuration proceed with a non-dynamic crate type + elif self.rust_crate_type not in ['dylib', 'cdylib', 'proc-macro']: + raise InvalidArguments(f'Crate type "{self.rust_crate_type}" invalid for dynamic libraries; must be "dylib", "cdylib", or "proc-macro"') + if not hasattr(self, 'prefix'): + self.prefix = None + if not hasattr(self, 'suffix'): + self.suffix = None + self.basic_filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + self.determine_filenames() + + def get_link_deps_mapping(self, prefix: str) -> T.Mapping[str, str]: + result: T.Dict[str, str] = {} + mappings = self.get_transitive_link_deps_mapping(prefix) + old = get_target_macos_dylib_install_name(self) + if old not in mappings: + fname = self.get_filename() + outdirs, _, _ = self.get_install_dir() + new = os.path.join(prefix, outdirs[0], fname) + result.update({old: new}) + mappings.update(result) + return mappings + + def get_default_install_dir(self) -> T.Tuple[str, str]: + return self.environment.get_shared_lib_dir(), '{libdir_shared}' + + def determine_filenames(self): + """ + See https://github.com/mesonbuild/meson/pull/417 for details. + + First we determine the filename template (self.filename_tpl), then we + set the output filename (self.filename). + + The template is needed while creating aliases (self.get_aliases), + which are needed while generating .so shared libraries for Linux. + + Besides this, there's also the import library name, which is only used + on Windows since on that platform the linker uses a separate library + called the "import library" during linking instead of the shared + library (DLL). The toolchain will output an import library in one of + two formats: GCC or Visual Studio. + + When we're building with Visual Studio, the import library that will be + generated by the toolchain is self.vs_import_filename, and with + MinGW/GCC, it's self.gcc_import_filename. self.import_filename will + always contain the import library name this target will generate. + """ + prefix = '' + suffix = '' + create_debug_file = False + self.filename_tpl = self.basic_filename_tpl + # NOTE: manual prefix/suffix override is currently only tested for C/C++ + # C# and Mono + if 'cs' in self.compilers: + prefix = '' + suffix = 'dll' + self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + create_debug_file = True + # C, C++, Swift, Vala + # Only Windows uses a separate import library for linking + # For all other targets/platforms import_filename stays None + elif self.environment.machines[self.for_machine].is_windows(): + suffix = 'dll' + self.vs_import_filename = '{}{}.lib'.format(self.prefix if self.prefix is not None else '', self.name) + self.gcc_import_filename = '{}{}.dll.a'.format(self.prefix if self.prefix is not None else 'lib', self.name) + if self.uses_rust(): + # Shared library is of the form foo.dll + prefix = '' + # Import library is called foo.dll.lib + self.import_filename = f'{self.name}.dll.lib' + # Debug files(.pdb) is only created with debug buildtype + create_debug_file = self.environment.coredata.get_option(OptionKey("debug")) + elif self.get_using_msvc(): + # Shared library is of the form foo.dll + prefix = '' + # Import library is called foo.lib + self.import_filename = self.vs_import_filename + # Debug files(.pdb) is only created with debug buildtype + create_debug_file = self.environment.coredata.get_option(OptionKey("debug")) + # Assume GCC-compatible naming + else: + # Shared library is of the form libfoo.dll + prefix = 'lib' + # Import library is called libfoo.dll.a + self.import_filename = self.gcc_import_filename + # Shared library has the soversion if it is defined + if self.soversion: + self.filename_tpl = '{0.prefix}{0.name}-{0.soversion}.{0.suffix}' + else: + self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + elif self.environment.machines[self.for_machine].is_cygwin(): + suffix = 'dll' + self.gcc_import_filename = '{}{}.dll.a'.format(self.prefix if self.prefix is not None else 'lib', self.name) + # Shared library is of the form cygfoo.dll + # (ld --dll-search-prefix=cyg is the default) + prefix = 'cyg' + # Import library is called libfoo.dll.a + self.import_filename = self.gcc_import_filename + if self.soversion: + self.filename_tpl = '{0.prefix}{0.name}-{0.soversion}.{0.suffix}' + else: + self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + elif self.environment.machines[self.for_machine].is_darwin(): + prefix = 'lib' + suffix = 'dylib' + # On macOS, the filename can only contain the major version + if self.soversion: + # libfoo.X.dylib + self.filename_tpl = '{0.prefix}{0.name}.{0.soversion}.{0.suffix}' + else: + # libfoo.dylib + self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + elif self.environment.machines[self.for_machine].is_android(): + prefix = 'lib' + suffix = 'so' + # Android doesn't support shared_library versioning + self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + else: + prefix = 'lib' + suffix = 'so' + if self.ltversion: + # libfoo.so.X[.Y[.Z]] (.Y and .Z are optional) + self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}.{0.ltversion}' + elif self.soversion: + # libfoo.so.X + self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}.{0.soversion}' + else: + # No versioning, libfoo.so + self.filename_tpl = '{0.prefix}{0.name}.{0.suffix}' + if self.prefix is None: + self.prefix = prefix + if self.suffix is None: + self.suffix = suffix + self.filename = self.filename_tpl.format(self) + # There may have been more outputs added by the time we get here, so + # only replace the first entry + self.outputs[0] = self.filename + if create_debug_file: + self.debug_filename = os.path.splitext(self.filename)[0] + '.pdb' + + @staticmethod + def _validate_darwin_versions(darwin_versions): + try: + if isinstance(darwin_versions, int): + darwin_versions = str(darwin_versions) + if isinstance(darwin_versions, str): + darwin_versions = 2 * [darwin_versions] + if not isinstance(darwin_versions, list): + raise InvalidArguments('Shared library darwin_versions: must be a string, integer,' + f'or a list, not {darwin_versions!r}') + if len(darwin_versions) > 2: + raise InvalidArguments('Shared library darwin_versions: list must contain 2 or fewer elements') + if len(darwin_versions) == 1: + darwin_versions = 2 * darwin_versions + for i, v in enumerate(darwin_versions[:]): + if isinstance(v, int): + v = str(v) + if not isinstance(v, str): + raise InvalidArguments('Shared library darwin_versions: list elements ' + f'must be strings or integers, not {v!r}') + if not re.fullmatch(r'[0-9]+(\.[0-9]+){0,2}', v): + raise InvalidArguments('Shared library darwin_versions: must be X.Y.Z where ' + 'X, Y, Z are numbers, and Y and Z are optional') + parts = v.split('.') + if len(parts) in {1, 2, 3} and int(parts[0]) > 65535: + raise InvalidArguments('Shared library darwin_versions: must be X.Y.Z ' + 'where X is [0, 65535] and Y, Z are optional') + if len(parts) in {2, 3} and int(parts[1]) > 255: + raise InvalidArguments('Shared library darwin_versions: must be X.Y.Z ' + 'where Y is [0, 255] and Y, Z are optional') + if len(parts) == 3 and int(parts[2]) > 255: + raise InvalidArguments('Shared library darwin_versions: must be X.Y.Z ' + 'where Z is [0, 255] and Y, Z are optional') + darwin_versions[i] = v + except ValueError: + raise InvalidArguments('Shared library darwin_versions: value is invalid') + return darwin_versions + + def process_kwargs(self, kwargs): + super().process_kwargs(kwargs) + + if not self.environment.machines[self.for_machine].is_android(): + # Shared library version + if 'version' in kwargs: + self.ltversion = kwargs['version'] + if not isinstance(self.ltversion, str): + raise InvalidArguments('Shared library version needs to be a string, not ' + type(self.ltversion).__name__) + if not re.fullmatch(r'[0-9]+(\.[0-9]+){0,2}', self.ltversion): + raise InvalidArguments(f'Invalid Shared library version "{self.ltversion}". Must be of the form X.Y.Z where all three are numbers. Y and Z are optional.') + # Try to extract/deduce the soversion + if 'soversion' in kwargs: + self.soversion = kwargs['soversion'] + if isinstance(self.soversion, int): + self.soversion = str(self.soversion) + if not isinstance(self.soversion, str): + raise InvalidArguments('Shared library soversion is not a string or integer.') + elif self.ltversion: + # library version is defined, get the soversion from that + # We replicate what Autotools does here and take the first + # number of the version by default. + self.soversion = self.ltversion.split('.')[0] + # macOS, iOS and tvOS dylib compatibility_version and current_version + if 'darwin_versions' in kwargs: + self.darwin_versions = self._validate_darwin_versions(kwargs['darwin_versions']) + elif self.soversion: + # If unspecified, pick the soversion + self.darwin_versions = 2 * [self.soversion] + + # Visual Studio module-definitions file + if 'vs_module_defs' in kwargs: + path = kwargs['vs_module_defs'] + if isinstance(path, str): + if os.path.isabs(path): + self.vs_module_defs = File.from_absolute_file(path) + else: + self.vs_module_defs = File.from_source_file(self.environment.source_dir, self.subdir, path) + elif isinstance(path, File): + # When passing a generated file. + self.vs_module_defs = path + elif hasattr(path, 'get_filename'): + # When passing output of a Custom Target + self.vs_module_defs = File.from_built_file(path.subdir, path.get_filename()) + else: + raise InvalidArguments( + 'Shared library vs_module_defs must be either a string, ' + 'a file object or a Custom Target') + self.process_link_depends(path) + + if 'rust_crate_type' in kwargs: + rust_crate_type = kwargs['rust_crate_type'] + if isinstance(rust_crate_type, str): + self.rust_crate_type = rust_crate_type + else: + raise InvalidArguments(f'Invalid rust_crate_type "{rust_crate_type}": must be a string.') + if rust_crate_type == 'proc-macro': + FeatureNew.single_use('Rust crate type "proc-macro"', '0.62.0', self.subproject) + + def get_import_filename(self) -> T.Optional[str]: + """ + The name of the import library that will be outputted by the compiler + + Returns None if there is no import library required for this platform + """ + return self.import_filename + + def get_debug_filename(self) -> T.Optional[str]: + """ + The name of debuginfo file that will be created by the compiler + + Returns None if the build won't create any debuginfo file + """ + return self.debug_filename + + def get_import_filenameslist(self): + if self.import_filename: + return [self.vs_import_filename, self.gcc_import_filename] + return [] + + def get_all_link_deps(self): + return [self] + self.get_transitive_link_deps() + + def get_aliases(self) -> T.List[T.Tuple[str, str, str]]: + """ + If the versioned library name is libfoo.so.0.100.0, aliases are: + * libfoo.so.0 (soversion) -> libfoo.so.0.100.0 + * libfoo.so (unversioned; for linking) -> libfoo.so.0 + Same for dylib: + * libfoo.dylib (unversioned; for linking) -> libfoo.0.dylib + """ + aliases: T.List[T.Tuple[str, str, str]] = [] + # Aliases are only useful with .so and .dylib libraries. Also if + # there's no self.soversion (no versioning), we don't need aliases. + if self.suffix not in ('so', 'dylib') or not self.soversion: + return aliases + # With .so libraries, the minor and micro versions are also in the + # filename. If ltversion != soversion we create an soversion alias: + # libfoo.so.0 -> libfoo.so.0.100.0 + # Where libfoo.so.0.100.0 is the actual library + if self.suffix == 'so' and self.ltversion and self.ltversion != self.soversion: + alias_tpl = self.filename_tpl.replace('ltversion', 'soversion') + ltversion_filename = alias_tpl.format(self) + tag = self.install_tag[0] or 'runtime' + aliases.append((ltversion_filename, self.filename, tag)) + # libfoo.so.0/libfoo.0.dylib is the actual library + else: + ltversion_filename = self.filename + # Unversioned alias: + # libfoo.so -> libfoo.so.0 + # libfoo.dylib -> libfoo.0.dylib + tag = self.install_tag[0] or 'devel' + aliases.append((self.basic_filename_tpl.format(self), ltversion_filename, tag)) + return aliases + + def type_suffix(self): + return "@sha" + + def is_linkable_target(self): + return True + +# A shared library that is meant to be used with dlopen rather than linking +# into something else. +class SharedModule(SharedLibrary): + known_kwargs = known_shmod_kwargs + + typename = 'shared module' + + def __init__(self, name: str, subdir: str, subproject: str, for_machine: MachineChoice, + sources: T.List[SourceOutputs], structured_sources: T.Optional['StructuredSources'], + objects, environment: environment.Environment, + compilers: T.Dict[str, 'Compiler'], kwargs): + if 'version' in kwargs: + raise MesonException('Shared modules must not specify the version kwarg.') + if 'soversion' in kwargs: + raise MesonException('Shared modules must not specify the soversion kwarg.') + super().__init__(name, subdir, subproject, for_machine, sources, + structured_sources, objects, environment, compilers, kwargs) + # We need to set the soname in cases where build files link the module + # to build targets, see: https://github.com/mesonbuild/meson/issues/9492 + self.force_soname = False + + def get_default_install_dir(self) -> T.Tuple[str, str]: + return self.environment.get_shared_module_dir(), '{moduledir_shared}' + +class BothLibraries(SecondLevelHolder): + def __init__(self, shared: SharedLibrary, static: StaticLibrary) -> None: + self._preferred_library = 'shared' + self.shared = shared + self.static = static + self.subproject = self.shared.subproject + + def __repr__(self) -> str: + return f'<BothLibraries: static={repr(self.static)}; shared={repr(self.shared)}>' + + def get_default_object(self) -> BuildTarget: + if self._preferred_library == 'shared': + return self.shared + elif self._preferred_library == 'static': + return self.static + raise MesonBugException(f'self._preferred_library == "{self._preferred_library}" is neither "shared" nor "static".') + +class CommandBase: + + depend_files: T.List[File] + dependencies: T.List[T.Union[BuildTarget, 'CustomTarget']] + subproject: str + + def flatten_command(self, cmd: T.Sequence[T.Union[str, File, programs.ExternalProgram, BuildTargetTypes]]) -> \ + T.List[T.Union[str, File, BuildTarget, 'CustomTarget']]: + cmd = listify(cmd) + final_cmd: T.List[T.Union[str, File, BuildTarget, 'CustomTarget']] = [] + for c in cmd: + if isinstance(c, str): + final_cmd.append(c) + elif isinstance(c, File): + self.depend_files.append(c) + final_cmd.append(c) + elif isinstance(c, programs.ExternalProgram): + if not c.found(): + raise InvalidArguments('Tried to use not-found external program in "command"') + path = c.get_path() + if os.path.isabs(path): + # Can only add a dependency on an external program which we + # know the absolute path of + self.depend_files.append(File.from_absolute_file(path)) + final_cmd += c.get_command() + elif isinstance(c, (BuildTarget, CustomTarget)): + self.dependencies.append(c) + final_cmd.append(c) + elif isinstance(c, CustomTargetIndex): + FeatureNew.single_use('CustomTargetIndex for command argument', '0.60', self.subproject) + self.dependencies.append(c.target) + final_cmd += self.flatten_command(File.from_built_file(c.get_subdir(), c.get_filename())) + elif isinstance(c, list): + final_cmd += self.flatten_command(c) + else: + raise InvalidArguments(f'Argument {c!r} in "command" is invalid') + return final_cmd + +class CustomTarget(Target, CommandBase): + + typename = 'custom' + + def __init__(self, + name: T.Optional[str], + subdir: str, + subproject: str, + environment: environment.Environment, + command: T.Sequence[T.Union[ + str, BuildTargetTypes, GeneratedList, + programs.ExternalProgram, File]], + sources: T.Sequence[T.Union[ + str, File, BuildTargetTypes, ExtractedObjects, + GeneratedList, programs.ExternalProgram]], + outputs: T.List[str], + *, + build_always_stale: bool = False, + build_by_default: T.Optional[bool] = None, + capture: bool = False, + console: bool = False, + depend_files: T.Optional[T.Sequence[FileOrString]] = None, + extra_depends: T.Optional[T.Sequence[T.Union[str, SourceOutputs]]] = None, + depfile: T.Optional[str] = None, + env: T.Optional[EnvironmentVariables] = None, + feed: bool = False, + install: bool = False, + install_dir: T.Optional[T.List[T.Union[str, Literal[False]]]] = None, + install_mode: T.Optional[FileMode] = None, + install_tag: T.Optional[T.List[T.Optional[str]]] = None, + absolute_paths: bool = False, + backend: T.Optional['Backend'] = None, + ): + # TODO expose keyword arg to make MachineChoice.HOST configurable + super().__init__(name, subdir, subproject, False, MachineChoice.HOST, environment) + self.sources = list(sources) + self.outputs = substitute_values( + outputs, get_filenames_templates_dict( + get_sources_string_names(sources, backend), + [])) + self.build_by_default = build_by_default if build_by_default is not None else install + self.build_always_stale = build_always_stale + self.capture = capture + self.console = console + self.depend_files = list(depend_files or []) + self.dependencies: T.List[T.Union[CustomTarget, BuildTarget]] = [] + # must be after depend_files and dependencies + self.command = self.flatten_command(command) + self.depfile = depfile + self.env = env or EnvironmentVariables() + self.extra_depends = list(extra_depends or []) + self.feed = feed + self.install = install + self.install_dir = list(install_dir or []) + self.install_mode = install_mode + self.install_tag = _process_install_tag(install_tag, len(self.outputs)) + self.name = name if name else self.outputs[0] + + # Whether to use absolute paths for all files on the commandline + self.absolute_paths = absolute_paths + + def get_default_install_dir(self) -> T.Tuple[str, str]: + return None, None + + def __repr__(self): + repr_str = "<{0} {1}: {2}>" + return repr_str.format(self.__class__.__name__, self.get_id(), self.command) + + def get_target_dependencies(self) -> T.List[T.Union[SourceOutputs, str]]: + deps: T.List[T.Union[SourceOutputs, str]] = [] + deps.extend(self.dependencies) + deps.extend(self.extra_depends) + for c in self.sources: + if isinstance(c, CustomTargetIndex): + deps.append(c.target) + elif not isinstance(c, programs.ExternalProgram): + deps.append(c) + return deps + + def get_transitive_build_target_deps(self) -> T.Set[T.Union[BuildTarget, 'CustomTarget']]: + ''' + Recursively fetch the build targets that this custom target depends on, + whether through `command:`, `depends:`, or `sources:` The recursion is + only performed on custom targets. + This is useful for setting PATH on Windows for finding required DLLs. + F.ex, if you have a python script that loads a C module that links to + other DLLs in your project. + ''' + bdeps: T.Set[T.Union[BuildTarget, 'CustomTarget']] = set() + deps = self.get_target_dependencies() + for d in deps: + if isinstance(d, BuildTarget): + bdeps.add(d) + elif isinstance(d, CustomTarget): + bdeps.update(d.get_transitive_build_target_deps()) + return bdeps + + def get_dependencies(self): + return self.dependencies + + def should_install(self) -> bool: + return self.install + + def get_custom_install_dir(self) -> T.List[T.Union[str, Literal[False]]]: + return self.install_dir + + def get_custom_install_mode(self) -> T.Optional['FileMode']: + return self.install_mode + + def get_outputs(self) -> T.List[str]: + return self.outputs + + def get_filename(self) -> str: + return self.outputs[0] + + def get_sources(self) -> T.List[T.Union[str, File, BuildTarget, GeneratedTypes, ExtractedObjects, programs.ExternalProgram]]: + return self.sources + + def get_generated_lists(self) -> T.List[GeneratedList]: + genlists: T.List[GeneratedList] = [] + for c in self.sources: + if isinstance(c, GeneratedList): + genlists.append(c) + return genlists + + def get_generated_sources(self) -> T.List[GeneratedList]: + return self.get_generated_lists() + + def get_dep_outname(self, infilenames): + if self.depfile is None: + raise InvalidArguments('Tried to get depfile name for custom_target that does not have depfile defined.') + if infilenames: + plainname = os.path.basename(infilenames[0]) + basename = os.path.splitext(plainname)[0] + return self.depfile.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname) + else: + if '@BASENAME@' in self.depfile or '@PLAINNAME@' in self.depfile: + raise InvalidArguments('Substitution in depfile for custom_target that does not have an input file.') + return self.depfile + + def is_linkable_target(self) -> bool: + if len(self.outputs) != 1: + return False + suf = os.path.splitext(self.outputs[0])[-1] + return suf in {'.a', '.dll', '.lib', '.so', '.dylib'} + + def links_dynamically(self) -> bool: + """Whether this target links dynamically or statically + + Does not assert the target is linkable, just that it is not shared + + :return: True if is dynamically linked, otherwise False + """ + suf = os.path.splitext(self.outputs[0])[-1] + return suf not in {'.a', '.lib'} + + def get_link_deps_mapping(self, prefix: str) -> T.Mapping[str, str]: + return {} + + def get_link_dep_subdirs(self) -> T.AbstractSet[str]: + return OrderedSet() + + def get_all_link_deps(self): + return [] + + def is_internal(self) -> bool: + ''' + Returns True if this is a not installed static library. + ''' + if len(self.outputs) != 1: + return False + return CustomTargetIndex(self, self.outputs[0]).is_internal() + + def extract_all_objects_recurse(self) -> T.List[T.Union[str, 'ExtractedObjects']]: + return self.get_outputs() + + def type_suffix(self): + return "@cus" + + def __getitem__(self, index: int) -> 'CustomTargetIndex': + return CustomTargetIndex(self, self.outputs[index]) + + def __setitem__(self, index, value): + raise NotImplementedError + + def __delitem__(self, index): + raise NotImplementedError + + def __iter__(self): + for i in self.outputs: + yield CustomTargetIndex(self, i) + + def __len__(self) -> int: + return len(self.outputs) + +class CompileTarget(BuildTarget): + ''' + Target that only compile sources without linking them together. + It can be used as preprocessor, or transpiler. + ''' + + typename = 'compile' + + def __init__(self, + name: str, + subdir: str, + subproject: str, + environment: environment.Environment, + sources: T.List[File], + output_templ: str, + compiler: Compiler, + kwargs): + compilers = {compiler.get_language(): compiler} + super().__init__(name, subdir, subproject, compiler.for_machine, + sources, None, [], environment, compilers, kwargs) + self.filename = name + self.compiler = compiler + self.output_templ = output_templ + self.outputs = [] + for f in sources: + plainname = os.path.basename(f.fname) + basename = os.path.splitext(plainname)[0] + self.outputs.append(output_templ.replace('@BASENAME@', basename).replace('@PLAINNAME@', plainname)) + self.sources_map = dict(zip(sources, self.outputs)) + + def type_suffix(self) -> str: + return "@compile" + + @property + def is_unity(self) -> bool: + return False + + +class RunTarget(Target, CommandBase): + + typename = 'run' + + def __init__(self, name: str, + command: T.Sequence[T.Union[str, File, BuildTargetTypes, programs.ExternalProgram]], + dependencies: T.Sequence[Target], + subdir: str, + subproject: str, + environment: environment.Environment, + env: T.Optional['EnvironmentVariables'] = None, + default_env: bool = True): + # These don't produce output artifacts + super().__init__(name, subdir, subproject, False, MachineChoice.BUILD, environment) + self.dependencies = dependencies + self.depend_files = [] + self.command = self.flatten_command(command) + self.absolute_paths = False + self.env = env + self.default_env = default_env + + def __repr__(self) -> str: + repr_str = "<{0} {1}: {2}>" + return repr_str.format(self.__class__.__name__, self.get_id(), self.command[0]) + + def get_dependencies(self) -> T.List[T.Union[BuildTarget, 'CustomTarget']]: + return self.dependencies + + def get_generated_sources(self) -> T.List['GeneratedTypes']: + return [] + + def get_sources(self) -> T.List[File]: + return [] + + def should_install(self) -> bool: + return False + + def get_filename(self) -> str: + return self.name + + def get_outputs(self) -> T.List[str]: + if isinstance(self.name, str): + return [self.name] + elif isinstance(self.name, list): + return self.name + else: + raise RuntimeError('RunTarget: self.name is neither a list nor a string. This is a bug') + + def type_suffix(self) -> str: + return "@run" + +class AliasTarget(RunTarget): + def __init__(self, name: str, dependencies: T.Sequence['Target'], + subdir: str, subproject: str, environment: environment.Environment): + super().__init__(name, [], dependencies, subdir, subproject, environment) + + def __repr__(self): + repr_str = "<{0} {1}>" + return repr_str.format(self.__class__.__name__, self.get_id()) + +class Jar(BuildTarget): + known_kwargs = known_jar_kwargs + + typename = 'jar' + + def __init__(self, name: str, subdir: str, subproject: str, for_machine: MachineChoice, + sources: T.List[SourceOutputs], structured_sources: T.Optional['StructuredSources'], + objects, environment: environment.Environment, compilers: T.Dict[str, 'Compiler'], + kwargs): + super().__init__(name, subdir, subproject, for_machine, sources, structured_sources, objects, + environment, compilers, kwargs) + for s in self.sources: + if not s.endswith('.java'): + raise InvalidArguments(f'Jar source {s} is not a java file.') + for t in self.link_targets: + if not isinstance(t, Jar): + raise InvalidArguments(f'Link target {t} is not a jar target.') + if self.structured_sources: + raise InvalidArguments('structured sources are not supported in Java targets.') + self.filename = self.name + '.jar' + self.outputs = [self.filename] + self.java_args = kwargs.get('java_args', []) + self.java_resources: T.Optional[StructuredSources] = kwargs.get('java_resources', None) + + def get_main_class(self): + return self.main_class + + def type_suffix(self): + return "@jar" + + def get_java_args(self): + return self.java_args + + def get_java_resources(self) -> T.Optional[StructuredSources]: + return self.java_resources + + def validate_install(self): + # All jar targets are installable. + pass + + def is_linkable_target(self): + return True + + def get_classpath_args(self): + cp_paths = [os.path.join(l.get_subdir(), l.get_filename()) for l in self.link_targets] + cp_string = os.pathsep.join(cp_paths) + if cp_string: + return ['-cp', os.pathsep.join(cp_paths)] + return [] + + def get_default_install_dir(self) -> T.Tuple[str, str]: + return self.environment.get_jar_dir(), '{jardir}' + +@dataclass(eq=False) +class CustomTargetIndex(HoldableObject): + + """A special opaque object returned by indexing a CustomTarget. This object + exists in Meson, but acts as a proxy in the backends, making targets depend + on the CustomTarget it's derived from, but only adding one source file to + the sources. + """ + + typename: T.ClassVar[str] = 'custom' + + target: T.Union[CustomTarget, CompileTarget] + output: str + + def __post_init__(self) -> None: + self.for_machine = self.target.for_machine + + @property + def name(self) -> str: + return f'{self.target.name}[{self.output}]' + + def __repr__(self): + return '<CustomTargetIndex: {!r}[{}]>'.format(self.target, self.output) + + def get_outputs(self) -> T.List[str]: + return [self.output] + + def get_subdir(self) -> str: + return self.target.get_subdir() + + def get_filename(self) -> str: + return self.output + + def get_id(self) -> str: + return self.target.get_id() + + def get_all_link_deps(self): + return self.target.get_all_link_deps() + + def get_link_deps_mapping(self, prefix: str) -> T.Mapping[str, str]: + return self.target.get_link_deps_mapping(prefix) + + def get_link_dep_subdirs(self) -> T.AbstractSet[str]: + return self.target.get_link_dep_subdirs() + + def is_linkable_target(self) -> bool: + suf = os.path.splitext(self.output)[-1] + return suf in {'.a', '.dll', '.lib', '.so', '.dylib'} + + def links_dynamically(self) -> bool: + """Whether this target links dynamically or statically + + Does not assert the target is linkable, just that it is not shared + + :return: True if is dynamically linked, otherwise False + """ + suf = os.path.splitext(self.output)[-1] + return suf not in {'.a', '.lib'} + + def should_install(self) -> bool: + return self.target.should_install() + + def is_internal(self) -> bool: + ''' + Returns True if this is a not installed static library + ''' + suf = os.path.splitext(self.output)[-1] + return suf in {'.a', '.lib'} and not self.should_install() + + def extract_all_objects_recurse(self) -> T.List[T.Union[str, 'ExtractedObjects']]: + return self.target.extract_all_objects_recurse() + + def get_custom_install_dir(self) -> T.List[T.Union[str, Literal[False]]]: + return self.target.get_custom_install_dir() + +class ConfigurationData(HoldableObject): + def __init__(self, initial_values: T.Optional[T.Union[ + T.Dict[str, T.Tuple[T.Union[str, int, bool], T.Optional[str]]], + T.Dict[str, T.Union[str, int, bool]]] + ] = None): + super().__init__() + self.values: T.Dict[str, T.Tuple[T.Union[str, int, bool], T.Optional[str]]] = \ + {k: v if isinstance(v, tuple) else (v, None) for k, v in initial_values.items()} if initial_values else {} + self.used: bool = False + + def __repr__(self) -> str: + return repr(self.values) + + def __contains__(self, value: str) -> bool: + return value in self.values + + def __bool__(self) -> bool: + return bool(self.values) + + def get(self, name: str) -> T.Tuple[T.Union[str, int, bool], T.Optional[str]]: + return self.values[name] # (val, desc) + + def keys(self) -> T.Iterator[str]: + return self.values.keys() + +# A bit poorly named, but this represents plain data files to copy +# during install. +@dataclass(eq=False) +class Data(HoldableObject): + sources: T.List[File] + install_dir: str + install_dir_name: str + install_mode: 'FileMode' + subproject: str + rename: T.List[str] = None + install_tag: T.Optional[str] = None + data_type: str = None + + def __post_init__(self) -> None: + if self.rename is None: + self.rename = [os.path.basename(f.fname) for f in self.sources] + +@dataclass(eq=False) +class SymlinkData(HoldableObject): + target: str + name: str + install_dir: str + subproject: str + install_tag: T.Optional[str] = None + + def __post_init__(self) -> None: + if self.name != os.path.basename(self.name): + raise InvalidArguments(f'Link name is "{self.name}", but link names cannot contain path separators. ' + 'The dir part should be in install_dir.') + +@dataclass(eq=False) +class TestSetup: + exe_wrapper: T.List[str] + gdb: bool + timeout_multiplier: int + env: EnvironmentVariables + exclude_suites: T.List[str] + +def get_sources_string_names(sources, backend): + ''' + For the specified list of @sources which can be strings, Files, or targets, + get all the output basenames. + ''' + names = [] + for s in sources: + if isinstance(s, str): + names.append(s) + elif isinstance(s, (BuildTarget, CustomTarget, CustomTargetIndex, GeneratedList)): + names += s.get_outputs() + elif isinstance(s, ExtractedObjects): + names += backend.determine_ext_objs(s) + elif isinstance(s, File): + names.append(s.fname) + else: + raise AssertionError(f'Unknown source type: {s!r}') + return names + +def load(build_dir: str) -> Build: + filename = os.path.join(build_dir, 'meson-private', 'build.dat') + try: + return pickle_load(filename, 'Build data', Build) + except FileNotFoundError: + raise MesonException(f'No such build data file as {filename!r}.') + + +def save(obj: Build, filename: str) -> None: + with open(filename, 'wb') as f: + pickle.dump(obj, f) |