summaryrefslogtreecommitdiffstats
path: root/mesonbuild/backend
diff options
context:
space:
mode:
Diffstat (limited to 'mesonbuild/backend')
-rw-r--r--mesonbuild/backend/__init__.py0
-rw-r--r--mesonbuild/backend/backends.py1914
-rw-r--r--mesonbuild/backend/ninjabackend.py3623
-rw-r--r--mesonbuild/backend/vs2010backend.py1575
-rw-r--r--mesonbuild/backend/vs2012backend.py43
-rw-r--r--mesonbuild/backend/vs2013backend.py42
-rw-r--r--mesonbuild/backend/vs2015backend.py43
-rw-r--r--mesonbuild/backend/vs2017backend.py67
-rw-r--r--mesonbuild/backend/vs2019backend.py62
-rw-r--r--mesonbuild/backend/vs2022backend.py62
-rw-r--r--mesonbuild/backend/xcodebackend.py1719
11 files changed, 9150 insertions, 0 deletions
diff --git a/mesonbuild/backend/__init__.py b/mesonbuild/backend/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/mesonbuild/backend/__init__.py
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py
new file mode 100644
index 0000000..27004f8
--- /dev/null
+++ b/mesonbuild/backend/backends.py
@@ -0,0 +1,1914 @@
+# Copyright 2012-2016 The Meson development team
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from __future__ import annotations
+
+from collections import OrderedDict
+from dataclasses import dataclass, InitVar
+from functools import lru_cache
+from itertools import chain
+from pathlib import Path
+import copy
+import enum
+import json
+import os
+import pickle
+import re
+import shutil
+import typing as T
+import hashlib
+
+from .. import build
+from .. import dependencies
+from .. import programs
+from .. import mesonlib
+from .. import mlog
+from ..compilers import LANGUAGES_USING_LDFLAGS, detect
+from ..mesonlib import (
+ File, MachineChoice, MesonException, OrderedSet,
+ classify_unity_sources, OptionKey, join_args,
+ ExecutableSerialisation
+)
+
+if T.TYPE_CHECKING:
+ from .._typing import ImmutableListProtocol
+ from ..arglist import CompilerArgs
+ from ..compilers import Compiler
+ from ..environment import Environment
+ from ..interpreter import Interpreter, Test
+ from ..linkers import StaticLinker
+ from ..mesonlib import FileMode, FileOrString
+
+ from typing_extensions import TypedDict
+
+ _ALL_SOURCES_TYPE = T.List[T.Union[File, build.CustomTarget, build.CustomTargetIndex, build.GeneratedList]]
+
+ class TargetIntrospectionData(TypedDict):
+
+ language: str
+ compiler: T.List[str]
+ parameters: T.List[str]
+ sources: T.List[str]
+ generated_sources: T.List[str]
+
+
+# Languages that can mix with C or C++ but don't support unity builds yet
+# because the syntax we use for unity builds is specific to C/++/ObjC/++.
+# Assembly files cannot be unitified and neither can LLVM IR files
+LANGS_CANT_UNITY = ('d', 'fortran', 'vala')
+
+@dataclass(eq=False)
+class RegenInfo:
+ source_dir: str
+ build_dir: str
+ depfiles: T.List[str]
+
+class TestProtocol(enum.Enum):
+
+ EXITCODE = 0
+ TAP = 1
+ GTEST = 2
+ RUST = 3
+
+ @classmethod
+ def from_str(cls, string: str) -> 'TestProtocol':
+ if string == 'exitcode':
+ return cls.EXITCODE
+ elif string == 'tap':
+ return cls.TAP
+ elif string == 'gtest':
+ return cls.GTEST
+ elif string == 'rust':
+ return cls.RUST
+ raise MesonException(f'unknown test format {string}')
+
+ def __str__(self) -> str:
+ cls = type(self)
+ if self is cls.EXITCODE:
+ return 'exitcode'
+ elif self is cls.GTEST:
+ return 'gtest'
+ elif self is cls.RUST:
+ return 'rust'
+ return 'tap'
+
+
+@dataclass(eq=False)
+class CleanTrees:
+ '''
+ Directories outputted by custom targets that have to be manually cleaned
+ because on Linux `ninja clean` only deletes empty directories.
+ '''
+ build_dir: str
+ trees: T.List[str]
+
+@dataclass(eq=False)
+class InstallData:
+ source_dir: str
+ build_dir: str
+ prefix: str
+ libdir: str
+ strip_bin: T.List[str]
+ # TODO: in python 3.8 or with typing_Extensions this could be:
+ # `T.Union[T.Literal['preserve'], int]`, which would be more accurate.
+ install_umask: T.Union[str, int]
+ mesonintrospect: T.List[str]
+ version: str
+
+ def __post_init__(self) -> None:
+ self.targets: T.List[TargetInstallData] = []
+ self.headers: T.List[InstallDataBase] = []
+ self.man: T.List[InstallDataBase] = []
+ self.emptydir: T.List[InstallEmptyDir] = []
+ self.data: T.List[InstallDataBase] = []
+ self.symlinks: T.List[InstallSymlinkData] = []
+ self.install_scripts: T.List[ExecutableSerialisation] = []
+ self.install_subdirs: T.List[SubdirInstallData] = []
+
+@dataclass(eq=False)
+class TargetInstallData:
+ fname: str
+ outdir: str
+ outdir_name: InitVar[T.Optional[str]]
+ strip: bool
+ install_name_mappings: T.Mapping[str, str]
+ rpath_dirs_to_remove: T.Set[bytes]
+ install_rpath: str
+ # TODO: install_mode should just always be a FileMode object
+ install_mode: T.Optional['FileMode']
+ subproject: str
+ optional: bool = False
+ tag: T.Optional[str] = None
+ can_strip: bool = False
+
+ def __post_init__(self, outdir_name: T.Optional[str]) -> None:
+ if outdir_name is None:
+ outdir_name = os.path.join('{prefix}', self.outdir)
+ self.out_name = os.path.join(outdir_name, os.path.basename(self.fname))
+
+@dataclass(eq=False)
+class InstallEmptyDir:
+ path: str
+ install_mode: 'FileMode'
+ subproject: str
+ tag: T.Optional[str] = None
+
+@dataclass(eq=False)
+class InstallDataBase:
+ path: str
+ install_path: str
+ install_path_name: str
+ install_mode: 'FileMode'
+ subproject: str
+ tag: T.Optional[str] = None
+ data_type: T.Optional[str] = None
+
+@dataclass(eq=False)
+class InstallSymlinkData:
+ target: str
+ name: str
+ install_path: str
+ subproject: str
+ tag: T.Optional[str] = None
+ allow_missing: bool = False
+
+# cannot use dataclass here because "exclude" is out of order
+class SubdirInstallData(InstallDataBase):
+ def __init__(self, path: str, install_path: str, install_path_name: str,
+ install_mode: 'FileMode', exclude: T.Tuple[T.Set[str], T.Set[str]],
+ subproject: str, tag: T.Optional[str] = None, data_type: T.Optional[str] = None):
+ super().__init__(path, install_path, install_path_name, install_mode, subproject, tag, data_type)
+ self.exclude = exclude
+
+
+@dataclass(eq=False)
+class TestSerialisation:
+ name: str
+ project_name: str
+ suite: T.List[str]
+ fname: T.List[str]
+ is_cross_built: bool
+ exe_wrapper: T.Optional[programs.ExternalProgram]
+ needs_exe_wrapper: bool
+ is_parallel: bool
+ cmd_args: T.List[str]
+ env: build.EnvironmentVariables
+ should_fail: bool
+ timeout: T.Optional[int]
+ workdir: T.Optional[str]
+ extra_paths: T.List[str]
+ protocol: TestProtocol
+ priority: int
+ cmd_is_built: bool
+ cmd_is_exe: bool
+ depends: T.List[str]
+ version: str
+ verbose: bool
+
+ def __post_init__(self) -> None:
+ if self.exe_wrapper is not None:
+ assert isinstance(self.exe_wrapper, programs.ExternalProgram)
+
+
+def get_backend_from_name(backend: str, build: T.Optional[build.Build] = None, interpreter: T.Optional['Interpreter'] = None) -> T.Optional['Backend']:
+ if backend == 'ninja':
+ from . import ninjabackend
+ return ninjabackend.NinjaBackend(build, interpreter)
+ elif backend == 'vs':
+ from . import vs2010backend
+ return vs2010backend.autodetect_vs_version(build, interpreter)
+ elif backend == 'vs2010':
+ from . import vs2010backend
+ return vs2010backend.Vs2010Backend(build, interpreter)
+ elif backend == 'vs2012':
+ from . import vs2012backend
+ return vs2012backend.Vs2012Backend(build, interpreter)
+ elif backend == 'vs2013':
+ from . import vs2013backend
+ return vs2013backend.Vs2013Backend(build, interpreter)
+ elif backend == 'vs2015':
+ from . import vs2015backend
+ return vs2015backend.Vs2015Backend(build, interpreter)
+ elif backend == 'vs2017':
+ from . import vs2017backend
+ return vs2017backend.Vs2017Backend(build, interpreter)
+ elif backend == 'vs2019':
+ from . import vs2019backend
+ return vs2019backend.Vs2019Backend(build, interpreter)
+ elif backend == 'vs2022':
+ from . import vs2022backend
+ return vs2022backend.Vs2022Backend(build, interpreter)
+ elif backend == 'xcode':
+ from . import xcodebackend
+ return xcodebackend.XCodeBackend(build, interpreter)
+ return None
+
+# This class contains the basic functionality that is needed by all backends.
+# Feel free to move stuff in and out of it as you see fit.
+class Backend:
+
+ environment: T.Optional['Environment']
+
+ def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional['Interpreter']):
+ # Make it possible to construct a dummy backend
+ # This is used for introspection without a build directory
+ if build is None:
+ self.environment = None
+ return
+ self.build = build
+ self.interpreter = interpreter
+ self.environment = build.environment
+ self.processed_targets: T.Set[str] = set()
+ self.name = '<UNKNOWN>'
+ self.build_dir = self.environment.get_build_dir()
+ self.source_dir = self.environment.get_source_dir()
+ self.build_to_src = mesonlib.relpath(self.environment.get_source_dir(),
+ self.environment.get_build_dir())
+ self.src_to_build = mesonlib.relpath(self.environment.get_build_dir(),
+ self.environment.get_source_dir())
+
+ def generate(self) -> None:
+ raise RuntimeError(f'generate is not implemented in {type(self).__name__}')
+
+ def get_target_filename(self, t: T.Union[build.Target, build.CustomTargetIndex], *, warn_multi_output: bool = True) -> str:
+ if isinstance(t, build.CustomTarget):
+ if warn_multi_output and len(t.get_outputs()) != 1:
+ mlog.warning(f'custom_target {t.name!r} has more than one output! '
+ 'Using the first one.')
+ filename = t.get_outputs()[0]
+ elif isinstance(t, build.CustomTargetIndex):
+ filename = t.get_outputs()[0]
+ else:
+ assert isinstance(t, build.BuildTarget), t
+ filename = t.get_filename()
+ return os.path.join(self.get_target_dir(t), filename)
+
+ def get_target_filename_abs(self, target: T.Union[build.Target, build.CustomTargetIndex]) -> str:
+ return os.path.join(self.environment.get_build_dir(), self.get_target_filename(target))
+
+ def get_source_dir_include_args(self, target: build.BuildTarget, compiler: 'Compiler', *, absolute_path: bool = False) -> T.List[str]:
+ curdir = target.get_subdir()
+ if absolute_path:
+ lead = self.source_dir
+ else:
+ lead = self.build_to_src
+ tmppath = os.path.normpath(os.path.join(lead, curdir))
+ return compiler.get_include_args(tmppath, False)
+
+ def get_build_dir_include_args(self, target: build.BuildTarget, compiler: 'Compiler', *, absolute_path: bool = False) -> T.List[str]:
+ if absolute_path:
+ curdir = os.path.join(self.build_dir, target.get_subdir())
+ else:
+ curdir = target.get_subdir()
+ if curdir == '':
+ curdir = '.'
+ return compiler.get_include_args(curdir, False)
+
+ def get_target_filename_for_linking(self, target: T.Union[build.Target, build.CustomTargetIndex]) -> T.Optional[str]:
+ # On some platforms (msvc for instance), the file that is used for
+ # dynamic linking is not the same as the dynamic library itself. This
+ # file is called an import library, and we want to link against that.
+ # On all other platforms, we link to the library directly.
+ if isinstance(target, build.SharedLibrary):
+ link_lib = target.get_import_filename() or target.get_filename()
+ return os.path.join(self.get_target_dir(target), link_lib)
+ elif isinstance(target, build.StaticLibrary):
+ return os.path.join(self.get_target_dir(target), target.get_filename())
+ elif isinstance(target, (build.CustomTarget, build.CustomTargetIndex)):
+ if not target.is_linkable_target():
+ raise MesonException(f'Tried to link against custom target "{target.name}", which is not linkable.')
+ return os.path.join(self.get_target_dir(target), target.get_filename())
+ elif isinstance(target, build.Executable):
+ if target.import_filename:
+ return os.path.join(self.get_target_dir(target), target.get_import_filename())
+ else:
+ return None
+ raise AssertionError(f'BUG: Tried to link to {target!r} which is not linkable')
+
+ @lru_cache(maxsize=None)
+ def get_target_dir(self, target: T.Union[build.Target, build.CustomTargetIndex]) -> str:
+ if isinstance(target, build.RunTarget):
+ # this produces no output, only a dummy top-level name
+ dirname = ''
+ elif self.environment.coredata.get_option(OptionKey('layout')) == 'mirror':
+ dirname = target.get_subdir()
+ else:
+ dirname = 'meson-out'
+ return dirname
+
+ def get_target_dir_relative_to(self, t: build.Target, o: build.Target) -> str:
+ '''Get a target dir relative to another target's directory'''
+ target_dir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(t))
+ othert_dir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(o))
+ return os.path.relpath(target_dir, othert_dir)
+
+ def get_target_source_dir(self, target: build.Target) -> str:
+ # if target dir is empty, avoid extraneous trailing / from os.path.join()
+ target_dir = self.get_target_dir(target)
+ if target_dir:
+ return os.path.join(self.build_to_src, target_dir)
+ return self.build_to_src
+
+ def get_target_private_dir(self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]) -> str:
+ return os.path.join(self.get_target_filename(target, warn_multi_output=False) + '.p')
+
+ def get_target_private_dir_abs(self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]) -> str:
+ return os.path.join(self.environment.get_build_dir(), self.get_target_private_dir(target))
+
+ @lru_cache(maxsize=None)
+ def get_target_generated_dir(
+ self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex],
+ gensrc: T.Union[build.CustomTarget, build.CustomTargetIndex, build.GeneratedList],
+ src: str) -> str:
+ """
+ Takes a BuildTarget, a generator source (CustomTarget or GeneratedList),
+ and a generated source filename.
+ Returns the full path of the generated source relative to the build root
+ """
+ # CustomTarget generators output to the build dir of the CustomTarget
+ if isinstance(gensrc, (build.CustomTarget, build.CustomTargetIndex)):
+ return os.path.join(self.get_target_dir(gensrc), src)
+ # GeneratedList generators output to the private build directory of the
+ # target that the GeneratedList is used in
+ return os.path.join(self.get_target_private_dir(target), src)
+
+ def get_unity_source_file(self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex],
+ suffix: str, number: int) -> mesonlib.File:
+ # There is a potential conflict here, but it is unlikely that
+ # anyone both enables unity builds and has a file called foo-unity.cpp.
+ osrc = f'{target.name}-unity{number}.{suffix}'
+ return mesonlib.File.from_built_file(self.get_target_private_dir(target), osrc)
+
+ def generate_unity_files(self, target: build.BuildTarget, unity_src: str) -> T.List[mesonlib.File]:
+ abs_files: T.List[str] = []
+ result: T.List[mesonlib.File] = []
+ compsrcs = classify_unity_sources(target.compilers.values(), unity_src)
+ unity_size = target.get_option(OptionKey('unity_size'))
+ assert isinstance(unity_size, int), 'for mypy'
+
+ def init_language_file(suffix: str, unity_file_number: int) -> T.TextIO:
+ unity_src = self.get_unity_source_file(target, suffix, unity_file_number)
+ outfileabs = unity_src.absolute_path(self.environment.get_source_dir(),
+ self.environment.get_build_dir())
+ outfileabs_tmp = outfileabs + '.tmp'
+ abs_files.append(outfileabs)
+ outfileabs_tmp_dir = os.path.dirname(outfileabs_tmp)
+ if not os.path.exists(outfileabs_tmp_dir):
+ os.makedirs(outfileabs_tmp_dir)
+ result.append(unity_src)
+ return open(outfileabs_tmp, 'w', encoding='utf-8')
+
+ # For each language, generate unity source files and return the list
+ for comp, srcs in compsrcs.items():
+ files_in_current = unity_size + 1
+ unity_file_number = 0
+ # TODO: this could be simplified with an algorithm that pre-sorts
+ # the sources into the size of chunks we want
+ ofile = None
+ for src in srcs:
+ if files_in_current >= unity_size:
+ if ofile:
+ ofile.close()
+ ofile = init_language_file(comp.get_default_suffix(), unity_file_number)
+ unity_file_number += 1
+ files_in_current = 0
+ ofile.write(f'#include<{src}>\n')
+ files_in_current += 1
+ if ofile:
+ ofile.close()
+
+ for x in abs_files:
+ mesonlib.replace_if_different(x, x + '.tmp')
+ return result
+
+ @staticmethod
+ def relpath(todir: str, fromdir: str) -> str:
+ return os.path.relpath(os.path.join('dummyprefixdir', todir),
+ os.path.join('dummyprefixdir', fromdir))
+
+ def flatten_object_list(self, target: build.BuildTarget, proj_dir_to_build_root: str = ''
+ ) -> T.Tuple[T.List[str], T.List[build.BuildTargetTypes]]:
+ obj_list, deps = self._flatten_object_list(target, target.get_objects(), proj_dir_to_build_root)
+ return list(dict.fromkeys(obj_list)), deps
+
+ def determine_ext_objs(self, objects: build.ExtractedObjects, proj_dir_to_build_root: str = '') -> T.List[str]:
+ obj_list, _ = self._flatten_object_list(objects.target, [objects], proj_dir_to_build_root)
+ return list(dict.fromkeys(obj_list))
+
+ def _flatten_object_list(self, target: build.BuildTarget,
+ objects: T.Sequence[T.Union[str, 'File', build.ExtractedObjects]],
+ proj_dir_to_build_root: str) -> T.Tuple[T.List[str], T.List[build.BuildTargetTypes]]:
+ obj_list: T.List[str] = []
+ deps: T.List[build.BuildTargetTypes] = []
+ for obj in objects:
+ if isinstance(obj, str):
+ o = os.path.join(proj_dir_to_build_root,
+ self.build_to_src, target.get_subdir(), obj)
+ obj_list.append(o)
+ elif isinstance(obj, mesonlib.File):
+ if obj.is_built:
+ o = os.path.join(proj_dir_to_build_root,
+ obj.rel_to_builddir(self.build_to_src))
+ obj_list.append(o)
+ else:
+ o = os.path.join(proj_dir_to_build_root,
+ self.build_to_src)
+ obj_list.append(obj.rel_to_builddir(o))
+ elif isinstance(obj, build.ExtractedObjects):
+ if obj.recursive:
+ objs, d = self._flatten_object_list(obj.target, obj.objlist, proj_dir_to_build_root)
+ obj_list.extend(objs)
+ deps.extend(d)
+ obj_list.extend(self._determine_ext_objs(obj, proj_dir_to_build_root))
+ deps.append(obj.target)
+ else:
+ raise MesonException('Unknown data type in object list.')
+ return obj_list, deps
+
+ @staticmethod
+ def is_swift_target(target: build.BuildTarget) -> bool:
+ for s in target.sources:
+ if s.endswith('swift'):
+ return True
+ return False
+
+ def determine_swift_dep_dirs(self, target: build.BuildTarget) -> T.List[str]:
+ result: T.List[str] = []
+ for l in target.link_targets:
+ result.append(self.get_target_private_dir_abs(l))
+ return result
+
+ def get_executable_serialisation(
+ self, cmd: T.Sequence[T.Union[programs.ExternalProgram, build.BuildTarget, build.CustomTarget, File, str]],
+ workdir: T.Optional[str] = None,
+ extra_bdeps: T.Optional[T.List[build.BuildTarget]] = None,
+ capture: T.Optional[bool] = None,
+ feed: T.Optional[bool] = None,
+ env: T.Optional[build.EnvironmentVariables] = None,
+ tag: T.Optional[str] = None,
+ verbose: bool = False) -> 'ExecutableSerialisation':
+
+ # XXX: cmd_args either need to be lowered to strings, or need to be checked for non-string arguments, right?
+ exe, *raw_cmd_args = cmd
+ if isinstance(exe, programs.ExternalProgram):
+ exe_cmd = exe.get_command()
+ exe_for_machine = exe.for_machine
+ elif isinstance(exe, build.BuildTarget):
+ exe_cmd = [self.get_target_filename_abs(exe)]
+ exe_for_machine = exe.for_machine
+ elif isinstance(exe, build.CustomTarget):
+ # The output of a custom target can either be directly runnable
+ # or not, that is, a script, a native binary or a cross compiled
+ # binary when exe wrapper is available and when it is not.
+ # This implementation is not exhaustive but it works in the
+ # common cases.
+ exe_cmd = [self.get_target_filename_abs(exe)]
+ exe_for_machine = MachineChoice.BUILD
+ elif isinstance(exe, mesonlib.File):
+ exe_cmd = [exe.rel_to_builddir(self.environment.source_dir)]
+ exe_for_machine = MachineChoice.BUILD
+ else:
+ exe_cmd = [exe]
+ exe_for_machine = MachineChoice.BUILD
+
+ cmd_args: T.List[str] = []
+ for c in raw_cmd_args:
+ if isinstance(c, programs.ExternalProgram):
+ p = c.get_path()
+ assert isinstance(p, str)
+ cmd_args.append(p)
+ elif isinstance(c, (build.BuildTarget, build.CustomTarget)):
+ cmd_args.append(self.get_target_filename_abs(c))
+ elif isinstance(c, mesonlib.File):
+ cmd_args.append(c.rel_to_builddir(self.environment.source_dir))
+ else:
+ cmd_args.append(c)
+
+ machine = self.environment.machines[exe_for_machine]
+ if machine.is_windows() or machine.is_cygwin():
+ extra_paths = self.determine_windows_extra_paths(exe, extra_bdeps or [])
+ else:
+ extra_paths = []
+
+ is_cross_built = not self.environment.machines.matches_build_machine(exe_for_machine)
+ if is_cross_built and self.environment.need_exe_wrapper():
+ exe_wrapper = self.environment.get_exe_wrapper()
+ if not exe_wrapper or not exe_wrapper.found():
+ msg = 'An exe_wrapper is needed but was not found. Please define one ' \
+ 'in cross file and check the command and/or add it to PATH.'
+ raise MesonException(msg)
+ else:
+ if exe_cmd[0].endswith('.jar'):
+ exe_cmd = ['java', '-jar'] + exe_cmd
+ elif exe_cmd[0].endswith('.exe') and not (mesonlib.is_windows() or mesonlib.is_cygwin() or mesonlib.is_wsl()):
+ exe_cmd = ['mono'] + exe_cmd
+ exe_wrapper = None
+
+ workdir = workdir or self.environment.get_build_dir()
+ return ExecutableSerialisation(exe_cmd + cmd_args, env,
+ exe_wrapper, workdir,
+ extra_paths, capture, feed, tag, verbose)
+
+ def as_meson_exe_cmdline(self, exe: T.Union[str, mesonlib.File, build.BuildTarget, build.CustomTarget, programs.ExternalProgram],
+ cmd_args: T.Sequence[T.Union[str, mesonlib.File, build.BuildTarget, build.CustomTarget, programs.ExternalProgram]],
+ workdir: T.Optional[str] = None,
+ extra_bdeps: T.Optional[T.List[build.BuildTarget]] = None,
+ capture: T.Optional[bool] = None,
+ feed: T.Optional[bool] = None,
+ force_serialize: bool = False,
+ env: T.Optional[build.EnvironmentVariables] = None,
+ verbose: bool = False) -> T.Tuple[T.Sequence[T.Union[str, File, build.Target, programs.ExternalProgram]], str]:
+ '''
+ Serialize an executable for running with a generator or a custom target
+ '''
+ cmd: T.List[T.Union[str, mesonlib.File, build.BuildTarget, build.CustomTarget, programs.ExternalProgram]] = []
+ cmd.append(exe)
+ cmd.extend(cmd_args)
+ es = self.get_executable_serialisation(cmd, workdir, extra_bdeps, capture, feed, env, verbose=verbose)
+ reasons: T.List[str] = []
+ if es.extra_paths:
+ reasons.append('to set PATH')
+
+ if es.exe_wrapper:
+ reasons.append('to use exe_wrapper')
+
+ if workdir:
+ reasons.append('to set workdir')
+
+ if any('\n' in c for c in es.cmd_args):
+ reasons.append('because command contains newlines')
+
+ if env and env.varnames:
+ reasons.append('to set env')
+
+ # force_serialize passed to this function means that the VS backend has
+ # decided it absolutely cannot use real commands. This is "always",
+ # because it's not clear what will work (other than compilers) and so
+ # we don't bother to handle a variety of common cases that probably do
+ # work.
+ #
+ # It's also overridden for a few conditions that can't be handled
+ # inside a command line
+
+ can_use_env = not force_serialize
+ force_serialize = force_serialize or bool(reasons)
+
+ if capture:
+ reasons.append('to capture output')
+ if feed:
+ reasons.append('to feed input')
+
+ if can_use_env and reasons == ['to set env'] and shutil.which('env'):
+ envlist = []
+ for k, v in env.get_env({}).items():
+ envlist.append(f'{k}={v}')
+ return ['env'] + envlist + es.cmd_args, ', '.join(reasons)
+
+ if not force_serialize:
+ if not capture and not feed:
+ return es.cmd_args, ''
+ args: T.List[str] = []
+ if capture:
+ args += ['--capture', str(capture)]
+ if feed:
+ args += ['--feed', str(feed)]
+
+ return (
+ self.environment.get_build_command() + ['--internal', 'exe'] + args + ['--'] + es.cmd_args,
+ ', '.join(reasons)
+ )
+
+ if isinstance(exe, (programs.ExternalProgram,
+ build.BuildTarget, build.CustomTarget)):
+ basename = exe.name
+ elif isinstance(exe, mesonlib.File):
+ basename = os.path.basename(exe.fname)
+ else:
+ basename = os.path.basename(exe)
+
+ # Can't just use exe.name here; it will likely be run more than once
+ # Take a digest of the cmd args, env, workdir, capture, and feed. This
+ # avoids collisions and also makes the name deterministic over
+ # regenerations which avoids a rebuild by Ninja because the cmdline
+ # stays the same.
+ hasher = hashlib.sha1()
+ if es.env:
+ es.env.hash(hasher)
+ hasher.update(bytes(str(es.cmd_args), encoding='utf-8'))
+ hasher.update(bytes(str(es.workdir), encoding='utf-8'))
+ hasher.update(bytes(str(capture), encoding='utf-8'))
+ hasher.update(bytes(str(feed), encoding='utf-8'))
+ digest = hasher.hexdigest()
+ scratch_file = f'meson_exe_{basename}_{digest}.dat'
+ exe_data = os.path.join(self.environment.get_scratch_dir(), scratch_file)
+ with open(exe_data, 'wb') as f:
+ pickle.dump(es, f)
+ return (self.environment.get_build_command() + ['--internal', 'exe', '--unpickle', exe_data],
+ ', '.join(reasons))
+
+ def serialize_tests(self) -> T.Tuple[str, str]:
+ test_data = os.path.join(self.environment.get_scratch_dir(), 'meson_test_setup.dat')
+ with open(test_data, 'wb') as datafile:
+ self.write_test_file(datafile)
+ benchmark_data = os.path.join(self.environment.get_scratch_dir(), 'meson_benchmark_setup.dat')
+ with open(benchmark_data, 'wb') as datafile:
+ self.write_benchmark_file(datafile)
+ return test_data, benchmark_data
+
+ def determine_linker_and_stdlib_args(self, target: build.BuildTarget) -> T.Tuple[T.Union['Compiler', 'StaticLinker'], T.List[str]]:
+ '''
+ If we're building a static library, there is only one static linker.
+ Otherwise, we query the target for the dynamic linker.
+ '''
+ if isinstance(target, build.StaticLibrary):
+ return self.build.static_linker[target.for_machine], []
+ l, stdlib_args = target.get_clink_dynamic_linker_and_stdlibs()
+ return l, stdlib_args
+
+ @staticmethod
+ def _libdir_is_system(libdir: str, compilers: T.Mapping[str, 'Compiler'], env: 'Environment') -> bool:
+ libdir = os.path.normpath(libdir)
+ for cc in compilers.values():
+ if libdir in cc.get_library_dirs(env):
+ return True
+ return False
+
+ def get_external_rpath_dirs(self, target: build.BuildTarget) -> T.Set[str]:
+ args: T.List[str] = []
+ for lang in LANGUAGES_USING_LDFLAGS:
+ try:
+ e = self.environment.coredata.get_external_link_args(target.for_machine, lang)
+ if isinstance(e, str):
+ args.append(e)
+ else:
+ args.extend(e)
+ except Exception:
+ pass
+ return self.get_rpath_dirs_from_link_args(args)
+
+ @staticmethod
+ def get_rpath_dirs_from_link_args(args: T.List[str]) -> T.Set[str]:
+ dirs: T.Set[str] = set()
+ # Match rpath formats:
+ # -Wl,-rpath=
+ # -Wl,-rpath,
+ rpath_regex = re.compile(r'-Wl,-rpath[=,]([^,]+)')
+ # Match solaris style compat runpath formats:
+ # -Wl,-R
+ # -Wl,-R,
+ runpath_regex = re.compile(r'-Wl,-R[,]?([^,]+)')
+ # Match symbols formats:
+ # -Wl,--just-symbols=
+ # -Wl,--just-symbols,
+ symbols_regex = re.compile(r'-Wl,--just-symbols[=,]([^,]+)')
+ for arg in args:
+ rpath_match = rpath_regex.match(arg)
+ if rpath_match:
+ for dir in rpath_match.group(1).split(':'):
+ dirs.add(dir)
+ runpath_match = runpath_regex.match(arg)
+ if runpath_match:
+ for dir in runpath_match.group(1).split(':'):
+ # The symbols arg is an rpath if the path is a directory
+ if Path(dir).is_dir():
+ dirs.add(dir)
+ symbols_match = symbols_regex.match(arg)
+ if symbols_match:
+ for dir in symbols_match.group(1).split(':'):
+ # Prevent usage of --just-symbols to specify rpath
+ if Path(dir).is_dir():
+ raise MesonException(f'Invalid arg for --just-symbols, {dir} is a directory.')
+ return dirs
+
+ @lru_cache(maxsize=None)
+ def rpaths_for_non_system_absolute_shared_libraries(self, target: build.BuildTarget, exclude_system: bool = True) -> 'ImmutableListProtocol[str]':
+ paths: OrderedSet[str] = OrderedSet()
+ for dep in target.external_deps:
+ if not isinstance(dep, (dependencies.ExternalLibrary, dependencies.PkgConfigDependency)):
+ continue
+ for libpath in dep.link_args:
+ # For all link args that are absolute paths to a library file, add RPATH args
+ if not os.path.isabs(libpath):
+ continue
+ libdir = os.path.dirname(libpath)
+ if exclude_system and self._libdir_is_system(libdir, target.compilers, self.environment):
+ # No point in adding system paths.
+ continue
+ # Don't remove rpaths specified in LDFLAGS.
+ if libdir in self.get_external_rpath_dirs(target):
+ continue
+ # Windows doesn't support rpaths, but we use this function to
+ # emulate rpaths by setting PATH, so also accept DLLs here
+ if os.path.splitext(libpath)[1] not in ['.dll', '.lib', '.so', '.dylib']:
+ continue
+ if libdir.startswith(self.environment.get_source_dir()):
+ rel_to_src = libdir[len(self.environment.get_source_dir()) + 1:]
+ assert not os.path.isabs(rel_to_src), f'rel_to_src: {rel_to_src} is absolute'
+ paths.add(os.path.join(self.build_to_src, rel_to_src))
+ else:
+ paths.add(libdir)
+ # Don't remove rpaths specified by the dependency
+ paths.difference_update(self.get_rpath_dirs_from_link_args(dep.link_args))
+ for i in chain(target.link_targets, target.link_whole_targets):
+ if isinstance(i, build.BuildTarget):
+ paths.update(self.rpaths_for_non_system_absolute_shared_libraries(i, exclude_system))
+ return list(paths)
+
+ # This may take other types
+ def determine_rpath_dirs(self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]
+ ) -> T.Tuple[str, ...]:
+ result: OrderedSet[str]
+ if self.environment.coredata.get_option(OptionKey('layout')) == 'mirror':
+ # Need a copy here
+ result = OrderedSet(target.get_link_dep_subdirs())
+ else:
+ result = OrderedSet()
+ result.add('meson-out')
+ if isinstance(target, build.BuildTarget):
+ result.update(self.rpaths_for_non_system_absolute_shared_libraries(target))
+ target.rpath_dirs_to_remove.update([d.encode('utf-8') for d in result])
+ return tuple(result)
+
+ @staticmethod
+ def canonicalize_filename(fname: str) -> str:
+ parts = Path(fname).parts
+ hashed = ''
+ if len(parts) > 5:
+ temp = '/'.join(parts[-5:])
+ # is it shorter to hash the beginning of the path?
+ if len(fname) > len(temp) + 41:
+ hashed = hashlib.sha1(fname.encode('utf-8')).hexdigest() + '_'
+ fname = temp
+ for ch in ('/', '\\', ':'):
+ fname = fname.replace(ch, '_')
+ return hashed + fname
+
+ def object_filename_from_source(self, target: build.BuildTarget, source: 'FileOrString') -> str:
+ assert isinstance(source, mesonlib.File)
+ if isinstance(target, build.CompileTarget):
+ return target.sources_map[source]
+ build_dir = self.environment.get_build_dir()
+ rel_src = source.rel_to_builddir(self.build_to_src)
+
+ # foo.vala files compile down to foo.c and then foo.c.o, not foo.vala.o
+ if rel_src.endswith(('.vala', '.gs')):
+ # See description in generate_vala_compile for this logic.
+ if source.is_built:
+ if os.path.isabs(rel_src):
+ rel_src = rel_src[len(build_dir) + 1:]
+ rel_src = os.path.relpath(rel_src, self.get_target_private_dir(target))
+ else:
+ rel_src = os.path.basename(rel_src)
+ # A meson- prefixed directory is reserved; hopefully no-one creates a file name with such a weird prefix.
+ gen_source = 'meson-generated_' + rel_src[:-5] + '.c'
+ elif source.is_built:
+ if os.path.isabs(rel_src):
+ rel_src = rel_src[len(build_dir) + 1:]
+ targetdir = self.get_target_private_dir(target)
+ # A meson- prefixed directory is reserved; hopefully no-one creates a file name with such a weird prefix.
+ gen_source = 'meson-generated_' + os.path.relpath(rel_src, targetdir)
+ else:
+ if os.path.isabs(rel_src):
+ # Use the absolute path directly to avoid file name conflicts
+ gen_source = rel_src
+ else:
+ gen_source = os.path.relpath(os.path.join(build_dir, rel_src),
+ os.path.join(self.environment.get_source_dir(), target.get_subdir()))
+ machine = self.environment.machines[target.for_machine]
+ return self.canonicalize_filename(gen_source) + '.' + machine.get_object_suffix()
+
+ def _determine_ext_objs(self, extobj: 'build.ExtractedObjects', proj_dir_to_build_root: str) -> T.List[str]:
+ result: T.List[str] = []
+
+ # Merge sources and generated sources
+ raw_sources = list(extobj.srclist)
+ for gensrc in extobj.genlist:
+ for r in gensrc.get_outputs():
+ path = self.get_target_generated_dir(extobj.target, gensrc, r)
+ dirpart, fnamepart = os.path.split(path)
+ raw_sources.append(File(True, dirpart, fnamepart))
+
+ # Filter out headers and all non-source files
+ sources: T.List['FileOrString'] = []
+ for s in raw_sources:
+ if self.environment.is_source(s):
+ sources.append(s)
+ elif self.environment.is_object(s):
+ result.append(s.relative_name())
+
+ # extobj could contain only objects and no sources
+ if not sources:
+ return result
+
+ targetdir = self.get_target_private_dir(extobj.target)
+
+ # With unity builds, sources don't map directly to objects,
+ # we only support extracting all the objects in this mode,
+ # so just return all object files.
+ if extobj.target.is_unity:
+ compsrcs = classify_unity_sources(extobj.target.compilers.values(), sources)
+ sources = []
+ unity_size = extobj.target.get_option(OptionKey('unity_size'))
+ assert isinstance(unity_size, int), 'for mypy'
+
+ for comp, srcs in compsrcs.items():
+ if comp.language in LANGS_CANT_UNITY:
+ sources += srcs
+ continue
+ for i in range(len(srcs) // unity_size + 1):
+ _src = self.get_unity_source_file(extobj.target,
+ comp.get_default_suffix(), i)
+ sources.append(_src)
+
+ for osrc in sources:
+ objname = self.object_filename_from_source(extobj.target, osrc)
+ objpath = os.path.join(proj_dir_to_build_root, targetdir, objname)
+ result.append(objpath)
+
+ return result
+
+ def get_pch_include_args(self, compiler: 'Compiler', target: build.BuildTarget) -> T.List[str]:
+ args: T.List[str] = []
+ pchpath = self.get_target_private_dir(target)
+ includeargs = compiler.get_include_args(pchpath, False)
+ p = target.get_pch(compiler.get_language())
+ if p:
+ args += compiler.get_pch_use_args(pchpath, p[0])
+ return includeargs + args
+
+ def create_msvc_pch_implementation(self, target: build.BuildTarget, lang: str, pch_header: str) -> str:
+ # We have to include the language in the file name, otherwise
+ # pch.c and pch.cpp will both end up as pch.obj in VS backends.
+ impl_name = f'meson_pch-{lang}.{lang}'
+ pch_rel_to_build = os.path.join(self.get_target_private_dir(target), impl_name)
+ # Make sure to prepend the build dir, since the working directory is
+ # not defined. Otherwise, we might create the file in the wrong path.
+ pch_file = os.path.join(self.build_dir, pch_rel_to_build)
+ os.makedirs(os.path.dirname(pch_file), exist_ok=True)
+
+ content = f'#include "{os.path.basename(pch_header)}"'
+ pch_file_tmp = pch_file + '.tmp'
+ with open(pch_file_tmp, 'w', encoding='utf-8') as f:
+ f.write(content)
+ mesonlib.replace_if_different(pch_file, pch_file_tmp)
+ return pch_rel_to_build
+
+ @staticmethod
+ def escape_extra_args(args: T.List[str]) -> T.List[str]:
+ # all backslashes in defines are doubly-escaped
+ extra_args: T.List[str] = []
+ for arg in args:
+ if arg.startswith(('-D', '/D')):
+ arg = arg.replace('\\', '\\\\')
+ extra_args.append(arg)
+
+ return extra_args
+
+ def get_no_stdlib_args(self, target: 'build.BuildTarget', compiler: 'Compiler') -> T.List[str]:
+ if compiler.language in self.build.stdlibs[target.for_machine]:
+ return compiler.get_no_stdinc_args()
+ return []
+
+ def generate_basic_compiler_args(self, target: build.BuildTarget, compiler: 'Compiler', no_warn_args: bool = False) -> 'CompilerArgs':
+ # Create an empty commands list, and start adding arguments from
+ # various sources in the order in which they must override each other
+ # starting from hard-coded defaults followed by build options and so on.
+ commands = compiler.compiler_args()
+
+ copt_proxy = target.get_options()
+ # First, the trivial ones that are impossible to override.
+ #
+ # Add -nostdinc/-nostdinc++ if needed; can't be overridden
+ commands += self.get_no_stdlib_args(target, compiler)
+ # Add things like /NOLOGO or -pipe; usually can't be overridden
+ commands += compiler.get_always_args()
+ # Only add warning-flags by default if the buildtype enables it, and if
+ # we weren't explicitly asked to not emit warnings (for Vala, f.ex)
+ if no_warn_args:
+ commands += compiler.get_no_warn_args()
+ else:
+ # warning_level is a string, but mypy can't determine that
+ commands += compiler.get_warn_args(T.cast('str', target.get_option(OptionKey('warning_level'))))
+ # Add -Werror if werror=true is set in the build options set on the
+ # command-line or default_options inside project(). This only sets the
+ # action to be done for warnings if/when they are emitted, so it's ok
+ # to set it after get_no_warn_args() or get_warn_args().
+ if target.get_option(OptionKey('werror')):
+ commands += compiler.get_werror_args()
+ # Add compile args for c_* or cpp_* build options set on the
+ # command-line or default_options inside project().
+ commands += compiler.get_option_compile_args(copt_proxy)
+
+ # Add buildtype args: optimization level, debugging, etc.
+ buildtype = target.get_option(OptionKey('buildtype'))
+ assert isinstance(buildtype, str), 'for mypy'
+ commands += compiler.get_buildtype_args(buildtype)
+
+ optimization = target.get_option(OptionKey('optimization'))
+ assert isinstance(optimization, str), 'for mypy'
+ commands += compiler.get_optimization_args(optimization)
+
+ debug = target.get_option(OptionKey('debug'))
+ assert isinstance(debug, bool), 'for mypy'
+ commands += compiler.get_debug_args(debug)
+
+ # Add compile args added using add_project_arguments()
+ commands += self.build.get_project_args(compiler, target.subproject, target.for_machine)
+ # Add compile args added using add_global_arguments()
+ # These override per-project arguments
+ commands += self.build.get_global_args(compiler, target.for_machine)
+ # Compile args added from the env: CFLAGS/CXXFLAGS, etc, or the cross
+ # file. We want these to override all the defaults, but not the
+ # per-target compile args.
+ commands += self.environment.coredata.get_external_args(target.for_machine, compiler.get_language())
+ # Using both /Z7 or /ZI and /Zi at the same times produces a compiler warning.
+ # We do not add /Z7 or /ZI by default. If it is being used it is because the user has explicitly enabled it.
+ # /Zi needs to be removed in that case to avoid cl's warning to that effect (D9025 : overriding '/Zi' with '/ZI')
+ if ('/Zi' in commands) and (('/ZI' in commands) or ('/Z7' in commands)):
+ commands.remove('/Zi')
+ # Always set -fPIC for shared libraries
+ if isinstance(target, build.SharedLibrary):
+ commands += compiler.get_pic_args()
+ # Set -fPIC for static libraries by default unless explicitly disabled
+ if isinstance(target, build.StaticLibrary) and target.pic:
+ commands += compiler.get_pic_args()
+ elif isinstance(target, (build.StaticLibrary, build.Executable)) and target.pie:
+ commands += compiler.get_pie_args()
+ # Add compile args needed to find external dependencies. Link args are
+ # added while generating the link command.
+ # NOTE: We must preserve the order in which external deps are
+ # specified, so we reverse the list before iterating over it.
+ for dep in reversed(target.get_external_deps()):
+ if not dep.found():
+ continue
+
+ if compiler.language == 'vala':
+ if isinstance(dep, dependencies.PkgConfigDependency):
+ if dep.name == 'glib-2.0' and dep.version_reqs is not None:
+ for req in dep.version_reqs:
+ if req.startswith(('>=', '==')):
+ commands += ['--target-glib', req[2:]]
+ break
+ commands += ['--pkg', dep.name]
+ elif isinstance(dep, dependencies.ExternalLibrary):
+ commands += dep.get_link_args('vala')
+ else:
+ commands += compiler.get_dependency_compile_args(dep)
+ # Qt needs -fPIC for executables
+ # XXX: We should move to -fPIC for all executables
+ if isinstance(target, build.Executable):
+ commands += dep.get_exe_args(compiler)
+ # For 'automagic' deps: Boost and GTest. Also dependency('threads').
+ # pkg-config puts the thread flags itself via `Cflags:`
+ # Fortran requires extra include directives.
+ if compiler.language == 'fortran':
+ for lt in chain(target.link_targets, target.link_whole_targets):
+ priv_dir = self.get_target_private_dir(lt)
+ commands += compiler.get_include_args(priv_dir, False)
+ return commands
+
+ def build_target_link_arguments(self, compiler: 'Compiler', deps: T.List[build.Target]) -> T.List[str]:
+ args: T.List[str] = []
+ for d in deps:
+ if not d.is_linkable_target():
+ raise RuntimeError(f'Tried to link with a non-library target "{d.get_basename()}".')
+ arg = self.get_target_filename_for_linking(d)
+ if not arg:
+ continue
+ if compiler.get_language() == 'd':
+ arg = '-Wl,' + arg
+ else:
+ arg = compiler.get_linker_lib_prefix() + arg
+ args.append(arg)
+ return args
+
+ def get_mingw_extra_paths(self, target: build.BuildTarget) -> T.List[str]:
+ paths: OrderedSet[str] = OrderedSet()
+ # The cross bindir
+ root = self.environment.properties[target.for_machine].get_root()
+ if root:
+ paths.add(os.path.join(root, 'bin'))
+ # The toolchain bindir
+ sys_root = self.environment.properties[target.for_machine].get_sys_root()
+ if sys_root:
+ paths.add(os.path.join(sys_root, 'bin'))
+ # Get program and library dirs from all target compilers
+ if isinstance(target, build.BuildTarget):
+ for cc in target.compilers.values():
+ paths.update(cc.get_program_dirs(self.environment))
+ paths.update(cc.get_library_dirs(self.environment))
+ return list(paths)
+
+ def determine_windows_extra_paths(
+ self, target: T.Union[build.BuildTarget, build.CustomTarget, programs.ExternalProgram, mesonlib.File, str],
+ extra_bdeps: T.Sequence[T.Union[build.BuildTarget, build.CustomTarget]]) -> T.List[str]:
+ """On Windows there is no such thing as an rpath.
+
+ We must determine all locations of DLLs that this exe
+ links to and return them so they can be used in unit
+ tests.
+ """
+ result: T.Set[str] = set()
+ prospectives: T.Set[build.BuildTargetTypes] = set()
+ if isinstance(target, build.BuildTarget):
+ prospectives.update(target.get_transitive_link_deps())
+ # External deps
+ for deppath in self.rpaths_for_non_system_absolute_shared_libraries(target, exclude_system=False):
+ result.add(os.path.normpath(os.path.join(self.environment.get_build_dir(), deppath)))
+ for bdep in extra_bdeps:
+ prospectives.add(bdep)
+ if isinstance(bdep, build.BuildTarget):
+ prospectives.update(bdep.get_transitive_link_deps())
+ # Internal deps
+ for ld in prospectives:
+ dirseg = os.path.join(self.environment.get_build_dir(), self.get_target_dir(ld))
+ result.add(dirseg)
+ if (isinstance(target, build.BuildTarget) and
+ not self.environment.machines.matches_build_machine(target.for_machine)):
+ result.update(self.get_mingw_extra_paths(target))
+ return list(result)
+
+ def write_benchmark_file(self, datafile: T.BinaryIO) -> None:
+ self.write_test_serialisation(self.build.get_benchmarks(), datafile)
+
+ def write_test_file(self, datafile: T.BinaryIO) -> None:
+ self.write_test_serialisation(self.build.get_tests(), datafile)
+
+ def create_test_serialisation(self, tests: T.List['Test']) -> T.List[TestSerialisation]:
+ arr: T.List[TestSerialisation] = []
+ for t in sorted(tests, key=lambda tst: -1 * tst.priority):
+ exe = t.get_exe()
+ if isinstance(exe, programs.ExternalProgram):
+ cmd = exe.get_command()
+ else:
+ cmd = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(exe))]
+ if isinstance(exe, (build.BuildTarget, programs.ExternalProgram)):
+ test_for_machine = exe.for_machine
+ else:
+ # E.g. an external verifier or simulator program run on a generated executable.
+ # Can always be run without a wrapper.
+ test_for_machine = MachineChoice.BUILD
+
+ # we allow passing compiled executables to tests, which may be cross built.
+ # We need to consider these as well when considering whether the target is cross or not.
+ for a in t.cmd_args:
+ if isinstance(a, build.BuildTarget):
+ if a.for_machine is MachineChoice.HOST:
+ test_for_machine = MachineChoice.HOST
+ break
+
+ is_cross = self.environment.is_cross_build(test_for_machine)
+ exe_wrapper = self.environment.get_exe_wrapper()
+ machine = self.environment.machines[exe.for_machine]
+ if machine.is_windows() or machine.is_cygwin():
+ extra_bdeps: T.List[T.Union[build.BuildTarget, build.CustomTarget]] = []
+ if isinstance(exe, build.CustomTarget):
+ extra_bdeps = list(exe.get_transitive_build_target_deps())
+ extra_paths = self.determine_windows_extra_paths(exe, extra_bdeps)
+ else:
+ extra_paths = []
+
+ cmd_args: T.List[str] = []
+ depends: T.Set[build.Target] = set(t.depends)
+ if isinstance(exe, build.Target):
+ depends.add(exe)
+ for a in t.cmd_args:
+ if isinstance(a, build.Target):
+ depends.add(a)
+ elif isinstance(a, build.CustomTargetIndex):
+ depends.add(a.target)
+ if isinstance(a, build.BuildTarget):
+ extra_paths += self.determine_windows_extra_paths(a, [])
+
+ if isinstance(a, mesonlib.File):
+ a = os.path.join(self.environment.get_build_dir(), a.rel_to_builddir(self.build_to_src))
+ cmd_args.append(a)
+ elif isinstance(a, str):
+ cmd_args.append(a)
+ elif isinstance(a, (build.Target, build.CustomTargetIndex)):
+ cmd_args.extend(self.construct_target_rel_paths(a, t.workdir))
+ else:
+ raise MesonException('Bad object in test command.')
+
+ t_env = copy.deepcopy(t.env)
+ if not machine.is_windows() and not machine.is_cygwin() and not machine.is_darwin():
+ ld_lib_path: T.Set[str] = set()
+ for d in depends:
+ if isinstance(d, build.BuildTarget):
+ for l in d.get_all_link_deps():
+ if isinstance(l, build.SharedLibrary):
+ ld_lib_path.add(os.path.join(self.environment.get_build_dir(), l.get_subdir()))
+ if ld_lib_path:
+ t_env.prepend('LD_LIBRARY_PATH', list(ld_lib_path), ':')
+
+ ts = TestSerialisation(t.get_name(), t.project_name, t.suite, cmd, is_cross,
+ exe_wrapper, self.environment.need_exe_wrapper(),
+ t.is_parallel, cmd_args, t_env,
+ t.should_fail, t.timeout, t.workdir,
+ extra_paths, t.protocol, t.priority,
+ isinstance(exe, build.Target),
+ isinstance(exe, build.Executable),
+ [x.get_id() for x in depends],
+ self.environment.coredata.version,
+ t.verbose)
+ arr.append(ts)
+ return arr
+
+ def write_test_serialisation(self, tests: T.List['Test'], datafile: T.BinaryIO) -> None:
+ pickle.dump(self.create_test_serialisation(tests), datafile)
+
+ def construct_target_rel_paths(self, t: T.Union[build.Target, build.CustomTargetIndex], workdir: T.Optional[str]) -> T.List[str]:
+ target_dir = self.get_target_dir(t)
+ # ensure that test executables can be run when passed as arguments
+ if isinstance(t, build.Executable) and workdir is None:
+ target_dir = target_dir or '.'
+
+ if isinstance(t, build.BuildTarget):
+ outputs = [t.get_filename()]
+ else:
+ assert isinstance(t, (build.CustomTarget, build.CustomTargetIndex))
+ outputs = t.get_outputs()
+
+ outputs = [os.path.join(target_dir, x) for x in outputs]
+ if workdir is not None:
+ assert os.path.isabs(workdir)
+ outputs = [os.path.join(self.environment.get_build_dir(), x) for x in outputs]
+ outputs = [os.path.relpath(x, workdir) for x in outputs]
+ return outputs
+
+ def generate_depmf_install(self, d: InstallData) -> None:
+ if self.build.dep_manifest_name is None:
+ return
+ ifilename = os.path.join(self.environment.get_build_dir(), 'depmf.json')
+ ofilename = os.path.join(self.environment.get_prefix(), self.build.dep_manifest_name)
+ out_name = os.path.join('{prefix}', self.build.dep_manifest_name)
+ mfobj = {'type': 'dependency manifest', 'version': '1.0',
+ 'projects': {k: v.to_json() for k, v in self.build.dep_manifest.items()}}
+ with open(ifilename, 'w', encoding='utf-8') as f:
+ f.write(json.dumps(mfobj))
+ # Copy file from, to, and with mode unchanged
+ d.data.append(InstallDataBase(ifilename, ofilename, out_name, None, '',
+ tag='devel', data_type='depmf'))
+
+ def get_regen_filelist(self) -> T.List[str]:
+ '''List of all files whose alteration means that the build
+ definition needs to be regenerated.'''
+ deps = OrderedSet([str(Path(self.build_to_src) / df)
+ for df in self.interpreter.get_build_def_files()])
+ if self.environment.is_cross_build():
+ deps.update(self.environment.coredata.cross_files)
+ deps.update(self.environment.coredata.config_files)
+ deps.add('meson-private/coredata.dat')
+ self.check_clock_skew(deps)
+ return list(deps)
+
+ def generate_regen_info(self) -> None:
+ deps = self.get_regen_filelist()
+ regeninfo = RegenInfo(self.environment.get_source_dir(),
+ self.environment.get_build_dir(),
+ deps)
+ filename = os.path.join(self.environment.get_scratch_dir(),
+ 'regeninfo.dump')
+ with open(filename, 'wb') as f:
+ pickle.dump(regeninfo, f)
+
+ def check_clock_skew(self, file_list: T.Iterable[str]) -> None:
+ # If a file that leads to reconfiguration has a time
+ # stamp in the future, it will trigger an eternal reconfigure
+ # loop.
+ import time
+ now = time.time()
+ for f in file_list:
+ absf = os.path.join(self.environment.get_build_dir(), f)
+ ftime = os.path.getmtime(absf)
+ delta = ftime - now
+ # On Windows disk time stamps sometimes point
+ # to the future by a minuscule amount, less than
+ # 0.001 seconds. I don't know why.
+ if delta > 0.001:
+ raise MesonException(f'Clock skew detected. File {absf} has a time stamp {delta:.4f}s in the future.')
+
+ def build_target_to_cmd_array(self, bt: T.Union[build.BuildTarget, programs.ExternalProgram]) -> T.List[str]:
+ if isinstance(bt, build.BuildTarget):
+ arr = [os.path.join(self.environment.get_build_dir(), self.get_target_filename(bt))]
+ else:
+ arr = bt.get_command()
+ return arr
+
+ def replace_extra_args(self, args: T.List[str], genlist: 'build.GeneratedList') -> T.List[str]:
+ final_args: T.List[str] = []
+ for a in args:
+ if a == '@EXTRA_ARGS@':
+ final_args += genlist.get_extra_args()
+ else:
+ final_args.append(a)
+ return final_args
+
+ def replace_outputs(self, args: T.List[str], private_dir: str, output_list: T.List[str]) -> T.List[str]:
+ newargs: T.List[str] = []
+ regex = re.compile(r'@OUTPUT(\d+)@')
+ for arg in args:
+ m = regex.search(arg)
+ while m is not None:
+ index = int(m.group(1))
+ src = f'@OUTPUT{index}@'
+ arg = arg.replace(src, os.path.join(private_dir, output_list[index]))
+ m = regex.search(arg)
+ newargs.append(arg)
+ return newargs
+
+ def get_build_by_default_targets(self) -> 'T.OrderedDict[str, T.Union[build.BuildTarget, build.CustomTarget]]':
+ result: 'T.OrderedDict[str, T.Union[build.BuildTarget, build.CustomTarget]]' = OrderedDict()
+ # Get all build and custom targets that must be built by default
+ for name, b in self.build.get_targets().items():
+ if b.build_by_default:
+ result[name] = b
+ return result
+
+ def get_testlike_targets(self, benchmark: bool = False) -> T.OrderedDict[str, T.Union[build.BuildTarget, build.CustomTarget]]:
+ result: T.OrderedDict[str, T.Union[build.BuildTarget, build.CustomTarget]] = OrderedDict()
+ targets = self.build.get_benchmarks() if benchmark else self.build.get_tests()
+ for t in targets:
+ exe = t.exe
+ if isinstance(exe, (build.CustomTarget, build.BuildTarget)):
+ result[exe.get_id()] = exe
+ for arg in t.cmd_args:
+ if not isinstance(arg, (build.CustomTarget, build.BuildTarget)):
+ continue
+ result[arg.get_id()] = arg
+ for dep in t.depends:
+ assert isinstance(dep, (build.CustomTarget, build.BuildTarget))
+ result[dep.get_id()] = dep
+ return result
+
+ @lru_cache(maxsize=None)
+ def get_custom_target_provided_by_generated_source(self, generated_source: build.CustomTarget) -> 'ImmutableListProtocol[str]':
+ libs: T.List[str] = []
+ for f in generated_source.get_outputs():
+ if self.environment.is_library(f):
+ libs.append(os.path.join(self.get_target_dir(generated_source), f))
+ return libs
+
+ @lru_cache(maxsize=None)
+ def get_custom_target_provided_libraries(self, target: T.Union[build.BuildTarget, build.CustomTarget]) -> 'ImmutableListProtocol[str]':
+ libs: T.List[str] = []
+ for t in target.get_generated_sources():
+ if not isinstance(t, build.CustomTarget):
+ continue
+ libs.extend(self.get_custom_target_provided_by_generated_source(t))
+ return libs
+
+ def get_custom_target_sources(self, target: build.CustomTarget) -> T.List[str]:
+ '''
+ Custom target sources can be of various object types; strings, File,
+ BuildTarget, even other CustomTargets.
+ Returns the path to them relative to the build root directory.
+ '''
+ srcs: T.List[str] = []
+ for i in target.get_sources():
+ if isinstance(i, str):
+ fname = [os.path.join(self.build_to_src, target.subdir, i)]
+ elif isinstance(i, build.BuildTarget):
+ fname = [self.get_target_filename(i)]
+ elif isinstance(i, (build.CustomTarget, build.CustomTargetIndex)):
+ fname = [os.path.join(self.get_custom_target_output_dir(i), p) for p in i.get_outputs()]
+ elif isinstance(i, build.GeneratedList):
+ fname = [os.path.join(self.get_target_private_dir(target), p) for p in i.get_outputs()]
+ elif isinstance(i, build.ExtractedObjects):
+ fname = self.determine_ext_objs(i)
+ elif isinstance(i, programs.ExternalProgram):
+ assert i.found(), "This shouldn't be possible"
+ assert i.path is not None, 'for mypy'
+ fname = [i.path]
+ else:
+ fname = [i.rel_to_builddir(self.build_to_src)]
+ if target.absolute_paths:
+ fname = [os.path.join(self.environment.get_build_dir(), f) for f in fname]
+ srcs += fname
+ return srcs
+
+ def get_custom_target_depend_files(self, target: build.CustomTarget, absolute_paths: bool = False) -> T.List[str]:
+ deps: T.List[str] = []
+ for i in target.depend_files:
+ if isinstance(i, mesonlib.File):
+ if absolute_paths:
+ deps.append(i.absolute_path(self.environment.get_source_dir(),
+ self.environment.get_build_dir()))
+ else:
+ deps.append(i.rel_to_builddir(self.build_to_src))
+ else:
+ if absolute_paths:
+ deps.append(os.path.join(self.environment.get_source_dir(), target.subdir, i))
+ else:
+ deps.append(os.path.join(self.build_to_src, target.subdir, i))
+ return deps
+
+ def get_custom_target_output_dir(self, target: T.Union[build.Target, build.CustomTargetIndex]) -> str:
+ # The XCode backend is special. A target foo/bar does
+ # not go to ${BUILDDIR}/foo/bar but instead to
+ # ${BUILDDIR}/${BUILDTYPE}/foo/bar.
+ # Currently we set the include dir to be the former,
+ # and not the latter. Thus we need this extra customisation
+ # point. If in the future we make include dirs et al match
+ # ${BUILDDIR}/${BUILDTYPE} instead, this becomes unnecessary.
+ return self.get_target_dir(target)
+
+ @lru_cache(maxsize=None)
+ def get_normpath_target(self, source: str) -> str:
+ return os.path.normpath(source)
+
+ def get_custom_target_dirs(self, target: build.CustomTarget, compiler: 'Compiler', *,
+ absolute_path: bool = False) -> T.List[str]:
+ custom_target_include_dirs: T.List[str] = []
+ for i in target.get_generated_sources():
+ # Generator output goes into the target private dir which is
+ # already in the include paths list. Only custom targets have their
+ # own target build dir.
+ if not isinstance(i, (build.CustomTarget, build.CustomTargetIndex)):
+ continue
+ idir = self.get_normpath_target(self.get_custom_target_output_dir(i))
+ if not idir:
+ idir = '.'
+ if absolute_path:
+ idir = os.path.join(self.environment.get_build_dir(), idir)
+ if idir not in custom_target_include_dirs:
+ custom_target_include_dirs.append(idir)
+ return custom_target_include_dirs
+
+ def get_custom_target_dir_include_args(
+ self, target: build.CustomTarget, compiler: 'Compiler', *,
+ absolute_path: bool = False) -> T.List[str]:
+ incs: T.List[str] = []
+ for i in self.get_custom_target_dirs(target, compiler, absolute_path=absolute_path):
+ incs += compiler.get_include_args(i, False)
+ return incs
+
+ def eval_custom_target_command(
+ self, target: build.CustomTarget, absolute_outputs: bool = False) -> \
+ T.Tuple[T.List[str], T.List[str], T.List[str]]:
+ # We want the outputs to be absolute only when using the VS backend
+ # XXX: Maybe allow the vs backend to use relative paths too?
+ source_root = self.build_to_src
+ build_root = '.'
+ outdir = self.get_custom_target_output_dir(target)
+ if absolute_outputs:
+ source_root = self.environment.get_source_dir()
+ build_root = self.environment.get_build_dir()
+ outdir = os.path.join(self.environment.get_build_dir(), outdir)
+ outputs = [os.path.join(outdir, i) for i in target.get_outputs()]
+ inputs = self.get_custom_target_sources(target)
+ # Evaluate the command list
+ cmd: T.List[str] = []
+ for i in target.command:
+ if isinstance(i, build.BuildTarget):
+ cmd += self.build_target_to_cmd_array(i)
+ continue
+ elif isinstance(i, build.CustomTarget):
+ # GIR scanner will attempt to execute this binary but
+ # it assumes that it is in path, so always give it a full path.
+ tmp = i.get_outputs()[0]
+ i = os.path.join(self.get_custom_target_output_dir(i), tmp)
+ elif isinstance(i, mesonlib.File):
+ i = i.rel_to_builddir(self.build_to_src)
+ if target.absolute_paths or absolute_outputs:
+ i = os.path.join(self.environment.get_build_dir(), i)
+ # FIXME: str types are blindly added ignoring 'target.absolute_paths'
+ # because we can't know if they refer to a file or just a string
+ elif isinstance(i, str):
+ if '@SOURCE_ROOT@' in i:
+ i = i.replace('@SOURCE_ROOT@', source_root)
+ if '@BUILD_ROOT@' in i:
+ i = i.replace('@BUILD_ROOT@', build_root)
+ if '@CURRENT_SOURCE_DIR@' in i:
+ i = i.replace('@CURRENT_SOURCE_DIR@', os.path.join(source_root, target.subdir))
+ if '@DEPFILE@' in i:
+ if target.depfile is None:
+ msg = f'Custom target {target.name!r} has @DEPFILE@ but no depfile ' \
+ 'keyword argument.'
+ raise MesonException(msg)
+ dfilename = os.path.join(outdir, target.depfile)
+ i = i.replace('@DEPFILE@', dfilename)
+ if '@PRIVATE_DIR@' in i:
+ if target.absolute_paths:
+ pdir = self.get_target_private_dir_abs(target)
+ else:
+ pdir = self.get_target_private_dir(target)
+ i = i.replace('@PRIVATE_DIR@', pdir)
+ else:
+ raise RuntimeError(f'Argument {i} is of unknown type {type(i)}')
+ cmd.append(i)
+ # Substitute the rest of the template strings
+ values = mesonlib.get_filenames_templates_dict(inputs, outputs)
+ cmd = mesonlib.substitute_values(cmd, values)
+ # This should not be necessary but removing it breaks
+ # building GStreamer on Windows. The underlying issue
+ # is problems with quoting backslashes on Windows
+ # which is the seventh circle of hell. The downside is
+ # that this breaks custom targets whose command lines
+ # have backslashes. If you try to fix this be sure to
+ # check that it does not break GST.
+ #
+ # The bug causes file paths such as c:\foo to get escaped
+ # into c:\\foo.
+ #
+ # Unfortunately we have not been able to come up with an
+ # isolated test case for this so unless you manage to come up
+ # with one, the only way is to test the building with Gst's
+ # setup. Note this in your MR or ping us and we will get it
+ # fixed.
+ #
+ # https://github.com/mesonbuild/meson/pull/737
+ cmd = [i.replace('\\', '/') for i in cmd]
+ return inputs, outputs, cmd
+
+ def get_run_target_env(self, target: build.RunTarget) -> build.EnvironmentVariables:
+ env = target.env if target.env else build.EnvironmentVariables()
+ if target.default_env:
+ introspect_cmd = join_args(self.environment.get_build_command() + ['introspect'])
+ env.set('MESON_SOURCE_ROOT', [self.environment.get_source_dir()])
+ env.set('MESON_BUILD_ROOT', [self.environment.get_build_dir()])
+ env.set('MESON_SUBDIR', [target.subdir])
+ env.set('MESONINTROSPECT', [introspect_cmd])
+ return env
+
+ def run_postconf_scripts(self) -> None:
+ from ..scripts.meson_exe import run_exe
+ introspect_cmd = join_args(self.environment.get_build_command() + ['introspect'])
+ env = {'MESON_SOURCE_ROOT': self.environment.get_source_dir(),
+ 'MESON_BUILD_ROOT': self.environment.get_build_dir(),
+ 'MESONINTROSPECT': introspect_cmd,
+ }
+
+ for s in self.build.postconf_scripts:
+ name = ' '.join(s.cmd_args)
+ mlog.log(f'Running postconf script {name!r}')
+ run_exe(s, env)
+
+ @lru_cache(maxsize=1)
+ def create_install_data(self) -> InstallData:
+ strip_bin = self.environment.lookup_binary_entry(MachineChoice.HOST, 'strip')
+ if strip_bin is None:
+ if self.environment.is_cross_build():
+ mlog.warning('Cross file does not specify strip binary, result will not be stripped.')
+ else:
+ # TODO go through all candidates, like others
+ strip_bin = [detect.defaults['strip'][0]]
+
+ umask = self.environment.coredata.get_option(OptionKey('install_umask'))
+ assert isinstance(umask, (str, int)), 'for mypy'
+
+ d = InstallData(self.environment.get_source_dir(),
+ self.environment.get_build_dir(),
+ self.environment.get_prefix(),
+ self.environment.get_libdir(),
+ strip_bin,
+ umask,
+ self.environment.get_build_command() + ['introspect'],
+ self.environment.coredata.version)
+ self.generate_depmf_install(d)
+ self.generate_target_install(d)
+ self.generate_header_install(d)
+ self.generate_man_install(d)
+ self.generate_emptydir_install(d)
+ self.generate_data_install(d)
+ self.generate_symlink_install(d)
+ self.generate_custom_install_script(d)
+ self.generate_subdir_install(d)
+ return d
+
+ def create_install_data_files(self) -> None:
+ install_data_file = os.path.join(self.environment.get_scratch_dir(), 'install.dat')
+ with open(install_data_file, 'wb') as ofile:
+ pickle.dump(self.create_install_data(), ofile)
+
+ def guess_install_tag(self, fname: str, outdir: T.Optional[str] = None) -> T.Optional[str]:
+ prefix = self.environment.get_prefix()
+ bindir = Path(prefix, self.environment.get_bindir())
+ libdir = Path(prefix, self.environment.get_libdir())
+ incdir = Path(prefix, self.environment.get_includedir())
+ _ldir = self.environment.coredata.get_option(mesonlib.OptionKey('localedir'))
+ assert isinstance(_ldir, str), 'for mypy'
+ localedir = Path(prefix, _ldir)
+ dest_path = Path(prefix, outdir, Path(fname).name) if outdir else Path(prefix, fname)
+ if bindir in dest_path.parents:
+ return 'runtime'
+ elif libdir in dest_path.parents:
+ if dest_path.suffix in {'.a', '.pc'}:
+ return 'devel'
+ elif dest_path.suffix in {'.so', '.dll'}:
+ return 'runtime'
+ elif incdir in dest_path.parents:
+ return 'devel'
+ elif localedir in dest_path.parents:
+ return 'i18n'
+ elif 'installed-tests' in dest_path.parts:
+ return 'tests'
+ elif 'systemtap' in dest_path.parts:
+ return 'systemtap'
+ mlog.debug('Failed to guess install tag for', dest_path)
+ return None
+
+ def generate_target_install(self, d: InstallData) -> None:
+ for t in self.build.get_targets().values():
+ if not t.should_install():
+ continue
+ outdirs, install_dir_names, custom_install_dir = t.get_install_dir()
+ # Sanity-check the outputs and install_dirs
+ num_outdirs, num_out = len(outdirs), len(t.get_outputs())
+ if num_outdirs not in {1, num_out}:
+ m = 'Target {!r} has {} outputs: {!r}, but only {} "install_dir"s were found.\n' \
+ "Pass 'false' for outputs that should not be installed and 'true' for\n" \
+ 'using the default installation directory for an output.'
+ raise MesonException(m.format(t.name, num_out, t.get_outputs(), num_outdirs))
+ assert len(t.install_tag) == num_out
+ install_mode = t.get_custom_install_mode()
+ # because mypy gets confused type narrowing in lists
+ first_outdir = outdirs[0]
+ first_outdir_name = install_dir_names[0]
+
+ # Install the target output(s)
+ if isinstance(t, build.BuildTarget):
+ # In general, stripping static archives is tricky and full of pitfalls.
+ # Wholesale stripping of static archives with a command such as
+ #
+ # strip libfoo.a
+ #
+ # is broken, as GNU's strip will remove *every* symbol in a static
+ # archive. One solution to this nonintuitive behaviour would be
+ # to only strip local/debug symbols. Unfortunately, strip arguments
+ # are not specified by POSIX and therefore not portable. GNU's `-g`
+ # option (i.e. remove debug symbols) is equivalent to Apple's `-S`.
+ #
+ # TODO: Create GNUStrip/AppleStrip/etc. hierarchy for more
+ # fine-grained stripping of static archives.
+ can_strip = not isinstance(t, build.StaticLibrary)
+ should_strip = can_strip and t.get_option(OptionKey('strip'))
+ assert isinstance(should_strip, bool), 'for mypy'
+ # Install primary build output (library/executable/jar, etc)
+ # Done separately because of strip/aliases/rpath
+ if first_outdir is not False:
+ tag = t.install_tag[0] or ('devel' if isinstance(t, build.StaticLibrary) else 'runtime')
+ mappings = t.get_link_deps_mapping(d.prefix)
+ i = TargetInstallData(self.get_target_filename(t), first_outdir,
+ first_outdir_name,
+ should_strip, mappings, t.rpath_dirs_to_remove,
+ t.install_rpath, install_mode, t.subproject,
+ tag=tag, can_strip=can_strip)
+ d.targets.append(i)
+
+ for alias, to, tag in t.get_aliases():
+ alias = os.path.join(first_outdir, alias)
+ s = InstallSymlinkData(to, alias, first_outdir, t.subproject, tag, allow_missing=True)
+ d.symlinks.append(s)
+
+ if isinstance(t, (build.SharedLibrary, build.SharedModule, build.Executable)):
+ # On toolchains/platforms that use an import library for
+ # linking (separate from the shared library with all the
+ # code), we need to install that too (dll.a/.lib).
+ if t.get_import_filename():
+ if custom_install_dir:
+ # If the DLL is installed into a custom directory,
+ # install the import library into the same place so
+ # it doesn't go into a surprising place
+ implib_install_dir = first_outdir
+ else:
+ implib_install_dir = self.environment.get_import_lib_dir()
+ # Install the import library; may not exist for shared modules
+ i = TargetInstallData(self.get_target_filename_for_linking(t),
+ implib_install_dir, first_outdir_name,
+ False, {}, set(), '', install_mode,
+ t.subproject, optional=isinstance(t, build.SharedModule),
+ tag='devel')
+ d.targets.append(i)
+
+ if not should_strip and t.get_debug_filename():
+ debug_file = os.path.join(self.get_target_dir(t), t.get_debug_filename())
+ i = TargetInstallData(debug_file, first_outdir,
+ first_outdir_name,
+ False, {}, set(), '',
+ install_mode, t.subproject,
+ optional=True, tag='devel')
+ d.targets.append(i)
+ # Install secondary outputs. Only used for Vala right now.
+ if num_outdirs > 1:
+ for output, outdir, outdir_name, tag in zip(t.get_outputs()[1:], outdirs[1:], install_dir_names[1:], t.install_tag[1:]):
+ # User requested that we not install this output
+ if outdir is False:
+ continue
+ f = os.path.join(self.get_target_dir(t), output)
+ i = TargetInstallData(f, outdir, outdir_name, False, {}, set(), None,
+ install_mode, t.subproject,
+ tag=tag)
+ d.targets.append(i)
+ elif isinstance(t, build.CustomTarget):
+ # If only one install_dir is specified, assume that all
+ # outputs will be installed into it. This is for
+ # backwards-compatibility and because it makes sense to
+ # avoid repetition since this is a common use-case.
+ #
+ # To selectively install only some outputs, pass `false` as
+ # the install_dir for the corresponding output by index
+ #
+ # XXX: this wouldn't be needed if we just always matches outdirs
+ # to the length of outputs…
+ if num_outdirs == 1 and num_out > 1:
+ if first_outdir is not False:
+ for output, tag in zip(t.get_outputs(), t.install_tag):
+ tag = tag or self.guess_install_tag(output, first_outdir)
+ f = os.path.join(self.get_target_dir(t), output)
+ i = TargetInstallData(f, first_outdir, first_outdir_name,
+ False, {}, set(), None, install_mode,
+ t.subproject, optional=not t.build_by_default,
+ tag=tag)
+ d.targets.append(i)
+ else:
+ for output, outdir, outdir_name, tag in zip(t.get_outputs(), outdirs, install_dir_names, t.install_tag):
+ # User requested that we not install this output
+ if outdir is False:
+ continue
+ tag = tag or self.guess_install_tag(output, outdir)
+ f = os.path.join(self.get_target_dir(t), output)
+ i = TargetInstallData(f, outdir, outdir_name,
+ False, {}, set(), None, install_mode,
+ t.subproject, optional=not t.build_by_default,
+ tag=tag)
+ d.targets.append(i)
+
+ def generate_custom_install_script(self, d: InstallData) -> None:
+ d.install_scripts = self.build.install_scripts
+ for i in d.install_scripts:
+ if not i.tag:
+ mlog.debug('Failed to guess install tag for install script:', ' '.join(i.cmd_args))
+
+ def generate_header_install(self, d: InstallData) -> None:
+ incroot = self.environment.get_includedir()
+ headers = self.build.get_headers()
+
+ srcdir = self.environment.get_source_dir()
+ builddir = self.environment.get_build_dir()
+ for h in headers:
+ outdir = outdir_name = h.get_custom_install_dir()
+ if outdir is None:
+ subdir = h.get_install_subdir()
+ if subdir is None:
+ outdir = incroot
+ outdir_name = '{includedir}'
+ else:
+ outdir = os.path.join(incroot, subdir)
+ outdir_name = os.path.join('{includedir}', subdir)
+
+ for f in h.get_sources():
+ if not isinstance(f, File):
+ raise MesonException(f'Invalid header type {f!r} can\'t be installed')
+ abspath = f.absolute_path(srcdir, builddir)
+ i = InstallDataBase(abspath, outdir, outdir_name, h.get_custom_install_mode(), h.subproject, tag='devel')
+ d.headers.append(i)
+
+ def generate_man_install(self, d: InstallData) -> None:
+ manroot = self.environment.get_mandir()
+ man = self.build.get_man()
+ for m in man:
+ for f in m.get_sources():
+ num = f.split('.')[-1]
+ subdir = m.get_custom_install_dir()
+ if subdir is None:
+ if m.locale:
+ subdir = os.path.join('{mandir}', m.locale, 'man' + num)
+ else:
+ subdir = os.path.join('{mandir}', 'man' + num)
+ fname = f.fname
+ if m.locale: # strip locale from file name
+ fname = fname.replace(f'.{m.locale}', '')
+ srcabs = f.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir())
+ dstname = os.path.join(subdir, os.path.basename(fname))
+ dstabs = dstname.replace('{mandir}', manroot)
+ i = InstallDataBase(srcabs, dstabs, dstname, m.get_custom_install_mode(), m.subproject, tag='man')
+ d.man.append(i)
+
+ def generate_emptydir_install(self, d: InstallData) -> None:
+ emptydir: T.List[build.EmptyDir] = self.build.get_emptydir()
+ for e in emptydir:
+ tag = e.install_tag or self.guess_install_tag(e.path)
+ i = InstallEmptyDir(e.path, e.install_mode, e.subproject, tag)
+ d.emptydir.append(i)
+
+ def generate_data_install(self, d: InstallData) -> None:
+ data = self.build.get_data()
+ srcdir = self.environment.get_source_dir()
+ builddir = self.environment.get_build_dir()
+ for de in data:
+ assert isinstance(de, build.Data)
+ subdir = de.install_dir
+ subdir_name = de.install_dir_name
+ if not subdir:
+ subdir = os.path.join(self.environment.get_datadir(), self.interpreter.build.project_name)
+ subdir_name = os.path.join('{datadir}', self.interpreter.build.project_name)
+ for src_file, dst_name in zip(de.sources, de.rename):
+ assert isinstance(src_file, mesonlib.File)
+ dst_abs = os.path.join(subdir, dst_name)
+ dstdir_name = os.path.join(subdir_name, dst_name)
+ tag = de.install_tag or self.guess_install_tag(dst_abs)
+ i = InstallDataBase(src_file.absolute_path(srcdir, builddir), dst_abs, dstdir_name,
+ de.install_mode, de.subproject, tag=tag, data_type=de.data_type)
+ d.data.append(i)
+
+ def generate_symlink_install(self, d: InstallData) -> None:
+ links: T.List[build.SymlinkData] = self.build.get_symlinks()
+ for l in links:
+ assert isinstance(l, build.SymlinkData)
+ install_dir = l.install_dir
+ name_abs = os.path.join(install_dir, l.name)
+ tag = l.install_tag or self.guess_install_tag(name_abs)
+ s = InstallSymlinkData(l.target, name_abs, install_dir, l.subproject, tag)
+ d.symlinks.append(s)
+
+ def generate_subdir_install(self, d: InstallData) -> None:
+ for sd in self.build.get_install_subdirs():
+ if sd.from_source_dir:
+ from_dir = self.environment.get_source_dir()
+ else:
+ from_dir = self.environment.get_build_dir()
+ src_dir = os.path.join(from_dir,
+ sd.source_subdir,
+ sd.installable_subdir).rstrip('/')
+ dst_dir = os.path.join(self.environment.get_prefix(),
+ sd.install_dir)
+ dst_name = os.path.join('{prefix}', sd.install_dir)
+ if sd.install_dir != sd.install_dir_name:
+ dst_name = sd.install_dir_name
+ if not sd.strip_directory:
+ dst_dir = os.path.join(dst_dir, os.path.basename(src_dir))
+ dst_name = os.path.join(dst_name, os.path.basename(src_dir))
+ tag = sd.install_tag or self.guess_install_tag(os.path.join(sd.install_dir, 'dummy'))
+ i = SubdirInstallData(src_dir, dst_dir, dst_name, sd.install_mode, sd.exclude, sd.subproject, tag)
+ d.install_subdirs.append(i)
+
+ def get_introspection_data(self, target_id: str, target: build.Target) -> T.List['TargetIntrospectionData']:
+ '''
+ Returns a list of source dicts with the following format for a given target:
+ [
+ {
+ "language": "<LANG>",
+ "compiler": ["result", "of", "comp.get_exelist()"],
+ "parameters": ["list", "of", "compiler", "parameters],
+ "sources": ["list", "of", "all", "<LANG>", "source", "files"],
+ "generated_sources": ["list", "of", "generated", "source", "files"]
+ }
+ ]
+
+ This is a limited fallback / reference implementation. The backend should override this method.
+ '''
+ if isinstance(target, (build.CustomTarget, build.BuildTarget)):
+ source_list_raw = target.sources
+ source_list = []
+ for j in source_list_raw:
+ if isinstance(j, mesonlib.File):
+ source_list += [j.absolute_path(self.source_dir, self.build_dir)]
+ elif isinstance(j, str):
+ source_list += [os.path.join(self.source_dir, j)]
+ elif isinstance(j, (build.CustomTarget, build.BuildTarget)):
+ source_list += [os.path.join(self.build_dir, j.get_subdir(), o) for o in j.get_outputs()]
+ source_list = [os.path.normpath(s) for s in source_list]
+
+ compiler: T.List[str] = []
+ if isinstance(target, build.CustomTarget):
+ tmp_compiler = target.command
+ for j in tmp_compiler:
+ if isinstance(j, mesonlib.File):
+ compiler += [j.absolute_path(self.source_dir, self.build_dir)]
+ elif isinstance(j, str):
+ compiler += [j]
+ elif isinstance(j, (build.BuildTarget, build.CustomTarget)):
+ compiler += j.get_outputs()
+ else:
+ raise RuntimeError(f'Type "{type(j).__name__}" is not supported in get_introspection_data. This is a bug')
+
+ return [{
+ 'language': 'unknown',
+ 'compiler': compiler,
+ 'parameters': [],
+ 'sources': source_list,
+ 'generated_sources': []
+ }]
+
+ return []
+
+ def get_devenv(self) -> build.EnvironmentVariables:
+ env = build.EnvironmentVariables()
+ extra_paths = set()
+ library_paths = set()
+ build_machine = self.environment.machines[MachineChoice.BUILD]
+ host_machine = self.environment.machines[MachineChoice.HOST]
+ need_wine = not build_machine.is_windows() and host_machine.is_windows()
+ for t in self.build.get_targets().values():
+ in_default_dir = t.should_install() and not t.get_install_dir()[2]
+ if t.for_machine != MachineChoice.HOST or not in_default_dir:
+ continue
+ tdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(t))
+ if isinstance(t, build.Executable):
+ # Add binaries that are going to be installed in bindir into PATH
+ # so they get used by default instead of searching on system when
+ # in developer environment.
+ extra_paths.add(tdir)
+ if host_machine.is_windows() or host_machine.is_cygwin():
+ # On windows we cannot rely on rpath to run executables from build
+ # directory. We have to add in PATH the location of every DLL needed.
+ library_paths.update(self.determine_windows_extra_paths(t, []))
+ elif isinstance(t, build.SharedLibrary):
+ # Add libraries that are going to be installed in libdir into
+ # LD_LIBRARY_PATH. This allows running system applications using
+ # that library.
+ library_paths.add(tdir)
+ if need_wine:
+ # Executable paths should be in both PATH and WINEPATH.
+ # - Having them in PATH makes bash completion find it,
+ # and make running "foo.exe" find it when wine-binfmt is installed.
+ # - Having them in WINEPATH makes "wine foo.exe" find it.
+ library_paths.update(extra_paths)
+ if library_paths:
+ if need_wine:
+ env.prepend('WINEPATH', list(library_paths), separator=';')
+ elif host_machine.is_windows() or host_machine.is_cygwin():
+ extra_paths.update(library_paths)
+ elif host_machine.is_darwin():
+ env.prepend('DYLD_LIBRARY_PATH', list(library_paths))
+ else:
+ env.prepend('LD_LIBRARY_PATH', list(library_paths))
+ if extra_paths:
+ env.prepend('PATH', list(extra_paths))
+ return env
+
+ def compiler_to_generator(self, target: build.BuildTarget,
+ compiler: 'Compiler',
+ sources: _ALL_SOURCES_TYPE,
+ output_templ: str) -> build.GeneratedList:
+ '''
+ Some backends don't support custom compilers. This is a convenience
+ method to convert a Compiler to a Generator.
+ '''
+ exelist = compiler.get_exelist()
+ exe = programs.ExternalProgram(exelist[0])
+ args = exelist[1:]
+ # FIXME: There are many other args missing
+ commands = self.generate_basic_compiler_args(target, compiler)
+ commands += compiler.get_dependency_gen_args('@OUTPUT@', '@DEPFILE@')
+ commands += compiler.get_output_args('@OUTPUT@')
+ commands += compiler.get_compile_only_args() + ['@INPUT@']
+ commands += self.get_source_dir_include_args(target, compiler)
+ commands += self.get_build_dir_include_args(target, compiler)
+ generator = build.Generator(exe, args + commands.to_native(), [output_templ], depfile='@PLAINNAME@.d')
+ return generator.process_files(sources, self.interpreter)
+
+ def compile_target_to_generator(self, target: build.CompileTarget) -> build.GeneratedList:
+ all_sources = T.cast('_ALL_SOURCES_TYPE', target.sources) + T.cast('_ALL_SOURCES_TYPE', target.generated)
+ return self.compiler_to_generator(target, target.compiler, all_sources, target.output_templ)
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
new file mode 100644
index 0000000..3d6bfb9
--- /dev/null
+++ b/mesonbuild/backend/ninjabackend.py
@@ -0,0 +1,3623 @@
+# 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 OrderedDict
+from dataclasses import dataclass
+from enum import Enum, unique
+from functools import lru_cache
+from pathlib import PurePath, Path
+from textwrap import dedent
+import itertools
+import json
+import os
+import pickle
+import re
+import shlex
+import subprocess
+import typing as T
+
+from . import backends
+from .. import modules
+from ..modules import gnome
+from .. import environment, mesonlib
+from .. import build
+from .. import mlog
+from .. import compilers
+from ..arglist import CompilerArgs
+from ..compilers import Compiler
+from ..linkers import ArLinker, RSPFileSyntax
+from ..mesonlib import (
+ File, LibType, MachineChoice, MesonException, OrderedSet, PerMachine,
+ ProgressBar, quote_arg
+)
+from ..mesonlib import get_compiler_for_source, has_path_sep, OptionKey
+from .backends import CleanTrees
+from ..build import GeneratedList, InvalidArguments
+
+if T.TYPE_CHECKING:
+ from typing_extensions import Literal
+
+ from .._typing import ImmutableListProtocol
+ from ..build import ExtractedObjects
+ from ..interpreter import Interpreter
+ from ..linkers import DynamicLinker, StaticLinker
+ from ..compilers.cs import CsCompiler
+ from ..compilers.fortran import FortranCompiler
+
+ RUST_EDITIONS = Literal['2015', '2018', '2021']
+
+
+FORTRAN_INCLUDE_PAT = r"^\s*#?include\s*['\"](\w+\.\w+)['\"]"
+FORTRAN_MODULE_PAT = r"^\s*\bmodule\b\s+(\w+)\s*(?:!+.*)*$"
+FORTRAN_SUBMOD_PAT = r"^\s*\bsubmodule\b\s*\((\w+:?\w+)\)\s*(\w+)"
+FORTRAN_USE_PAT = r"^\s*use,?\s*(?:non_intrinsic)?\s*(?:::)?\s*(\w+)"
+
+def cmd_quote(s):
+ # see: https://docs.microsoft.com/en-us/windows/desktop/api/shellapi/nf-shellapi-commandlinetoargvw#remarks
+
+ # backslash escape any existing double quotes
+ # any existing backslashes preceding a quote are doubled
+ s = re.sub(r'(\\*)"', lambda m: '\\' * (len(m.group(1)) * 2 + 1) + '"', s)
+ # any terminal backslashes likewise need doubling
+ s = re.sub(r'(\\*)$', lambda m: '\\' * (len(m.group(1)) * 2), s)
+ # and double quote
+ s = f'"{s}"'
+
+ return s
+
+def gcc_rsp_quote(s):
+ # see: the function buildargv() in libiberty
+ #
+ # this differs from sh-quoting in that a backslash *always* escapes the
+ # following character, even inside single quotes.
+
+ s = s.replace('\\', '\\\\')
+
+ return shlex.quote(s)
+
+# How ninja executes command lines differs between Unix and Windows
+# (see https://ninja-build.org/manual.html#ref_rule_command)
+if mesonlib.is_windows():
+ quote_func = cmd_quote
+ execute_wrapper = ['cmd', '/c'] # unused
+ rmfile_prefix = ['del', '/f', '/s', '/q', '{}', '&&']
+else:
+ quote_func = quote_arg
+ execute_wrapper = []
+ rmfile_prefix = ['rm', '-f', '{}', '&&']
+
+
+def get_rsp_threshold():
+ '''Return a conservative estimate of the commandline size in bytes
+ above which a response file should be used. May be overridden for
+ debugging by setting environment variable MESON_RSP_THRESHOLD.'''
+
+ if mesonlib.is_windows():
+ # Usually 32k, but some projects might use cmd.exe,
+ # and that has a limit of 8k.
+ limit = 8192
+ else:
+ # On Linux, ninja always passes the commandline as a single
+ # big string to /bin/sh, and the kernel limits the size of a
+ # single argument; see MAX_ARG_STRLEN
+ limit = 131072
+ # Be conservative
+ limit = limit / 2
+ return int(os.environ.get('MESON_RSP_THRESHOLD', limit))
+
+# a conservative estimate of the command-line length limit
+rsp_threshold = get_rsp_threshold()
+
+# ninja variables whose value should remain unquoted. The value of these ninja
+# variables (or variables we use them in) is interpreted directly by ninja
+# (e.g. the value of the depfile variable is a pathname that ninja will read
+# from, etc.), so it must not be shell quoted.
+raw_names = {'DEPFILE_UNQUOTED', 'DESC', 'pool', 'description', 'targetdep', 'dyndep'}
+
+NINJA_QUOTE_BUILD_PAT = re.compile(r"[$ :\n]")
+NINJA_QUOTE_VAR_PAT = re.compile(r"[$ \n]")
+
+def ninja_quote(text: str, is_build_line=False) -> str:
+ if is_build_line:
+ quote_re = NINJA_QUOTE_BUILD_PAT
+ else:
+ quote_re = NINJA_QUOTE_VAR_PAT
+ # Fast path for when no quoting is necessary
+ if not quote_re.search(text):
+ return text
+ if '\n' in text:
+ errmsg = f'''Ninja does not support newlines in rules. The content was:
+
+{text}
+
+Please report this error with a test case to the Meson bug tracker.'''
+ raise MesonException(errmsg)
+ return quote_re.sub(r'$\g<0>', text)
+
+class TargetDependencyScannerInfo:
+ def __init__(self, private_dir: str, source2object: T.Dict[str, str]):
+ self.private_dir = private_dir
+ self.source2object = source2object
+
+@unique
+class Quoting(Enum):
+ both = 0
+ notShell = 1
+ notNinja = 2
+ none = 3
+
+class NinjaCommandArg:
+ def __init__(self, s, quoting = Quoting.both):
+ self.s = s
+ self.quoting = quoting
+
+ def __str__(self):
+ return self.s
+
+ @staticmethod
+ def list(l, q):
+ return [NinjaCommandArg(i, q) for i in l]
+
+class NinjaComment:
+ def __init__(self, comment):
+ self.comment = comment
+
+ def write(self, outfile):
+ for l in self.comment.split('\n'):
+ outfile.write('# ')
+ outfile.write(l)
+ outfile.write('\n')
+ outfile.write('\n')
+
+class NinjaRule:
+ def __init__(self, rule, command, args, description,
+ rspable = False, deps = None, depfile = None, extra = None,
+ rspfile_quote_style: RSPFileSyntax = RSPFileSyntax.GCC):
+
+ def strToCommandArg(c):
+ if isinstance(c, NinjaCommandArg):
+ return c
+
+ # deal with common cases here, so we don't have to explicitly
+ # annotate the required quoting everywhere
+ if c == '&&':
+ # shell constructs shouldn't be shell quoted
+ return NinjaCommandArg(c, Quoting.notShell)
+ if c.startswith('$'):
+ var = re.search(r'\$\{?(\w*)\}?', c).group(1)
+ if var not in raw_names:
+ # ninja variables shouldn't be ninja quoted, and their value
+ # is already shell quoted
+ return NinjaCommandArg(c, Quoting.none)
+ else:
+ # shell quote the use of ninja variables whose value must
+ # not be shell quoted (as it also used by ninja)
+ return NinjaCommandArg(c, Quoting.notNinja)
+
+ return NinjaCommandArg(c)
+
+ self.name = rule
+ self.command = [strToCommandArg(c) for c in command] # includes args which never go into a rspfile
+ self.args = [strToCommandArg(a) for a in args] # args which will go into a rspfile, if used
+ self.description = description
+ self.deps = deps # depstyle 'gcc' or 'msvc'
+ self.depfile = depfile
+ self.extra = extra
+ self.rspable = rspable # if a rspfile can be used
+ self.refcount = 0
+ self.rsprefcount = 0
+ self.rspfile_quote_style = rspfile_quote_style
+
+ if self.depfile == '$DEPFILE':
+ self.depfile += '_UNQUOTED'
+
+ @staticmethod
+ def _quoter(x, qf = quote_func):
+ if isinstance(x, NinjaCommandArg):
+ if x.quoting == Quoting.none:
+ return x.s
+ elif x.quoting == Quoting.notNinja:
+ return qf(x.s)
+ elif x.quoting == Quoting.notShell:
+ return ninja_quote(x.s)
+ # fallthrough
+ return ninja_quote(qf(str(x)))
+
+ def write(self, outfile):
+ if self.rspfile_quote_style is RSPFileSyntax.MSVC:
+ rspfile_quote_func = cmd_quote
+ else:
+ rspfile_quote_func = gcc_rsp_quote
+
+ def rule_iter():
+ if self.refcount:
+ yield ''
+ if self.rsprefcount:
+ yield '_RSP'
+
+ for rsp in rule_iter():
+ outfile.write(f'rule {self.name}{rsp}\n')
+ if rsp == '_RSP':
+ outfile.write(' command = {} @$out.rsp\n'.format(' '.join([self._quoter(x) for x in self.command])))
+ outfile.write(' rspfile = $out.rsp\n')
+ outfile.write(' rspfile_content = {}\n'.format(' '.join([self._quoter(x, rspfile_quote_func) for x in self.args])))
+ else:
+ outfile.write(' command = {}\n'.format(' '.join([self._quoter(x) for x in self.command + self.args])))
+ if self.deps:
+ outfile.write(f' deps = {self.deps}\n')
+ if self.depfile:
+ outfile.write(f' depfile = {self.depfile}\n')
+ outfile.write(f' description = {self.description}\n')
+ if self.extra:
+ for l in self.extra.split('\n'):
+ outfile.write(' ')
+ outfile.write(l)
+ outfile.write('\n')
+ outfile.write('\n')
+
+ def length_estimate(self, infiles, outfiles, elems):
+ # determine variables
+ # this order of actions only approximates ninja's scoping rules, as
+ # documented at: https://ninja-build.org/manual.html#ref_scope
+ ninja_vars = {}
+ for e in elems:
+ (name, value) = e
+ ninja_vars[name] = value
+ ninja_vars['deps'] = self.deps
+ ninja_vars['depfile'] = self.depfile
+ ninja_vars['in'] = infiles
+ ninja_vars['out'] = outfiles
+
+ # expand variables in command
+ command = ' '.join([self._quoter(x) for x in self.command + self.args])
+ estimate = len(command)
+ for m in re.finditer(r'(\${\w+}|\$\w+)?[^$]*', command):
+ if m.start(1) != -1:
+ estimate -= m.end(1) - m.start(1) + 1
+ chunk = m.group(1)
+ if chunk[1] == '{':
+ chunk = chunk[2:-1]
+ else:
+ chunk = chunk[1:]
+ chunk = ninja_vars.get(chunk, []) # undefined ninja variables are empty
+ estimate += len(' '.join(chunk))
+
+ # determine command length
+ return estimate
+
+class NinjaBuildElement:
+ def __init__(self, all_outputs, outfilenames, rulename, infilenames, implicit_outs=None):
+ self.implicit_outfilenames = implicit_outs or []
+ if isinstance(outfilenames, str):
+ self.outfilenames = [outfilenames]
+ else:
+ self.outfilenames = outfilenames
+ assert isinstance(rulename, str)
+ self.rulename = rulename
+ if isinstance(infilenames, str):
+ self.infilenames = [infilenames]
+ else:
+ self.infilenames = infilenames
+ self.deps = OrderedSet()
+ self.orderdeps = OrderedSet()
+ self.elems = []
+ self.all_outputs = all_outputs
+ self.output_errors = ''
+
+ def add_dep(self, dep):
+ if isinstance(dep, list):
+ self.deps.update(dep)
+ else:
+ self.deps.add(dep)
+
+ def add_orderdep(self, dep):
+ if isinstance(dep, list):
+ self.orderdeps.update(dep)
+ else:
+ self.orderdeps.add(dep)
+
+ def add_item(self, name, elems):
+ # Always convert from GCC-style argument naming to the naming used by the
+ # current compiler. Also filter system include paths, deduplicate, etc.
+ if isinstance(elems, CompilerArgs):
+ elems = elems.to_native()
+ if isinstance(elems, str):
+ elems = [elems]
+ self.elems.append((name, elems))
+
+ if name == 'DEPFILE':
+ self.elems.append((name + '_UNQUOTED', elems))
+
+ def _should_use_rspfile(self):
+ # 'phony' is a rule built-in to ninja
+ if self.rulename == 'phony':
+ return False
+
+ if not self.rule.rspable:
+ return False
+
+ infilenames = ' '.join([ninja_quote(i, True) for i in self.infilenames])
+ outfilenames = ' '.join([ninja_quote(i, True) for i in self.outfilenames])
+
+ return self.rule.length_estimate(infilenames,
+ outfilenames,
+ self.elems) >= rsp_threshold
+
+ def count_rule_references(self):
+ if self.rulename != 'phony':
+ if self._should_use_rspfile():
+ self.rule.rsprefcount += 1
+ else:
+ self.rule.refcount += 1
+
+ def write(self, outfile):
+ if self.output_errors:
+ raise MesonException(self.output_errors)
+ ins = ' '.join([ninja_quote(i, True) for i in self.infilenames])
+ outs = ' '.join([ninja_quote(i, True) for i in self.outfilenames])
+ implicit_outs = ' '.join([ninja_quote(i, True) for i in self.implicit_outfilenames])
+ if implicit_outs:
+ implicit_outs = ' | ' + implicit_outs
+ use_rspfile = self._should_use_rspfile()
+ if use_rspfile:
+ rulename = self.rulename + '_RSP'
+ mlog.debug(f'Command line for building {self.outfilenames} is long, using a response file')
+ else:
+ rulename = self.rulename
+ line = f'build {outs}{implicit_outs}: {rulename} {ins}'
+ if len(self.deps) > 0:
+ line += ' | ' + ' '.join([ninja_quote(x, True) for x in sorted(self.deps)])
+ if len(self.orderdeps) > 0:
+ line += ' || ' + ' '.join([ninja_quote(x, True) for x in sorted(self.orderdeps)])
+ line += '\n'
+ # This is the only way I could find to make this work on all
+ # platforms including Windows command shell. Slash is a dir separator
+ # on Windows, too, so all characters are unambiguous and, more importantly,
+ # do not require quoting, unless explicitly specified, which is necessary for
+ # the csc compiler.
+ line = line.replace('\\', '/')
+ if mesonlib.is_windows():
+ # Support network paths as backslash, otherwise they are interpreted as
+ # arguments for compile/link commands when using MSVC
+ line = ' '.join(
+ (l.replace('//', '\\\\', 1) if l.startswith('//') else l)
+ for l in line.split(' ')
+ )
+ outfile.write(line)
+
+ if use_rspfile:
+ if self.rule.rspfile_quote_style is RSPFileSyntax.MSVC:
+ qf = cmd_quote
+ else:
+ qf = gcc_rsp_quote
+ else:
+ qf = quote_func
+
+ for e in self.elems:
+ (name, elems) = e
+ should_quote = name not in raw_names
+ line = f' {name} = '
+ newelems = []
+ for i in elems:
+ if not should_quote or i == '&&': # Hackety hack hack
+ newelems.append(ninja_quote(i))
+ else:
+ newelems.append(ninja_quote(qf(i)))
+ line += ' '.join(newelems)
+ line += '\n'
+ outfile.write(line)
+ outfile.write('\n')
+
+ def check_outputs(self):
+ for n in self.outfilenames:
+ if n in self.all_outputs:
+ self.output_errors = f'Multiple producers for Ninja target "{n}". Please rename your targets.'
+ self.all_outputs[n] = True
+
+@dataclass
+class RustDep:
+
+ name: str
+
+ # equal to the order value of the `RustCrate`
+ crate: int
+
+ def to_json(self) -> T.Dict[str, object]:
+ return {
+ "crate": self.crate,
+ "name": self.name,
+ }
+
+@dataclass
+class RustCrate:
+
+ # When the json file is written, the list of Crates will be sorted by this
+ # value
+ order: int
+
+ display_name: str
+ root_module: str
+ edition: RUST_EDITIONS
+ deps: T.List[RustDep]
+ cfg: T.List[str]
+ is_proc_macro: bool
+
+ # This is set to True for members of this project, and False for all
+ # subprojects
+ is_workspace_member: bool
+ proc_macro_dylib_path: T.Optional[str] = None
+
+ def to_json(self) -> T.Dict[str, object]:
+ ret: T.Dict[str, object] = {
+ "display_name": self.display_name,
+ "root_module": self.root_module,
+ "edition": self.edition,
+ "cfg": self.cfg,
+ "is_proc_macro": self.is_proc_macro,
+ "deps": [d.to_json() for d in self.deps],
+ }
+
+ if self.is_proc_macro:
+ assert self.proc_macro_dylib_path is not None, "This shouldn't happen"
+ ret["proc_macro_dylib_path"] = self.proc_macro_dylib_path
+
+ return ret
+
+
+class NinjaBackend(backends.Backend):
+
+ def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional[Interpreter]):
+ super().__init__(build, interpreter)
+ self.name = 'ninja'
+ self.ninja_filename = 'build.ninja'
+ self.fortran_deps = {}
+ self.all_outputs = {}
+ self.introspection_data = {}
+ self.created_llvm_ir_rule = PerMachine(False, False)
+ self.rust_crates: T.Dict[str, RustCrate] = {}
+
+ def create_phony_target(self, all_outputs, dummy_outfile, rulename, phony_infilename, implicit_outs=None):
+ '''
+ We need to use aliases for targets that might be used as directory
+ names to workaround a Ninja bug that breaks `ninja -t clean`.
+ This is used for 'reserved' targets such as 'test', 'install',
+ 'benchmark', etc, and also for RunTargets.
+ https://github.com/mesonbuild/meson/issues/1644
+ '''
+ if dummy_outfile.startswith('meson-internal__'):
+ raise AssertionError(f'Invalid usage of create_phony_target with {dummy_outfile!r}')
+
+ to_name = f'meson-internal__{dummy_outfile}'
+ elem = NinjaBuildElement(all_outputs, dummy_outfile, 'phony', to_name)
+ self.add_build(elem)
+
+ return NinjaBuildElement(all_outputs, to_name, rulename, phony_infilename, implicit_outs)
+
+ def detect_vs_dep_prefix(self, tempfilename):
+ '''VS writes its dependency in a locale dependent format.
+ Detect the search prefix to use.'''
+ # TODO don't hard-code host
+ for compiler in self.environment.coredata.compilers.host.values():
+ # Have to detect the dependency format
+
+ # IFort / masm on windows is MSVC like, but doesn't have /showincludes
+ if compiler.language in {'fortran', 'masm'}:
+ continue
+ if compiler.id == 'pgi' and mesonlib.is_windows():
+ # for the purpose of this function, PGI doesn't act enough like MSVC
+ return open(tempfilename, 'a', encoding='utf-8')
+ if compiler.get_argument_syntax() == 'msvc':
+ break
+ else:
+ # None of our compilers are MSVC, we're done.
+ return open(tempfilename, 'a', encoding='utf-8')
+ filebase = 'incdetect.' + compilers.lang_suffixes[compiler.language][0]
+ filename = os.path.join(self.environment.get_scratch_dir(),
+ filebase)
+ with open(filename, 'w', encoding='utf-8') as f:
+ f.write(dedent('''\
+ #include<stdio.h>
+ int dummy;
+ '''))
+
+ # The output of cl dependency information is language
+ # and locale dependent. Any attempt at converting it to
+ # Python strings leads to failure. We _must_ do this detection
+ # in raw byte mode and write the result in raw bytes.
+ pc = subprocess.Popen(compiler.get_exelist() +
+ ['/showIncludes', '/c', filebase],
+ cwd=self.environment.get_scratch_dir(),
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ (stdout, stderr) = pc.communicate()
+
+ # We want to match 'Note: including file: ' in the line
+ # 'Note: including file: d:\MyDir\include\stdio.h', however
+ # different locales have different messages with a different
+ # number of colons. Match up to the the drive name 'd:\'.
+ # When used in cross compilation, the path separator is a
+ # forward slash rather than a backslash so handle both; i.e.
+ # the path is /MyDir/include/stdio.h.
+ # With certain cross compilation wrappings of MSVC, the paths
+ # use backslashes, but without the leading drive name, so
+ # allow the path to start with any path separator, i.e.
+ # \MyDir\include\stdio.h.
+ matchre = re.compile(rb"^(.*\s)([a-zA-Z]:[\\/]|[\\\/]).*stdio.h$")
+
+ def detect_prefix(out):
+ for line in re.split(rb'\r?\n', out):
+ match = matchre.match(line)
+ if match:
+ with open(tempfilename, 'ab') as binfile:
+ binfile.write(b'msvc_deps_prefix = ' + match.group(1) + b'\n')
+ return open(tempfilename, 'a', encoding='utf-8')
+ return None
+
+ # Some cl wrappers (e.g. Squish Coco) output dependency info
+ # to stderr rather than stdout
+ result = detect_prefix(stdout) or detect_prefix(stderr)
+ if result:
+ return result
+
+ raise MesonException(f'Could not determine vs dep dependency prefix string. output: {stderr} {stdout}')
+
+ def generate(self):
+ ninja = environment.detect_ninja_command_and_version(log=True)
+ if self.build.need_vsenv:
+ builddir = Path(self.environment.get_build_dir())
+ try:
+ # For prettier printing, reduce to a relative path. If
+ # impossible (e.g., because builddir and cwd are on
+ # different Windows drives), skip and use the full path.
+ builddir = builddir.relative_to(Path.cwd())
+ except ValueError:
+ pass
+ meson_command = mesonlib.join_args(mesonlib.get_meson_command())
+ mlog.log()
+ mlog.log('Visual Studio environment is needed to run Ninja. It is recommended to use Meson wrapper:')
+ mlog.log(f'{meson_command} compile -C {builddir}')
+ if ninja is None:
+ raise MesonException('Could not detect Ninja v1.8.2 or newer')
+ (self.ninja_command, self.ninja_version) = ninja
+ outfilename = os.path.join(self.environment.get_build_dir(), self.ninja_filename)
+ tempfilename = outfilename + '~'
+ with open(tempfilename, 'w', encoding='utf-8') as outfile:
+ outfile.write(f'# This is the build file for project "{self.build.get_project()}"\n')
+ outfile.write('# It is autogenerated by the Meson build system.\n')
+ outfile.write('# Do not edit by hand.\n\n')
+ outfile.write('ninja_required_version = 1.8.2\n\n')
+
+ num_pools = self.environment.coredata.options[OptionKey('backend_max_links')].value
+ if num_pools > 0:
+ outfile.write(f'''pool link_pool
+ depth = {num_pools}
+
+''')
+
+ with self.detect_vs_dep_prefix(tempfilename) as outfile:
+ self.generate_rules()
+
+ self.build_elements = []
+ self.generate_phony()
+ self.add_build_comment(NinjaComment('Build rules for targets'))
+ for t in ProgressBar(self.build.get_targets().values(), desc='Generating targets'):
+ self.generate_target(t)
+ self.add_build_comment(NinjaComment('Test rules'))
+ self.generate_tests()
+ self.add_build_comment(NinjaComment('Install rules'))
+ self.generate_install()
+ self.generate_dist()
+ key = OptionKey('b_coverage')
+ if (key in self.environment.coredata.options and
+ self.environment.coredata.options[key].value):
+ gcovr_exe, gcovr_version, lcov_exe, genhtml_exe, _ = environment.find_coverage_tools()
+ if gcovr_exe or (lcov_exe and genhtml_exe):
+ self.add_build_comment(NinjaComment('Coverage rules'))
+ self.generate_coverage_rules(gcovr_exe, gcovr_version)
+ else:
+ # FIXME: since we explicitly opted in, should this be an error?
+ # The docs just say these targets will be created "if possible".
+ mlog.warning('Need gcovr or lcov/genhtml to generate any coverage reports')
+ self.add_build_comment(NinjaComment('Suffix'))
+ self.generate_utils()
+ self.generate_ending()
+
+ self.write_rules(outfile)
+ self.write_builds(outfile)
+
+ default = 'default all\n\n'
+ outfile.write(default)
+ # Only overwrite the old build file after the new one has been
+ # fully created.
+ os.replace(tempfilename, outfilename)
+ mlog.cmd_ci_include(outfilename) # For CI debugging
+ # Refresh Ninja's caches. https://github.com/ninja-build/ninja/pull/1685
+ if mesonlib.version_compare(self.ninja_version, '>=1.10.0') and os.path.exists('.ninja_deps'):
+ subprocess.call(self.ninja_command + ['-t', 'restat'])
+ subprocess.call(self.ninja_command + ['-t', 'cleandead'])
+ self.generate_compdb()
+ self.generate_rust_project_json()
+
+ def generate_rust_project_json(self) -> None:
+ """Generate a rust-analyzer compatible rust-project.json file."""
+ if not self.rust_crates:
+ return
+ with open(os.path.join(self.environment.get_build_dir(), 'rust-project.json'),
+ 'w', encoding='utf-8') as f:
+ json.dump(
+ {
+ "sysroot_src": os.path.join(self.environment.coredata.compilers.host['rust'].get_sysroot(),
+ 'lib/rustlib/src/rust/library/'),
+ "crates": [c.to_json() for c in self.rust_crates.values()],
+ },
+ f, indent=4)
+
+ # http://clang.llvm.org/docs/JSONCompilationDatabase.html
+ def generate_compdb(self):
+ rules = []
+ # TODO: Rather than an explicit list here, rules could be marked in the
+ # rule store as being wanted in compdb
+ for for_machine in MachineChoice:
+ for compiler in self.environment.coredata.compilers[for_machine].values():
+ rules += [f"{rule}{ext}" for rule in [self.compiler_to_rule_name(compiler)]
+ for ext in ['', '_RSP']]
+ rules += [f"{rule}{ext}" for rule in [self.compiler_to_pch_rule_name(compiler)]
+ for ext in ['', '_RSP']]
+ compdb_options = ['-x'] if mesonlib.version_compare(self.ninja_version, '>=1.9') else []
+ ninja_compdb = self.ninja_command + ['-t', 'compdb'] + compdb_options + rules
+ builddir = self.environment.get_build_dir()
+ try:
+ jsondb = subprocess.check_output(ninja_compdb, cwd=builddir)
+ with open(os.path.join(builddir, 'compile_commands.json'), 'wb') as f:
+ f.write(jsondb)
+ except Exception:
+ mlog.warning('Could not create compilation database.', fatal=False)
+
+ # Get all generated headers. Any source file might need them so
+ # we need to add an order dependency to them.
+ def get_generated_headers(self, target):
+ if hasattr(target, 'cached_generated_headers'):
+ return target.cached_generated_headers
+ header_deps = []
+ # XXX: Why don't we add deps to CustomTarget headers here?
+ for genlist in target.get_generated_sources():
+ if isinstance(genlist, (build.CustomTarget, build.CustomTargetIndex)):
+ continue
+ for src in genlist.get_outputs():
+ if self.environment.is_header(src):
+ header_deps.append(self.get_target_generated_dir(target, genlist, src))
+ if 'vala' in target.compilers and not isinstance(target, build.Executable):
+ vala_header = File.from_built_file(self.get_target_dir(target), target.vala_header)
+ header_deps.append(vala_header)
+ # Recurse and find generated headers
+ for dep in itertools.chain(target.link_targets, target.link_whole_targets):
+ if isinstance(dep, (build.StaticLibrary, build.SharedLibrary)):
+ header_deps += self.get_generated_headers(dep)
+ target.cached_generated_headers = header_deps
+ return header_deps
+
+ def get_target_generated_sources(self, target: build.BuildTarget) -> T.MutableMapping[str, File]:
+ """
+ Returns a dictionary with the keys being the path to the file
+ (relative to the build directory) and the value being the File object
+ representing the same path.
+ """
+ srcs: T.MutableMapping[str, File] = OrderedDict()
+ for gensrc in target.get_generated_sources():
+ for s in gensrc.get_outputs():
+ rel_src = self.get_target_generated_dir(target, gensrc, s)
+ srcs[rel_src] = File.from_built_relative(rel_src)
+ return srcs
+
+ def get_target_sources(self, target: build.BuildTarget) -> T.MutableMapping[str, File]:
+ srcs: T.MutableMapping[str, File] = OrderedDict()
+ for s in target.get_sources():
+ # BuildTarget sources are always mesonlib.File files which are
+ # either in the source root, or generated with configure_file and
+ # in the build root
+ if not isinstance(s, File):
+ raise InvalidArguments(f'All sources in target {s!r} must be of type mesonlib.File')
+ f = s.rel_to_builddir(self.build_to_src)
+ srcs[f] = s
+ return srcs
+
+ def get_target_source_can_unity(self, target, source):
+ if isinstance(source, File):
+ source = source.fname
+ if self.environment.is_llvm_ir(source) or \
+ self.environment.is_assembly(source):
+ return False
+ suffix = os.path.splitext(source)[1][1:].lower()
+ for lang in backends.LANGS_CANT_UNITY:
+ if lang not in target.compilers:
+ continue
+ if suffix in target.compilers[lang].file_suffixes:
+ return False
+ return True
+
+ def create_target_source_introspection(self, target: build.Target, comp: compilers.Compiler, parameters, sources, generated_sources):
+ '''
+ Adds the source file introspection information for a language of a target
+
+ Internal introspection storage formart:
+ self.introspection_data = {
+ '<target ID>': {
+ <id tuple>: {
+ 'language: 'lang',
+ 'compiler': ['comp', 'exe', 'list'],
+ 'parameters': ['UNIQUE', 'parameter', 'list'],
+ 'sources': [],
+ 'generated_sources': [],
+ }
+ }
+ }
+ '''
+ tid = target.get_id()
+ lang = comp.get_language()
+ tgt = self.introspection_data[tid]
+ # Find an existing entry or create a new one
+ id_hash = (lang, tuple(parameters))
+ src_block = tgt.get(id_hash, None)
+ if src_block is None:
+ # Convert parameters
+ if isinstance(parameters, CompilerArgs):
+ parameters = parameters.to_native(copy=True)
+ parameters = comp.compute_parameters_with_absolute_paths(parameters, self.build_dir)
+ # The new entry
+ src_block = {
+ 'language': lang,
+ 'compiler': comp.get_exelist(),
+ 'parameters': parameters,
+ 'sources': [],
+ 'generated_sources': [],
+ }
+ tgt[id_hash] = src_block
+ # Make source files absolute
+ sources = [x.absolute_path(self.source_dir, self.build_dir) if isinstance(x, File) else os.path.normpath(os.path.join(self.build_dir, x))
+ for x in sources]
+ generated_sources = [x.absolute_path(self.source_dir, self.build_dir) if isinstance(x, File) else os.path.normpath(os.path.join(self.build_dir, x))
+ for x in generated_sources]
+ # Add the source files
+ src_block['sources'] += sources
+ src_block['generated_sources'] += generated_sources
+
+ def generate_target(self, target):
+ try:
+ if isinstance(target, build.BuildTarget):
+ os.makedirs(self.get_target_private_dir_abs(target))
+ except FileExistsError:
+ pass
+ if isinstance(target, build.CustomTarget):
+ self.generate_custom_target(target)
+ if isinstance(target, build.RunTarget):
+ self.generate_run_target(target)
+ compiled_sources = []
+ source2object = {}
+ name = target.get_id()
+ if name in self.processed_targets:
+ return
+ self.processed_targets.add(name)
+ # Initialize an empty introspection source list
+ self.introspection_data[name] = {}
+ # Generate rules for all dependency targets
+ self.process_target_dependencies(target)
+
+ self.generate_shlib_aliases(target, self.get_target_dir(target))
+
+ # If target uses a language that cannot link to C objects,
+ # just generate for that language and return.
+ if isinstance(target, build.Jar):
+ self.generate_jar_target(target)
+ return
+ if target.uses_rust():
+ self.generate_rust_target(target)
+ return
+ if 'cs' in target.compilers:
+ self.generate_cs_target(target)
+ return
+ if 'swift' in target.compilers:
+ self.generate_swift_target(target)
+ return
+
+ # Pre-existing target C/C++ sources to be built; dict of full path to
+ # source relative to build root and the original File object.
+ target_sources: T.MutableMapping[str, File]
+
+ # GeneratedList and CustomTarget sources to be built; dict of the full
+ # path to source relative to build root and the generating target/list
+ generated_sources: T.MutableMapping[str, File]
+
+ # List of sources that have been transpiled from a DSL (like Vala) into
+ # a language that is haneled below, such as C or C++
+ transpiled_sources: T.List[str]
+
+ if 'vala' in target.compilers:
+ # Sources consumed by valac are filtered out. These only contain
+ # C/C++ sources, objects, generated libs, and unknown sources now.
+ target_sources, generated_sources, \
+ transpiled_sources = self.generate_vala_compile(target)
+ elif 'cython' in target.compilers:
+ target_sources, generated_sources, \
+ transpiled_sources = self.generate_cython_transpile(target)
+ else:
+ target_sources = self.get_target_sources(target)
+ generated_sources = self.get_target_generated_sources(target)
+ transpiled_sources = []
+ self.scan_fortran_module_outputs(target)
+ # Generate rules for GeneratedLists
+ self.generate_generator_list_rules(target)
+
+ # Generate rules for building the remaining source files in this target
+ outname = self.get_target_filename(target)
+ obj_list = []
+ is_unity = target.is_unity
+ header_deps = []
+ unity_src = []
+ unity_deps = [] # Generated sources that must be built before compiling a Unity target.
+ header_deps += self.get_generated_headers(target)
+
+ if is_unity:
+ # Warn about incompatible sources if a unity build is enabled
+ langs = set(target.compilers.keys())
+ langs_cant = langs.intersection(backends.LANGS_CANT_UNITY)
+ if langs_cant:
+ langs_are = langs = ', '.join(langs_cant).upper()
+ langs_are += ' are' if len(langs_cant) > 1 else ' is'
+ msg = f'{langs_are} not supported in Unity builds yet, so {langs} ' \
+ f'sources in the {target.name!r} target will be compiled normally'
+ mlog.log(mlog.red('FIXME'), msg)
+
+ # Get a list of all generated headers that will be needed while building
+ # this target's sources (generated sources and pre-existing sources).
+ # This will be set as dependencies of all the target's sources. At the
+ # same time, also deal with generated sources that need to be compiled.
+ generated_source_files = []
+ for rel_src in generated_sources.keys():
+ raw_src = File.from_built_relative(rel_src)
+ if self.environment.is_source(rel_src):
+ if is_unity and self.get_target_source_can_unity(target, rel_src):
+ unity_deps.append(raw_src)
+ abs_src = os.path.join(self.environment.get_build_dir(), rel_src)
+ unity_src.append(abs_src)
+ else:
+ generated_source_files.append(raw_src)
+ elif self.environment.is_object(rel_src):
+ obj_list.append(rel_src)
+ elif self.environment.is_library(rel_src) or modules.is_module_library(rel_src):
+ pass
+ else:
+ # Assume anything not specifically a source file is a header. This is because
+ # people generate files with weird suffixes (.inc, .fh) that they then include
+ # in their source files.
+ header_deps.append(raw_src)
+
+ # For D language, the object of generated source files are added
+ # as order only deps because other files may depend on them
+ d_generated_deps = []
+
+ # These are the generated source files that need to be built for use by
+ # this target. We create the Ninja build file elements for this here
+ # because we need `header_deps` to be fully generated in the above loop.
+ for src in generated_source_files:
+ if self.environment.is_llvm_ir(src):
+ o, s = self.generate_llvm_ir_compile(target, src)
+ else:
+ o, s = self.generate_single_compile(target, src, True,
+ order_deps=header_deps)
+ compiled_sources.append(s)
+ source2object[s] = o
+ obj_list.append(o)
+ if s.split('.')[-1] in compilers.lang_suffixes['d']:
+ d_generated_deps.append(o)
+
+ use_pch = self.environment.coredata.options.get(OptionKey('b_pch'))
+ if use_pch and target.has_pch():
+ pch_objects = self.generate_pch(target, header_deps=header_deps)
+ else:
+ pch_objects = []
+
+ o, od = self.flatten_object_list(target)
+ obj_targets = [t for t in od if t.uses_fortran()]
+ obj_list.extend(o)
+
+ fortran_order_deps = [self.get_target_filename(t) for t in obj_targets]
+ fortran_inc_args: T.List[str] = []
+ if target.uses_fortran():
+ fortran_inc_args = mesonlib.listify([target.compilers['fortran'].get_include_args(
+ self.get_target_private_dir(t), is_system=False) for t in obj_targets])
+
+ # Generate compilation targets for C sources generated from Vala
+ # sources. This can be extended to other $LANG->C compilers later if
+ # necessary. This needs to be separate for at least Vala
+ #
+ # Do not try to unity-build the generated c files from vala, as these
+ # often contain duplicate symbols and will fail to compile properly
+ vala_generated_source_files = []
+ for src in transpiled_sources:
+ raw_src = File.from_built_relative(src)
+ # Generated targets are ordered deps because the must exist
+ # before the sources compiling them are used. After the first
+ # compile we get precise dependency info from dep files.
+ # This should work in all cases. If it does not, then just
+ # move them from orderdeps to proper deps.
+ if self.environment.is_header(src):
+ header_deps.append(raw_src)
+ else:
+ # We gather all these and generate compile rules below
+ # after `header_deps` (above) is fully generated
+ vala_generated_source_files.append(raw_src)
+ for src in vala_generated_source_files:
+ # Passing 'vala' here signifies that we want the compile
+ # arguments to be specialized for C code generated by
+ # valac. For instance, no warnings should be emitted.
+ o, s = self.generate_single_compile(target, src, 'vala', [], header_deps)
+ obj_list.append(o)
+
+ # Generate compile targets for all the pre-existing sources for this target
+ for src in target_sources.values():
+ if not self.environment.is_header(src):
+ if self.environment.is_llvm_ir(src):
+ o, s = self.generate_llvm_ir_compile(target, src)
+ obj_list.append(o)
+ elif is_unity and self.get_target_source_can_unity(target, src):
+ abs_src = os.path.join(self.environment.get_build_dir(),
+ src.rel_to_builddir(self.build_to_src))
+ unity_src.append(abs_src)
+ else:
+ o, s = self.generate_single_compile(target, src, False, [],
+ header_deps + d_generated_deps + fortran_order_deps,
+ fortran_inc_args)
+ obj_list.append(o)
+ compiled_sources.append(s)
+ source2object[s] = o
+
+ if is_unity:
+ for src in self.generate_unity_files(target, unity_src):
+ o, s = self.generate_single_compile(target, src, True, unity_deps + header_deps + d_generated_deps,
+ fortran_order_deps, fortran_inc_args)
+ obj_list.append(o)
+ compiled_sources.append(s)
+ source2object[s] = o
+ if isinstance(target, build.CompileTarget):
+ # Skip the link stage for this special type of target
+ return
+ linker, stdlib_args = self.determine_linker_and_stdlib_args(target)
+ if isinstance(target, build.StaticLibrary) and target.prelink:
+ final_obj_list = self.generate_prelink(target, obj_list)
+ else:
+ final_obj_list = obj_list
+ elem = self.generate_link(target, outname, final_obj_list, linker, pch_objects, stdlib_args=stdlib_args)
+ self.generate_dependency_scan_target(target, compiled_sources, source2object, generated_source_files, fortran_order_deps)
+ self.add_build(elem)
+
+ def should_use_dyndeps_for_target(self, target: 'build.BuildTarget') -> bool:
+ if mesonlib.version_compare(self.ninja_version, '<1.10.0'):
+ return False
+ if 'fortran' in target.compilers:
+ return True
+ if 'cpp' not in target.compilers:
+ return False
+ if '-fmodules-ts' in target.extra_args.get('cpp', []):
+ return True
+ # Currently only the preview version of Visual Studio is supported.
+ cpp = target.compilers['cpp']
+ if cpp.get_id() != 'msvc':
+ return False
+ cppversion = self.environment.coredata.options[OptionKey('std', machine=target.for_machine, lang='cpp')].value
+ if cppversion not in ('latest', 'c++latest', 'vc++latest'):
+ return False
+ if not mesonlib.current_vs_supports_modules():
+ return False
+ if mesonlib.version_compare(cpp.version, '<19.28.28617'):
+ return False
+ return True
+
+ def generate_dependency_scan_target(self, target, compiled_sources, source2object, generated_source_files: T.List[mesonlib.File],
+ object_deps: T.List[str]) -> None:
+ if not self.should_use_dyndeps_for_target(target):
+ return
+ depscan_file = self.get_dep_scan_file_for(target)
+ pickle_base = target.name + '.dat'
+ pickle_file = os.path.join(self.get_target_private_dir(target), pickle_base).replace('\\', '/')
+ pickle_abs = os.path.join(self.get_target_private_dir_abs(target), pickle_base).replace('\\', '/')
+ json_abs = os.path.join(self.get_target_private_dir_abs(target), f'{target.name}-deps.json').replace('\\', '/')
+ rule_name = 'depscan'
+ scan_sources = self.select_sources_to_scan(compiled_sources)
+
+ # Dump the sources as a json list. This avoids potential probllems where
+ # the number of sources passed to depscan exceedes the limit imposed by
+ # the OS.
+ with open(json_abs, 'w', encoding='utf-8') as f:
+ json.dump(scan_sources, f)
+ elem = NinjaBuildElement(self.all_outputs, depscan_file, rule_name, json_abs)
+ elem.add_item('picklefile', pickle_file)
+ # Add any generated outputs to the order deps of the scan target, so
+ # that those sources are present
+ for g in generated_source_files:
+ elem.orderdeps.add(g.relative_name())
+ elem.orderdeps.update(object_deps)
+ scaninfo = TargetDependencyScannerInfo(self.get_target_private_dir(target), source2object)
+ with open(pickle_abs, 'wb') as p:
+ pickle.dump(scaninfo, p)
+ self.add_build(elem)
+
+ def select_sources_to_scan(self, compiled_sources):
+ # in practice pick up C++ and Fortran files. If some other language
+ # requires scanning (possibly Java to deal with inner class files)
+ # then add them here.
+ all_suffixes = set(compilers.lang_suffixes['cpp']) | set(compilers.lang_suffixes['fortran'])
+ selected_sources = []
+ for source in compiled_sources:
+ ext = os.path.splitext(source)[1][1:]
+ if ext != 'C':
+ ext = ext.lower()
+ if ext in all_suffixes:
+ selected_sources.append(source)
+ return selected_sources
+
+ def process_target_dependencies(self, target):
+ for t in target.get_dependencies():
+ if t.get_id() not in self.processed_targets:
+ self.generate_target(t)
+
+ def custom_target_generator_inputs(self, target):
+ for s in target.sources:
+ if isinstance(s, build.GeneratedList):
+ self.generate_genlist_for_target(s, target)
+
+ def unwrap_dep_list(self, target):
+ deps = []
+ for i in target.get_dependencies():
+ # FIXME, should not grab element at zero but rather expand all.
+ if isinstance(i, list):
+ i = i[0]
+ # Add a dependency on all the outputs of this target
+ for output in i.get_outputs():
+ deps.append(os.path.join(self.get_target_dir(i), output))
+ return deps
+
+ def generate_custom_target(self, target):
+ self.custom_target_generator_inputs(target)
+ (srcs, ofilenames, cmd) = self.eval_custom_target_command(target)
+ deps = self.unwrap_dep_list(target)
+ deps += self.get_custom_target_depend_files(target)
+ if target.build_always_stale:
+ deps.append('PHONY')
+ if target.depfile is None:
+ rulename = 'CUSTOM_COMMAND'
+ else:
+ rulename = 'CUSTOM_COMMAND_DEP'
+ elem = NinjaBuildElement(self.all_outputs, ofilenames, rulename, srcs)
+ elem.add_dep(deps)
+ for d in target.extra_depends:
+ # Add a dependency on all the outputs of this target
+ for output in d.get_outputs():
+ elem.add_dep(os.path.join(self.get_target_dir(d), output))
+
+ cmd, reason = self.as_meson_exe_cmdline(target.command[0], cmd[1:],
+ extra_bdeps=target.get_transitive_build_target_deps(),
+ capture=ofilenames[0] if target.capture else None,
+ feed=srcs[0] if target.feed else None,
+ env=target.env,
+ verbose=target.console)
+ if reason:
+ cmd_type = f' (wrapped by meson {reason})'
+ else:
+ cmd_type = ''
+ if target.depfile is not None:
+ depfile = target.get_dep_outname(elem.infilenames)
+ rel_dfile = os.path.join(self.get_target_dir(target), depfile)
+ abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target))
+ os.makedirs(abs_pdir, exist_ok=True)
+ elem.add_item('DEPFILE', rel_dfile)
+ if target.console:
+ elem.add_item('pool', 'console')
+ full_name = Path(target.subdir, target.name).as_posix()
+ elem.add_item('COMMAND', cmd)
+ elem.add_item('description', f'Generating {full_name} with a custom command{cmd_type}')
+ self.add_build(elem)
+ self.processed_targets.add(target.get_id())
+
+ def build_run_target_name(self, target):
+ if target.subproject != '':
+ subproject_prefix = f'{target.subproject}@@'
+ else:
+ subproject_prefix = ''
+ return f'{subproject_prefix}{target.name}'
+
+ def generate_run_target(self, target: build.RunTarget):
+ target_name = self.build_run_target_name(target)
+ if not target.command:
+ # This is an alias target, it has no command, it just depends on
+ # other targets.
+ elem = NinjaBuildElement(self.all_outputs, target_name, 'phony', [])
+ else:
+ target_env = self.get_run_target_env(target)
+ _, _, cmd = self.eval_custom_target_command(target)
+ meson_exe_cmd, reason = self.as_meson_exe_cmdline(target.command[0], cmd[1:],
+ env=target_env,
+ verbose=True)
+ cmd_type = f' (wrapped by meson {reason})' if reason else ''
+ elem = self.create_phony_target(self.all_outputs, target_name, 'CUSTOM_COMMAND', [])
+ elem.add_item('COMMAND', meson_exe_cmd)
+ elem.add_item('description', f'Running external command {target.name}{cmd_type}')
+ elem.add_item('pool', 'console')
+ deps = self.unwrap_dep_list(target)
+ deps += self.get_custom_target_depend_files(target)
+ elem.add_dep(deps)
+ self.add_build(elem)
+ self.processed_targets.add(target.get_id())
+
+ def generate_coverage_command(self, elem, outputs):
+ targets = self.build.get_targets().values()
+ use_llvm_cov = False
+ for target in targets:
+ if not hasattr(target, 'compilers'):
+ continue
+ for compiler in target.compilers.values():
+ if compiler.get_id() == 'clang' and not compiler.info.is_darwin():
+ use_llvm_cov = True
+ break
+ elem.add_item('COMMAND', self.environment.get_build_command() +
+ ['--internal', 'coverage'] +
+ outputs +
+ [self.environment.get_source_dir(),
+ os.path.join(self.environment.get_source_dir(),
+ self.build.get_subproject_dir()),
+ self.environment.get_build_dir(),
+ self.environment.get_log_dir()] +
+ (['--use_llvm_cov'] if use_llvm_cov else []))
+
+ def generate_coverage_rules(self, gcovr_exe: T.Optional[str], gcovr_version: T.Optional[str]):
+ e = self.create_phony_target(self.all_outputs, 'coverage', 'CUSTOM_COMMAND', 'PHONY')
+ self.generate_coverage_command(e, [])
+ e.add_item('description', 'Generates coverage reports')
+ self.add_build(e)
+ self.generate_coverage_legacy_rules(gcovr_exe, gcovr_version)
+
+ def generate_coverage_legacy_rules(self, gcovr_exe: T.Optional[str], gcovr_version: T.Optional[str]):
+ e = self.create_phony_target(self.all_outputs, 'coverage-html', 'CUSTOM_COMMAND', 'PHONY')
+ self.generate_coverage_command(e, ['--html'])
+ e.add_item('description', 'Generates HTML coverage report')
+ self.add_build(e)
+
+ if gcovr_exe:
+ e = self.create_phony_target(self.all_outputs, 'coverage-xml', 'CUSTOM_COMMAND', 'PHONY')
+ self.generate_coverage_command(e, ['--xml'])
+ e.add_item('description', 'Generates XML coverage report')
+ self.add_build(e)
+
+ e = self.create_phony_target(self.all_outputs, 'coverage-text', 'CUSTOM_COMMAND', 'PHONY')
+ self.generate_coverage_command(e, ['--text'])
+ e.add_item('description', 'Generates text coverage report')
+ self.add_build(e)
+
+ if mesonlib.version_compare(gcovr_version, '>=4.2'):
+ e = self.create_phony_target(self.all_outputs, 'coverage-sonarqube', 'CUSTOM_COMMAND', 'PHONY')
+ self.generate_coverage_command(e, ['--sonarqube'])
+ e.add_item('description', 'Generates Sonarqube XML coverage report')
+ self.add_build(e)
+
+ def generate_install(self):
+ self.create_install_data_files()
+ elem = self.create_phony_target(self.all_outputs, 'install', 'CUSTOM_COMMAND', 'PHONY')
+ elem.add_dep('all')
+ elem.add_item('DESC', 'Installing files.')
+ elem.add_item('COMMAND', self.environment.get_build_command() + ['install', '--no-rebuild'])
+ elem.add_item('pool', 'console')
+ self.add_build(elem)
+
+ def generate_tests(self):
+ self.serialize_tests()
+ cmd = self.environment.get_build_command(True) + ['test', '--no-rebuild']
+ if not self.environment.coredata.get_option(OptionKey('stdsplit')):
+ cmd += ['--no-stdsplit']
+ if self.environment.coredata.get_option(OptionKey('errorlogs')):
+ cmd += ['--print-errorlogs']
+ elem = self.create_phony_target(self.all_outputs, 'test', 'CUSTOM_COMMAND', ['all', 'PHONY'])
+ elem.add_item('COMMAND', cmd)
+ elem.add_item('DESC', 'Running all tests.')
+ elem.add_item('pool', 'console')
+ self.add_build(elem)
+
+ # And then benchmarks.
+ cmd = self.environment.get_build_command(True) + [
+ 'test', '--benchmark', '--logbase',
+ 'benchmarklog', '--num-processes=1', '--no-rebuild']
+ elem = self.create_phony_target(self.all_outputs, 'benchmark', 'CUSTOM_COMMAND', ['all', 'PHONY'])
+ elem.add_item('COMMAND', cmd)
+ elem.add_item('DESC', 'Running benchmark suite.')
+ elem.add_item('pool', 'console')
+ self.add_build(elem)
+
+ def generate_rules(self):
+ self.rules = []
+ self.ruledict = {}
+
+ self.add_rule_comment(NinjaComment('Rules for module scanning.'))
+ self.generate_scanner_rules()
+ self.add_rule_comment(NinjaComment('Rules for compiling.'))
+ self.generate_compile_rules()
+ self.add_rule_comment(NinjaComment('Rules for linking.'))
+ self.generate_static_link_rules()
+ self.generate_dynamic_link_rules()
+ self.add_rule_comment(NinjaComment('Other rules'))
+ # Ninja errors out if you have deps = gcc but no depfile, so we must
+ # have two rules for custom commands.
+ self.add_rule(NinjaRule('CUSTOM_COMMAND', ['$COMMAND'], [], '$DESC',
+ extra='restat = 1'))
+ self.add_rule(NinjaRule('CUSTOM_COMMAND_DEP', ['$COMMAND'], [], '$DESC',
+ deps='gcc', depfile='$DEPFILE',
+ extra='restat = 1'))
+ self.add_rule(NinjaRule('COPY_FILE', self.environment.get_build_command() + ['--internal', 'copy'],
+ ['$in', '$out'], 'Copying $in to $out'))
+
+ c = self.environment.get_build_command() + \
+ ['--internal',
+ 'regenerate',
+ self.environment.get_source_dir(),
+ self.environment.get_build_dir()]
+ self.add_rule(NinjaRule('REGENERATE_BUILD',
+ c, [],
+ 'Regenerating build files.',
+ extra='generator = 1'))
+
+ def add_rule_comment(self, comment):
+ self.rules.append(comment)
+
+ def add_build_comment(self, comment):
+ self.build_elements.append(comment)
+
+ def add_rule(self, rule):
+ if rule.name in self.ruledict:
+ raise MesonException(f'Tried to add rule {rule.name} twice.')
+ self.rules.append(rule)
+ self.ruledict[rule.name] = rule
+
+ def add_build(self, build):
+ build.check_outputs()
+ self.build_elements.append(build)
+
+ if build.rulename != 'phony':
+ # reference rule
+ if build.rulename in self.ruledict:
+ build.rule = self.ruledict[build.rulename]
+ else:
+ mlog.warning(f"build statement for {build.outfilenames} references non-existent rule {build.rulename}")
+
+ def write_rules(self, outfile):
+ for b in self.build_elements:
+ if isinstance(b, NinjaBuildElement):
+ b.count_rule_references()
+
+ for r in self.rules:
+ r.write(outfile)
+
+ def write_builds(self, outfile):
+ for b in ProgressBar(self.build_elements, desc='Writing build.ninja'):
+ b.write(outfile)
+
+ def generate_phony(self):
+ self.add_build_comment(NinjaComment('Phony build target, always out of date'))
+ elem = NinjaBuildElement(self.all_outputs, 'PHONY', 'phony', '')
+ self.add_build(elem)
+
+ def generate_jar_target(self, target: build.Jar):
+ fname = target.get_filename()
+ outname_rel = os.path.join(self.get_target_dir(target), fname)
+ src_list = target.get_sources()
+ resources = target.get_java_resources()
+ class_list = []
+ compiler = target.compilers['java']
+ c = 'c'
+ m = 'm'
+ e = ''
+ f = 'f'
+ main_class = target.get_main_class()
+ if main_class != '':
+ e = 'e'
+
+ # Add possible java generated files to src list
+ generated_sources = self.get_target_generated_sources(target)
+ gen_src_list = []
+ for rel_src in generated_sources.keys():
+ raw_src = File.from_built_relative(rel_src)
+ if rel_src.endswith('.java'):
+ gen_src_list.append(raw_src)
+
+ compile_args = self.determine_single_java_compile_args(target, compiler)
+ for src in src_list + gen_src_list:
+ plain_class_path = self.generate_single_java_compile(src, target, compiler, compile_args)
+ class_list.append(plain_class_path)
+ class_dep_list = [os.path.join(self.get_target_private_dir(target), i) for i in class_list]
+ manifest_path = os.path.join(self.get_target_private_dir(target), 'META-INF', 'MANIFEST.MF')
+ manifest_fullpath = os.path.join(self.environment.get_build_dir(), manifest_path)
+ os.makedirs(os.path.dirname(manifest_fullpath), exist_ok=True)
+ with open(manifest_fullpath, 'w', encoding='utf-8') as manifest:
+ if any(target.link_targets):
+ manifest.write('Class-Path: ')
+ cp_paths = [os.path.join(self.get_target_dir(l), l.get_filename()) for l in target.link_targets]
+ manifest.write(' '.join(cp_paths))
+ manifest.write('\n')
+ jar_rule = 'java_LINKER'
+ commands = [c + m + e + f]
+ commands.append(manifest_path)
+ if e != '':
+ commands.append(main_class)
+ commands.append(self.get_target_filename(target))
+ # Java compilation can produce an arbitrary number of output
+ # class files for a single source file. Thus tell jar to just
+ # grab everything in the final package.
+ commands += ['-C', self.get_target_private_dir(target), '.']
+ elem = NinjaBuildElement(self.all_outputs, outname_rel, jar_rule, [])
+ elem.add_dep(class_dep_list)
+ if resources:
+ # Copy all resources into the root of the jar.
+ elem.add_orderdep(self.__generate_sources_structure(Path(self.get_target_private_dir(target)), resources)[0])
+ elem.add_item('ARGS', commands)
+ self.add_build(elem)
+ # Create introspection information
+ self.create_target_source_introspection(target, compiler, compile_args, src_list, gen_src_list)
+
+ def generate_cs_resource_tasks(self, target):
+ args = []
+ deps = []
+ for r in target.resources:
+ rel_sourcefile = os.path.join(self.build_to_src, target.subdir, r)
+ if r.endswith('.resources'):
+ a = '-resource:' + rel_sourcefile
+ elif r.endswith('.txt') or r.endswith('.resx'):
+ ofilebase = os.path.splitext(os.path.basename(r))[0] + '.resources'
+ ofilename = os.path.join(self.get_target_private_dir(target), ofilebase)
+ elem = NinjaBuildElement(self.all_outputs, ofilename, "CUSTOM_COMMAND", rel_sourcefile)
+ elem.add_item('COMMAND', ['resgen', rel_sourcefile, ofilename])
+ elem.add_item('DESC', f'Compiling resource {rel_sourcefile}')
+ self.add_build(elem)
+ deps.append(ofilename)
+ a = '-resource:' + ofilename
+ else:
+ raise InvalidArguments(f'Unknown resource file {r}.')
+ args.append(a)
+ return args, deps
+
+ def generate_cs_target(self, target: build.BuildTarget):
+ buildtype = target.get_option(OptionKey('buildtype'))
+ fname = target.get_filename()
+ outname_rel = os.path.join(self.get_target_dir(target), fname)
+ src_list = target.get_sources()
+ compiler = target.compilers['cs']
+ rel_srcs = [os.path.normpath(s.rel_to_builddir(self.build_to_src)) for s in src_list]
+ deps = []
+ commands = compiler.compiler_args(target.extra_args.get('cs', []))
+ commands += compiler.get_buildtype_args(buildtype)
+ commands += compiler.get_optimization_args(target.get_option(OptionKey('optimization')))
+ commands += compiler.get_debug_args(target.get_option(OptionKey('debug')))
+ if isinstance(target, build.Executable):
+ commands.append('-target:exe')
+ elif isinstance(target, build.SharedLibrary):
+ commands.append('-target:library')
+ else:
+ raise MesonException('Unknown C# target type.')
+ (resource_args, resource_deps) = self.generate_cs_resource_tasks(target)
+ commands += resource_args
+ deps += resource_deps
+ commands += compiler.get_output_args(outname_rel)
+ for l in target.link_targets:
+ lname = os.path.join(self.get_target_dir(l), l.get_filename())
+ commands += compiler.get_link_args(lname)
+ deps.append(lname)
+ if '-g' in commands:
+ outputs = [outname_rel, outname_rel + '.mdb']
+ else:
+ outputs = [outname_rel]
+ generated_sources = self.get_target_generated_sources(target)
+ generated_rel_srcs = []
+ for rel_src in generated_sources.keys():
+ if rel_src.lower().endswith('.cs'):
+ generated_rel_srcs.append(os.path.normpath(rel_src))
+ deps.append(os.path.normpath(rel_src))
+
+ for dep in target.get_external_deps():
+ commands.extend_direct(dep.get_link_args())
+ commands += self.build.get_project_args(compiler, target.subproject, target.for_machine)
+ commands += self.build.get_global_args(compiler, target.for_machine)
+
+ elem = NinjaBuildElement(self.all_outputs, outputs, self.compiler_to_rule_name(compiler), rel_srcs + generated_rel_srcs)
+ elem.add_dep(deps)
+ elem.add_item('ARGS', commands)
+ self.add_build(elem)
+
+ self.generate_generator_list_rules(target)
+ self.create_target_source_introspection(target, compiler, commands, rel_srcs, generated_rel_srcs)
+
+ def determine_single_java_compile_args(self, target, compiler):
+ args = []
+ args += compiler.get_buildtype_args(target.get_option(OptionKey('buildtype')))
+ args += self.build.get_global_args(compiler, target.for_machine)
+ args += self.build.get_project_args(compiler, target.subproject, target.for_machine)
+ args += target.get_java_args()
+ args += compiler.get_output_args(self.get_target_private_dir(target))
+ args += target.get_classpath_args()
+ curdir = target.get_subdir()
+ sourcepath = os.path.join(self.build_to_src, curdir) + os.pathsep
+ sourcepath += os.path.normpath(curdir) + os.pathsep
+ for i in target.include_dirs:
+ for idir in i.get_incdirs():
+ sourcepath += os.path.join(self.build_to_src, i.curdir, idir) + os.pathsep
+ args += ['-sourcepath', sourcepath]
+ return args
+
+ def generate_single_java_compile(self, src, target, compiler, args):
+ deps = [os.path.join(self.get_target_dir(l), l.get_filename()) for l in target.link_targets]
+ generated_sources = self.get_target_generated_sources(target)
+ for rel_src in generated_sources.keys():
+ if rel_src.endswith('.java'):
+ deps.append(rel_src)
+ rel_src = src.rel_to_builddir(self.build_to_src)
+ plain_class_path = src.fname[:-4] + 'class'
+ rel_obj = os.path.join(self.get_target_private_dir(target), plain_class_path)
+ element = NinjaBuildElement(self.all_outputs, rel_obj, self.compiler_to_rule_name(compiler), rel_src)
+ element.add_dep(deps)
+ element.add_item('ARGS', args)
+ self.add_build(element)
+ return plain_class_path
+
+ def generate_java_link(self):
+ rule = 'java_LINKER'
+ command = ['jar', '$ARGS']
+ description = 'Creating JAR $out'
+ self.add_rule(NinjaRule(rule, command, [], description))
+
+ def determine_dep_vapis(self, target):
+ """
+ Peek into the sources of BuildTargets we're linking with, and if any of
+ them was built with Vala, assume that it also generated a .vapi file of
+ the same name as the BuildTarget and return the path to it relative to
+ the build directory.
+ """
+ result = OrderedSet()
+ for dep in itertools.chain(target.link_targets, target.link_whole_targets):
+ if not dep.is_linkable_target():
+ continue
+ for i in dep.sources:
+ if hasattr(i, 'fname'):
+ i = i.fname
+ if i.split('.')[-1] in compilers.lang_suffixes['vala']:
+ vapiname = dep.vala_vapi
+ fullname = os.path.join(self.get_target_dir(dep), vapiname)
+ result.add(fullname)
+ break
+ return list(result)
+
+ def split_vala_sources(self, t: build.BuildTarget) -> \
+ T.Tuple[T.MutableMapping[str, File], T.MutableMapping[str, File],
+ T.Tuple[T.MutableMapping[str, File], T.MutableMapping]]:
+ """
+ Splits the target's sources into .vala, .gs, .vapi, and other sources.
+ Handles both pre-existing and generated sources.
+
+ Returns a tuple (vala, vapi, others) each of which is a dictionary with
+ the keys being the path to the file (relative to the build directory)
+ and the value being the object that generated or represents the file.
+ """
+ vala: T.MutableMapping[str, File] = OrderedDict()
+ vapi: T.MutableMapping[str, File] = OrderedDict()
+ others: T.MutableMapping[str, File] = OrderedDict()
+ othersgen: T.MutableMapping[str, File] = OrderedDict()
+ # Split pre-existing sources
+ for s in t.get_sources():
+ # BuildTarget sources are always mesonlib.File files which are
+ # either in the source root, or generated with configure_file and
+ # in the build root
+ if not isinstance(s, File):
+ raise InvalidArguments(f'All sources in target {t!r} must be of type mesonlib.File, not {s!r}')
+ f = s.rel_to_builddir(self.build_to_src)
+ if s.endswith(('.vala', '.gs')):
+ srctype = vala
+ elif s.endswith('.vapi'):
+ srctype = vapi
+ else:
+ srctype = others
+ srctype[f] = s
+ # Split generated sources
+ for gensrc in t.get_generated_sources():
+ for s in gensrc.get_outputs():
+ f = self.get_target_generated_dir(t, gensrc, s)
+ if s.endswith(('.vala', '.gs')):
+ srctype = vala
+ elif s.endswith('.vapi'):
+ srctype = vapi
+ # Generated non-Vala (C/C++) sources. Won't be used for
+ # generating the Vala compile rule below.
+ else:
+ srctype = othersgen
+ # Duplicate outputs are disastrous
+ if f in srctype and srctype[f] is not gensrc:
+ msg = 'Duplicate output {0!r} from {1!r} {2!r}; ' \
+ 'conflicts with {0!r} from {4!r} {3!r}' \
+ ''.format(f, type(gensrc).__name__, gensrc.name,
+ srctype[f].name, type(srctype[f]).__name__)
+ raise InvalidArguments(msg)
+ # Store 'somefile.vala': GeneratedList (or CustomTarget)
+ srctype[f] = gensrc
+ return vala, vapi, (others, othersgen)
+
+ def generate_vala_compile(self, target: build.BuildTarget) -> \
+ T.Tuple[T.MutableMapping[str, File], T.MutableMapping[str, File], T.List[str]]:
+ """Vala is compiled into C. Set up all necessary build steps here."""
+ (vala_src, vapi_src, other_src) = self.split_vala_sources(target)
+ extra_dep_files = []
+ if not vala_src:
+ raise InvalidArguments(f'Vala library {target.name!r} has no Vala or Genie source files.')
+
+ valac = target.compilers['vala']
+ c_out_dir = self.get_target_private_dir(target)
+ # C files generated by valac
+ vala_c_src: T.List[str] = []
+ # Files generated by valac
+ valac_outputs: T.List = []
+ # All sources that are passed to valac on the commandline
+ all_files = list(vapi_src)
+ # Passed as --basedir
+ srcbasedir = os.path.join(self.build_to_src, target.get_subdir())
+ for (vala_file, gensrc) in vala_src.items():
+ all_files.append(vala_file)
+ # Figure out where the Vala compiler will write the compiled C file
+ #
+ # If the Vala file is in a subdir of the build dir (in our case
+ # because it was generated/built by something else), and is also
+ # a subdir of --basedir (because the builddir is in the source
+ # tree, and the target subdir is the source root), the subdir
+ # components from the source root till the private builddir will be
+ # duplicated inside the private builddir. Otherwise, just the
+ # basename will be used.
+ #
+ # If the Vala file is outside the build directory, the paths from
+ # the --basedir till the subdir will be duplicated inside the
+ # private builddir.
+ if isinstance(gensrc, (build.CustomTarget, build.GeneratedList)) or gensrc.is_built:
+ vala_c_file = os.path.splitext(os.path.basename(vala_file))[0] + '.c'
+ # Check if the vala file is in a subdir of --basedir
+ abs_srcbasedir = os.path.join(self.environment.get_source_dir(), target.get_subdir())
+ abs_vala_file = os.path.join(self.environment.get_build_dir(), vala_file)
+ if PurePath(os.path.commonpath((abs_srcbasedir, abs_vala_file))) == PurePath(abs_srcbasedir):
+ vala_c_subdir = PurePath(abs_vala_file).parent.relative_to(abs_srcbasedir)
+ vala_c_file = os.path.join(str(vala_c_subdir), vala_c_file)
+ else:
+ path_to_target = os.path.join(self.build_to_src, target.get_subdir())
+ if vala_file.startswith(path_to_target):
+ vala_c_file = os.path.splitext(os.path.relpath(vala_file, path_to_target))[0] + '.c'
+ else:
+ vala_c_file = os.path.splitext(os.path.basename(vala_file))[0] + '.c'
+ # All this will be placed inside the c_out_dir
+ vala_c_file = os.path.join(c_out_dir, vala_c_file)
+ vala_c_src.append(vala_c_file)
+ valac_outputs.append(vala_c_file)
+
+ args = self.generate_basic_compiler_args(target, valac)
+ args += valac.get_colorout_args(self.environment.coredata.options.get(OptionKey('b_colorout')).value)
+ # Tell Valac to output everything in our private directory. Sadly this
+ # means it will also preserve the directory components of Vala sources
+ # found inside the build tree (generated sources).
+ args += ['--directory', c_out_dir]
+ args += ['--basedir', srcbasedir]
+ if target.is_linkable_target():
+ # Library name
+ args += ['--library', target.name]
+ # Outputted header
+ hname = os.path.join(self.get_target_dir(target), target.vala_header)
+ args += ['--header', hname]
+ if target.is_unity:
+ # Without this the declarations will get duplicated in the .c
+ # files and cause a build failure when all of them are
+ # #include-d in one .c file.
+ # https://github.com/mesonbuild/meson/issues/1969
+ args += ['--use-header']
+ valac_outputs.append(hname)
+ # Outputted vapi file
+ vapiname = os.path.join(self.get_target_dir(target), target.vala_vapi)
+ # Force valac to write the vapi and gir files in the target build dir.
+ # Without this, it will write it inside c_out_dir
+ args += ['--vapi', os.path.join('..', target.vala_vapi)]
+ valac_outputs.append(vapiname)
+ target.outputs += [target.vala_header, target.vala_vapi]
+ target.install_tag += ['devel', 'devel']
+ # Install header and vapi to default locations if user requests this
+ if len(target.install_dir) > 1 and target.install_dir[1] is True:
+ target.install_dir[1] = self.environment.get_includedir()
+ if len(target.install_dir) > 2 and target.install_dir[2] is True:
+ target.install_dir[2] = os.path.join(self.environment.get_datadir(), 'vala', 'vapi')
+ # Generate GIR if requested
+ if isinstance(target.vala_gir, str):
+ girname = os.path.join(self.get_target_dir(target), target.vala_gir)
+ args += ['--gir', os.path.join('..', target.vala_gir)]
+ valac_outputs.append(girname)
+ target.outputs.append(target.vala_gir)
+ target.install_tag.append('devel')
+ # Install GIR to default location if requested by user
+ if len(target.install_dir) > 3 and target.install_dir[3] is True:
+ target.install_dir[3] = os.path.join(self.environment.get_datadir(), 'gir-1.0')
+ # Detect gresources and add --gresources arguments for each
+ for gensrc in other_src[1].values():
+ if isinstance(gensrc, gnome.GResourceTarget):
+ gres_xml, = self.get_custom_target_sources(gensrc)
+ args += ['--gresources=' + gres_xml]
+ extra_args = []
+
+ for a in target.extra_args.get('vala', []):
+ if isinstance(a, File):
+ relname = a.rel_to_builddir(self.build_to_src)
+ extra_dep_files.append(relname)
+ extra_args.append(relname)
+ else:
+ extra_args.append(a)
+ dependency_vapis = self.determine_dep_vapis(target)
+ extra_dep_files += dependency_vapis
+ args += extra_args
+ element = NinjaBuildElement(self.all_outputs, valac_outputs,
+ self.compiler_to_rule_name(valac),
+ all_files + dependency_vapis)
+ element.add_item('ARGS', args)
+ element.add_dep(extra_dep_files)
+ self.add_build(element)
+ self.create_target_source_introspection(target, valac, args, all_files, [])
+ return other_src[0], other_src[1], vala_c_src
+
+ def generate_cython_transpile(self, target: build.BuildTarget) -> \
+ T.Tuple[T.MutableMapping[str, File], T.MutableMapping[str, File], T.List[str]]:
+ """Generate rules for transpiling Cython files to C or C++
+
+ XXX: Currently only C is handled.
+ """
+ static_sources: T.MutableMapping[str, File] = OrderedDict()
+ generated_sources: T.MutableMapping[str, File] = OrderedDict()
+ cython_sources: T.List[str] = []
+
+ cython = target.compilers['cython']
+
+ args: T.List[str] = []
+ args += cython.get_always_args()
+ args += cython.get_buildtype_args(target.get_option(OptionKey('buildtype')))
+ args += cython.get_debug_args(target.get_option(OptionKey('debug')))
+ args += cython.get_optimization_args(target.get_option(OptionKey('optimization')))
+ args += cython.get_option_compile_args(target.get_options())
+ args += self.build.get_global_args(cython, target.for_machine)
+ args += self.build.get_project_args(cython, target.subproject, target.for_machine)
+ args += target.get_extra_args('cython')
+
+ ext = target.get_option(OptionKey('language', machine=target.for_machine, lang='cython'))
+
+ pyx_sources = [] # Keep track of sources we're adding to build
+
+ for src in target.get_sources():
+ if src.endswith('.pyx'):
+ output = os.path.join(self.get_target_private_dir(target), f'{src}.{ext}')
+ element = NinjaBuildElement(
+ self.all_outputs, [output],
+ self.compiler_to_rule_name(cython),
+ [src.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir())])
+ element.add_item('ARGS', args)
+ self.add_build(element)
+ # TODO: introspection?
+ cython_sources.append(output)
+ pyx_sources.append(element)
+ else:
+ static_sources[src.rel_to_builddir(self.build_to_src)] = src
+
+ header_deps = [] # Keep track of generated headers for those sources
+ for gen in target.get_generated_sources():
+ for ssrc in gen.get_outputs():
+ if isinstance(gen, GeneratedList):
+ ssrc = os.path.join(self.get_target_private_dir(target), ssrc)
+ else:
+ ssrc = os.path.join(gen.get_subdir(), ssrc)
+ if ssrc.endswith('.pyx'):
+ output = os.path.join(self.get_target_private_dir(target), f'{ssrc}.{ext}')
+ element = NinjaBuildElement(
+ self.all_outputs, [output],
+ self.compiler_to_rule_name(cython),
+ [ssrc])
+ element.add_item('ARGS', args)
+ self.add_build(element)
+ pyx_sources.append(element)
+ # TODO: introspection?
+ cython_sources.append(output)
+ else:
+ generated_sources[ssrc] = mesonlib.File.from_built_file(gen.get_subdir(), ssrc)
+ # Following logic in L883-900 where we determine whether to add generated source
+ # as a header(order-only) dep to the .so compilation rule
+ if not self.environment.is_source(ssrc) and \
+ not self.environment.is_object(ssrc) and \
+ not self.environment.is_library(ssrc) and \
+ not modules.is_module_library(ssrc):
+ header_deps.append(ssrc)
+ for source in pyx_sources:
+ source.add_orderdep(header_deps)
+
+ return static_sources, generated_sources, cython_sources
+
+ def _generate_copy_target(self, src: 'mesonlib.FileOrString', output: Path) -> None:
+ """Create a target to copy a source file from one location to another."""
+ if isinstance(src, File):
+ instr = src.absolute_path(self.environment.source_dir, self.environment.build_dir)
+ else:
+ instr = src
+ elem = NinjaBuildElement(self.all_outputs, [str(output)], 'COPY_FILE', [instr])
+ elem.add_orderdep(instr)
+ self.add_build(elem)
+
+ def __generate_sources_structure(self, root: Path, structured_sources: build.StructuredSources) -> T.Tuple[T.List[str], T.Optional[str]]:
+ first_file: T.Optional[str] = None
+ orderdeps: T.List[str] = []
+ for path, files in structured_sources.sources.items():
+ for file in files:
+ if isinstance(file, File):
+ out = root / path / Path(file.fname).name
+ orderdeps.append(str(out))
+ self._generate_copy_target(file, out)
+ if first_file is None:
+ first_file = str(out)
+ else:
+ for f in file.get_outputs():
+ out = root / path / f
+ orderdeps.append(str(out))
+ self._generate_copy_target(str(Path(file.subdir) / f), out)
+ if first_file is None:
+ first_file = str(out)
+ return orderdeps, first_file
+
+ def _add_rust_project_entry(self, name: str, main_rust_file: str, args: CompilerArgs,
+ from_subproject: bool, is_proc_macro: bool,
+ output: str, deps: T.List[RustDep]) -> None:
+ raw_edition: T.Optional[str] = mesonlib.first(reversed(args), lambda x: x.startswith('--edition'))
+ edition: RUST_EDITIONS = '2015' if not raw_edition else raw_edition.split('=')[-1]
+
+ cfg: T.List[str] = []
+ arg_itr: T.Iterator[str] = iter(args)
+ for arg in arg_itr:
+ if arg == '--cfg':
+ cfg.append(next(arg_itr))
+ elif arg.startswith('--cfg'):
+ cfg.append(arg[len('--cfg'):])
+
+ crate = RustCrate(
+ len(self.rust_crates),
+ name,
+ main_rust_file,
+ edition,
+ deps,
+ cfg,
+ is_workspace_member=not from_subproject,
+ is_proc_macro=is_proc_macro,
+ proc_macro_dylib_path=output if is_proc_macro else None,
+ )
+
+ self.rust_crates[name] = crate
+
+ def generate_rust_target(self, target: build.BuildTarget) -> None:
+ rustc = target.compilers['rust']
+ # Rust compiler takes only the main file as input and
+ # figures out what other files are needed via import
+ # statements and magic.
+ base_proxy = target.get_options()
+ args = rustc.compiler_args()
+ # Compiler args for compiling this target
+ args += compilers.get_base_compile_args(base_proxy, rustc)
+ self.generate_generator_list_rules(target)
+
+ # dependencies need to cause a relink, they're not just for ordering
+ deps = [
+ os.path.join(t.subdir, t.get_filename())
+ for t in itertools.chain(target.link_targets, target.link_whole_targets)
+ ]
+
+ # Dependencies for rust-project.json
+ project_deps: T.List[RustDep] = []
+
+ orderdeps: T.List[str] = []
+
+ main_rust_file = None
+ if target.structured_sources:
+ if target.structured_sources.needs_copy():
+ _ods, main_rust_file = self.__generate_sources_structure(Path(
+ self.get_target_private_dir(target)) / 'structured', target.structured_sources)
+ orderdeps.extend(_ods)
+ else:
+ # The only way to get here is to have only files in the "root"
+ # positional argument, which are all generated into the same
+ # directory
+ g = target.structured_sources.first_file()
+
+ if isinstance(g, File):
+ main_rust_file = g.rel_to_builddir(self.build_to_src)
+ elif isinstance(g, GeneratedList):
+ main_rust_file = os.path.join(self.get_target_private_dir(target), g.get_outputs()[0])
+ else:
+ main_rust_file = os.path.join(g.get_subdir(), g.get_outputs()[0])
+
+ for f in target.structured_sources.as_list():
+ if isinstance(f, File):
+ orderdeps.append(f.rel_to_builddir(self.build_to_src))
+ else:
+ orderdeps.extend([os.path.join(self.build_to_src, f.subdir, s)
+ for s in f.get_outputs()])
+
+ for i in target.get_sources():
+ if not rustc.can_compile(i):
+ raise InvalidArguments(f'Rust target {target.get_basename()} contains a non-rust source file.')
+ if main_rust_file is None:
+ main_rust_file = i.rel_to_builddir(self.build_to_src)
+ for g in target.get_generated_sources():
+ for i in g.get_outputs():
+ if not rustc.can_compile(i):
+ raise InvalidArguments(f'Rust target {target.get_basename()} contains a non-rust source file.')
+ if isinstance(g, GeneratedList):
+ fname = os.path.join(self.get_target_private_dir(target), i)
+ else:
+ fname = os.path.join(g.get_subdir(), i)
+ if main_rust_file is None:
+ main_rust_file = fname
+ orderdeps.append(fname)
+ if main_rust_file is None:
+ raise RuntimeError('A Rust target has no Rust sources. This is weird. Also a bug. Please report')
+ target_name = os.path.join(target.subdir, target.get_filename())
+ if isinstance(target, build.Executable):
+ cratetype = 'bin'
+ elif hasattr(target, 'rust_crate_type'):
+ cratetype = target.rust_crate_type
+ elif isinstance(target, build.SharedLibrary):
+ cratetype = 'dylib'
+ elif isinstance(target, build.StaticLibrary):
+ cratetype = 'rlib'
+ else:
+ raise InvalidArguments('Unknown target type for rustc.')
+ args.extend(['--crate-type', cratetype])
+
+ # If we're dynamically linking, add those arguments
+ #
+ # Rust is super annoying, calling -C link-arg foo does not work, it has
+ # to be -C link-arg=foo
+ if cratetype in {'bin', 'dylib'}:
+ args.extend(rustc.get_linker_always_args())
+
+ args += self.generate_basic_compiler_args(target, rustc, False)
+ # Rustc replaces - with _. spaces are not allowed, so we replace them with underscores
+ args += ['--crate-name', target.name.replace('-', '_').replace(' ', '_')]
+ depfile = os.path.join(target.subdir, target.name + '.d')
+ args += ['--emit', f'dep-info={depfile}', '--emit', 'link']
+ args += target.get_extra_args('rust')
+ output = rustc.get_output_args(os.path.join(target.subdir, target.get_filename()))
+ args += output
+ linkdirs = mesonlib.OrderedSet()
+ external_deps = target.external_deps.copy()
+ # TODO: we likely need to use verbatim to handle name_prefix and name_suffix
+ for d in target.link_targets:
+ linkdirs.add(d.subdir)
+ if d.uses_rust():
+ # specify `extern CRATE_NAME=OUTPUT_FILE` for each Rust
+ # dependency, so that collisions with libraries in rustc's
+ # sysroot don't cause ambiguity
+ args += ['--extern', '{}={}'.format(d.name, os.path.join(d.subdir, d.filename))]
+ project_deps.append(RustDep(d.name, self.rust_crates[d.name].order))
+ elif d.typename == 'static library':
+ # Rustc doesn't follow Meson's convention that static libraries
+ # are called .a, and import libraries are .lib, so we have to
+ # manually handle that.
+ if rustc.linker.id in {'link', 'lld-link'}:
+ args += ['-C', f'link-arg={self.get_target_filename_for_linking(d)}']
+ else:
+ args += ['-l', f'static={d.name}']
+ external_deps.extend(d.external_deps)
+ else:
+ # Rust uses -l for non rust dependencies, but we still need to
+ # add dylib=foo
+ args += ['-l', f'dylib={d.name}']
+
+ # Since 1.61.0 Rust has a special modifier for whole-archive linking,
+ # before that it would treat linking two static libraries as
+ # whole-archive linking. However, to make this work we have to disable
+ # bundling, which can't be done until 1.63.0… So for 1.61–1.62 we just
+ # have to hope that the default cases of +whole-archive are sufficent.
+ # See: https://github.com/rust-lang/rust/issues/99429
+ if mesonlib.version_compare(rustc.version, '>= 1.63.0'):
+ whole_archive = ':+whole-archive,-bundle'
+ else:
+ whole_archive = ''
+
+ if mesonlib.version_compare(rustc.version, '>= 1.67.0'):
+ verbatim = ',+verbatim'
+ else:
+ verbatim = ''
+
+ for d in target.link_whole_targets:
+ linkdirs.add(d.subdir)
+ if d.uses_rust():
+ # specify `extern CRATE_NAME=OUTPUT_FILE` for each Rust
+ # dependency, so that collisions with libraries in rustc's
+ # sysroot don't cause ambiguity
+ args += ['--extern', '{}={}'.format(d.name, os.path.join(d.subdir, d.filename))]
+ project_deps.append(RustDep(d.name, self.rust_crates[d.name].order))
+ else:
+ if rustc.linker.id in {'link', 'lld-link'}:
+ if verbatim:
+ # If we can use the verbatim modifier, then everything is great
+ args += ['-l', f'static{whole_archive}{verbatim}={d.get_outputs()[0]}']
+ elif isinstance(target, build.StaticLibrary):
+ # If we don't, for static libraries the only option is
+ # to make a copy, since we can't pass objects in, or
+ # directly affect the archiver. but we're not going to
+ # do that given how quickly rustc versions go out of
+ # support unless there's a compelling reason to do so.
+ # This only affects 1.61–1.66
+ mlog.warning('Due to limitations in Rustc versions 1.61–1.66 and meson library naming',
+ 'whole-archive linking with MSVC may or may not work. Upgrade rustc to',
+ '>= 1.67. A best effort is being made, but likely won\'t work')
+ args += ['-l', f'static={d.name}']
+ else:
+ # When doing dynamic linking (binaries and [c]dylibs),
+ # we can instead just proxy the correct arguments to the linker
+ for link_whole_arg in rustc.linker.get_link_whole_for([self.get_target_filename_for_linking(d)]):
+ args += ['-C', f'link-arg={link_whole_arg}']
+ else:
+ args += ['-l', f'static{whole_archive}={d.name}']
+ external_deps.extend(d.external_deps)
+ for e in external_deps:
+ for a in e.get_link_args():
+ if a.endswith(('.dll', '.so', '.dylib')):
+ dir_, lib = os.path.split(a)
+ linkdirs.add(dir_)
+ lib, ext = os.path.splitext(lib)
+ if lib.startswith('lib'):
+ lib = lib[3:]
+ args.extend(['-l', f'dylib={lib}'])
+ elif a.startswith('-L'):
+ args.append(a)
+ elif a.startswith('-l'):
+ _type = 'static' if e.static else 'dylib'
+ args.extend(['-l', f'{_type}={a[2:]}'])
+ for d in linkdirs:
+ if d == '':
+ d = '.'
+ args += ['-L', d]
+ has_shared_deps = any(isinstance(dep, build.SharedLibrary) for dep in target.get_dependencies())
+ if isinstance(target, build.SharedLibrary) or has_shared_deps:
+ # add prefer-dynamic if any of the Rust libraries we link
+ # against are dynamic, otherwise we'll end up with
+ # multiple implementations of crates
+ args += ['-C', 'prefer-dynamic']
+
+ # build the usual rpath arguments as well...
+
+ # Set runtime-paths so we can run executables without needing to set
+ # LD_LIBRARY_PATH, etc in the environment. Doesn't work on Windows.
+ if has_path_sep(target.name):
+ # Target names really should not have slashes in them, but
+ # unfortunately we did not check for that and some downstream projects
+ # now have them. Once slashes are forbidden, remove this bit.
+ target_slashname_workaround_dir = os.path.join(os.path.dirname(target.name),
+ self.get_target_dir(target))
+ else:
+ target_slashname_workaround_dir = self.get_target_dir(target)
+ rpath_args, target.rpath_dirs_to_remove = (
+ rustc.build_rpath_args(self.environment,
+ self.environment.get_build_dir(),
+ target_slashname_workaround_dir,
+ self.determine_rpath_dirs(target),
+ target.build_rpath,
+ target.install_rpath))
+ # ... but then add rustc's sysroot to account for rustup
+ # installations
+ for rpath_arg in rpath_args:
+ args += ['-C', 'link-arg=' + rpath_arg + ':' + os.path.join(rustc.get_sysroot(), 'lib')]
+
+ self._add_rust_project_entry(target.name, main_rust_file, args, bool(target.subproject),
+ #XXX: There is a fix for this pending
+ getattr(target, 'rust_crate_type', '') == 'procmacro',
+ output, project_deps)
+
+ compiler_name = self.compiler_to_rule_name(rustc)
+ element = NinjaBuildElement(self.all_outputs, target_name, compiler_name, main_rust_file)
+ if orderdeps:
+ element.add_orderdep(orderdeps)
+ if deps:
+ element.add_dep(deps)
+ element.add_item('ARGS', args)
+ element.add_item('targetdep', depfile)
+ element.add_item('cratetype', cratetype)
+ self.add_build(element)
+ if isinstance(target, build.SharedLibrary):
+ self.generate_shsym(target)
+ self.create_target_source_introspection(target, rustc, args, [main_rust_file], [])
+
+ @staticmethod
+ def get_rule_suffix(for_machine: MachineChoice) -> str:
+ return PerMachine('_FOR_BUILD', '')[for_machine]
+
+ @classmethod
+ def get_compiler_rule_name(cls, lang: str, for_machine: MachineChoice, mode: str = 'COMPILER') -> str:
+ return f'{lang}_{mode}{cls.get_rule_suffix(for_machine)}'
+
+ @classmethod
+ def compiler_to_rule_name(cls, compiler: Compiler) -> str:
+ return cls.get_compiler_rule_name(compiler.get_language(), compiler.for_machine, compiler.mode)
+
+ @classmethod
+ def compiler_to_pch_rule_name(cls, compiler: Compiler) -> str:
+ return cls.get_compiler_rule_name(compiler.get_language(), compiler.for_machine, 'PCH')
+
+ def swift_module_file_name(self, target):
+ return os.path.join(self.get_target_private_dir(target),
+ self.target_swift_modulename(target) + '.swiftmodule')
+
+ def target_swift_modulename(self, target):
+ return target.name
+
+ def determine_swift_dep_modules(self, target):
+ result = []
+ for l in target.link_targets:
+ if self.is_swift_target(l):
+ result.append(self.swift_module_file_name(l))
+ return result
+
+ def get_swift_link_deps(self, target):
+ result = []
+ for l in target.link_targets:
+ result.append(self.get_target_filename(l))
+ return result
+
+ def split_swift_generated_sources(self, target):
+ all_srcs = self.get_target_generated_sources(target)
+ srcs = []
+ others = []
+ for i in all_srcs:
+ if i.endswith('.swift'):
+ srcs.append(i)
+ else:
+ others.append(i)
+ return srcs, others
+
+ def generate_swift_target(self, target):
+ module_name = self.target_swift_modulename(target)
+ swiftc = target.compilers['swift']
+ abssrc = []
+ relsrc = []
+ abs_headers = []
+ header_imports = []
+ for i in target.get_sources():
+ if swiftc.can_compile(i):
+ rels = i.rel_to_builddir(self.build_to_src)
+ abss = os.path.normpath(os.path.join(self.environment.get_build_dir(), rels))
+ relsrc.append(rels)
+ abssrc.append(abss)
+ elif self.environment.is_header(i):
+ relh = i.rel_to_builddir(self.build_to_src)
+ absh = os.path.normpath(os.path.join(self.environment.get_build_dir(), relh))
+ abs_headers.append(absh)
+ header_imports += swiftc.get_header_import_args(absh)
+ else:
+ raise InvalidArguments(f'Swift target {target.get_basename()} contains a non-swift source file.')
+ os.makedirs(self.get_target_private_dir_abs(target), exist_ok=True)
+ compile_args = swiftc.get_compile_only_args()
+ compile_args += swiftc.get_optimization_args(target.get_option(OptionKey('optimization')))
+ compile_args += swiftc.get_debug_args(target.get_option(OptionKey('debug')))
+ compile_args += swiftc.get_module_args(module_name)
+ compile_args += self.build.get_project_args(swiftc, target.subproject, target.for_machine)
+ compile_args += self.build.get_global_args(swiftc, target.for_machine)
+ for i in reversed(target.get_include_dirs()):
+ basedir = i.get_curdir()
+ for d in i.get_incdirs():
+ if d not in ('', '.'):
+ expdir = os.path.join(basedir, d)
+ else:
+ expdir = basedir
+ srctreedir = os.path.normpath(os.path.join(self.environment.get_build_dir(), self.build_to_src, expdir))
+ sargs = swiftc.get_include_args(srctreedir, False)
+ compile_args += sargs
+ link_args = swiftc.get_output_args(os.path.join(self.environment.get_build_dir(), self.get_target_filename(target)))
+ link_args += self.build.get_project_link_args(swiftc, target.subproject, target.for_machine)
+ link_args += self.build.get_global_link_args(swiftc, target.for_machine)
+ rundir = self.get_target_private_dir(target)
+ out_module_name = self.swift_module_file_name(target)
+ in_module_files = self.determine_swift_dep_modules(target)
+ abs_module_dirs = self.determine_swift_dep_dirs(target)
+ module_includes = []
+ for x in abs_module_dirs:
+ module_includes += swiftc.get_include_args(x, False)
+ link_deps = self.get_swift_link_deps(target)
+ abs_link_deps = [os.path.join(self.environment.get_build_dir(), x) for x in link_deps]
+ for d in target.link_targets:
+ reldir = self.get_target_dir(d)
+ if reldir == '':
+ reldir = '.'
+ link_args += ['-L', os.path.normpath(os.path.join(self.environment.get_build_dir(), reldir))]
+ (rel_generated, _) = self.split_swift_generated_sources(target)
+ abs_generated = [os.path.join(self.environment.get_build_dir(), x) for x in rel_generated]
+ # We need absolute paths because swiftc needs to be invoked in a subdir
+ # and this is the easiest way about it.
+ objects = [] # Relative to swift invocation dir
+ rel_objects = [] # Relative to build.ninja
+ for i in abssrc + abs_generated:
+ base = os.path.basename(i)
+ oname = os.path.splitext(base)[0] + '.o'
+ objects.append(oname)
+ rel_objects.append(os.path.join(self.get_target_private_dir(target), oname))
+
+ rulename = self.compiler_to_rule_name(swiftc)
+
+ # Swiftc does not seem to be able to emit objects and module files in one go.
+ elem = NinjaBuildElement(self.all_outputs, rel_objects, rulename, abssrc)
+ elem.add_dep(in_module_files + rel_generated)
+ elem.add_dep(abs_headers)
+ elem.add_item('ARGS', compile_args + header_imports + abs_generated + module_includes)
+ elem.add_item('RUNDIR', rundir)
+ self.add_build(elem)
+ elem = NinjaBuildElement(self.all_outputs, out_module_name, rulename, abssrc)
+ elem.add_dep(in_module_files + rel_generated)
+ elem.add_item('ARGS', compile_args + abs_generated + module_includes + swiftc.get_mod_gen_args())
+ elem.add_item('RUNDIR', rundir)
+ self.add_build(elem)
+ if isinstance(target, build.StaticLibrary):
+ elem = self.generate_link(target, self.get_target_filename(target),
+ rel_objects, self.build.static_linker[target.for_machine])
+ self.add_build(elem)
+ elif isinstance(target, build.Executable):
+ elem = NinjaBuildElement(self.all_outputs, self.get_target_filename(target), rulename, [])
+ elem.add_dep(rel_objects)
+ elem.add_dep(link_deps)
+ elem.add_item('ARGS', link_args + swiftc.get_std_exe_link_args() + objects + abs_link_deps)
+ elem.add_item('RUNDIR', rundir)
+ self.add_build(elem)
+ else:
+ raise MesonException('Swift supports only executable and static library targets.')
+ # Introspection information
+ self.create_target_source_introspection(target, swiftc, compile_args + header_imports + module_includes, relsrc, rel_generated)
+
+ def _rsp_options(self, tool: T.Union['Compiler', 'StaticLinker', 'DynamicLinker']) -> T.Dict[str, T.Union[bool, RSPFileSyntax]]:
+ """Helper method to get rsp options.
+
+ rsp_file_syntax() is only guaranteed to be implemented if
+ can_linker_accept_rsp() returns True.
+ """
+ options = {'rspable': tool.can_linker_accept_rsp()}
+ if options['rspable']:
+ options['rspfile_quote_style'] = tool.rsp_file_syntax()
+ return options
+
+ def generate_static_link_rules(self):
+ num_pools = self.environment.coredata.options[OptionKey('backend_max_links')].value
+ if 'java' in self.environment.coredata.compilers.host:
+ self.generate_java_link()
+ for for_machine in MachineChoice:
+ static_linker = self.build.static_linker[for_machine]
+ if static_linker is None:
+ continue
+ rule = 'STATIC_LINKER{}'.format(self.get_rule_suffix(for_machine))
+ cmdlist = []
+ args = ['$in']
+ # FIXME: Must normalize file names with pathlib.Path before writing
+ # them out to fix this properly on Windows. See:
+ # https://github.com/mesonbuild/meson/issues/1517
+ # https://github.com/mesonbuild/meson/issues/1526
+ if isinstance(static_linker, ArLinker) and not mesonlib.is_windows():
+ # `ar` has no options to overwrite archives. It always appends,
+ # which is never what we want. Delete an existing library first if
+ # it exists. https://github.com/mesonbuild/meson/issues/1355
+ cmdlist = execute_wrapper + [c.format('$out') for c in rmfile_prefix]
+ cmdlist += static_linker.get_exelist()
+ cmdlist += ['$LINK_ARGS']
+ cmdlist += NinjaCommandArg.list(static_linker.get_output_args('$out'), Quoting.none)
+ description = 'Linking static target $out'
+ if num_pools > 0:
+ pool = 'pool = link_pool'
+ else:
+ pool = None
+
+ options = self._rsp_options(static_linker)
+ self.add_rule(NinjaRule(rule, cmdlist, args, description, **options, extra=pool))
+
+ def generate_dynamic_link_rules(self):
+ num_pools = self.environment.coredata.options[OptionKey('backend_max_links')].value
+ for for_machine in MachineChoice:
+ complist = self.environment.coredata.compilers[for_machine]
+ for langname, compiler in complist.items():
+ if langname in {'java', 'vala', 'rust', 'cs', 'cython'}:
+ continue
+ rule = '{}_LINKER{}'.format(langname, self.get_rule_suffix(for_machine))
+ command = compiler.get_linker_exelist()
+ args = ['$ARGS'] + NinjaCommandArg.list(compiler.get_linker_output_args('$out'), Quoting.none) + ['$in', '$LINK_ARGS']
+ description = 'Linking target $out'
+ if num_pools > 0:
+ pool = 'pool = link_pool'
+ else:
+ pool = None
+
+ options = self._rsp_options(compiler)
+ self.add_rule(NinjaRule(rule, command, args, description, **options, extra=pool))
+
+ args = self.environment.get_build_command() + \
+ ['--internal',
+ 'symbolextractor',
+ self.environment.get_build_dir(),
+ '$in',
+ '$IMPLIB',
+ '$out']
+ symrule = 'SHSYM'
+ symcmd = args + ['$CROSS']
+ syndesc = 'Generating symbol file $out'
+ synstat = 'restat = 1'
+ self.add_rule(NinjaRule(symrule, symcmd, [], syndesc, extra=synstat))
+
+ def generate_java_compile_rule(self, compiler):
+ rule = self.compiler_to_rule_name(compiler)
+ command = compiler.get_exelist() + ['$ARGS', '$in']
+ description = 'Compiling Java object $in'
+ self.add_rule(NinjaRule(rule, command, [], description))
+
+ def generate_cs_compile_rule(self, compiler: 'CsCompiler') -> None:
+ rule = self.compiler_to_rule_name(compiler)
+ command = compiler.get_exelist()
+ args = ['$ARGS', '$in']
+ description = 'Compiling C Sharp target $out'
+ self.add_rule(NinjaRule(rule, command, args, description,
+ rspable=mesonlib.is_windows(),
+ rspfile_quote_style=compiler.rsp_file_syntax()))
+
+ def generate_vala_compile_rules(self, compiler):
+ rule = self.compiler_to_rule_name(compiler)
+ command = compiler.get_exelist() + ['$ARGS', '$in']
+ description = 'Compiling Vala source $in'
+ self.add_rule(NinjaRule(rule, command, [], description, extra='restat = 1'))
+
+ def generate_cython_compile_rules(self, compiler: 'Compiler') -> None:
+ rule = self.compiler_to_rule_name(compiler)
+ description = 'Compiling Cython source $in'
+ command = compiler.get_exelist()
+
+ depargs = compiler.get_dependency_gen_args('$out', '$DEPFILE')
+ depfile = '$out.dep' if depargs else None
+
+ args = depargs + ['$ARGS', '$in']
+ args += NinjaCommandArg.list(compiler.get_output_args('$out'), Quoting.none)
+ self.add_rule(NinjaRule(rule, command + args, [],
+ description,
+ depfile=depfile,
+ extra='restat = 1'))
+
+ def generate_rust_compile_rules(self, compiler):
+ rule = self.compiler_to_rule_name(compiler)
+ command = compiler.get_exelist() + ['$ARGS', '$in']
+ description = 'Compiling Rust source $in'
+ depfile = '$targetdep'
+ depstyle = 'gcc'
+ self.add_rule(NinjaRule(rule, command, [], description, deps=depstyle,
+ depfile=depfile))
+
+ def generate_swift_compile_rules(self, compiler):
+ rule = self.compiler_to_rule_name(compiler)
+ full_exe = self.environment.get_build_command() + [
+ '--internal',
+ 'dirchanger',
+ '$RUNDIR',
+ ]
+ invoc = full_exe + compiler.get_exelist()
+ command = invoc + ['$ARGS', '$in']
+ description = 'Compiling Swift source $in'
+ self.add_rule(NinjaRule(rule, command, [], description))
+
+ def use_dyndeps_for_fortran(self) -> bool:
+ '''Use the new Ninja feature for scanning dependencies during build,
+ rather than up front. Remove this and all old scanning code once Ninja
+ minimum version is bumped to 1.10.'''
+ return mesonlib.version_compare(self.ninja_version, '>=1.10.0')
+
+ def generate_fortran_dep_hack(self, crstr: str) -> None:
+ if self.use_dyndeps_for_fortran():
+ return
+ rule = f'FORTRAN_DEP_HACK{crstr}'
+ if mesonlib.is_windows():
+ cmd = ['cmd', '/C']
+ else:
+ cmd = ['true']
+ self.add_rule_comment(NinjaComment('''Workaround for these issues:
+https://groups.google.com/forum/#!topic/ninja-build/j-2RfBIOd_8
+https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
+ self.add_rule(NinjaRule(rule, cmd, [], 'Dep hack', extra='restat = 1'))
+
+ def generate_llvm_ir_compile_rule(self, compiler):
+ if self.created_llvm_ir_rule[compiler.for_machine]:
+ return
+ rule = self.get_compiler_rule_name('llvm_ir', compiler.for_machine)
+ command = compiler.get_exelist()
+ args = ['$ARGS'] + NinjaCommandArg.list(compiler.get_output_args('$out'), Quoting.none) + compiler.get_compile_only_args() + ['$in']
+ description = 'Compiling LLVM IR object $in'
+
+ options = self._rsp_options(compiler)
+
+ self.add_rule(NinjaRule(rule, command, args, description, **options))
+ self.created_llvm_ir_rule[compiler.for_machine] = True
+
+ def generate_compile_rule_for(self, langname, compiler):
+ if langname == 'java':
+ self.generate_java_compile_rule(compiler)
+ return
+ if langname == 'cs':
+ if self.environment.machines.matches_build_machine(compiler.for_machine):
+ self.generate_cs_compile_rule(compiler)
+ return
+ if langname == 'vala':
+ self.generate_vala_compile_rules(compiler)
+ return
+ if langname == 'rust':
+ self.generate_rust_compile_rules(compiler)
+ return
+ if langname == 'swift':
+ if self.environment.machines.matches_build_machine(compiler.for_machine):
+ self.generate_swift_compile_rules(compiler)
+ return
+ if langname == 'cython':
+ self.generate_cython_compile_rules(compiler)
+ return
+ crstr = self.get_rule_suffix(compiler.for_machine)
+ if langname == 'fortran':
+ self.generate_fortran_dep_hack(crstr)
+ rule = self.compiler_to_rule_name(compiler)
+ depargs = NinjaCommandArg.list(compiler.get_dependency_gen_args('$out', '$DEPFILE'), Quoting.none)
+ command = compiler.get_exelist()
+ args = ['$ARGS'] + depargs + NinjaCommandArg.list(compiler.get_output_args('$out'), Quoting.none) + compiler.get_compile_only_args() + ['$in']
+ description = f'Compiling {compiler.get_display_language()} object $out'
+ if compiler.get_argument_syntax() == 'msvc':
+ deps = 'msvc'
+ depfile = None
+ else:
+ deps = 'gcc'
+ depfile = '$DEPFILE'
+ options = self._rsp_options(compiler)
+ self.add_rule(NinjaRule(rule, command, args, description, **options,
+ deps=deps, depfile=depfile))
+
+ def generate_pch_rule_for(self, langname, compiler):
+ if langname not in {'c', 'cpp'}:
+ return
+ rule = self.compiler_to_pch_rule_name(compiler)
+ depargs = compiler.get_dependency_gen_args('$out', '$DEPFILE')
+
+ if compiler.get_argument_syntax() == 'msvc':
+ output = []
+ else:
+ output = NinjaCommandArg.list(compiler.get_output_args('$out'), Quoting.none)
+ command = compiler.get_exelist() + ['$ARGS'] + depargs + output + compiler.get_compile_only_args() + ['$in']
+ description = 'Precompiling header $in'
+ if compiler.get_argument_syntax() == 'msvc':
+ deps = 'msvc'
+ depfile = None
+ else:
+ deps = 'gcc'
+ depfile = '$DEPFILE'
+ self.add_rule(NinjaRule(rule, command, [], description, deps=deps,
+ depfile=depfile))
+
+ def generate_scanner_rules(self):
+ rulename = 'depscan'
+ if rulename in self.ruledict:
+ # Scanning command is the same for native and cross compilation.
+ return
+ command = self.environment.get_build_command() + \
+ ['--internal', 'depscan']
+ args = ['$picklefile', '$out', '$in']
+ description = 'Module scanner.'
+ rule = NinjaRule(rulename, command, args, description)
+ self.add_rule(rule)
+
+ def generate_compile_rules(self):
+ for for_machine in MachineChoice:
+ clist = self.environment.coredata.compilers[for_machine]
+ for langname, compiler in clist.items():
+ if compiler.get_id() == 'clang':
+ self.generate_llvm_ir_compile_rule(compiler)
+ self.generate_compile_rule_for(langname, compiler)
+ self.generate_pch_rule_for(langname, compiler)
+ for mode in compiler.get_modes():
+ self.generate_compile_rule_for(langname, mode)
+
+ def generate_generator_list_rules(self, target):
+ # CustomTargets have already written their rules and
+ # CustomTargetIndexes don't actually get generated, so write rules for
+ # GeneratedLists here
+ for genlist in target.get_generated_sources():
+ if isinstance(genlist, (build.CustomTarget, build.CustomTargetIndex)):
+ continue
+ self.generate_genlist_for_target(genlist, target)
+
+ def replace_paths(self, target, args, override_subdir=None):
+ if override_subdir:
+ source_target_dir = os.path.join(self.build_to_src, override_subdir)
+ else:
+ source_target_dir = self.get_target_source_dir(target)
+ relout = self.get_target_private_dir(target)
+ args = [x.replace("@SOURCE_DIR@", self.build_to_src).replace("@BUILD_DIR@", relout)
+ for x in args]
+ args = [x.replace("@CURRENT_SOURCE_DIR@", source_target_dir) for x in args]
+ args = [x.replace("@SOURCE_ROOT@", self.build_to_src).replace("@BUILD_ROOT@", '.')
+ for x in args]
+ args = [x.replace('\\', '/') for x in args]
+ return args
+
+ def generate_genlist_for_target(self, genlist: build.GeneratedList, target: build.BuildTarget) -> None:
+ generator = genlist.get_generator()
+ subdir = genlist.subdir
+ exe = generator.get_exe()
+ infilelist = genlist.get_inputs()
+ outfilelist = genlist.get_outputs()
+ extra_dependencies = self.get_custom_target_depend_files(genlist)
+ for i, curfile in enumerate(infilelist):
+ if len(generator.outputs) == 1:
+ sole_output = os.path.join(self.get_target_private_dir(target), outfilelist[i])
+ else:
+ sole_output = f'{curfile}'
+ infilename = curfile.rel_to_builddir(self.build_to_src)
+ base_args = generator.get_arglist(infilename)
+ outfiles = genlist.get_outputs_for(curfile)
+ outfiles = [os.path.join(self.get_target_private_dir(target), of) for of in outfiles]
+ if generator.depfile is None:
+ rulename = 'CUSTOM_COMMAND'
+ args = base_args
+ else:
+ rulename = 'CUSTOM_COMMAND_DEP'
+ depfilename = generator.get_dep_outname(infilename)
+ depfile = os.path.join(self.get_target_private_dir(target), depfilename)
+ args = [x.replace('@DEPFILE@', depfile) for x in base_args]
+ args = [x.replace("@INPUT@", infilename).replace('@OUTPUT@', sole_output)
+ for x in args]
+ args = self.replace_outputs(args, self.get_target_private_dir(target), outfilelist)
+ # We have consumed output files, so drop them from the list of remaining outputs.
+ if len(generator.outputs) > 1:
+ outfilelist = outfilelist[len(generator.outputs):]
+ args = self.replace_paths(target, args, override_subdir=subdir)
+ cmdlist, reason = self.as_meson_exe_cmdline(exe,
+ self.replace_extra_args(args, genlist),
+ capture=outfiles[0] if generator.capture else None)
+ abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target))
+ os.makedirs(abs_pdir, exist_ok=True)
+
+ elem = NinjaBuildElement(self.all_outputs, outfiles, rulename, infilename)
+ elem.add_dep([self.get_target_filename(x) for x in generator.depends])
+ if generator.depfile is not None:
+ elem.add_item('DEPFILE', depfile)
+ if len(extra_dependencies) > 0:
+ elem.add_dep(extra_dependencies)
+
+ if len(generator.outputs) == 1:
+ what = f'{sole_output!r}'
+ else:
+ # since there are multiple outputs, we log the source that caused the rebuild
+ what = f'from {sole_output!r}.'
+ if reason:
+ reason = f' (wrapped by meson {reason})'
+ elem.add_item('DESC', f'Generating {what}{reason}.')
+
+ if isinstance(exe, build.BuildTarget):
+ elem.add_dep(self.get_target_filename(exe))
+ elem.add_item('COMMAND', cmdlist)
+ self.add_build(elem)
+
+ def scan_fortran_module_outputs(self, target):
+ """
+ Find all module and submodule made available in a Fortran code file.
+ """
+ if self.use_dyndeps_for_fortran():
+ return
+ compiler = None
+ # TODO other compilers
+ for lang, c in self.environment.coredata.compilers.host.items():
+ if lang == 'fortran':
+ compiler = c
+ break
+ if compiler is None:
+ self.fortran_deps[target.get_basename()] = {}
+ return
+
+ modre = re.compile(FORTRAN_MODULE_PAT, re.IGNORECASE)
+ submodre = re.compile(FORTRAN_SUBMOD_PAT, re.IGNORECASE)
+ module_files = {}
+ submodule_files = {}
+ for s in target.get_sources():
+ # FIXME, does not work for Fortran sources generated by
+ # custom_target() and generator() as those are run after
+ # the configuration (configure_file() is OK)
+ if not compiler.can_compile(s):
+ continue
+ filename = s.absolute_path(self.environment.get_source_dir(),
+ self.environment.get_build_dir())
+ # Fortran keywords must be ASCII.
+ with open(filename, encoding='ascii', errors='ignore') as f:
+ for line in f:
+ modmatch = modre.match(line)
+ if modmatch is not None:
+ modname = modmatch.group(1).lower()
+ if modname in module_files:
+ raise InvalidArguments(
+ f'Namespace collision: module {modname} defined in '
+ f'two files {module_files[modname]} and {s}.')
+ module_files[modname] = s
+ else:
+ submodmatch = submodre.match(line)
+ if submodmatch is not None:
+ # '_' is arbitrarily used to distinguish submod from mod.
+ parents = submodmatch.group(1).lower().split(':')
+ submodname = parents[0] + '_' + submodmatch.group(2).lower()
+
+ if submodname in submodule_files:
+ raise InvalidArguments(
+ f'Namespace collision: submodule {submodname} defined in '
+ f'two files {submodule_files[submodname]} and {s}.')
+ submodule_files[submodname] = s
+
+ self.fortran_deps[target.get_basename()] = {**module_files, **submodule_files}
+
+ def get_fortran_deps(self, compiler: FortranCompiler, src: Path, target) -> T.List[str]:
+ """
+ Find all module and submodule needed by a Fortran target
+ """
+ if self.use_dyndeps_for_fortran():
+ return []
+
+ dirname = Path(self.get_target_private_dir(target))
+ tdeps = self.fortran_deps[target.get_basename()]
+ srcdir = Path(self.source_dir)
+
+ mod_files = _scan_fortran_file_deps(src, srcdir, dirname, tdeps, compiler)
+ return mod_files
+
+ def get_no_stdlib_link_args(self, target, linker):
+ if hasattr(linker, 'language') and linker.language in self.build.stdlibs[target.for_machine]:
+ return linker.get_no_stdlib_link_args()
+ return []
+
+ def get_compile_debugfile_args(self, compiler, target, objfile):
+ # The way MSVC uses PDB files is documented exactly nowhere so
+ # the following is what we have been able to decipher via
+ # reverse engineering.
+ #
+ # Each object file gets the path of its PDB file written
+ # inside it. This can be either the final PDB (for, say,
+ # foo.exe) or an object pdb (for foo.obj). If the former, then
+ # each compilation step locks the pdb file for writing, which
+ # is a bottleneck and object files from one target can not be
+ # used in a different target. The latter seems to be the
+ # sensible one (and what Unix does) but there is a catch. If
+ # you try to use precompiled headers MSVC will error out
+ # because both source and pch pdbs go in the same file and
+ # they must be the same.
+ #
+ # This means:
+ #
+ # - pch files must be compiled anew for every object file (negating
+ # the entire point of having them in the first place)
+ # - when using pch, output must go to the target pdb
+ #
+ # Since both of these are broken in some way, use the one that
+ # works for each target. This unfortunately means that you
+ # can't combine pch and object extraction in a single target.
+ #
+ # PDB files also lead to filename collisions. A target foo.exe
+ # has a corresponding foo.pdb. A shared library foo.dll _also_
+ # has pdb file called foo.pdb. So will a static library
+ # foo.lib, which clobbers both foo.pdb _and_ the dll file's
+ # export library called foo.lib (by default, currently we name
+ # them libfoo.a to avoidt this issue). You can give the files
+ # unique names such as foo_exe.pdb but VC also generates a
+ # bunch of other files which take their names from the target
+ # basename (i.e. "foo") and stomp on each other.
+ #
+ # CMake solves this problem by doing two things. First of all
+ # static libraries do not generate pdb files at
+ # all. Presumably you don't need them and VC is smart enough
+ # to look up the original data when linking (speculation, not
+ # tested). The second solution is that you can only have
+ # target named "foo" as an exe, shared lib _or_ static
+ # lib. This makes filename collisions not happen. The downside
+ # is that you can't have an executable foo that uses a shared
+ # library libfoo.so, which is a common idiom on Unix.
+ #
+ # If you feel that the above is completely wrong and all of
+ # this is actually doable, please send patches.
+
+ if target.has_pch():
+ tfilename = self.get_target_filename_abs(target)
+ return compiler.get_compile_debugfile_args(tfilename, pch=True)
+ else:
+ return compiler.get_compile_debugfile_args(objfile, pch=False)
+
+ def get_link_debugfile_name(self, linker, target, outname):
+ return linker.get_link_debugfile_name(outname)
+
+ def get_link_debugfile_args(self, linker, target, outname):
+ return linker.get_link_debugfile_args(outname)
+
+ def generate_llvm_ir_compile(self, target, src):
+ base_proxy = target.get_options()
+ compiler = get_compiler_for_source(target.compilers.values(), src)
+ commands = compiler.compiler_args()
+ # Compiler args for compiling this target
+ commands += compilers.get_base_compile_args(base_proxy, compiler)
+ if isinstance(src, File):
+ if src.is_built:
+ src_filename = os.path.join(src.subdir, src.fname)
+ else:
+ src_filename = src.fname
+ elif os.path.isabs(src):
+ src_filename = os.path.basename(src)
+ else:
+ src_filename = src
+ obj_basename = self.canonicalize_filename(src_filename)
+ rel_obj = os.path.join(self.get_target_private_dir(target), obj_basename)
+ rel_obj += '.' + self.environment.machines[target.for_machine].get_object_suffix()
+ commands += self.get_compile_debugfile_args(compiler, target, rel_obj)
+ if isinstance(src, File) and src.is_built:
+ rel_src = src.fname
+ elif isinstance(src, File):
+ rel_src = src.rel_to_builddir(self.build_to_src)
+ else:
+ raise InvalidArguments(f'Invalid source type: {src!r}')
+ # Write the Ninja build command
+ compiler_name = self.get_compiler_rule_name('llvm_ir', compiler.for_machine)
+ element = NinjaBuildElement(self.all_outputs, rel_obj, compiler_name, rel_src)
+ element.add_item('ARGS', commands)
+ self.add_build(element)
+ return (rel_obj, rel_src)
+
+ @lru_cache(maxsize=None)
+ def generate_inc_dir(self, compiler: 'Compiler', d: str, basedir: str, is_system: bool) -> \
+ T.Tuple['ImmutableListProtocol[str]', 'ImmutableListProtocol[str]']:
+ # Avoid superfluous '/.' at the end of paths when d is '.'
+ if d not in ('', '.'):
+ expdir = os.path.normpath(os.path.join(basedir, d))
+ else:
+ expdir = basedir
+ srctreedir = os.path.normpath(os.path.join(self.build_to_src, expdir))
+ sargs = compiler.get_include_args(srctreedir, is_system)
+ # There may be include dirs where a build directory has not been
+ # created for some source dir. For example if someone does this:
+ #
+ # inc = include_directories('foo/bar/baz')
+ #
+ # But never subdir()s into the actual dir.
+ if os.path.isdir(os.path.join(self.environment.get_build_dir(), expdir)):
+ bargs = compiler.get_include_args(expdir, is_system)
+ else:
+ bargs = []
+ return (sargs, bargs)
+
+ def _generate_single_compile(self, target: build.BuildTarget, compiler: 'Compiler',
+ is_generated: bool = False) -> 'CompilerArgs':
+ commands = self._generate_single_compile_base_args(target, compiler)
+ commands += self._generate_single_compile_target_args(target, compiler, is_generated)
+ return commands
+
+ def _generate_single_compile_base_args(self, target: build.BuildTarget, compiler: 'Compiler') -> 'CompilerArgs':
+ base_proxy = target.get_options()
+ # Create an empty commands list, and start adding arguments from
+ # various sources in the order in which they must override each other
+ commands = compiler.compiler_args()
+ # Start with symbol visibility.
+ commands += compiler.gnu_symbol_visibility_args(target.gnu_symbol_visibility)
+ # Add compiler args for compiling this target derived from 'base' build
+ # options passed on the command-line, in default_options, etc.
+ # These have the lowest priority.
+ commands += compilers.get_base_compile_args(base_proxy,
+ compiler)
+ return commands
+
+ @lru_cache(maxsize=None)
+ def _generate_single_compile_target_args(self, target: build.BuildTarget, compiler: 'Compiler',
+ is_generated: bool = False) -> 'ImmutableListProtocol[str]':
+ # The code generated by valac is usually crap and has tons of unused
+ # variables and such, so disable warnings for Vala C sources.
+ no_warn_args = is_generated == 'vala'
+ # Add compiler args and include paths from several sources; defaults,
+ # build options, external dependencies, etc.
+ commands = self.generate_basic_compiler_args(target, compiler, no_warn_args)
+ # Add custom target dirs as includes automatically, but before
+ # target-specific include directories.
+ if target.implicit_include_directories:
+ commands += self.get_custom_target_dir_include_args(target, compiler)
+ # Add include dirs from the `include_directories:` kwarg on the target
+ # and from `include_directories:` of internal deps of the target.
+ #
+ # Target include dirs should override internal deps include dirs.
+ # This is handled in BuildTarget.process_kwargs()
+ #
+ # Include dirs from internal deps should override include dirs from
+ # external deps and must maintain the order in which they are specified.
+ # Hence, we must reverse the list so that the order is preserved.
+ for i in reversed(target.get_include_dirs()):
+ basedir = i.get_curdir()
+ # We should iterate include dirs in reversed orders because
+ # -Ipath will add to begin of array. And without reverse
+ # flags will be added in reversed order.
+ for d in reversed(i.get_incdirs()):
+ # Add source subdir first so that the build subdir overrides it
+ (compile_obj, includeargs) = self.generate_inc_dir(compiler, d, basedir, i.is_system)
+ commands += compile_obj
+ commands += includeargs
+ for d in i.get_extra_build_dirs():
+ commands += compiler.get_include_args(d, i.is_system)
+ # Add per-target compile args, f.ex, `c_args : ['-DFOO']`. We set these
+ # near the end since these are supposed to override everything else.
+ commands += self.escape_extra_args(target.get_extra_args(compiler.get_language()))
+
+ # D specific additional flags
+ if compiler.language == 'd':
+ commands += compiler.get_feature_args(target.d_features, self.build_to_src)
+
+ # Add source dir and build dir. Project-specific and target-specific
+ # include paths must override per-target compile args, include paths
+ # from external dependencies, internal dependencies, and from
+ # per-target `include_directories:`
+ #
+ # We prefer headers in the build dir over the source dir since, for
+ # instance, the user might have an srcdir == builddir Autotools build
+ # in their source tree. Many projects that are moving to Meson have
+ # both Meson and Autotools in parallel as part of the transition.
+ if target.implicit_include_directories:
+ commands += self.get_source_dir_include_args(target, compiler)
+ if target.implicit_include_directories:
+ commands += self.get_build_dir_include_args(target, compiler)
+ # Finally add the private dir for the target to the include path. This
+ # must override everything else and must be the final path added.
+ commands += compiler.get_include_args(self.get_target_private_dir(target), False)
+ return commands
+
+ def generate_single_compile(self, target: build.BuildTarget, src,
+ is_generated=False, header_deps=None,
+ order_deps: T.Optional[T.List[str]] = None,
+ extra_args: T.Optional[T.List[str]] = None) -> None:
+ """
+ Compiles C/C++, ObjC/ObjC++, Fortran, and D sources
+ """
+ header_deps = header_deps if header_deps is not None else []
+ order_deps = order_deps if order_deps is not None else []
+
+ if isinstance(src, str) and src.endswith('.h'):
+ raise AssertionError(f'BUG: sources should not contain headers {src!r}')
+
+ compiler = get_compiler_for_source(target.compilers.values(), src)
+ commands = self._generate_single_compile_base_args(target, compiler)
+
+ # Include PCH header as first thing as it must be the first one or it will be
+ # ignored by gcc https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100462
+ if self.environment.coredata.options.get(OptionKey('b_pch')) and is_generated != 'pch':
+ commands += self.get_pch_include_args(compiler, target)
+
+ commands += self._generate_single_compile_target_args(target, compiler, is_generated)
+ commands = commands.compiler.compiler_args(commands)
+
+ # Create introspection information
+ if is_generated is False:
+ self.create_target_source_introspection(target, compiler, commands, [src], [])
+ else:
+ self.create_target_source_introspection(target, compiler, commands, [], [src])
+
+ build_dir = self.environment.get_build_dir()
+ if isinstance(src, File):
+ rel_src = src.rel_to_builddir(self.build_to_src)
+ if os.path.isabs(rel_src):
+ # Source files may not be from the source directory if they originate in source-only libraries,
+ # so we can't assert that the absolute path is anywhere in particular.
+ if src.is_built:
+ assert rel_src.startswith(build_dir)
+ rel_src = rel_src[len(build_dir) + 1:]
+ elif is_generated:
+ raise AssertionError(f'BUG: broken generated source file handling for {src!r}')
+ else:
+ raise InvalidArguments(f'Invalid source type: {src!r}')
+ obj_basename = self.object_filename_from_source(target, src)
+ rel_obj = os.path.join(self.get_target_private_dir(target), obj_basename)
+ dep_file = compiler.depfile_for_object(rel_obj)
+
+ # Add MSVC debug file generation compile flags: /Fd /FS
+ commands += self.get_compile_debugfile_args(compiler, target, rel_obj)
+
+ # PCH handling
+ if self.environment.coredata.options.get(OptionKey('b_pch')):
+ pchlist = target.get_pch(compiler.language)
+ else:
+ pchlist = []
+ if not pchlist:
+ pch_dep = []
+ elif compiler.id == 'intel':
+ pch_dep = []
+ else:
+ arr = []
+ i = os.path.join(self.get_target_private_dir(target), compiler.get_pch_name(pchlist[0]))
+ arr.append(i)
+ pch_dep = arr
+
+ compiler_name = self.compiler_to_rule_name(compiler)
+ extra_deps = []
+ if compiler.get_language() == 'fortran':
+ # Can't read source file to scan for deps if it's generated later
+ # at build-time. Skip scanning for deps, and just set the module
+ # outdir argument instead.
+ # https://github.com/mesonbuild/meson/issues/1348
+ if not is_generated:
+ abs_src = Path(build_dir) / rel_src
+ extra_deps += self.get_fortran_deps(compiler, abs_src, target)
+ if not self.use_dyndeps_for_fortran():
+ # Dependency hack. Remove once multiple outputs in Ninja is fixed:
+ # https://groups.google.com/forum/#!topic/ninja-build/j-2RfBIOd_8
+ for modname, srcfile in self.fortran_deps[target.get_basename()].items():
+ modfile = os.path.join(self.get_target_private_dir(target),
+ compiler.module_name_to_filename(modname))
+
+ if srcfile == src:
+ crstr = self.get_rule_suffix(target.for_machine)
+ depelem = NinjaBuildElement(self.all_outputs,
+ modfile,
+ 'FORTRAN_DEP_HACK' + crstr,
+ rel_obj)
+ self.add_build(depelem)
+ commands += compiler.get_module_outdir_args(self.get_target_private_dir(target))
+ if extra_args is not None:
+ commands.extend(extra_args)
+
+ element = NinjaBuildElement(self.all_outputs, rel_obj, compiler_name, rel_src)
+ self.add_header_deps(target, element, header_deps)
+ for d in extra_deps:
+ element.add_dep(d)
+ for d in order_deps:
+ if isinstance(d, File):
+ d = d.rel_to_builddir(self.build_to_src)
+ elif not self.has_dir_part(d):
+ d = os.path.join(self.get_target_private_dir(target), d)
+ element.add_orderdep(d)
+ element.add_dep(pch_dep)
+ for i in self.get_fortran_orderdeps(target, compiler):
+ element.add_orderdep(i)
+ if dep_file:
+ element.add_item('DEPFILE', dep_file)
+ element.add_item('ARGS', commands)
+
+ self.add_dependency_scanner_entries_to_element(target, compiler, element, src)
+ self.add_build(element)
+ assert isinstance(rel_obj, str)
+ assert isinstance(rel_src, str)
+ return (rel_obj, rel_src.replace('\\', '/'))
+
+ def add_dependency_scanner_entries_to_element(self, target, compiler, element, src):
+ if not self.should_use_dyndeps_for_target(target):
+ return
+ extension = os.path.splitext(src.fname)[1][1:]
+ if extension != 'C':
+ extension = extension.lower()
+ if not (extension in compilers.lang_suffixes['fortran'] or extension in compilers.lang_suffixes['cpp']):
+ return
+ dep_scan_file = self.get_dep_scan_file_for(target)
+ element.add_item('dyndep', dep_scan_file)
+ element.add_orderdep(dep_scan_file)
+
+ def get_dep_scan_file_for(self, target):
+ return os.path.join(self.get_target_private_dir(target), 'depscan.dd')
+
+ def add_header_deps(self, target, ninja_element, header_deps):
+ for d in header_deps:
+ if isinstance(d, File):
+ d = d.rel_to_builddir(self.build_to_src)
+ elif not self.has_dir_part(d):
+ d = os.path.join(self.get_target_private_dir(target), d)
+ ninja_element.add_dep(d)
+
+ def has_dir_part(self, fname):
+ # FIXME FIXME: The usage of this is a terrible and unreliable hack
+ if isinstance(fname, File):
+ return fname.subdir != ''
+ return has_path_sep(fname)
+
+ # Fortran is a bit weird (again). When you link against a library, just compiling a source file
+ # requires the mod files that are output when single files are built. To do this right we would need to
+ # scan all inputs and write out explicit deps for each file. That is stoo slow and too much effort so
+ # instead just have an ordered dependency on the library. This ensures all required mod files are created.
+ # The real deps are then detected via dep file generation from the compiler. This breaks on compilers that
+ # produce incorrect dep files but such is life.
+ def get_fortran_orderdeps(self, target, compiler):
+ if compiler.language != 'fortran':
+ return []
+ return [
+ os.path.join(self.get_target_dir(lt), lt.get_filename())
+ for lt in itertools.chain(target.link_targets, target.link_whole_targets)
+ ]
+
+ def generate_msvc_pch_command(self, target, compiler, pch):
+ header = pch[0]
+ pchname = compiler.get_pch_name(header)
+ dst = os.path.join(self.get_target_private_dir(target), pchname)
+
+ commands = []
+ commands += self.generate_basic_compiler_args(target, compiler)
+
+ if len(pch) == 1:
+ # Auto generate PCH.
+ source = self.create_msvc_pch_implementation(target, compiler.get_language(), pch[0])
+ pch_header_dir = os.path.dirname(os.path.join(self.build_to_src, target.get_source_subdir(), header))
+ commands += compiler.get_include_args(pch_header_dir, False)
+ else:
+ source = os.path.join(self.build_to_src, target.get_source_subdir(), pch[1])
+
+ just_name = os.path.basename(header)
+ (objname, pch_args) = compiler.gen_pch_args(just_name, source, dst)
+ commands += pch_args
+ commands += self._generate_single_compile(target, compiler)
+ commands += self.get_compile_debugfile_args(compiler, target, objname)
+ dep = dst + '.' + compiler.get_depfile_suffix()
+ return commands, dep, dst, [objname], source
+
+ def generate_gcc_pch_command(self, target, compiler, pch):
+ commands = self._generate_single_compile(target, compiler)
+ if pch.split('.')[-1] == 'h' and compiler.language == 'cpp':
+ # Explicitly compile pch headers as C++. If Clang is invoked in C++ mode, it actually warns if
+ # this option is not set, and for gcc it also makes sense to use it.
+ commands += ['-x', 'c++-header']
+ dst = os.path.join(self.get_target_private_dir(target),
+ os.path.basename(pch) + '.' + compiler.get_pch_suffix())
+ dep = dst + '.' + compiler.get_depfile_suffix()
+ return commands, dep, dst, [] # Gcc does not create an object file during pch generation.
+
+ def generate_pch(self, target, header_deps=None):
+ header_deps = header_deps if header_deps is not None else []
+ pch_objects = []
+ for lang in ['c', 'cpp']:
+ pch = target.get_pch(lang)
+ if not pch:
+ continue
+ if not has_path_sep(pch[0]) or not has_path_sep(pch[-1]):
+ msg = f'Precompiled header of {target.get_basename()!r} must not be in the same ' \
+ 'directory as source, please put it in a subdirectory.'
+ raise InvalidArguments(msg)
+ compiler: Compiler = target.compilers[lang]
+ if compiler.get_argument_syntax() == 'msvc':
+ (commands, dep, dst, objs, src) = self.generate_msvc_pch_command(target, compiler, pch)
+ extradep = os.path.join(self.build_to_src, target.get_source_subdir(), pch[0])
+ elif compiler.id == 'intel':
+ # Intel generates on target generation
+ continue
+ else:
+ src = os.path.join(self.build_to_src, target.get_source_subdir(), pch[0])
+ (commands, dep, dst, objs) = self.generate_gcc_pch_command(target, compiler, pch[0])
+ extradep = None
+ pch_objects += objs
+ rulename = self.compiler_to_pch_rule_name(compiler)
+ elem = NinjaBuildElement(self.all_outputs, dst, rulename, src)
+ if extradep is not None:
+ elem.add_dep(extradep)
+ self.add_header_deps(target, elem, header_deps)
+ elem.add_item('ARGS', commands)
+ elem.add_item('DEPFILE', dep)
+ self.add_build(elem)
+ return pch_objects
+
+ def get_target_shsym_filename(self, target):
+ # Always name the .symbols file after the primary build output because it always exists
+ targetdir = self.get_target_private_dir(target)
+ return os.path.join(targetdir, target.get_filename() + '.symbols')
+
+ def generate_shsym(self, target):
+ target_file = self.get_target_filename(target)
+ symname = self.get_target_shsym_filename(target)
+ elem = NinjaBuildElement(self.all_outputs, symname, 'SHSYM', target_file)
+ # The library we will actually link to, which is an import library on Windows (not the DLL)
+ elem.add_item('IMPLIB', self.get_target_filename_for_linking(target))
+ if self.environment.is_cross_build():
+ elem.add_item('CROSS', '--cross-host=' + self.environment.machines[target.for_machine].system)
+ self.add_build(elem)
+
+ def get_import_filename(self, target):
+ return os.path.join(self.get_target_dir(target), target.import_filename)
+
+ def get_target_type_link_args(self, target, linker):
+ commands = []
+ if isinstance(target, build.Executable):
+ # Currently only used with the Swift compiler to add '-emit-executable'
+ commands += linker.get_std_exe_link_args()
+ # If export_dynamic, add the appropriate linker arguments
+ if target.export_dynamic:
+ commands += linker.gen_export_dynamic_link_args(self.environment)
+ # If implib, and that's significant on this platform (i.e. Windows using either GCC or Visual Studio)
+ if target.import_filename:
+ commands += linker.gen_import_library_args(self.get_import_filename(target))
+ if target.pie:
+ commands += linker.get_pie_link_args()
+ elif isinstance(target, build.SharedLibrary):
+ if isinstance(target, build.SharedModule):
+ options = self.environment.coredata.options
+ commands += linker.get_std_shared_module_link_args(options)
+ else:
+ commands += linker.get_std_shared_lib_link_args()
+ # All shared libraries are PIC
+ commands += linker.get_pic_args()
+ if not isinstance(target, build.SharedModule) or target.force_soname:
+ # Add -Wl,-soname arguments on Linux, -install_name on OS X
+ commands += linker.get_soname_args(
+ self.environment, target.prefix, target.name, target.suffix,
+ target.soversion, target.darwin_versions)
+ # This is only visited when building for Windows using either GCC or Visual Studio
+ if target.vs_module_defs and hasattr(linker, 'gen_vs_module_defs_args'):
+ commands += linker.gen_vs_module_defs_args(target.vs_module_defs.rel_to_builddir(self.build_to_src))
+ # This is only visited when building for Windows using either GCC or Visual Studio
+ if target.import_filename:
+ commands += linker.gen_import_library_args(self.get_import_filename(target))
+ elif isinstance(target, build.StaticLibrary):
+ commands += linker.get_std_link_args(self.environment, not target.should_install())
+ else:
+ raise RuntimeError('Unknown build target type.')
+ return commands
+
+ def get_target_type_link_args_post_dependencies(self, target, linker):
+ commands = []
+ if isinstance(target, build.Executable):
+ # If gui_app is significant on this platform, add the appropriate linker arguments.
+ # Unfortunately this can't be done in get_target_type_link_args, because some misguided
+ # libraries (such as SDL2) add -mwindows to their link flags.
+ m = self.environment.machines[target.for_machine]
+
+ if m.is_windows() or m.is_cygwin():
+ if target.gui_app is not None:
+ commands += linker.get_gui_app_args(target.gui_app)
+ else:
+ commands += linker.get_win_subsystem_args(target.win_subsystem)
+ return commands
+
+ def get_link_whole_args(self, linker, target):
+ use_custom = False
+ if linker.id == 'msvc':
+ # Expand our object lists manually if we are on pre-Visual Studio 2015 Update 2
+ # (incidentally, the "linker" here actually refers to cl.exe)
+ if mesonlib.version_compare(linker.version, '<19.00.23918'):
+ use_custom = True
+
+ if use_custom:
+ objects_from_static_libs: T.List[ExtractedObjects] = []
+ for dep in target.link_whole_targets:
+ l = dep.extract_all_objects(False)
+ objects_from_static_libs += self.determine_ext_objs(l, '')
+ objects_from_static_libs.extend(self.flatten_object_list(dep)[0])
+
+ return objects_from_static_libs
+ else:
+ target_args = self.build_target_link_arguments(linker, target.link_whole_targets)
+ return linker.get_link_whole_for(target_args) if target_args else []
+
+ @lru_cache(maxsize=None)
+ def guess_library_absolute_path(self, linker, libname, search_dirs, patterns) -> Path:
+ from ..compilers.c import CCompiler
+ for d in search_dirs:
+ for p in patterns:
+ trial = CCompiler._get_trials_from_pattern(p, d, libname)
+ if not trial:
+ continue
+ trial = CCompiler._get_file_from_list(self.environment, trial)
+ if not trial:
+ continue
+ # Return the first result
+ return trial
+
+ def guess_external_link_dependencies(self, linker, target, commands, internal):
+ # Ideally the linker would generate dependency information that could be used.
+ # But that has 2 problems:
+ # * currently ld can not create dependency information in a way that ninja can use:
+ # https://sourceware.org/bugzilla/show_bug.cgi?id=22843
+ # * Meson optimizes libraries from the same build using the symbol extractor.
+ # Just letting ninja use ld generated dependencies would undo this optimization.
+ search_dirs = OrderedSet()
+ libs = OrderedSet()
+ absolute_libs = []
+
+ build_dir = self.environment.get_build_dir()
+ # the following loop sometimes consumes two items from command in one pass
+ it = iter(linker.native_args_to_unix(commands))
+ for item in it:
+ if item in internal and not item.startswith('-'):
+ continue
+
+ if item.startswith('-L'):
+ if len(item) > 2:
+ path = item[2:]
+ else:
+ try:
+ path = next(it)
+ except StopIteration:
+ mlog.warning("Generated linker command has -L argument without following path")
+ break
+ if not os.path.isabs(path):
+ path = os.path.join(build_dir, path)
+ search_dirs.add(path)
+ elif item.startswith('-l'):
+ if len(item) > 2:
+ lib = item[2:]
+ else:
+ try:
+ lib = next(it)
+ except StopIteration:
+ mlog.warning("Generated linker command has '-l' argument without following library name")
+ break
+ libs.add(lib)
+ elif os.path.isabs(item) and self.environment.is_library(item) and os.path.isfile(item):
+ absolute_libs.append(item)
+
+ guessed_dependencies = []
+ # TODO The get_library_naming requirement currently excludes link targets that use d or fortran as their main linker
+ try:
+ static_patterns = linker.get_library_naming(self.environment, LibType.STATIC, strict=True)
+ shared_patterns = linker.get_library_naming(self.environment, LibType.SHARED, strict=True)
+ search_dirs = tuple(search_dirs) + tuple(linker.get_library_dirs(self.environment))
+ for libname in libs:
+ # be conservative and record most likely shared and static resolution, because we don't know exactly
+ # which one the linker will prefer
+ staticlibs = self.guess_library_absolute_path(linker, libname,
+ search_dirs, static_patterns)
+ sharedlibs = self.guess_library_absolute_path(linker, libname,
+ search_dirs, shared_patterns)
+ if staticlibs:
+ guessed_dependencies.append(staticlibs.resolve().as_posix())
+ if sharedlibs:
+ guessed_dependencies.append(sharedlibs.resolve().as_posix())
+ except (mesonlib.MesonException, AttributeError) as e:
+ if 'get_library_naming' not in str(e):
+ raise
+
+ return guessed_dependencies + absolute_libs
+
+ def generate_prelink(self, target, obj_list):
+ assert isinstance(target, build.StaticLibrary)
+ prelink_name = os.path.join(self.get_target_private_dir(target), target.name + '-prelink.o')
+ elem = NinjaBuildElement(self.all_outputs, [prelink_name], 'CUSTOM_COMMAND', obj_list)
+
+ prelinker = target.get_prelinker()
+ cmd = prelinker.exelist[:]
+ cmd += prelinker.get_prelink_args(prelink_name, obj_list)
+
+ cmd = self.replace_paths(target, cmd)
+ elem.add_item('COMMAND', cmd)
+ elem.add_item('description', f'Prelinking {prelink_name}.')
+ self.add_build(elem)
+ return [prelink_name]
+
+ def generate_link(self, target: build.BuildTarget, outname, obj_list, linker: T.Union['Compiler', 'StaticLinker'], extra_args=None, stdlib_args=None):
+ extra_args = extra_args if extra_args is not None else []
+ stdlib_args = stdlib_args if stdlib_args is not None else []
+ implicit_outs = []
+ if isinstance(target, build.StaticLibrary):
+ linker_base = 'STATIC'
+ else:
+ linker_base = linker.get_language() # Fixme.
+ if isinstance(target, build.SharedLibrary):
+ self.generate_shsym(target)
+ crstr = self.get_rule_suffix(target.for_machine)
+ linker_rule = linker_base + '_LINKER' + crstr
+ # Create an empty commands list, and start adding link arguments from
+ # various sources in the order in which they must override each other
+ # starting from hard-coded defaults followed by build options and so on.
+ #
+ # Once all the linker options have been passed, we will start passing
+ # libraries and library paths from internal and external sources.
+ commands = linker.compiler_args()
+ # First, the trivial ones that are impossible to override.
+ #
+ # Add linker args for linking this target derived from 'base' build
+ # options passed on the command-line, in default_options, etc.
+ # These have the lowest priority.
+ if isinstance(target, build.StaticLibrary):
+ commands += linker.get_base_link_args(target.get_options())
+ else:
+ commands += compilers.get_base_link_args(target.get_options(),
+ linker,
+ isinstance(target, build.SharedModule),
+ self.environment.get_build_dir())
+ # Add -nostdlib if needed; can't be overridden
+ commands += self.get_no_stdlib_link_args(target, linker)
+ # Add things like /NOLOGO; usually can't be overridden
+ commands += linker.get_linker_always_args()
+ # Add buildtype linker args: optimization level, etc.
+ commands += linker.get_buildtype_linker_args(target.get_option(OptionKey('buildtype')))
+ # Add /DEBUG and the pdb filename when using MSVC
+ if target.get_option(OptionKey('debug')):
+ commands += self.get_link_debugfile_args(linker, target, outname)
+ debugfile = self.get_link_debugfile_name(linker, target, outname)
+ if debugfile is not None:
+ implicit_outs += [debugfile]
+ # Add link args specific to this BuildTarget type, such as soname args,
+ # PIC, import library generation, etc.
+ commands += self.get_target_type_link_args(target, linker)
+ # Archives that are copied wholesale in the result. Must be before any
+ # other link targets so missing symbols from whole archives are found in those.
+ if not isinstance(target, build.StaticLibrary):
+ commands += self.get_link_whole_args(linker, target)
+
+ if not isinstance(target, build.StaticLibrary):
+ # Add link args added using add_project_link_arguments()
+ commands += self.build.get_project_link_args(linker, target.subproject, target.for_machine)
+ # Add link args added using add_global_link_arguments()
+ # These override per-project link arguments
+ commands += self.build.get_global_link_args(linker, target.for_machine)
+ # Link args added from the env: LDFLAGS. We want these to override
+ # all the defaults but not the per-target link args.
+ commands += self.environment.coredata.get_external_link_args(target.for_machine, linker.get_language())
+
+ # Now we will add libraries and library paths from various sources
+
+ # Set runtime-paths so we can run executables without needing to set
+ # LD_LIBRARY_PATH, etc in the environment. Doesn't work on Windows.
+ if has_path_sep(target.name):
+ # Target names really should not have slashes in them, but
+ # unfortunately we did not check for that and some downstream projects
+ # now have them. Once slashes are forbidden, remove this bit.
+ target_slashname_workaround_dir = os.path.join(
+ os.path.dirname(target.name),
+ self.get_target_dir(target))
+ else:
+ target_slashname_workaround_dir = self.get_target_dir(target)
+ (rpath_args, target.rpath_dirs_to_remove) = (
+ linker.build_rpath_args(self.environment,
+ self.environment.get_build_dir(),
+ target_slashname_workaround_dir,
+ self.determine_rpath_dirs(target),
+ target.build_rpath,
+ target.install_rpath))
+ commands += rpath_args
+
+ # Add link args to link to all internal libraries (link_with:) and
+ # internal dependencies needed by this target.
+ if linker_base == 'STATIC':
+ # Link arguments of static libraries are not put in the command
+ # line of the library. They are instead appended to the command
+ # line where the static library is used.
+ dependencies = []
+ else:
+ dependencies = target.get_dependencies()
+ internal = self.build_target_link_arguments(linker, dependencies)
+ commands += internal
+ # Only non-static built targets need link args and link dependencies
+ if not isinstance(target, build.StaticLibrary):
+ # For 'automagic' deps: Boost and GTest. Also dependency('threads').
+ # pkg-config puts the thread flags itself via `Cflags:`
+
+ commands += linker.get_target_link_args(target)
+ # External deps must be last because target link libraries may depend on them.
+ for dep in target.get_external_deps():
+ # Extend without reordering or de-dup to preserve `-L -l` sets
+ # https://github.com/mesonbuild/meson/issues/1718
+ commands.extend_preserving_lflags(linker.get_dependency_link_args(dep))
+ for d in target.get_dependencies():
+ if isinstance(d, build.StaticLibrary):
+ for dep in d.get_external_deps():
+ commands.extend_preserving_lflags(linker.get_dependency_link_args(dep))
+
+ # Add link args specific to this BuildTarget type that must not be overridden by dependencies
+ commands += self.get_target_type_link_args_post_dependencies(target, linker)
+
+ # Add link args for c_* or cpp_* build options. Currently this only
+ # adds c_winlibs and cpp_winlibs when building for Windows. This needs
+ # to be after all internal and external libraries so that unresolved
+ # symbols from those can be found here. This is needed when the
+ # *_winlibs that we want to link to are static mingw64 libraries.
+ if isinstance(linker, Compiler):
+ # The static linker doesn't know what language it is building, so we
+ # don't know what option. Fortunately, it doesn't care to see the
+ # language-specific options either.
+ #
+ # We shouldn't check whether we are making a static library, because
+ # in the LTO case we do use a real compiler here.
+ commands += linker.get_option_link_args(self.environment.coredata.options)
+
+ dep_targets = []
+ dep_targets.extend(self.guess_external_link_dependencies(linker, target, commands, internal))
+
+ # Add libraries generated by custom targets
+ custom_target_libraries = self.get_custom_target_provided_libraries(target)
+ commands += extra_args
+ commands += custom_target_libraries
+ commands += stdlib_args # Standard library arguments go last, because they never depend on anything.
+ dep_targets.extend([self.get_dependency_filename(t) for t in dependencies])
+ dep_targets.extend([self.get_dependency_filename(t)
+ for t in target.link_depends])
+ elem = NinjaBuildElement(self.all_outputs, outname, linker_rule, obj_list, implicit_outs=implicit_outs)
+ elem.add_dep(dep_targets + custom_target_libraries)
+ elem.add_item('LINK_ARGS', commands)
+ return elem
+
+ def get_dependency_filename(self, t):
+ if isinstance(t, build.SharedLibrary):
+ return self.get_target_shsym_filename(t)
+ elif isinstance(t, mesonlib.File):
+ if t.is_built:
+ return t.relative_name()
+ else:
+ return t.absolute_path(self.environment.get_source_dir(),
+ self.environment.get_build_dir())
+ return self.get_target_filename(t)
+
+ def generate_shlib_aliases(self, target, outdir):
+ for alias, to, tag in target.get_aliases():
+ aliasfile = os.path.join(self.environment.get_build_dir(), outdir, alias)
+ try:
+ os.remove(aliasfile)
+ except Exception:
+ pass
+ try:
+ os.symlink(to, aliasfile)
+ except NotImplementedError:
+ mlog.debug("Library versioning disabled because symlinks are not supported.")
+ except OSError:
+ mlog.debug("Library versioning disabled because we do not have symlink creation privileges.")
+
+ def generate_custom_target_clean(self, trees: T.List[str]) -> str:
+ e = self.create_phony_target(self.all_outputs, 'clean-ctlist', 'CUSTOM_COMMAND', 'PHONY')
+ d = CleanTrees(self.environment.get_build_dir(), trees)
+ d_file = os.path.join(self.environment.get_scratch_dir(), 'cleantrees.dat')
+ e.add_item('COMMAND', self.environment.get_build_command() + ['--internal', 'cleantrees', d_file])
+ e.add_item('description', 'Cleaning custom target directories')
+ self.add_build(e)
+ # Write out the data file passed to the script
+ with open(d_file, 'wb') as ofile:
+ pickle.dump(d, ofile)
+ return 'clean-ctlist'
+
+ def generate_gcov_clean(self):
+ gcno_elem = self.create_phony_target(self.all_outputs, 'clean-gcno', 'CUSTOM_COMMAND', 'PHONY')
+ gcno_elem.add_item('COMMAND', mesonlib.get_meson_command() + ['--internal', 'delwithsuffix', '.', 'gcno'])
+ gcno_elem.add_item('description', 'Deleting gcno files')
+ self.add_build(gcno_elem)
+
+ gcda_elem = self.create_phony_target(self.all_outputs, 'clean-gcda', 'CUSTOM_COMMAND', 'PHONY')
+ gcda_elem.add_item('COMMAND', mesonlib.get_meson_command() + ['--internal', 'delwithsuffix', '.', 'gcda'])
+ gcda_elem.add_item('description', 'Deleting gcda files')
+ self.add_build(gcda_elem)
+
+ def get_user_option_args(self):
+ cmds = []
+ for (k, v) in self.environment.coredata.options.items():
+ if k.is_project():
+ cmds.append('-D' + str(k) + '=' + (v.value if isinstance(v.value, str) else str(v.value).lower()))
+ # The order of these arguments must be the same between runs of Meson
+ # to ensure reproducible output. The order we pass them shouldn't
+ # affect behavior in any other way.
+ return sorted(cmds)
+
+ def generate_dist(self):
+ elem = self.create_phony_target(self.all_outputs, 'dist', 'CUSTOM_COMMAND', 'PHONY')
+ elem.add_item('DESC', 'Creating source packages')
+ elem.add_item('COMMAND', self.environment.get_build_command() + ['dist'])
+ elem.add_item('pool', 'console')
+ self.add_build(elem)
+
+ def generate_scanbuild(self):
+ if not environment.detect_scanbuild():
+ return
+ if 'scan-build' in self.all_outputs:
+ return
+ cmd = self.environment.get_build_command() + \
+ ['--internal', 'scanbuild', self.environment.source_dir, self.environment.build_dir] + \
+ self.environment.get_build_command() + self.get_user_option_args()
+ elem = self.create_phony_target(self.all_outputs, 'scan-build', 'CUSTOM_COMMAND', 'PHONY')
+ elem.add_item('COMMAND', cmd)
+ elem.add_item('pool', 'console')
+ self.add_build(elem)
+
+ def generate_clangtool(self, name, extra_arg=None):
+ target_name = 'clang-' + name
+ extra_args = []
+ if extra_arg:
+ target_name += f'-{extra_arg}'
+ extra_args.append(f'--{extra_arg}')
+ if not os.path.exists(os.path.join(self.environment.source_dir, '.clang-' + name)) and \
+ not os.path.exists(os.path.join(self.environment.source_dir, '_clang-' + name)):
+ return
+ if target_name in self.all_outputs:
+ return
+ cmd = self.environment.get_build_command() + \
+ ['--internal', 'clang' + name, self.environment.source_dir, self.environment.build_dir] + \
+ extra_args
+ elem = self.create_phony_target(self.all_outputs, target_name, 'CUSTOM_COMMAND', 'PHONY')
+ elem.add_item('COMMAND', cmd)
+ elem.add_item('pool', 'console')
+ self.add_build(elem)
+
+ def generate_clangformat(self):
+ if not environment.detect_clangformat():
+ return
+ self.generate_clangtool('format')
+ self.generate_clangtool('format', 'check')
+
+ def generate_clangtidy(self):
+ import shutil
+ if not shutil.which('clang-tidy'):
+ return
+ self.generate_clangtool('tidy')
+
+ def generate_tags(self, tool, target_name):
+ import shutil
+ if not shutil.which(tool):
+ return
+ if target_name in self.all_outputs:
+ return
+ cmd = self.environment.get_build_command() + \
+ ['--internal', 'tags', tool, self.environment.source_dir]
+ elem = self.create_phony_target(self.all_outputs, target_name, 'CUSTOM_COMMAND', 'PHONY')
+ elem.add_item('COMMAND', cmd)
+ elem.add_item('pool', 'console')
+ self.add_build(elem)
+
+ # For things like scan-build and other helper tools we might have.
+ def generate_utils(self):
+ self.generate_scanbuild()
+ self.generate_clangformat()
+ self.generate_clangtidy()
+ self.generate_tags('etags', 'TAGS')
+ self.generate_tags('ctags', 'ctags')
+ self.generate_tags('cscope', 'cscope')
+ cmd = self.environment.get_build_command() + ['--internal', 'uninstall']
+ elem = self.create_phony_target(self.all_outputs, 'uninstall', 'CUSTOM_COMMAND', 'PHONY')
+ elem.add_item('COMMAND', cmd)
+ elem.add_item('pool', 'console')
+ self.add_build(elem)
+
+ def generate_ending(self):
+ for targ, deps in [
+ ('all', self.get_build_by_default_targets()),
+ ('meson-test-prereq', self.get_testlike_targets()),
+ ('meson-benchmark-prereq', self.get_testlike_targets(True))]:
+ targetlist = []
+ # These must also be built by default.
+ # XXX: Sometime in the future these should be built only before running tests.
+ if targ == 'all':
+ targetlist.extend(['meson-test-prereq', 'meson-benchmark-prereq'])
+ for t in deps.values():
+ # Add the first output of each target to the 'all' target so that
+ # they are all built
+ targetlist.append(os.path.join(self.get_target_dir(t), t.get_outputs()[0]))
+
+ elem = NinjaBuildElement(self.all_outputs, targ, 'phony', targetlist)
+ self.add_build(elem)
+
+ elem = self.create_phony_target(self.all_outputs, 'clean', 'CUSTOM_COMMAND', 'PHONY')
+ elem.add_item('COMMAND', self.ninja_command + ['-t', 'clean'])
+ elem.add_item('description', 'Cleaning')
+
+ # If we have custom targets in this project, add all their outputs to
+ # the list that is passed to the `cleantrees.py` script. The script
+ # will manually delete all custom_target outputs that are directories
+ # instead of files. This is needed because on platforms other than
+ # Windows, Ninja only deletes directories while cleaning if they are
+ # empty. https://github.com/mesonbuild/meson/issues/1220
+ ctlist = []
+ for t in self.build.get_targets().values():
+ if isinstance(t, build.CustomTarget):
+ # Create a list of all custom target outputs
+ for o in t.get_outputs():
+ ctlist.append(os.path.join(self.get_target_dir(t), o))
+ if ctlist:
+ elem.add_dep(self.generate_custom_target_clean(ctlist))
+
+ if OptionKey('b_coverage') in self.environment.coredata.options and \
+ self.environment.coredata.options[OptionKey('b_coverage')].value:
+ self.generate_gcov_clean()
+ elem.add_dep('clean-gcda')
+ elem.add_dep('clean-gcno')
+ self.add_build(elem)
+
+ deps = self.get_regen_filelist()
+ elem = NinjaBuildElement(self.all_outputs, 'build.ninja', 'REGENERATE_BUILD', deps)
+ elem.add_item('pool', 'console')
+ self.add_build(elem)
+
+ elem = NinjaBuildElement(self.all_outputs, 'reconfigure', 'REGENERATE_BUILD', 'PHONY')
+ elem.add_item('pool', 'console')
+ self.add_build(elem)
+
+ elem = NinjaBuildElement(self.all_outputs, deps, 'phony', '')
+ self.add_build(elem)
+
+ def get_introspection_data(self, target_id: str, target: build.Target) -> T.List[T.Dict[str, T.Union[bool, str, T.List[T.Union[str, T.Dict[str, T.Union[str, T.List[str], bool]]]]]]]:
+ if target_id not in self.introspection_data or len(self.introspection_data[target_id]) == 0:
+ return super().get_introspection_data(target_id, target)
+
+ result = []
+ for i in self.introspection_data[target_id].values():
+ result += [i]
+ return result
+
+
+def _scan_fortran_file_deps(src: Path, srcdir: Path, dirname: Path, tdeps, compiler) -> T.List[str]:
+ """
+ scan a Fortran file for dependencies. Needs to be distinct from target
+ to allow for recursion induced by `include` statements.er
+
+ It makes a number of assumptions, including
+
+ * `use`, `module`, `submodule` name is not on a continuation line
+
+ Regex
+ -----
+
+ * `incre` works for `#include "foo.f90"` and `include "foo.f90"`
+ * `usere` works for legacy and Fortran 2003 `use` statements
+ * `submodre` is for Fortran >= 2008 `submodule`
+ """
+
+ incre = re.compile(FORTRAN_INCLUDE_PAT, re.IGNORECASE)
+ usere = re.compile(FORTRAN_USE_PAT, re.IGNORECASE)
+ submodre = re.compile(FORTRAN_SUBMOD_PAT, re.IGNORECASE)
+
+ mod_files = []
+ src = Path(src)
+ with src.open(encoding='ascii', errors='ignore') as f:
+ for line in f:
+ # included files
+ incmatch = incre.match(line)
+ if incmatch is not None:
+ incfile = src.parent / incmatch.group(1)
+ # NOTE: src.parent is most general, in particular for CMake subproject with Fortran file
+ # having an `include 'foo.f'` statement.
+ if incfile.suffix.lower()[1:] in compiler.file_suffixes:
+ mod_files.extend(_scan_fortran_file_deps(incfile, srcdir, dirname, tdeps, compiler))
+ # modules
+ usematch = usere.match(line)
+ if usematch is not None:
+ usename = usematch.group(1).lower()
+ if usename == 'intrinsic': # this keeps the regex simpler
+ continue
+ if usename not in tdeps:
+ # The module is not provided by any source file. This
+ # is due to:
+ # a) missing file/typo/etc
+ # b) using a module provided by the compiler, such as
+ # OpenMP
+ # There's no easy way to tell which is which (that I
+ # know of) so just ignore this and go on. Ideally we
+ # would print a warning message to the user but this is
+ # a common occurrence, which would lead to lots of
+ # distracting noise.
+ continue
+ srcfile = srcdir / tdeps[usename].fname # type: Path
+ if not srcfile.is_file():
+ if srcfile.name != src.name: # generated source file
+ pass
+ else: # subproject
+ continue
+ elif srcfile.samefile(src): # self-reference
+ continue
+
+ mod_name = compiler.module_name_to_filename(usename)
+ mod_files.append(str(dirname / mod_name))
+ else: # submodules
+ submodmatch = submodre.match(line)
+ if submodmatch is not None:
+ parents = submodmatch.group(1).lower().split(':')
+ assert len(parents) in {1, 2}, (
+ 'submodule ancestry must be specified as'
+ f' ancestor:parent but Meson found {parents}')
+
+ ancestor_child = '_'.join(parents)
+ if ancestor_child not in tdeps:
+ raise MesonException("submodule {} relies on ancestor module {} that was not found.".format(submodmatch.group(2).lower(), ancestor_child.split('_', maxsplit=1)[0]))
+ submodsrcfile = srcdir / tdeps[ancestor_child].fname # type: Path
+ if not submodsrcfile.is_file():
+ if submodsrcfile.name != src.name: # generated source file
+ pass
+ else: # subproject
+ continue
+ elif submodsrcfile.samefile(src): # self-reference
+ continue
+ mod_name = compiler.module_name_to_filename(ancestor_child)
+ mod_files.append(str(dirname / mod_name))
+ return mod_files
diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py
new file mode 100644
index 0000000..cf15175
--- /dev/null
+++ b/mesonbuild/backend/vs2010backend.py
@@ -0,0 +1,1575 @@
+# Copyright 2014-2016 The Meson development team
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+import copy
+import itertools
+import os
+import xml.dom.minidom
+import xml.etree.ElementTree as ET
+import uuid
+import typing as T
+from pathlib import Path, PurePath
+import re
+
+from . import backends
+from .. import build
+from .. import dependencies
+from .. import mlog
+from .. import compilers
+from ..mesonlib import (
+ File, MesonException, replace_if_different, OptionKey, version_compare, MachineChoice
+)
+from ..environment import Environment, build_filename
+
+if T.TYPE_CHECKING:
+ from ..interpreter import Interpreter
+
+
+def autodetect_vs_version(build: T.Optional[build.Build], interpreter: T.Optional[Interpreter]) -> backends.Backend:
+ vs_version = os.getenv('VisualStudioVersion', None)
+ vs_install_dir = os.getenv('VSINSTALLDIR', None)
+ if not vs_install_dir:
+ raise MesonException('Could not detect Visual Studio: Environment variable VSINSTALLDIR is not set!\n'
+ 'Are you running meson from the Visual Studio Developer Command Prompt?')
+ # VisualStudioVersion is set since Visual Studio 11.0, but sometimes
+ # vcvarsall.bat doesn't set it, so also use VSINSTALLDIR
+ if vs_version == '11.0' or 'Visual Studio 11' in vs_install_dir:
+ from mesonbuild.backend.vs2012backend import Vs2012Backend
+ return Vs2012Backend(build, interpreter)
+ if vs_version == '12.0' or 'Visual Studio 12' in vs_install_dir:
+ from mesonbuild.backend.vs2013backend import Vs2013Backend
+ return Vs2013Backend(build, interpreter)
+ if vs_version == '14.0' or 'Visual Studio 14' in vs_install_dir:
+ from mesonbuild.backend.vs2015backend import Vs2015Backend
+ return Vs2015Backend(build, interpreter)
+ if vs_version == '15.0' or 'Visual Studio 17' in vs_install_dir or \
+ 'Visual Studio\\2017' in vs_install_dir:
+ from mesonbuild.backend.vs2017backend import Vs2017Backend
+ return Vs2017Backend(build, interpreter)
+ if vs_version == '16.0' or 'Visual Studio 19' in vs_install_dir or \
+ 'Visual Studio\\2019' in vs_install_dir:
+ from mesonbuild.backend.vs2019backend import Vs2019Backend
+ return Vs2019Backend(build, interpreter)
+ if vs_version == '17.0' or 'Visual Studio 22' in vs_install_dir or \
+ 'Visual Studio\\2022' in vs_install_dir:
+ from mesonbuild.backend.vs2022backend import Vs2022Backend
+ return Vs2022Backend(build, interpreter)
+ if 'Visual Studio 10.0' in vs_install_dir:
+ return Vs2010Backend(build, interpreter)
+ raise MesonException('Could not detect Visual Studio using VisualStudioVersion: {!r} or VSINSTALLDIR: {!r}!\n'
+ 'Please specify the exact backend to use.'.format(vs_version, vs_install_dir))
+
+
+def split_o_flags_args(args):
+ """
+ Splits any /O args and returns them. Does not take care of flags overriding
+ previous ones. Skips non-O flag arguments.
+
+ ['/Ox', '/Ob1'] returns ['/Ox', '/Ob1']
+ ['/Oxj', '/MP'] returns ['/Ox', '/Oj']
+ """
+ o_flags = []
+ for arg in args:
+ if not arg.startswith('/O'):
+ continue
+ flags = list(arg[2:])
+ # Assume that this one can't be clumped with the others since it takes
+ # an argument itself
+ if 'b' in flags:
+ o_flags.append(arg)
+ else:
+ o_flags += ['/O' + f for f in flags]
+ return o_flags
+
+
+def generate_guid_from_path(path, path_type):
+ return str(uuid.uuid5(uuid.NAMESPACE_URL, 'meson-vs-' + path_type + ':' + str(path))).upper()
+
+def detect_microsoft_gdk(platform: str) -> bool:
+ return re.match(r'Gaming\.(Desktop|Xbox.XboxOne|Xbox.Scarlett)\.x64', platform, re.IGNORECASE)
+
+class Vs2010Backend(backends.Backend):
+ def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional[Interpreter]):
+ super().__init__(build, interpreter)
+ self.name = 'vs2010'
+ self.project_file_version = '10.0.30319.1'
+ self.sln_file_version = '11.00'
+ self.sln_version_comment = '2010'
+ self.platform_toolset = None
+ self.vs_version = '2010'
+ self.windows_target_platform_version = None
+ self.subdirs = {}
+ self.handled_target_deps = {}
+
+ def get_target_private_dir(self, target):
+ return os.path.join(self.get_target_dir(target), target.get_id())
+
+ def generate_custom_generator_commands(self, target, parent_node):
+ generator_output_files = []
+ custom_target_include_dirs = []
+ custom_target_output_files = []
+ target_private_dir = self.relpath(self.get_target_private_dir(target), self.get_target_dir(target))
+ down = self.target_to_build_root(target)
+ for genlist in target.get_generated_sources():
+ if isinstance(genlist, (build.CustomTarget, build.CustomTargetIndex)):
+ for i in genlist.get_outputs():
+ # Path to the generated source from the current vcxproj dir via the build root
+ ipath = os.path.join(down, self.get_target_dir(genlist), i)
+ custom_target_output_files.append(ipath)
+ idir = self.relpath(self.get_target_dir(genlist), self.get_target_dir(target))
+ if idir not in custom_target_include_dirs:
+ custom_target_include_dirs.append(idir)
+ else:
+ generator = genlist.get_generator()
+ exe = generator.get_exe()
+ infilelist = genlist.get_inputs()
+ outfilelist = genlist.get_outputs()
+ source_dir = os.path.join(down, self.build_to_src, genlist.subdir)
+ idgroup = ET.SubElement(parent_node, 'ItemGroup')
+ for i, curfile in enumerate(infilelist):
+ if len(infilelist) == len(outfilelist):
+ sole_output = os.path.join(target_private_dir, outfilelist[i])
+ else:
+ sole_output = ''
+ infilename = os.path.join(down, curfile.rel_to_builddir(self.build_to_src))
+ deps = self.get_custom_target_depend_files(genlist, True)
+ base_args = generator.get_arglist(infilename)
+ outfiles_rel = genlist.get_outputs_for(curfile)
+ outfiles = [os.path.join(target_private_dir, of) for of in outfiles_rel]
+ generator_output_files += outfiles
+ args = [x.replace("@INPUT@", infilename).replace('@OUTPUT@', sole_output)
+ for x in base_args]
+ args = self.replace_outputs(args, target_private_dir, outfiles_rel)
+ args = [x.replace("@SOURCE_DIR@", self.environment.get_source_dir())
+ .replace("@BUILD_DIR@", target_private_dir)
+ for x in args]
+ args = [x.replace("@CURRENT_SOURCE_DIR@", source_dir) for x in args]
+ args = [x.replace("@SOURCE_ROOT@", self.environment.get_source_dir())
+ .replace("@BUILD_ROOT@", self.environment.get_build_dir())
+ for x in args]
+ args = [x.replace('\\', '/') for x in args]
+ # Always use a wrapper because MSBuild eats random characters when
+ # there are many arguments.
+ tdir_abs = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target))
+ cmd, _ = self.as_meson_exe_cmdline(
+ exe,
+ self.replace_extra_args(args, genlist),
+ workdir=tdir_abs,
+ capture=outfiles[0] if generator.capture else None,
+ force_serialize=True
+ )
+ deps = cmd[-1:] + deps
+ abs_pdir = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target))
+ os.makedirs(abs_pdir, exist_ok=True)
+ cbs = ET.SubElement(idgroup, 'CustomBuild', Include=infilename)
+ ET.SubElement(cbs, 'Command').text = ' '.join(self.quote_arguments(cmd))
+ ET.SubElement(cbs, 'Outputs').text = ';'.join(outfiles)
+ ET.SubElement(cbs, 'AdditionalInputs').text = ';'.join(deps)
+ return generator_output_files, custom_target_output_files, custom_target_include_dirs
+
+ def generate(self):
+ target_machine = self.interpreter.builtin['target_machine'].cpu_family_method(None, None)
+ if target_machine in {'64', 'x86_64'}:
+ # amd64 or x86_64
+ target_system = self.interpreter.builtin['target_machine'].system_method(None, None)
+ if detect_microsoft_gdk(target_system):
+ self.platform = target_system
+ else:
+ self.platform = 'x64'
+ elif target_machine == 'x86':
+ # x86
+ self.platform = 'Win32'
+ elif target_machine in {'aarch64', 'arm64'}:
+ target_cpu = self.interpreter.builtin['target_machine'].cpu_method(None, None)
+ if target_cpu == 'arm64ec':
+ self.platform = 'arm64ec'
+ else:
+ self.platform = 'arm64'
+ elif 'arm' in target_machine.lower():
+ self.platform = 'ARM'
+ else:
+ raise MesonException('Unsupported Visual Studio platform: ' + target_machine)
+
+ build_machine = self.interpreter.builtin['build_machine'].cpu_family_method(None, None)
+ if build_machine in {'64', 'x86_64'}:
+ # amd64 or x86_64
+ self.build_platform = 'x64'
+ elif build_machine == 'x86':
+ # x86
+ self.build_platform = 'Win32'
+ elif build_machine in {'aarch64', 'arm64'}:
+ target_cpu = self.interpreter.builtin['build_machine'].cpu_method(None, None)
+ if target_cpu == 'arm64ec':
+ self.build_platform = 'arm64ec'
+ else:
+ self.build_platform = 'arm64'
+ elif 'arm' in build_machine.lower():
+ self.build_platform = 'ARM'
+ else:
+ raise MesonException('Unsupported Visual Studio platform: ' + build_machine)
+
+ self.buildtype = self.environment.coredata.get_option(OptionKey('buildtype'))
+ self.optimization = self.environment.coredata.get_option(OptionKey('optimization'))
+ self.debug = self.environment.coredata.get_option(OptionKey('debug'))
+ try:
+ self.sanitize = self.environment.coredata.get_option(OptionKey('b_sanitize'))
+ except MesonException:
+ self.sanitize = 'none'
+ sln_filename = os.path.join(self.environment.get_build_dir(), self.build.project_name + '.sln')
+ projlist = self.generate_projects()
+ self.gen_testproj('RUN_TESTS', os.path.join(self.environment.get_build_dir(), 'RUN_TESTS.vcxproj'))
+ self.gen_installproj('RUN_INSTALL', os.path.join(self.environment.get_build_dir(), 'RUN_INSTALL.vcxproj'))
+ self.gen_regenproj('REGEN', os.path.join(self.environment.get_build_dir(), 'REGEN.vcxproj'))
+ self.generate_solution(sln_filename, projlist)
+ self.generate_regen_info()
+ Vs2010Backend.touch_regen_timestamp(self.environment.get_build_dir())
+
+ @staticmethod
+ def get_regen_stampfile(build_dir: str) -> None:
+ return os.path.join(os.path.join(build_dir, Environment.private_dir), 'regen.stamp')
+
+ @staticmethod
+ def touch_regen_timestamp(build_dir: str) -> None:
+ with open(Vs2010Backend.get_regen_stampfile(build_dir), 'w', encoding='utf-8'):
+ pass
+
+ def get_vcvars_command(self):
+ has_arch_values = 'VSCMD_ARG_TGT_ARCH' in os.environ and 'VSCMD_ARG_HOST_ARCH' in os.environ
+
+ # Use vcvarsall.bat if we found it.
+ if 'VCINSTALLDIR' in os.environ:
+ vs_version = os.environ['VisualStudioVersion'] \
+ if 'VisualStudioVersion' in os.environ else None
+ relative_path = 'Auxiliary\\Build\\' if vs_version is not None and vs_version >= '15.0' else ''
+ script_path = os.environ['VCINSTALLDIR'] + relative_path + 'vcvarsall.bat'
+ if os.path.exists(script_path):
+ if has_arch_values:
+ target_arch = os.environ['VSCMD_ARG_TGT_ARCH']
+ host_arch = os.environ['VSCMD_ARG_HOST_ARCH']
+ else:
+ target_arch = os.environ.get('Platform', 'x86')
+ host_arch = target_arch
+ arch = host_arch + '_' + target_arch if host_arch != target_arch else target_arch
+ return f'"{script_path}" {arch}'
+
+ # Otherwise try the VS2017 Developer Command Prompt.
+ if 'VS150COMNTOOLS' in os.environ and has_arch_values:
+ script_path = os.environ['VS150COMNTOOLS'] + 'VsDevCmd.bat'
+ if os.path.exists(script_path):
+ return '"%s" -arch=%s -host_arch=%s' % \
+ (script_path, os.environ['VSCMD_ARG_TGT_ARCH'], os.environ['VSCMD_ARG_HOST_ARCH'])
+ return ''
+
+ def get_obj_target_deps(self, obj_list):
+ result = {}
+ for o in obj_list:
+ if isinstance(o, build.ExtractedObjects):
+ result[o.target.get_id()] = o.target
+ return result.items()
+
+ def get_target_deps(self, t: T.Dict[T.Any, build.Target], recursive=False):
+ all_deps: T.Dict[str, build.Target] = {}
+ for target in t.values():
+ if isinstance(target, build.CustomTarget):
+ for d in target.get_target_dependencies():
+ # FIXME: this isn't strictly correct, as the target doesn't
+ # Get dependencies on non-targets, such as Files
+ if isinstance(d, build.Target):
+ all_deps[d.get_id()] = d
+ elif isinstance(target, build.RunTarget):
+ for d in target.get_dependencies():
+ all_deps[d.get_id()] = d
+ elif isinstance(target, build.BuildTarget):
+ for ldep in target.link_targets:
+ if isinstance(ldep, build.CustomTargetIndex):
+ all_deps[ldep.get_id()] = ldep.target
+ else:
+ all_deps[ldep.get_id()] = ldep
+ for ldep in target.link_whole_targets:
+ if isinstance(ldep, build.CustomTargetIndex):
+ all_deps[ldep.get_id()] = ldep.target
+ else:
+ all_deps[ldep.get_id()] = ldep
+
+ for ldep in target.link_depends:
+ if isinstance(ldep, build.CustomTargetIndex):
+ all_deps[ldep.get_id()] = ldep.target
+ elif isinstance(ldep, File):
+ # Already built, no target references needed
+ pass
+ else:
+ all_deps[ldep.get_id()] = ldep
+
+ for obj_id, objdep in self.get_obj_target_deps(target.objects):
+ all_deps[obj_id] = objdep
+ else:
+ raise MesonException(f'Unknown target type for target {target}')
+
+ for gendep in target.get_generated_sources():
+ if isinstance(gendep, build.CustomTarget):
+ all_deps[gendep.get_id()] = gendep
+ elif isinstance(gendep, build.CustomTargetIndex):
+ all_deps[gendep.target.get_id()] = gendep.target
+ else:
+ generator = gendep.get_generator()
+ gen_exe = generator.get_exe()
+ if isinstance(gen_exe, build.Executable):
+ all_deps[gen_exe.get_id()] = gen_exe
+ for d in itertools.chain(generator.depends, gendep.depends):
+ if isinstance(d, build.CustomTargetIndex):
+ all_deps[d.get_id()] = d.target
+ elif isinstance(d, build.Target):
+ all_deps[d.get_id()] = d
+ # FIXME: we don't handle other kinds of deps correctly here, such
+ # as GeneratedLists, StructuredSources, and generated File.
+
+ if not t or not recursive:
+ return all_deps
+ ret = self.get_target_deps(all_deps, recursive)
+ ret.update(all_deps)
+ return ret
+
+ def generate_solution_dirs(self, ofile, parents):
+ prj_templ = 'Project("{%s}") = "%s", "%s", "{%s}"\n'
+ iterpaths = reversed(parents)
+ # Skip first path
+ next(iterpaths)
+ for path in iterpaths:
+ if path not in self.subdirs:
+ basename = path.name
+ identifier = generate_guid_from_path(path, 'subdir')
+ # top-level directories have None as their parent_dir
+ parent_dir = path.parent
+ parent_identifier = self.subdirs[parent_dir][0] \
+ if parent_dir != PurePath('.') else None
+ self.subdirs[path] = (identifier, parent_identifier)
+ prj_line = prj_templ % (
+ self.environment.coredata.lang_guids['directory'],
+ basename, basename, self.subdirs[path][0])
+ ofile.write(prj_line)
+ ofile.write('EndProject\n')
+
+ def generate_solution(self, sln_filename, projlist):
+ default_projlist = self.get_build_by_default_targets()
+ default_projlist.update(self.get_testlike_targets())
+ sln_filename_tmp = sln_filename + '~'
+ # Note using the utf-8 BOM requires the blank line, otherwise Visual Studio Version Selector fails.
+ # Without the BOM, VSVS fails if there is a blank line.
+ with open(sln_filename_tmp, 'w', encoding='utf-8-sig') as ofile:
+ ofile.write('\nMicrosoft Visual Studio Solution File, Format Version %s\n' % self.sln_file_version)
+ ofile.write('# Visual Studio %s\n' % self.sln_version_comment)
+ prj_templ = 'Project("{%s}") = "%s", "%s", "{%s}"\n'
+ for prj in projlist:
+ coredata = self.environment.coredata
+ if coredata.get_option(OptionKey('layout')) == 'mirror':
+ self.generate_solution_dirs(ofile, prj[1].parents)
+ target = self.build.targets[prj[0]]
+ lang = 'default'
+ if hasattr(target, 'compilers') and target.compilers:
+ for lang_out in target.compilers.keys():
+ lang = lang_out
+ break
+ prj_line = prj_templ % (
+ self.environment.coredata.lang_guids[lang],
+ prj[0], prj[1], prj[2])
+ ofile.write(prj_line)
+ target_dict = {target.get_id(): target}
+ # Get recursive deps
+ recursive_deps = self.get_target_deps(
+ target_dict, recursive=True)
+ ofile.write('EndProject\n')
+ for dep, target in recursive_deps.items():
+ if prj[0] in default_projlist:
+ default_projlist[dep] = target
+
+ test_line = prj_templ % (self.environment.coredata.lang_guids['default'],
+ 'RUN_TESTS', 'RUN_TESTS.vcxproj',
+ self.environment.coredata.test_guid)
+ ofile.write(test_line)
+ ofile.write('EndProject\n')
+ regen_line = prj_templ % (self.environment.coredata.lang_guids['default'],
+ 'REGEN', 'REGEN.vcxproj',
+ self.environment.coredata.regen_guid)
+ ofile.write(regen_line)
+ ofile.write('EndProject\n')
+ install_line = prj_templ % (self.environment.coredata.lang_guids['default'],
+ 'RUN_INSTALL', 'RUN_INSTALL.vcxproj',
+ self.environment.coredata.install_guid)
+ ofile.write(install_line)
+ ofile.write('EndProject\n')
+ ofile.write('Global\n')
+ ofile.write('\tGlobalSection(SolutionConfigurationPlatforms) = '
+ 'preSolution\n')
+ ofile.write('\t\t%s|%s = %s|%s\n' %
+ (self.buildtype, self.platform, self.buildtype,
+ self.platform))
+ ofile.write('\tEndGlobalSection\n')
+ ofile.write('\tGlobalSection(ProjectConfigurationPlatforms) = '
+ 'postSolution\n')
+ ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' %
+ (self.environment.coredata.regen_guid, self.buildtype,
+ self.platform, self.buildtype, self.platform))
+ ofile.write('\t\t{%s}.%s|%s.Build.0 = %s|%s\n' %
+ (self.environment.coredata.regen_guid, self.buildtype,
+ self.platform, self.buildtype, self.platform))
+ # Create the solution configuration
+ for p in projlist:
+ if p[3] is MachineChoice.BUILD:
+ config_platform = self.build_platform
+ else:
+ config_platform = self.platform
+ # Add to the list of projects in this solution
+ ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' %
+ (p[2], self.buildtype, self.platform,
+ self.buildtype, config_platform))
+ if p[0] in default_projlist and \
+ not isinstance(self.build.targets[p[0]], build.RunTarget):
+ # Add to the list of projects to be built
+ ofile.write('\t\t{%s}.%s|%s.Build.0 = %s|%s\n' %
+ (p[2], self.buildtype, self.platform,
+ self.buildtype, config_platform))
+ ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' %
+ (self.environment.coredata.test_guid, self.buildtype,
+ self.platform, self.buildtype, self.platform))
+ ofile.write('\t\t{%s}.%s|%s.ActiveCfg = %s|%s\n' %
+ (self.environment.coredata.install_guid, self.buildtype,
+ self.platform, self.buildtype, self.platform))
+ ofile.write('\tEndGlobalSection\n')
+ ofile.write('\tGlobalSection(SolutionProperties) = preSolution\n')
+ ofile.write('\t\tHideSolutionNode = FALSE\n')
+ ofile.write('\tEndGlobalSection\n')
+ if self.subdirs:
+ ofile.write('\tGlobalSection(NestedProjects) = '
+ 'preSolution\n')
+ for p in projlist:
+ if p[1].parent != PurePath('.'):
+ ofile.write("\t\t{{{}}} = {{{}}}\n".format(p[2], self.subdirs[p[1].parent][0]))
+ for subdir in self.subdirs.values():
+ if subdir[1]:
+ ofile.write("\t\t{{{}}} = {{{}}}\n".format(subdir[0], subdir[1]))
+ ofile.write('\tEndGlobalSection\n')
+ ofile.write('EndGlobal\n')
+ replace_if_different(sln_filename, sln_filename_tmp)
+
+ def generate_projects(self):
+ startup_project = self.environment.coredata.options[OptionKey('backend_startup_project')].value
+ projlist = []
+ startup_idx = 0
+ for (i, (name, target)) in enumerate(self.build.targets.items()):
+ if startup_project and startup_project == target.get_basename():
+ startup_idx = i
+ outdir = Path(
+ self.environment.get_build_dir(),
+ self.get_target_dir(target)
+ )
+ outdir.mkdir(exist_ok=True, parents=True)
+ fname = name + '.vcxproj'
+ target_dir = PurePath(self.get_target_dir(target))
+ relname = target_dir / fname
+ projfile_path = outdir / fname
+ proj_uuid = self.environment.coredata.target_guids[name]
+ self.gen_vcxproj(target, str(projfile_path), proj_uuid)
+ projlist.append((name, relname, proj_uuid, target.for_machine))
+
+ # Put the startup project first in the project list
+ if startup_idx:
+ projlist.insert(0, projlist.pop(startup_idx))
+
+ return projlist
+
+ def split_sources(self, srclist):
+ sources = []
+ headers = []
+ objects = []
+ languages = []
+ for i in srclist:
+ if self.environment.is_header(i):
+ headers.append(i)
+ elif self.environment.is_object(i):
+ objects.append(i)
+ elif self.environment.is_source(i):
+ sources.append(i)
+ lang = self.lang_from_source_file(i)
+ if lang not in languages:
+ languages.append(lang)
+ elif self.environment.is_library(i):
+ pass
+ else:
+ # Everything that is not an object or source file is considered a header.
+ headers.append(i)
+ return sources, headers, objects, languages
+
+ def target_to_build_root(self, target):
+ if self.get_target_dir(target) == '':
+ return ''
+
+ directories = os.path.normpath(self.get_target_dir(target)).split(os.sep)
+ return os.sep.join(['..'] * len(directories))
+
+ def quote_arguments(self, arr):
+ return ['"%s"' % i for i in arr]
+
+ def add_project_reference(self, root, include, projid, link_outputs=False):
+ ig = ET.SubElement(root, 'ItemGroup')
+ pref = ET.SubElement(ig, 'ProjectReference', Include=include)
+ ET.SubElement(pref, 'Project').text = '{%s}' % projid
+ if not link_outputs:
+ # Do not link in generated .lib files from dependencies automatically.
+ # We only use the dependencies for ordering and link in the generated
+ # objects and .lib files manually.
+ ET.SubElement(pref, 'LinkLibraryDependencies').text = 'false'
+
+ def add_target_deps(self, root, target):
+ target_dict = {target.get_id(): target}
+ for dep in self.get_target_deps(target_dict).values():
+ if dep.get_id() in self.handled_target_deps[target.get_id()]:
+ # This dependency was already handled manually.
+ continue
+ relpath = self.get_target_dir_relative_to(dep, target)
+ vcxproj = os.path.join(relpath, dep.get_id() + '.vcxproj')
+ tid = self.environment.coredata.target_guids[dep.get_id()]
+ self.add_project_reference(root, vcxproj, tid)
+
+ def create_basic_project(self, target_name, *,
+ temp_dir,
+ guid,
+ conftype='Utility',
+ target_ext=None,
+ target_platform=None):
+ root = ET.Element('Project', {'DefaultTargets': "Build",
+ 'ToolsVersion': '4.0',
+ 'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'})
+
+ confitems = ET.SubElement(root, 'ItemGroup', {'Label': 'ProjectConfigurations'})
+ if not target_platform:
+ target_platform = self.platform
+ prjconf = ET.SubElement(confitems, 'ProjectConfiguration',
+ {'Include': self.buildtype + '|' + target_platform})
+ p = ET.SubElement(prjconf, 'Configuration')
+ p.text = self.buildtype
+ pl = ET.SubElement(prjconf, 'Platform')
+ pl.text = target_platform
+
+ # Globals
+ globalgroup = ET.SubElement(root, 'PropertyGroup', Label='Globals')
+ guidelem = ET.SubElement(globalgroup, 'ProjectGuid')
+ guidelem.text = '{%s}' % guid
+ kw = ET.SubElement(globalgroup, 'Keyword')
+ kw.text = self.platform + 'Proj'
+ # XXX Wasn't here before for anything but gen_vcxproj , but seems fine?
+ ns = ET.SubElement(globalgroup, 'RootNamespace')
+ ns.text = target_name
+
+ p = ET.SubElement(globalgroup, 'Platform')
+ p.text = target_platform
+ pname = ET.SubElement(globalgroup, 'ProjectName')
+ pname.text = target_name
+ if self.windows_target_platform_version:
+ ET.SubElement(globalgroup, 'WindowsTargetPlatformVersion').text = self.windows_target_platform_version
+ ET.SubElement(globalgroup, 'UseMultiToolTask').text = 'true'
+
+ ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.Default.props')
+
+ # Start configuration
+ type_config = ET.SubElement(root, 'PropertyGroup', Label='Configuration')
+ ET.SubElement(type_config, 'ConfigurationType').text = conftype
+ ET.SubElement(type_config, 'CharacterSet').text = 'MultiByte'
+ # Fixme: wasn't here before for gen_vcxproj()
+ ET.SubElement(type_config, 'UseOfMfc').text = 'false'
+ if self.platform_toolset:
+ ET.SubElement(type_config, 'PlatformToolset').text = self.platform_toolset
+
+ # End configuration section (but it can be added to further via type_config)
+ ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.props')
+
+ # Project information
+ direlem = ET.SubElement(root, 'PropertyGroup')
+ fver = ET.SubElement(direlem, '_ProjectFileVersion')
+ fver.text = self.project_file_version
+ outdir = ET.SubElement(direlem, 'OutDir')
+ outdir.text = '.\\'
+ intdir = ET.SubElement(direlem, 'IntDir')
+ intdir.text = temp_dir + '\\'
+
+ tname = ET.SubElement(direlem, 'TargetName')
+ tname.text = target_name
+
+ if target_ext:
+ ET.SubElement(direlem, 'TargetExt').text = target_ext
+
+ return (root, type_config)
+
+ def gen_run_target_vcxproj(self, target, ofname, guid):
+ (root, type_config) = self.create_basic_project(target.name,
+ temp_dir=target.get_id(),
+ guid=guid)
+ depend_files = self.get_custom_target_depend_files(target)
+
+ if not target.command:
+ # This is an alias target and thus doesn't run any command. It's
+ # enough to emit the references to the other projects for them to
+ # be built/run/..., if necessary.
+ assert isinstance(target, build.AliasTarget)
+ assert len(depend_files) == 0
+ else:
+ assert not isinstance(target, build.AliasTarget)
+
+ target_env = self.get_run_target_env(target)
+ _, _, cmd_raw = self.eval_custom_target_command(target)
+ wrapper_cmd, _ = self.as_meson_exe_cmdline(target.command[0], cmd_raw[1:],
+ force_serialize=True, env=target_env,
+ verbose=True)
+ self.add_custom_build(root, 'run_target', ' '.join(self.quote_arguments(wrapper_cmd)),
+ deps=depend_files)
+
+ # The import is needed even for alias targets, otherwise the build
+ # target isn't defined
+ ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
+ self.add_regen_dependency(root)
+ self.add_target_deps(root, target)
+ self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
+
+ def gen_custom_target_vcxproj(self, target, ofname, guid):
+ if target.for_machine is MachineChoice.BUILD:
+ platform = self.build_platform
+ else:
+ platform = self.platform
+ (root, type_config) = self.create_basic_project(target.name,
+ temp_dir=target.get_id(),
+ guid=guid,
+ target_platform=platform)
+ # We need to always use absolute paths because our invocation is always
+ # from the target dir, not the build root.
+ target.absolute_paths = True
+ (srcs, ofilenames, cmd) = self.eval_custom_target_command(target, True)
+ depend_files = self.get_custom_target_depend_files(target, True)
+ # Always use a wrapper because MSBuild eats random characters when
+ # there are many arguments.
+ tdir_abs = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target))
+ extra_bdeps = target.get_transitive_build_target_deps()
+ wrapper_cmd, _ = self.as_meson_exe_cmdline(target.command[0], cmd[1:],
+ # All targets run from the target dir
+ workdir=tdir_abs,
+ extra_bdeps=extra_bdeps,
+ capture=ofilenames[0] if target.capture else None,
+ feed=srcs[0] if target.feed else None,
+ force_serialize=True,
+ env=target.env,
+ verbose=target.console)
+ if target.build_always_stale:
+ # Use a nonexistent file to always consider the target out-of-date.
+ ofilenames += [self.nonexistent_file(os.path.join(self.environment.get_scratch_dir(),
+ 'outofdate.file'))]
+ self.add_custom_build(root, 'custom_target', ' '.join(self.quote_arguments(wrapper_cmd)),
+ deps=wrapper_cmd[-1:] + srcs + depend_files, outputs=ofilenames,
+ verify_files=not target.build_always_stale)
+ ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
+ self.generate_custom_generator_commands(target, root)
+ self.add_regen_dependency(root)
+ self.add_target_deps(root, target)
+ self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
+
+ def gen_compile_target_vcxproj(self, target, ofname, guid):
+ if target.for_machine is MachineChoice.BUILD:
+ platform = self.build_platform
+ else:
+ platform = self.platform
+ (root, type_config) = self.create_basic_project(target.name,
+ temp_dir=target.get_id(),
+ guid=guid,
+ target_platform=platform)
+ ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
+ target.generated = [self.compile_target_to_generator(target)]
+ target.sources = []
+ self.generate_custom_generator_commands(target, root)
+ self.add_regen_dependency(root)
+ self.add_target_deps(root, target)
+ self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
+
+ @classmethod
+ def lang_from_source_file(cls, src):
+ ext = src.split('.')[-1]
+ if ext in compilers.c_suffixes:
+ return 'c'
+ if ext in compilers.cpp_suffixes:
+ return 'cpp'
+ raise MesonException(f'Could not guess language from source file {src}.')
+
+ def add_pch(self, pch_sources, lang, inc_cl):
+ if lang in pch_sources:
+ self.use_pch(pch_sources, lang, inc_cl)
+
+ def create_pch(self, pch_sources, lang, inc_cl):
+ pch = ET.SubElement(inc_cl, 'PrecompiledHeader')
+ pch.text = 'Create'
+ self.add_pch_files(pch_sources, lang, inc_cl)
+
+ def use_pch(self, pch_sources, lang, inc_cl):
+ pch = ET.SubElement(inc_cl, 'PrecompiledHeader')
+ pch.text = 'Use'
+ header = self.add_pch_files(pch_sources, lang, inc_cl)
+ pch_include = ET.SubElement(inc_cl, 'ForcedIncludeFiles')
+ pch_include.text = header + ';%(ForcedIncludeFiles)'
+
+ def add_pch_files(self, pch_sources, lang, inc_cl):
+ header = os.path.basename(pch_sources[lang][0])
+ pch_file = ET.SubElement(inc_cl, 'PrecompiledHeaderFile')
+ # When USING PCHs, MSVC will not do the regular include
+ # directory lookup, but simply use a string match to find the
+ # PCH to use. That means the #include directive must match the
+ # pch_file.text used during PCH CREATION verbatim.
+ # When CREATING a PCH, MSVC will do the include directory
+ # lookup to find the actual PCH header to use. Thus, the PCH
+ # header must either be in the include_directories of the target
+ # or be in the same directory as the PCH implementation.
+ pch_file.text = header
+ pch_out = ET.SubElement(inc_cl, 'PrecompiledHeaderOutputFile')
+ pch_out.text = f'$(IntDir)$(TargetName)-{lang}.pch'
+
+ # Need to set the name for the pdb, as cl otherwise gives it a static
+ # name. Which leads to problems when there is more than one pch
+ # (e.g. for different languages).
+ pch_pdb = ET.SubElement(inc_cl, 'ProgramDataBaseFileName')
+ pch_pdb.text = f'$(IntDir)$(TargetName)-{lang}.pdb'
+
+ return header
+
+ def is_argument_with_msbuild_xml_entry(self, entry):
+ # Remove arguments that have a top level XML entry so
+ # they are not used twice.
+ # FIXME add args as needed.
+ if entry[1:].startswith('fsanitize'):
+ return True
+ return entry[1:].startswith('M')
+
+ def add_additional_options(self, lang, parent_node, file_args):
+ args = []
+ for arg in file_args[lang].to_native():
+ if self.is_argument_with_msbuild_xml_entry(arg):
+ continue
+ if arg == '%(AdditionalOptions)':
+ args.append(arg)
+ else:
+ args.append(self.escape_additional_option(arg))
+ ET.SubElement(parent_node, "AdditionalOptions").text = ' '.join(args)
+
+ def add_preprocessor_defines(self, lang, parent_node, file_defines):
+ defines = []
+ for define in file_defines[lang]:
+ if define == '%(PreprocessorDefinitions)':
+ defines.append(define)
+ else:
+ defines.append(self.escape_preprocessor_define(define))
+ ET.SubElement(parent_node, "PreprocessorDefinitions").text = ';'.join(defines)
+
+ def add_include_dirs(self, lang, parent_node, file_inc_dirs):
+ dirs = file_inc_dirs[lang]
+ ET.SubElement(parent_node, "AdditionalIncludeDirectories").text = ';'.join(dirs)
+
+ @staticmethod
+ def has_objects(objects, additional_objects, generated_objects):
+ # Ignore generated objects, those are automatically used by MSBuild because they are part of
+ # the CustomBuild Outputs.
+ return len(objects) + len(additional_objects) > 0
+
+ @staticmethod
+ def add_generated_objects(node, generated_objects):
+ # Do not add generated objects to project file. Those are automatically used by MSBuild, because
+ # they are part of the CustomBuild Outputs.
+ return
+
+ @staticmethod
+ def escape_preprocessor_define(define):
+ # See: https://msdn.microsoft.com/en-us/library/bb383819.aspx
+ table = str.maketrans({'%': '%25', '$': '%24', '@': '%40',
+ "'": '%27', ';': '%3B', '?': '%3F', '*': '%2A',
+ # We need to escape backslash because it'll be un-escaped by
+ # Windows during process creation when it parses the arguments
+ # Basically, this converts `\` to `\\`.
+ '\\': '\\\\'})
+ return define.translate(table)
+
+ @staticmethod
+ def escape_additional_option(option):
+ # See: https://msdn.microsoft.com/en-us/library/bb383819.aspx
+ table = str.maketrans({'%': '%25', '$': '%24', '@': '%40',
+ "'": '%27', ';': '%3B', '?': '%3F', '*': '%2A', ' ': '%20'})
+ option = option.translate(table)
+ # Since we're surrounding the option with ", if it ends in \ that will
+ # escape the " when the process arguments are parsed and the starting
+ # " will not terminate. So we escape it if that's the case. I'm not
+ # kidding, this is how escaping works for process args on Windows.
+ if option.endswith('\\'):
+ option += '\\'
+ return f'"{option}"'
+
+ @staticmethod
+ def split_link_args(args):
+ """
+ Split a list of link arguments into three lists:
+ * library search paths
+ * library filenames (or paths)
+ * other link arguments
+ """
+ lpaths = []
+ libs = []
+ other = []
+ for arg in args:
+ if arg.startswith('/LIBPATH:'):
+ lpath = arg[9:]
+ # De-dup library search paths by removing older entries when
+ # a new one is found. This is necessary because unlike other
+ # search paths such as the include path, the library is
+ # searched for in the newest (right-most) search path first.
+ if lpath in lpaths:
+ lpaths.remove(lpath)
+ lpaths.append(lpath)
+ elif arg.startswith(('/', '-')):
+ other.append(arg)
+ # It's ok if we miss libraries with non-standard extensions here.
+ # They will go into the general link arguments.
+ elif arg.endswith('.lib') or arg.endswith('.a'):
+ # De-dup
+ if arg not in libs:
+ libs.append(arg)
+ else:
+ other.append(arg)
+ return lpaths, libs, other
+
+ def _get_cl_compiler(self, target):
+ for lang, c in target.compilers.items():
+ if lang in {'c', 'cpp'}:
+ return c
+ # No source files, only objects, but we still need a compiler, so
+ # return a found compiler
+ if len(target.objects) > 0:
+ for lang, c in self.environment.coredata.compilers[target.for_machine].items():
+ if lang in {'c', 'cpp'}:
+ return c
+ raise MesonException('Could not find a C or C++ compiler. MSVC can only build C/C++ projects.')
+
+ def _prettyprint_vcxproj_xml(self, tree, ofname):
+ ofname_tmp = ofname + '~'
+ tree.write(ofname_tmp, encoding='utf-8', xml_declaration=True)
+
+ # ElementTree can not do prettyprinting so do it manually
+ doc = xml.dom.minidom.parse(ofname_tmp)
+ with open(ofname_tmp, 'w', encoding='utf-8') as of:
+ of.write(doc.toprettyxml())
+ replace_if_different(ofname, ofname_tmp)
+
+ def gen_vcxproj(self, target, ofname, guid):
+ mlog.debug(f'Generating vcxproj {target.name}.')
+ subsystem = 'Windows'
+ self.handled_target_deps[target.get_id()] = []
+ if isinstance(target, build.Executable):
+ conftype = 'Application'
+ if target.gui_app is not None:
+ if not target.gui_app:
+ subsystem = 'Console'
+ else:
+ # If someone knows how to set the version properly,
+ # please send a patch.
+ subsystem = target.win_subsystem.split(',')[0]
+ elif isinstance(target, build.StaticLibrary):
+ conftype = 'StaticLibrary'
+ elif isinstance(target, build.SharedLibrary):
+ conftype = 'DynamicLibrary'
+ elif isinstance(target, build.CustomTarget):
+ return self.gen_custom_target_vcxproj(target, ofname, guid)
+ elif isinstance(target, build.RunTarget):
+ return self.gen_run_target_vcxproj(target, ofname, guid)
+ elif isinstance(target, build.CompileTarget):
+ return self.gen_compile_target_vcxproj(target, ofname, guid)
+ else:
+ raise MesonException(f'Unknown target type for {target.get_basename()}')
+ # Prefix to use to access the build root from the vcxproj dir
+ down = self.target_to_build_root(target)
+ # Prefix to use to access the source tree's root from the vcxproj dir
+ proj_to_src_root = os.path.join(down, self.build_to_src)
+ # Prefix to use to access the source tree's subdir from the vcxproj dir
+ proj_to_src_dir = os.path.join(proj_to_src_root, self.get_target_dir(target))
+ (sources, headers, objects, languages) = self.split_sources(target.sources)
+ if target.is_unity:
+ sources = self.generate_unity_files(target, sources)
+ compiler = self._get_cl_compiler(target)
+ build_args = compiler.get_buildtype_args(self.buildtype)
+ build_args += compiler.get_optimization_args(self.optimization)
+ build_args += compiler.get_debug_args(self.debug)
+ build_args += compiler.sanitizer_compile_args(self.sanitize)
+ buildtype_link_args = compiler.get_buildtype_linker_args(self.buildtype)
+ vscrt_type = self.environment.coredata.options[OptionKey('b_vscrt')]
+ target_name = target.name
+ if target.for_machine is MachineChoice.BUILD:
+ platform = self.build_platform
+ else:
+ platform = self.platform
+
+ tfilename = os.path.splitext(target.get_filename())
+
+ (root, type_config) = self.create_basic_project(tfilename[0],
+ temp_dir=target.get_id(),
+ guid=guid,
+ conftype=conftype,
+ target_ext=tfilename[1],
+ target_platform=platform)
+
+ # FIXME: Should these just be set in create_basic_project(), even if
+ # irrelevant for current target?
+
+ # FIXME: Meson's LTO support needs to be integrated here
+ ET.SubElement(type_config, 'WholeProgramOptimization').text = 'false'
+ # Let VS auto-set the RTC level
+ ET.SubElement(type_config, 'BasicRuntimeChecks').text = 'Default'
+ # Incremental linking increases code size
+ if '/INCREMENTAL:NO' in buildtype_link_args:
+ ET.SubElement(type_config, 'LinkIncremental').text = 'false'
+
+ # Build information
+ compiles = ET.SubElement(root, 'ItemDefinitionGroup')
+ clconf = ET.SubElement(compiles, 'ClCompile')
+ # CRT type; debug or release
+ if vscrt_type.value == 'from_buildtype':
+ if self.buildtype == 'debug':
+ ET.SubElement(type_config, 'UseDebugLibraries').text = 'true'
+ ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebugDLL'
+ else:
+ ET.SubElement(type_config, 'UseDebugLibraries').text = 'false'
+ ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDLL'
+ elif vscrt_type.value == 'static_from_buildtype':
+ if self.buildtype == 'debug':
+ ET.SubElement(type_config, 'UseDebugLibraries').text = 'true'
+ ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebug'
+ else:
+ ET.SubElement(type_config, 'UseDebugLibraries').text = 'false'
+ ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreaded'
+ elif vscrt_type.value == 'mdd':
+ ET.SubElement(type_config, 'UseDebugLibraries').text = 'true'
+ ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebugDLL'
+ elif vscrt_type.value == 'mt':
+ # FIXME, wrong
+ ET.SubElement(type_config, 'UseDebugLibraries').text = 'false'
+ ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreaded'
+ elif vscrt_type.value == 'mtd':
+ # FIXME, wrong
+ ET.SubElement(type_config, 'UseDebugLibraries').text = 'true'
+ ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDebug'
+ else:
+ ET.SubElement(type_config, 'UseDebugLibraries').text = 'false'
+ ET.SubElement(clconf, 'RuntimeLibrary').text = 'MultiThreadedDLL'
+ # Sanitizers
+ if '/fsanitize=address' in build_args:
+ ET.SubElement(type_config, 'EnableASAN').text = 'true'
+ # Debug format
+ if '/ZI' in build_args:
+ ET.SubElement(clconf, 'DebugInformationFormat').text = 'EditAndContinue'
+ elif '/Zi' in build_args:
+ ET.SubElement(clconf, 'DebugInformationFormat').text = 'ProgramDatabase'
+ elif '/Z7' in build_args:
+ ET.SubElement(clconf, 'DebugInformationFormat').text = 'OldStyle'
+ else:
+ ET.SubElement(clconf, 'DebugInformationFormat').text = 'None'
+ # Runtime checks
+ if '/RTC1' in build_args:
+ ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'EnableFastChecks'
+ elif '/RTCu' in build_args:
+ ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'UninitializedLocalUsageCheck'
+ elif '/RTCs' in build_args:
+ ET.SubElement(clconf, 'BasicRuntimeChecks').text = 'StackFrameRuntimeCheck'
+ # Exception handling has to be set in the xml in addition to the "AdditionalOptions" because otherwise
+ # cl will give warning D9025: overriding '/Ehs' with cpp_eh value
+ if 'cpp' in target.compilers:
+ eh = self.environment.coredata.options[OptionKey('eh', machine=target.for_machine, lang='cpp')]
+ if eh.value == 'a':
+ ET.SubElement(clconf, 'ExceptionHandling').text = 'Async'
+ elif eh.value == 's':
+ ET.SubElement(clconf, 'ExceptionHandling').text = 'SyncCThrow'
+ elif eh.value == 'none':
+ ET.SubElement(clconf, 'ExceptionHandling').text = 'false'
+ else: # 'sc' or 'default'
+ ET.SubElement(clconf, 'ExceptionHandling').text = 'Sync'
+ generated_files, custom_target_output_files, generated_files_include_dirs = self.generate_custom_generator_commands(
+ target, root)
+ (gen_src, gen_hdrs, gen_objs, gen_langs) = self.split_sources(generated_files)
+ (custom_src, custom_hdrs, custom_objs, custom_langs) = self.split_sources(custom_target_output_files)
+ gen_src += custom_src
+ gen_hdrs += custom_hdrs
+ gen_langs += custom_langs
+
+ # Arguments, include dirs, defines for all files in the current target
+ target_args = []
+ target_defines = []
+ target_inc_dirs = []
+ # Arguments, include dirs, defines passed to individual files in
+ # a target; perhaps because the args are language-specific
+ #
+ # file_args is also later split out into defines and include_dirs in
+ # case someone passed those in there
+ file_args = {l: c.compiler_args() for l, c in target.compilers.items()}
+ file_defines = {l: [] for l in target.compilers}
+ file_inc_dirs = {l: [] for l in target.compilers}
+ # The order in which these compile args are added must match
+ # generate_single_compile() and generate_basic_compiler_args()
+ for l, comp in target.compilers.items():
+ if l in file_args:
+ file_args[l] += compilers.get_base_compile_args(
+ target.get_options(), comp)
+ file_args[l] += comp.get_option_compile_args(
+ target.get_options())
+
+ # Add compile args added using add_project_arguments()
+ for l, args in self.build.projects_args[target.for_machine].get(target.subproject, {}).items():
+ if l in file_args:
+ file_args[l] += args
+ # Add compile args added using add_global_arguments()
+ # These override per-project arguments
+ for l, args in self.build.global_args[target.for_machine].items():
+ if l in file_args:
+ file_args[l] += args
+ # Compile args added from the env or cross file: CFLAGS/CXXFLAGS, etc. We want these
+ # to override all the defaults, but not the per-target compile args.
+ for l in file_args.keys():
+ opts = self.environment.coredata.options[OptionKey('args', machine=target.for_machine, lang=l)]
+ file_args[l] += opts.value
+ for args in file_args.values():
+ # This is where Visual Studio will insert target_args, target_defines,
+ # etc, which are added later from external deps (see below).
+ args += ['%(AdditionalOptions)', '%(PreprocessorDefinitions)', '%(AdditionalIncludeDirectories)']
+ # Add custom target dirs as includes automatically, but before
+ # target-specific include dirs. See _generate_single_compile() in
+ # the ninja backend for caveats.
+ args += ['-I' + arg for arg in generated_files_include_dirs]
+ # Add include dirs from the `include_directories:` kwarg on the target
+ # and from `include_directories:` of internal deps of the target.
+ #
+ # Target include dirs should override internal deps include dirs.
+ # This is handled in BuildTarget.process_kwargs()
+ #
+ # Include dirs from internal deps should override include dirs from
+ # external deps and must maintain the order in which they are
+ # specified. Hence, we must reverse so that the order is preserved.
+ #
+ # These are per-target, but we still add them as per-file because we
+ # need them to be looked in first.
+ for d in reversed(target.get_include_dirs()):
+ # reversed is used to keep order of includes
+ for i in reversed(d.get_incdirs()):
+ curdir = os.path.join(d.get_curdir(), i)
+ try:
+ args.append('-I' + self.relpath(curdir, target.subdir)) # build dir
+ args.append('-I' + os.path.join(proj_to_src_root, curdir)) # src dir
+ except ValueError:
+ # Include is on different drive
+ args.append('-I' + os.path.normpath(curdir))
+ for i in d.get_extra_build_dirs():
+ curdir = os.path.join(d.get_curdir(), i)
+ args.append('-I' + self.relpath(curdir, target.subdir)) # build dir
+ # Add per-target compile args, f.ex, `c_args : ['/DFOO']`. We set these
+ # near the end since these are supposed to override everything else.
+ for l, args in target.extra_args.items():
+ if l in file_args:
+ file_args[l] += args
+ # The highest priority includes. In order of directory search:
+ # target private dir, target build dir, target source dir
+ for args in file_args.values():
+ t_inc_dirs = [self.relpath(self.get_target_private_dir(target),
+ self.get_target_dir(target))]
+ if target.implicit_include_directories:
+ t_inc_dirs += ['.', proj_to_src_dir]
+ args += ['-I' + arg for arg in t_inc_dirs]
+
+ # Split preprocessor defines and include directories out of the list of
+ # all extra arguments. The rest go into %(AdditionalOptions).
+ for l, args in file_args.items():
+ for arg in args[:]:
+ if arg.startswith(('-D', '/D')) or arg == '%(PreprocessorDefinitions)':
+ file_args[l].remove(arg)
+ # Don't escape the marker
+ if arg == '%(PreprocessorDefinitions)':
+ define = arg
+ else:
+ define = arg[2:]
+ # De-dup
+ if define not in file_defines[l]:
+ file_defines[l].append(define)
+ elif arg.startswith(('-I', '/I')) or arg == '%(AdditionalIncludeDirectories)':
+ file_args[l].remove(arg)
+ # Don't escape the marker
+ if arg == '%(AdditionalIncludeDirectories)':
+ inc_dir = arg
+ else:
+ inc_dir = arg[2:]
+ # De-dup
+ if inc_dir not in file_inc_dirs[l]:
+ file_inc_dirs[l].append(inc_dir)
+ # Add include dirs to target as well so that "Go to Document" works in headers
+ if inc_dir not in target_inc_dirs:
+ target_inc_dirs.append(inc_dir)
+
+ # Split compile args needed to find external dependencies
+ # Link args are added while generating the link command
+ for d in reversed(target.get_external_deps()):
+ # Cflags required by external deps might have UNIX-specific flags,
+ # so filter them out if needed
+ if isinstance(d, dependencies.OpenMPDependency):
+ ET.SubElement(clconf, 'OpenMPSupport').text = 'true'
+ else:
+ d_compile_args = compiler.unix_args_to_native(d.get_compile_args())
+ for arg in d_compile_args:
+ if arg.startswith(('-D', '/D')):
+ define = arg[2:]
+ # De-dup
+ if define in target_defines:
+ target_defines.remove(define)
+ target_defines.append(define)
+ elif arg.startswith(('-I', '/I')):
+ inc_dir = arg[2:]
+ # De-dup
+ if inc_dir not in target_inc_dirs:
+ target_inc_dirs.append(inc_dir)
+ else:
+ target_args.append(arg)
+
+ languages += gen_langs
+ if '/Gw' in build_args:
+ target_args.append('/Gw')
+ if len(target_args) > 0:
+ target_args.append('%(AdditionalOptions)')
+ ET.SubElement(clconf, "AdditionalOptions").text = ' '.join(target_args)
+ ET.SubElement(clconf, 'AdditionalIncludeDirectories').text = ';'.join(target_inc_dirs)
+ target_defines.append('%(PreprocessorDefinitions)')
+ ET.SubElement(clconf, 'PreprocessorDefinitions').text = ';'.join(target_defines)
+ ET.SubElement(clconf, 'FunctionLevelLinking').text = 'true'
+ # Warning level
+ warning_level = target.get_option(OptionKey('warning_level'))
+ ET.SubElement(clconf, 'WarningLevel').text = 'Level' + str(1 + int(warning_level))
+ if target.get_option(OptionKey('werror')):
+ ET.SubElement(clconf, 'TreatWarningAsError').text = 'true'
+ # Optimization flags
+ o_flags = split_o_flags_args(build_args)
+ if '/Ox' in o_flags:
+ ET.SubElement(clconf, 'Optimization').text = 'Full'
+ elif '/O2' in o_flags:
+ ET.SubElement(clconf, 'Optimization').text = 'MaxSpeed'
+ elif '/O1' in o_flags:
+ ET.SubElement(clconf, 'Optimization').text = 'MinSpace'
+ elif '/Od' in o_flags:
+ ET.SubElement(clconf, 'Optimization').text = 'Disabled'
+ if '/Oi' in o_flags:
+ ET.SubElement(clconf, 'IntrinsicFunctions').text = 'true'
+ if '/Ob1' in o_flags:
+ ET.SubElement(clconf, 'InlineFunctionExpansion').text = 'OnlyExplicitInline'
+ elif '/Ob2' in o_flags:
+ ET.SubElement(clconf, 'InlineFunctionExpansion').text = 'AnySuitable'
+ # Size-preserving flags
+ if '/Os' in o_flags:
+ ET.SubElement(clconf, 'FavorSizeOrSpeed').text = 'Size'
+ # Note: setting FavorSizeOrSpeed with clang-cl conflicts with /Od and can make debugging difficult, so don't.
+ elif '/Od' not in o_flags:
+ ET.SubElement(clconf, 'FavorSizeOrSpeed').text = 'Speed'
+ # Note: SuppressStartupBanner is /NOLOGO and is 'true' by default
+ self.generate_lang_standard_info(file_args, clconf)
+ pch_sources = {}
+ if self.environment.coredata.options.get(OptionKey('b_pch')):
+ for lang in ['c', 'cpp']:
+ pch = target.get_pch(lang)
+ if not pch:
+ continue
+ if compiler.id == 'msvc':
+ if len(pch) == 1:
+ # Auto generate PCH.
+ src = os.path.join(down, self.create_msvc_pch_implementation(target, lang, pch[0]))
+ pch_header_dir = os.path.dirname(os.path.join(proj_to_src_dir, pch[0]))
+ else:
+ src = os.path.join(proj_to_src_dir, pch[1])
+ pch_header_dir = None
+ pch_sources[lang] = [pch[0], src, lang, pch_header_dir]
+ else:
+ # I don't know whether its relevant but let's handle other compilers
+ # used with a vs backend
+ pch_sources[lang] = [pch[0], None, lang, None]
+
+ resourcecompile = ET.SubElement(compiles, 'ResourceCompile')
+ ET.SubElement(resourcecompile, 'PreprocessorDefinitions')
+
+ # Linker options
+ link = ET.SubElement(compiles, 'Link')
+ extra_link_args = compiler.compiler_args()
+ # FIXME: Can these buildtype linker args be added as tags in the
+ # vcxproj file (similar to buildtype compiler args) instead of in
+ # AdditionalOptions?
+ extra_link_args += compiler.get_buildtype_linker_args(self.buildtype)
+ # Generate Debug info
+ if self.debug:
+ self.generate_debug_information(link)
+ else:
+ ET.SubElement(link, 'GenerateDebugInformation').text = 'false'
+ if not isinstance(target, build.StaticLibrary):
+ if isinstance(target, build.SharedModule):
+ options = self.environment.coredata.options
+ extra_link_args += compiler.get_std_shared_module_link_args(options)
+ # Add link args added using add_project_link_arguments()
+ extra_link_args += self.build.get_project_link_args(compiler, target.subproject, target.for_machine)
+ # Add link args added using add_global_link_arguments()
+ # These override per-project link arguments
+ extra_link_args += self.build.get_global_link_args(compiler, target.for_machine)
+ # Link args added from the env: LDFLAGS, or the cross file. We want
+ # these to override all the defaults but not the per-target link
+ # args.
+ extra_link_args += self.environment.coredata.get_external_link_args(
+ target.for_machine, compiler.get_language())
+ # Only non-static built targets need link args and link dependencies
+ extra_link_args += target.link_args
+ # External deps must be last because target link libraries may depend on them.
+ for dep in target.get_external_deps():
+ # Extend without reordering or de-dup to preserve `-L -l` sets
+ # https://github.com/mesonbuild/meson/issues/1718
+ if isinstance(dep, dependencies.OpenMPDependency):
+ ET.SubElement(clconf, 'OpenMPSuppport').text = 'true'
+ else:
+ extra_link_args.extend_direct(dep.get_link_args())
+ for d in target.get_dependencies():
+ if isinstance(d, build.StaticLibrary):
+ for dep in d.get_external_deps():
+ if isinstance(dep, dependencies.OpenMPDependency):
+ ET.SubElement(clconf, 'OpenMPSuppport').text = 'true'
+ else:
+ extra_link_args.extend_direct(dep.get_link_args())
+ # Add link args for c_* or cpp_* build options. Currently this only
+ # adds c_winlibs and cpp_winlibs when building for Windows. This needs
+ # to be after all internal and external libraries so that unresolved
+ # symbols from those can be found here. This is needed when the
+ # *_winlibs that we want to link to are static mingw64 libraries.
+ extra_link_args += compiler.get_option_link_args(self.environment.coredata.options)
+ (additional_libpaths, additional_links, extra_link_args) = self.split_link_args(extra_link_args.to_native())
+
+ # Add more libraries to be linked if needed
+ for t in target.get_dependencies():
+ if isinstance(t, build.CustomTargetIndex):
+ # We don't need the actual project here, just the library name
+ lobj = t
+ else:
+ lobj = self.build.targets[t.get_id()]
+ linkname = os.path.join(down, self.get_target_filename_for_linking(lobj))
+ if t in target.link_whole_targets:
+ if compiler.id == 'msvc' and version_compare(compiler.version, '<19.00.23918'):
+ # Expand our object lists manually if we are on pre-Visual Studio 2015 Update 2
+ l = t.extract_all_objects(False)
+
+ # Unfortunately, we can't use self.object_filename_from_source()
+ for gen in l.genlist:
+ for src in gen.get_outputs():
+ if self.environment.is_source(src):
+ path = self.get_target_generated_dir(t, gen, src)
+ gen_src_ext = '.' + os.path.splitext(path)[1][1:]
+ extra_link_args.append(path[:-len(gen_src_ext)] + '.obj')
+
+ for src in l.srclist:
+ obj_basename = None
+ if self.environment.is_source(src):
+ obj_basename = self.object_filename_from_source(t, src)
+ target_private_dir = self.relpath(self.get_target_private_dir(t),
+ self.get_target_dir(t))
+ rel_obj = os.path.join(target_private_dir, obj_basename)
+ extra_link_args.append(rel_obj)
+
+ extra_link_args.extend(self.flatten_object_list(t))
+ else:
+ # /WHOLEARCHIVE:foo must go into AdditionalOptions
+ extra_link_args += compiler.get_link_whole_for(linkname)
+ # To force Visual Studio to build this project even though it
+ # has no sources, we include a reference to the vcxproj file
+ # that builds this target. Technically we should add this only
+ # if the current target has no sources, but it doesn't hurt to
+ # have 'extra' references.
+ trelpath = self.get_target_dir_relative_to(t, target)
+ tvcxproj = os.path.join(trelpath, t.get_id() + '.vcxproj')
+ tid = self.environment.coredata.target_guids[t.get_id()]
+ self.add_project_reference(root, tvcxproj, tid, link_outputs=True)
+ # Mark the dependency as already handled to not have
+ # multiple references to the same target.
+ self.handled_target_deps[target.get_id()].append(t.get_id())
+ else:
+ # Other libraries go into AdditionalDependencies
+ if linkname not in additional_links:
+ additional_links.append(linkname)
+ for lib in self.get_custom_target_provided_libraries(target):
+ additional_links.append(self.relpath(lib, self.get_target_dir(target)))
+ additional_objects = []
+ for o in self.flatten_object_list(target, down)[0]:
+ assert isinstance(o, str)
+ additional_objects.append(o)
+ for o in custom_objs:
+ additional_objects.append(o)
+
+ if len(extra_link_args) > 0:
+ extra_link_args.append('%(AdditionalOptions)')
+ ET.SubElement(link, "AdditionalOptions").text = ' '.join(extra_link_args)
+ if len(additional_libpaths) > 0:
+ additional_libpaths.insert(0, '%(AdditionalLibraryDirectories)')
+ ET.SubElement(link, 'AdditionalLibraryDirectories').text = ';'.join(additional_libpaths)
+ if len(additional_links) > 0:
+ additional_links.append('%(AdditionalDependencies)')
+ ET.SubElement(link, 'AdditionalDependencies').text = ';'.join(additional_links)
+ ofile = ET.SubElement(link, 'OutputFile')
+ ofile.text = f'$(OutDir){target.get_filename()}'
+ subsys = ET.SubElement(link, 'SubSystem')
+ subsys.text = subsystem
+ if isinstance(target, (build.SharedLibrary, build.Executable)) and target.get_import_filename():
+ # DLLs built with MSVC always have an import library except when
+ # they're data-only DLLs, but we don't support those yet.
+ ET.SubElement(link, 'ImportLibrary').text = target.get_import_filename()
+ if isinstance(target, build.SharedLibrary):
+ # Add module definitions file, if provided
+ if target.vs_module_defs:
+ relpath = os.path.join(down, target.vs_module_defs.rel_to_builddir(self.build_to_src))
+ ET.SubElement(link, 'ModuleDefinitionFile').text = relpath
+ if self.debug:
+ pdb = ET.SubElement(link, 'ProgramDataBaseFileName')
+ pdb.text = f'$(OutDir){target_name}.pdb'
+ targetmachine = ET.SubElement(link, 'TargetMachine')
+ if target.for_machine is MachineChoice.BUILD:
+ targetplatform = platform.lower()
+ else:
+ targetplatform = self.platform.lower()
+ if targetplatform == 'win32':
+ targetmachine.text = 'MachineX86'
+ elif targetplatform == 'x64' or detect_microsoft_gdk(targetplatform):
+ targetmachine.text = 'MachineX64'
+ elif targetplatform == 'arm':
+ targetmachine.text = 'MachineARM'
+ elif targetplatform == 'arm64':
+ targetmachine.text = 'MachineARM64'
+ elif targetplatform == 'arm64ec':
+ targetmachine.text = 'MachineARM64EC'
+ else:
+ raise MesonException('Unsupported Visual Studio target machine: ' + targetplatform)
+ # /nologo
+ ET.SubElement(link, 'SuppressStartupBanner').text = 'true'
+ # /release
+ if not self.environment.coredata.get_option(OptionKey('debug')):
+ ET.SubElement(link, 'SetChecksum').text = 'true'
+
+ meson_file_group = ET.SubElement(root, 'ItemGroup')
+ ET.SubElement(meson_file_group, 'None', Include=os.path.join(proj_to_src_dir, build_filename))
+
+ # Visual Studio can't load projects that present duplicated items. Filter them out
+ # by keeping track of already added paths.
+ def path_normalize_add(path, lis):
+ normalized = os.path.normcase(os.path.normpath(path))
+ if normalized not in lis:
+ lis.append(normalized)
+ return True
+ else:
+ return False
+
+ previous_includes = []
+ if len(headers) + len(gen_hdrs) + len(target.extra_files) + len(pch_sources) > 0:
+ inc_hdrs = ET.SubElement(root, 'ItemGroup')
+ for h in headers:
+ relpath = os.path.join(down, h.rel_to_builddir(self.build_to_src))
+ if path_normalize_add(relpath, previous_includes):
+ ET.SubElement(inc_hdrs, 'CLInclude', Include=relpath)
+ for h in gen_hdrs:
+ if path_normalize_add(h, previous_includes):
+ ET.SubElement(inc_hdrs, 'CLInclude', Include=h)
+ for h in target.extra_files:
+ relpath = os.path.join(down, h.rel_to_builddir(self.build_to_src))
+ if path_normalize_add(relpath, previous_includes):
+ ET.SubElement(inc_hdrs, 'CLInclude', Include=relpath)
+ for headers in pch_sources.values():
+ path = os.path.join(proj_to_src_dir, headers[0])
+ if path_normalize_add(path, previous_includes):
+ ET.SubElement(inc_hdrs, 'CLInclude', Include=path)
+
+ previous_sources = []
+ if len(sources) + len(gen_src) + len(pch_sources) > 0:
+ inc_src = ET.SubElement(root, 'ItemGroup')
+ for s in sources:
+ relpath = os.path.join(down, s.rel_to_builddir(self.build_to_src))
+ if path_normalize_add(relpath, previous_sources):
+ inc_cl = ET.SubElement(inc_src, 'CLCompile', Include=relpath)
+ lang = Vs2010Backend.lang_from_source_file(s)
+ self.add_pch(pch_sources, lang, inc_cl)
+ self.add_additional_options(lang, inc_cl, file_args)
+ self.add_preprocessor_defines(lang, inc_cl, file_defines)
+ self.add_include_dirs(lang, inc_cl, file_inc_dirs)
+ ET.SubElement(inc_cl, 'ObjectFileName').text = "$(IntDir)" + \
+ self.object_filename_from_source(target, s)
+ for s in gen_src:
+ if path_normalize_add(s, previous_sources):
+ inc_cl = ET.SubElement(inc_src, 'CLCompile', Include=s)
+ lang = Vs2010Backend.lang_from_source_file(s)
+ self.add_pch(pch_sources, lang, inc_cl)
+ self.add_additional_options(lang, inc_cl, file_args)
+ self.add_preprocessor_defines(lang, inc_cl, file_defines)
+ self.add_include_dirs(lang, inc_cl, file_inc_dirs)
+ s = File.from_built_file(target.get_subdir(), s)
+ ET.SubElement(inc_cl, 'ObjectFileName').text = "$(IntDir)" + \
+ self.object_filename_from_source(target, s)
+ for lang, headers in pch_sources.items():
+ impl = headers[1]
+ if impl and path_normalize_add(impl, previous_sources):
+ inc_cl = ET.SubElement(inc_src, 'CLCompile', Include=impl)
+ self.create_pch(pch_sources, lang, inc_cl)
+ self.add_additional_options(lang, inc_cl, file_args)
+ self.add_preprocessor_defines(lang, inc_cl, file_defines)
+ pch_header_dir = pch_sources[lang][3]
+ if pch_header_dir:
+ inc_dirs = copy.deepcopy(file_inc_dirs)
+ inc_dirs[lang] = [pch_header_dir] + inc_dirs[lang]
+ else:
+ inc_dirs = file_inc_dirs
+ self.add_include_dirs(lang, inc_cl, inc_dirs)
+ # XXX: Do we need to set the object file name name here too?
+
+ previous_objects = []
+ if self.has_objects(objects, additional_objects, gen_objs):
+ inc_objs = ET.SubElement(root, 'ItemGroup')
+ for s in objects:
+ relpath = os.path.join(down, s.rel_to_builddir(self.build_to_src))
+ if path_normalize_add(relpath, previous_objects):
+ ET.SubElement(inc_objs, 'Object', Include=relpath)
+ for s in additional_objects:
+ if path_normalize_add(s, previous_objects):
+ ET.SubElement(inc_objs, 'Object', Include=s)
+ self.add_generated_objects(inc_objs, gen_objs)
+
+ ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
+ self.add_regen_dependency(root)
+ self.add_target_deps(root, target)
+ self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
+
+ def gen_regenproj(self, project_name, ofname):
+ guid = self.environment.coredata.regen_guid
+ (root, type_config) = self.create_basic_project(project_name,
+ temp_dir='regen-temp',
+ guid=guid)
+
+ action = ET.SubElement(root, 'ItemDefinitionGroup')
+ midl = ET.SubElement(action, 'Midl')
+ ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)'
+ ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)'
+ ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h'
+ ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb'
+ ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c'
+ ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c'
+ regen_command = self.environment.get_build_command() + ['--internal', 'regencheck']
+ cmd_templ = '''call %s > NUL
+"%s" "%s"'''
+ regen_command = cmd_templ % \
+ (self.get_vcvars_command(), '" "'.join(regen_command), self.environment.get_scratch_dir())
+ self.add_custom_build(root, 'regen', regen_command, deps=self.get_regen_filelist(),
+ outputs=[Vs2010Backend.get_regen_stampfile(self.environment.get_build_dir())],
+ msg='Checking whether solution needs to be regenerated.')
+ ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
+ ET.SubElement(root, 'ImportGroup', Label='ExtensionTargets')
+ self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
+
+ def gen_testproj(self, target_name, ofname):
+ guid = self.environment.coredata.test_guid
+ (root, type_config) = self.create_basic_project(target_name,
+ temp_dir='test-temp',
+ guid=guid)
+
+ action = ET.SubElement(root, 'ItemDefinitionGroup')
+ midl = ET.SubElement(action, 'Midl')
+ ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)'
+ ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)'
+ ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h'
+ ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb'
+ ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c'
+ ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c'
+ # FIXME: No benchmarks?
+ test_command = self.environment.get_build_command() + ['test', '--no-rebuild']
+ if not self.environment.coredata.get_option(OptionKey('stdsplit')):
+ test_command += ['--no-stdsplit']
+ if self.environment.coredata.get_option(OptionKey('errorlogs')):
+ test_command += ['--print-errorlogs']
+ self.serialize_tests()
+ self.add_custom_build(root, 'run_tests', '"%s"' % ('" "'.join(test_command)))
+ ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
+ self.add_regen_dependency(root)
+ self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
+
+ def gen_installproj(self, target_name, ofname):
+ self.create_install_data_files()
+
+ guid = self.environment.coredata.install_guid
+ (root, type_config) = self.create_basic_project(target_name,
+ temp_dir='install-temp',
+ guid=guid)
+
+ action = ET.SubElement(root, 'ItemDefinitionGroup')
+ midl = ET.SubElement(action, 'Midl')
+ ET.SubElement(midl, "AdditionalIncludeDirectories").text = '%(AdditionalIncludeDirectories)'
+ ET.SubElement(midl, "OutputDirectory").text = '$(IntDir)'
+ ET.SubElement(midl, 'HeaderFileName').text = '%(Filename).h'
+ ET.SubElement(midl, 'TypeLibraryName').text = '%(Filename).tlb'
+ ET.SubElement(midl, 'InterfaceIdentifierFilename').text = '%(Filename)_i.c'
+ ET.SubElement(midl, 'ProxyFileName').text = '%(Filename)_p.c'
+ install_command = self.environment.get_build_command() + ['install', '--no-rebuild']
+ self.add_custom_build(root, 'run_install', '"%s"' % ('" "'.join(install_command)))
+ ET.SubElement(root, 'Import', Project=r'$(VCTargetsPath)\Microsoft.Cpp.targets')
+ self.add_regen_dependency(root)
+ self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
+
+ def add_custom_build(self, node, rulename, command, deps=None, outputs=None, msg=None, verify_files=True):
+ igroup = ET.SubElement(node, 'ItemGroup')
+ rulefile = os.path.join(self.environment.get_scratch_dir(), rulename + '.rule')
+ if not os.path.exists(rulefile):
+ with open(rulefile, 'w', encoding='utf-8') as f:
+ f.write("# Meson regen file.")
+ custombuild = ET.SubElement(igroup, 'CustomBuild', Include=rulefile)
+ if msg:
+ message = ET.SubElement(custombuild, 'Message')
+ message.text = msg
+ if not verify_files:
+ ET.SubElement(custombuild, 'VerifyInputsAndOutputsExist').text = 'false'
+
+ # If a command ever were to change the current directory or set local
+ # variables this would need to be more complicated, as msbuild by
+ # default executes all CustomBuilds in a project using the same
+ # shell. Right now such tasks are all done inside the meson_exe
+ # wrapper. The trailing newline appears to be necessary to allow
+ # parallel custom builds to work.
+ ET.SubElement(custombuild, 'Command').text = f"{command}\n"
+
+ if not outputs:
+ # Use a nonexistent file to always consider the target out-of-date.
+ outputs = [self.nonexistent_file(os.path.join(self.environment.get_scratch_dir(),
+ 'outofdate.file'))]
+ ET.SubElement(custombuild, 'Outputs').text = ';'.join(outputs)
+ if deps:
+ ET.SubElement(custombuild, 'AdditionalInputs').text = ';'.join(deps)
+
+ @staticmethod
+ def nonexistent_file(prefix):
+ i = 0
+ file = prefix
+ while os.path.exists(file):
+ file = '%s%d' % (prefix, i)
+ return file
+
+ def generate_debug_information(self, link):
+ # valid values for vs2015 is 'false', 'true', 'DebugFastLink'
+ ET.SubElement(link, 'GenerateDebugInformation').text = 'true'
+
+ def add_regen_dependency(self, root):
+ regen_vcxproj = os.path.join(self.environment.get_build_dir(), 'REGEN.vcxproj')
+ self.add_project_reference(root, regen_vcxproj, self.environment.coredata.regen_guid)
+
+ def generate_lang_standard_info(self, file_args, clconf):
+ pass
diff --git a/mesonbuild/backend/vs2012backend.py b/mesonbuild/backend/vs2012backend.py
new file mode 100644
index 0000000..af8d5df
--- /dev/null
+++ b/mesonbuild/backend/vs2012backend.py
@@ -0,0 +1,43 @@
+# Copyright 2014-2016 The Meson development team
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from __future__ import annotations
+
+import typing as T
+
+from .vs2010backend import Vs2010Backend
+from ..mesonlib import MesonException
+
+if T.TYPE_CHECKING:
+ from ..build import Build
+ from ..interpreter import Interpreter
+
+class Vs2012Backend(Vs2010Backend):
+ def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter]):
+ super().__init__(build, interpreter)
+ self.name = 'vs2012'
+ self.vs_version = '2012'
+ self.sln_file_version = '12.00'
+ self.sln_version_comment = '2012'
+ if self.environment is not None:
+ # TODO: we assume host == build
+ comps = self.environment.coredata.compilers.host
+ if comps and all(c.id == 'intel-cl' for c in comps.values()):
+ c = list(comps.values())[0]
+ if c.version.startswith('19'):
+ self.platform_toolset = 'Intel C++ Compiler 19.0'
+ else:
+ # We don't have support for versions older than 2019 right now.
+ raise MesonException('There is currently no support for ICL before 19, patches welcome.')
+ if self.platform_toolset is None:
+ self.platform_toolset = 'v110'
diff --git a/mesonbuild/backend/vs2013backend.py b/mesonbuild/backend/vs2013backend.py
new file mode 100644
index 0000000..44d45d6
--- /dev/null
+++ b/mesonbuild/backend/vs2013backend.py
@@ -0,0 +1,42 @@
+# Copyright 2014-2016 The Meson development team
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from __future__ import annotations
+
+from .vs2010backend import Vs2010Backend
+from ..mesonlib import MesonException
+import typing as T
+
+if T.TYPE_CHECKING:
+ from ..build import Build
+ from ..interpreter import Interpreter
+
+class Vs2013Backend(Vs2010Backend):
+ def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter]):
+ super().__init__(build, interpreter)
+ self.name = 'vs2013'
+ self.vs_version = '2013'
+ self.sln_file_version = '12.00'
+ self.sln_version_comment = '2013'
+ if self.environment is not None:
+ # TODO: we assume host == build
+ comps = self.environment.coredata.compilers.host
+ if comps and all(c.id == 'intel-cl' for c in comps.values()):
+ c = list(comps.values())[0]
+ if c.version.startswith('19'):
+ self.platform_toolset = 'Intel C++ Compiler 19.0'
+ else:
+ # We don't have support for versions older than 2019 right now.
+ raise MesonException('There is currently no support for ICL before 19, patches welcome.')
+ if self.platform_toolset is None:
+ self.platform_toolset = 'v120'
diff --git a/mesonbuild/backend/vs2015backend.py b/mesonbuild/backend/vs2015backend.py
new file mode 100644
index 0000000..25e0a5e
--- /dev/null
+++ b/mesonbuild/backend/vs2015backend.py
@@ -0,0 +1,43 @@
+# Copyright 2014-2016 The Meson development team
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from __future__ import annotations
+
+import typing as T
+
+from .vs2010backend import Vs2010Backend
+from ..mesonlib import MesonException
+
+if T.TYPE_CHECKING:
+ from ..build import Build
+ from ..interpreter import Interpreter
+
+class Vs2015Backend(Vs2010Backend):
+ def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter]):
+ super().__init__(build, interpreter)
+ self.name = 'vs2015'
+ self.vs_version = '2015'
+ self.sln_file_version = '12.00'
+ self.sln_version_comment = '14'
+ if self.environment is not None:
+ # TODO: we assume host == build
+ comps = self.environment.coredata.compilers.host
+ if comps and all(c.id == 'intel-cl' for c in comps.values()):
+ c = list(comps.values())[0]
+ if c.version.startswith('19'):
+ self.platform_toolset = 'Intel C++ Compiler 19.0'
+ else:
+ # We don't have support for versions older than 2019 right now.
+ raise MesonException('There is currently no support for ICL before 19, patches welcome.')
+ if self.platform_toolset is None:
+ self.platform_toolset = 'v140'
diff --git a/mesonbuild/backend/vs2017backend.py b/mesonbuild/backend/vs2017backend.py
new file mode 100644
index 0000000..4ed5e48
--- /dev/null
+++ b/mesonbuild/backend/vs2017backend.py
@@ -0,0 +1,67 @@
+# Copyright 2014-2016 The Meson development team
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+# http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from __future__ import annotations
+
+import os
+import typing as T
+import xml.etree.ElementTree as ET
+
+from .vs2010backend import Vs2010Backend
+from ..mesonlib import MesonException
+
+if T.TYPE_CHECKING:
+ from ..build import Build
+ from ..interpreter import Interpreter
+
+
+class Vs2017Backend(Vs2010Backend):
+ def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter]):
+ super().__init__(build, interpreter)
+ self.name = 'vs2017'
+ self.vs_version = '2017'
+ self.sln_file_version = '12.00'
+ self.sln_version_comment = '15'
+ # We assume that host == build
+ if self.environment is not None:
+ comps = self.environment.coredata.compilers.host
+ if comps:
+ if comps and all(c.id == 'clang-cl' for c in comps.values()):
+ self.platform_toolset = 'llvm'
+ elif comps and all(c.id == 'intel-cl' for c in comps.values()):
+ c = list(comps.values())[0]
+ if c.version.startswith('19'):
+ self.platform_toolset = 'Intel C++ Compiler 19.0'
+ else:
+ # We don't have support for versions older than 2019 right now.
+ raise MesonException('There is currently no support for ICL before 19, patches welcome.')
+ if self.platform_toolset is None:
+ self.platform_toolset = 'v141'
+ # WindowsSDKVersion should be set by command prompt.
+ sdk_version = os.environ.get('WindowsSDKVersion', None)
+ if sdk_version:
+ self.windows_target_platform_version = sdk_version.rstrip('\\')
+
+ def generate_debug_information(self, link):
+ # valid values for vs2017 is 'false', 'true', 'DebugFastLink', 'DebugFull'
+ ET.SubElement(link, 'GenerateDebugInformation').text = 'DebugFull'
+
+ def generate_lang_standard_info(self, file_args, clconf):
+ if 'cpp' in file_args:
+ optargs = [x for x in file_args['cpp'] if x.startswith('/std:c++')]
+ if optargs:
+ ET.SubElement(clconf, 'LanguageStandard').text = optargs[0].replace("/std:c++", "stdcpp")
+ if 'c' in file_args:
+ optargs = [x for x in file_args['c'] if x.startswith('/std:c')]
+ if optargs:
+ ET.SubElement(clconf, 'LanguageStandard_C').text = optargs[0].replace("/std:c", "stdc")
diff --git a/mesonbuild/backend/vs2019backend.py b/mesonbuild/backend/vs2019backend.py
new file mode 100644
index 0000000..0734336
--- /dev/null
+++ b/mesonbuild/backend/vs2019backend.py
@@ -0,0 +1,62 @@
+# Copyright 2014-2019 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
+
+import os
+import typing as T
+import xml.etree.ElementTree as ET
+
+from .vs2010backend import Vs2010Backend
+
+if T.TYPE_CHECKING:
+ from ..build import Build
+ from ..interpreter import Interpreter
+
+
+class Vs2019Backend(Vs2010Backend):
+ def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter]):
+ super().__init__(build, interpreter)
+ self.name = 'vs2019'
+ self.sln_file_version = '12.00'
+ self.sln_version_comment = 'Version 16'
+ if self.environment is not None:
+ comps = self.environment.coredata.compilers.host
+ if comps and all(c.id == 'clang-cl' for c in comps.values()):
+ self.platform_toolset = 'ClangCL'
+ elif comps and all(c.id == 'intel-cl' for c in comps.values()):
+ c = list(comps.values())[0]
+ if c.version.startswith('19'):
+ self.platform_toolset = 'Intel C++ Compiler 19.0'
+ # We don't have support for versions older than 2019 right now.
+ if not self.platform_toolset:
+ self.platform_toolset = 'v142'
+ self.vs_version = '2019'
+ # WindowsSDKVersion should be set by command prompt.
+ sdk_version = os.environ.get('WindowsSDKVersion', None)
+ if sdk_version:
+ self.windows_target_platform_version = sdk_version.rstrip('\\')
+
+ def generate_debug_information(self, link):
+ # valid values for vs2019 is 'false', 'true', 'DebugFastLink', 'DebugFull'
+ ET.SubElement(link, 'GenerateDebugInformation').text = 'DebugFull'
+
+ def generate_lang_standard_info(self, file_args, clconf):
+ if 'cpp' in file_args:
+ optargs = [x for x in file_args['cpp'] if x.startswith('/std:c++')]
+ if optargs:
+ ET.SubElement(clconf, 'LanguageStandard').text = optargs[0].replace("/std:c++", "stdcpp")
+ if 'c' in file_args:
+ optargs = [x for x in file_args['c'] if x.startswith('/std:c')]
+ if optargs:
+ ET.SubElement(clconf, 'LanguageStandard_C').text = optargs[0].replace("/std:c", "stdc")
diff --git a/mesonbuild/backend/vs2022backend.py b/mesonbuild/backend/vs2022backend.py
new file mode 100644
index 0000000..b1f93c3
--- /dev/null
+++ b/mesonbuild/backend/vs2022backend.py
@@ -0,0 +1,62 @@
+# Copyright 2014-2021 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
+
+import os
+import typing as T
+import xml.etree.ElementTree as ET
+
+from .vs2010backend import Vs2010Backend
+
+if T.TYPE_CHECKING:
+ from ..build import Build
+ from ..interpreter import Interpreter
+
+
+class Vs2022Backend(Vs2010Backend):
+ def __init__(self, build: T.Optional[Build], interpreter: T.Optional[Interpreter]):
+ super().__init__(build, interpreter)
+ self.name = 'vs2022'
+ self.sln_file_version = '12.00'
+ self.sln_version_comment = 'Version 17'
+ if self.environment is not None:
+ comps = self.environment.coredata.compilers.host
+ if comps and all(c.id == 'clang-cl' for c in comps.values()):
+ self.platform_toolset = 'ClangCL'
+ elif comps and all(c.id == 'intel-cl' for c in comps.values()):
+ c = list(comps.values())[0]
+ if c.version.startswith('19'):
+ self.platform_toolset = 'Intel C++ Compiler 19.0'
+ # We don't have support for versions older than 2022 right now.
+ if not self.platform_toolset:
+ self.platform_toolset = 'v143'
+ self.vs_version = '2022'
+ # WindowsSDKVersion should be set by command prompt.
+ sdk_version = os.environ.get('WindowsSDKVersion', None)
+ if sdk_version:
+ self.windows_target_platform_version = sdk_version.rstrip('\\')
+
+ def generate_debug_information(self, link):
+ # valid values for vs2022 is 'false', 'true', 'DebugFastLink', 'DebugFull'
+ ET.SubElement(link, 'GenerateDebugInformation').text = 'DebugFull'
+
+ def generate_lang_standard_info(self, file_args, clconf):
+ if 'cpp' in file_args:
+ optargs = [x for x in file_args['cpp'] if x.startswith('/std:c++')]
+ if optargs:
+ ET.SubElement(clconf, 'LanguageStandard').text = optargs[0].replace("/std:c++", "stdcpp")
+ if 'c' in file_args:
+ optargs = [x for x in file_args['c'] if x.startswith('/std:c')]
+ if optargs:
+ ET.SubElement(clconf, 'LanguageStandard_C').text = optargs[0].replace("/std:c", "stdc")
diff --git a/mesonbuild/backend/xcodebackend.py b/mesonbuild/backend/xcodebackend.py
new file mode 100644
index 0000000..605ee22
--- /dev/null
+++ b/mesonbuild/backend/xcodebackend.py
@@ -0,0 +1,1719 @@
+# Copyright 2014-2021 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
+
+import uuid, os, operator
+import typing as T
+
+from . import backends
+from .. import build
+from .. import dependencies
+from .. import mesonlib
+from .. import mlog
+from ..mesonlib import MesonException, OptionKey
+
+if T.TYPE_CHECKING:
+ from ..interpreter import Interpreter
+
+INDENT = '\t'
+XCODETYPEMAP = {'c': 'sourcecode.c.c',
+ 'a': 'archive.ar',
+ 'cc': 'sourcecode.cpp.cpp',
+ 'cxx': 'sourcecode.cpp.cpp',
+ 'cpp': 'sourcecode.cpp.cpp',
+ 'c++': 'sourcecode.cpp.cpp',
+ 'm': 'sourcecode.c.objc',
+ 'mm': 'sourcecode.cpp.objcpp',
+ 'h': 'sourcecode.c.h',
+ 'hpp': 'sourcecode.cpp.h',
+ 'hxx': 'sourcecode.cpp.h',
+ 'hh': 'sourcecode.cpp.hh',
+ 'inc': 'sourcecode.c.h',
+ 'swift': 'sourcecode.swift',
+ 'dylib': 'compiled.mach-o.dylib',
+ 'o': 'compiled.mach-o.objfile',
+ 's': 'sourcecode.asm',
+ 'asm': 'sourcecode.asm',
+ }
+LANGNAMEMAP = {'c': 'C',
+ 'cpp': 'CPLUSPLUS',
+ 'objc': 'OBJC',
+ 'objcpp': 'OBJCPLUSPLUS',
+ 'swift': 'SWIFT_'
+ }
+OPT2XCODEOPT = {'plain': None,
+ '0': '0',
+ 'g': '0',
+ '1': '1',
+ '2': '2',
+ '3': '3',
+ 's': 's',
+ }
+BOOL2XCODEBOOL = {True: 'YES', False: 'NO'}
+LINKABLE_EXTENSIONS = {'.o', '.a', '.obj', '.so', '.dylib'}
+
+class FileTreeEntry:
+
+ def __init__(self):
+ self.subdirs = {}
+ self.targets = []
+
+class PbxItem:
+ def __init__(self, value, comment = ''):
+ self.value = value
+ self.comment = comment
+
+class PbxArray:
+ def __init__(self):
+ self.items = []
+
+ def add_item(self, item, comment=''):
+ if isinstance(item, PbxArrayItem):
+ self.items.append(item)
+ else:
+ self.items.append(PbxArrayItem(item, comment))
+
+ def write(self, ofile, indent_level):
+ ofile.write('(\n')
+ indent_level += 1
+ for i in self.items:
+ if i.comment:
+ ofile.write(indent_level*INDENT + f'{i.value} {i.comment},\n')
+ else:
+ ofile.write(indent_level*INDENT + f'{i.value},\n')
+ indent_level -= 1
+ ofile.write(indent_level*INDENT + ');\n')
+
+class PbxArrayItem:
+ def __init__(self, value, comment = ''):
+ self.value = value
+ if comment:
+ if '/*' in comment:
+ self.comment = comment
+ else:
+ self.comment = f'/* {comment} */'
+ else:
+ self.comment = comment
+
+class PbxComment:
+ def __init__(self, text):
+ assert isinstance(text, str)
+ assert '/*' not in text
+ self.text = f'/* {text} */'
+
+ def write(self, ofile, indent_level):
+ ofile.write(f'\n{self.text}\n')
+
+class PbxDictItem:
+ def __init__(self, key, value, comment = ''):
+ self.key = key
+ self.value = value
+ if comment:
+ if '/*' in comment:
+ self.comment = comment
+ else:
+ self.comment = f'/* {comment} */'
+ else:
+ self.comment = comment
+
+class PbxDict:
+ def __init__(self):
+ # This class is a bit weird, because we want to write PBX dicts in
+ # defined order _and_ we want to write intermediate comments also in order.
+ self.keys = set()
+ self.items = []
+
+ def add_item(self, key, value, comment=''):
+ assert key not in self.keys
+ item = PbxDictItem(key, value, comment)
+ self.keys.add(key)
+ self.items.append(item)
+
+ def has_item(self, key):
+ return key in self.keys
+
+ def add_comment(self, comment):
+ if isinstance(comment, str):
+ self.items.append(PbxComment(str))
+ else:
+ assert isinstance(comment, PbxComment)
+ self.items.append(comment)
+
+ def write(self, ofile, indent_level):
+ ofile.write('{\n')
+ indent_level += 1
+ for i in self.items:
+ if isinstance(i, PbxComment):
+ i.write(ofile, indent_level)
+ elif isinstance(i, PbxDictItem):
+ if isinstance(i.value, (str, int)):
+ if i.comment:
+ ofile.write(indent_level*INDENT + f'{i.key} = {i.value} {i.comment};\n')
+ else:
+ ofile.write(indent_level*INDENT + f'{i.key} = {i.value};\n')
+ elif isinstance(i.value, PbxDict):
+ if i.comment:
+ ofile.write(indent_level*INDENT + f'{i.key} {i.comment} = ')
+ else:
+ ofile.write(indent_level*INDENT + f'{i.key} = ')
+ i.value.write(ofile, indent_level)
+ elif isinstance(i.value, PbxArray):
+ if i.comment:
+ ofile.write(indent_level*INDENT + f'{i.key} {i.comment} = ')
+ else:
+ ofile.write(indent_level*INDENT + f'{i.key} = ')
+ i.value.write(ofile, indent_level)
+ else:
+ print(i)
+ print(i.key)
+ print(i.value)
+ raise RuntimeError('missing code')
+ else:
+ print(i)
+ raise RuntimeError('missing code2')
+
+ indent_level -= 1
+ ofile.write(indent_level*INDENT + '}')
+ if indent_level == 0:
+ ofile.write('\n')
+ else:
+ ofile.write(';\n')
+
+class XCodeBackend(backends.Backend):
+ def __init__(self, build: T.Optional[build.Build], interpreter: T.Optional[Interpreter]):
+ super().__init__(build, interpreter)
+ self.name = 'xcode'
+ self.project_uid = self.environment.coredata.lang_guids['default'].replace('-', '')[:24]
+ self.buildtype = self.environment.coredata.get_option(OptionKey('buildtype'))
+ self.project_conflist = self.gen_id()
+ self.maingroup_id = self.gen_id()
+ self.all_id = self.gen_id()
+ self.all_buildconf_id = self.gen_id()
+ self.buildtypes = [self.buildtype]
+ self.test_id = self.gen_id()
+ self.test_command_id = self.gen_id()
+ self.test_buildconf_id = self.gen_id()
+ self.regen_id = self.gen_id()
+ self.regen_command_id = self.gen_id()
+ self.regen_buildconf_id = self.gen_id()
+ self.regen_dependency_id = self.gen_id()
+ self.top_level_dict = PbxDict()
+ self.generator_outputs = {}
+ # In Xcode files are not accessed via their file names, but rather every one of them
+ # gets an unique id. More precisely they get one unique id per target they are used
+ # in. If you generate only one id per file and use them, compilation will work but the
+ # UI will only show the file in one target but not the others. Thus they key is
+ # a tuple containing the target and filename.
+ self.buildfile_ids = {}
+ # That is not enough, though. Each target/file combination also gets a unique id
+ # in the file reference section. Because why not. This means that a source file
+ # that is used in two targets gets a total of four unique ID numbers.
+ self.fileref_ids = {}
+
+ def write_pbxfile(self, top_level_dict, ofilename):
+ tmpname = ofilename + '.tmp'
+ with open(tmpname, 'w', encoding='utf-8') as ofile:
+ ofile.write('// !$*UTF8*$!\n')
+ top_level_dict.write(ofile, 0)
+ os.replace(tmpname, ofilename)
+
+ def gen_id(self):
+ return str(uuid.uuid4()).upper().replace('-', '')[:24]
+
+ def get_target_dir(self, target):
+ dirname = os.path.join(target.get_subdir(), self.environment.coredata.get_option(OptionKey('buildtype')))
+ #os.makedirs(os.path.join(self.environment.get_build_dir(), dirname), exist_ok=True)
+ return dirname
+
+ def get_custom_target_output_dir(self, target):
+ dirname = target.get_subdir()
+ os.makedirs(os.path.join(self.environment.get_build_dir(), dirname), exist_ok=True)
+ return dirname
+
+ def target_to_build_root(self, target):
+ if self.get_target_dir(target) == '':
+ return ''
+ directories = os.path.normpath(self.get_target_dir(target)).split(os.sep)
+ return os.sep.join(['..'] * len(directories))
+
+ def object_filename_from_source(self, target, source):
+ # Xcode has the following naming scheme:
+ # projectname.build/debug/prog@exe.build/Objects-normal/x86_64/func.o
+ project = self.build.project_name
+ buildtype = self.buildtype
+ tname = target.get_id()
+ arch = 'x86_64'
+ if isinstance(source, mesonlib.File):
+ source = source.fname
+ stem = os.path.splitext(os.path.basename(source))[0]
+ obj_path = f'{project}.build/{buildtype}/{tname}.build/Objects-normal/{arch}/{stem}.o'
+ return obj_path
+
+ def generate(self):
+ self.serialize_tests()
+ # Cache the result as the method rebuilds the array every time it is called.
+ self.build_targets = self.build.get_build_targets()
+ self.custom_targets = self.build.get_custom_targets()
+ self.generate_filemap()
+ self.generate_buildstylemap()
+ self.generate_build_phase_map()
+ self.generate_build_configuration_map()
+ self.generate_build_configurationlist_map()
+ self.generate_project_configurations_map()
+ self.generate_buildall_configurations_map()
+ self.generate_test_configurations_map()
+ self.generate_native_target_map()
+ self.generate_native_frameworks_map()
+ self.generate_custom_target_map()
+ self.generate_generator_target_map()
+ self.generate_source_phase_map()
+ self.generate_target_dependency_map()
+ self.generate_pbxdep_map()
+ self.generate_containerproxy_map()
+ self.generate_target_file_maps()
+ self.generate_build_file_maps()
+ self.proj_dir = os.path.join(self.environment.get_build_dir(), self.build.project_name + '.xcodeproj')
+ os.makedirs(self.proj_dir, exist_ok=True)
+ self.proj_file = os.path.join(self.proj_dir, 'project.pbxproj')
+ objects_dict = self.generate_prefix(self.top_level_dict)
+ objects_dict.add_comment(PbxComment('Begin PBXAggregateTarget section'))
+ self.generate_pbx_aggregate_target(objects_dict)
+ objects_dict.add_comment(PbxComment('End PBXAggregateTarget section'))
+ objects_dict.add_comment(PbxComment('Begin PBXBuildFile section'))
+ self.generate_pbx_build_file(objects_dict)
+ objects_dict.add_comment(PbxComment('End PBXBuildFile section'))
+ objects_dict.add_comment(PbxComment('Begin PBXBuildStyle section'))
+ self.generate_pbx_build_style(objects_dict)
+ objects_dict.add_comment(PbxComment('End PBXBuildStyle section'))
+ objects_dict.add_comment(PbxComment('Begin PBXContainerItemProxy section'))
+ self.generate_pbx_container_item_proxy(objects_dict)
+ objects_dict.add_comment(PbxComment('End PBXContainerItemProxy section'))
+ objects_dict.add_comment(PbxComment('Begin PBXFileReference section'))
+ self.generate_pbx_file_reference(objects_dict)
+ objects_dict.add_comment(PbxComment('End PBXFileReference section'))
+ objects_dict.add_comment(PbxComment('Begin PBXFrameworksBuildPhase section'))
+ self.generate_pbx_frameworks_buildphase(objects_dict)
+ objects_dict.add_comment(PbxComment('End PBXFrameworksBuildPhase section'))
+ objects_dict.add_comment(PbxComment('Begin PBXGroup section'))
+ self.generate_pbx_group(objects_dict)
+ objects_dict.add_comment(PbxComment('End PBXGroup section'))
+ objects_dict.add_comment(PbxComment('Begin PBXNativeTarget section'))
+ self.generate_pbx_native_target(objects_dict)
+ objects_dict.add_comment(PbxComment('End PBXNativeTarget section'))
+ objects_dict.add_comment(PbxComment('Begin PBXProject section'))
+ self.generate_pbx_project(objects_dict)
+ objects_dict.add_comment(PbxComment('End PBXProject section'))
+ objects_dict.add_comment(PbxComment('Begin PBXShellScriptBuildPhase section'))
+ self.generate_pbx_shell_build_phase(objects_dict)
+ objects_dict.add_comment(PbxComment('End PBXShellScriptBuildPhase section'))
+ objects_dict.add_comment(PbxComment('Begin PBXSourcesBuildPhase section'))
+ self.generate_pbx_sources_build_phase(objects_dict)
+ objects_dict.add_comment(PbxComment('End PBXSourcesBuildPhase section'))
+ objects_dict.add_comment(PbxComment('Begin PBXTargetDependency section'))
+ self.generate_pbx_target_dependency(objects_dict)
+ objects_dict.add_comment(PbxComment('End PBXTargetDependency section'))
+ objects_dict.add_comment(PbxComment('Begin XCBuildConfiguration section'))
+ self.generate_xc_build_configuration(objects_dict)
+ objects_dict.add_comment(PbxComment('End XCBuildConfiguration section'))
+ objects_dict.add_comment(PbxComment('Begin XCConfigurationList section'))
+ self.generate_xc_configurationList(objects_dict)
+ objects_dict.add_comment(PbxComment('End XCConfigurationList section'))
+ self.generate_suffix(self.top_level_dict)
+ self.write_pbxfile(self.top_level_dict, self.proj_file)
+ self.generate_regen_info()
+
+ def get_xcodetype(self, fname):
+ extension = fname.split('.')[-1]
+ if extension == 'C':
+ extension = 'cpp'
+ xcodetype = XCODETYPEMAP.get(extension.lower())
+ if not xcodetype:
+ xcodetype = 'sourcecode.unknown'
+ return xcodetype
+
+ def generate_filemap(self):
+ self.filemap = {} # Key is source file relative to src root.
+ self.target_filemap = {}
+ for name, t in self.build_targets.items():
+ for s in t.sources:
+ if isinstance(s, mesonlib.File):
+ s = os.path.join(s.subdir, s.fname)
+ self.filemap[s] = self.gen_id()
+ for o in t.objects:
+ if isinstance(o, str):
+ o = os.path.join(t.subdir, o)
+ self.filemap[o] = self.gen_id()
+ self.target_filemap[name] = self.gen_id()
+
+ def generate_buildstylemap(self):
+ self.buildstylemap = {self.buildtype: self.gen_id()}
+
+ def generate_build_phase_map(self):
+ for tname, t in self.build_targets.items():
+ # generate id for our own target-name
+ t.buildphasemap = {}
+ t.buildphasemap[tname] = self.gen_id()
+ # each target can have it's own Frameworks/Sources/..., generate id's for those
+ t.buildphasemap['Frameworks'] = self.gen_id()
+ t.buildphasemap['Resources'] = self.gen_id()
+ t.buildphasemap['Sources'] = self.gen_id()
+
+ def generate_build_configuration_map(self):
+ self.buildconfmap = {}
+ for t in self.build_targets:
+ bconfs = {self.buildtype: self.gen_id()}
+ self.buildconfmap[t] = bconfs
+ for t in self.custom_targets:
+ bconfs = {self.buildtype: self.gen_id()}
+ self.buildconfmap[t] = bconfs
+
+ def generate_project_configurations_map(self):
+ self.project_configurations = {self.buildtype: self.gen_id()}
+
+ def generate_buildall_configurations_map(self):
+ self.buildall_configurations = {self.buildtype: self.gen_id()}
+
+ def generate_test_configurations_map(self):
+ self.test_configurations = {self.buildtype: self.gen_id()}
+
+ def generate_build_configurationlist_map(self):
+ self.buildconflistmap = {}
+ for t in self.build_targets:
+ self.buildconflistmap[t] = self.gen_id()
+ for t in self.custom_targets:
+ self.buildconflistmap[t] = self.gen_id()
+
+ def generate_native_target_map(self):
+ self.native_targets = {}
+ for t in self.build_targets:
+ self.native_targets[t] = self.gen_id()
+
+ def generate_custom_target_map(self):
+ self.shell_targets = {}
+ self.custom_target_output_buildfile = {}
+ self.custom_target_output_fileref = {}
+ for tname, t in self.custom_targets.items():
+ self.shell_targets[tname] = self.gen_id()
+ if not isinstance(t, build.CustomTarget):
+ continue
+ (srcs, ofilenames, cmd) = self.eval_custom_target_command(t)
+ for o in ofilenames:
+ self.custom_target_output_buildfile[o] = self.gen_id()
+ self.custom_target_output_fileref[o] = self.gen_id()
+
+ def generate_generator_target_map(self):
+ # Generator objects do not have natural unique ids
+ # so use a counter.
+ self.generator_fileref_ids = {}
+ self.generator_buildfile_ids = {}
+ for tname, t in self.build_targets.items():
+ generator_id = 0
+ for genlist in t.generated:
+ if not isinstance(genlist, build.GeneratedList):
+ continue
+ self.gen_single_target_map(genlist, tname, t, generator_id)
+ generator_id += 1
+ # FIXME add outputs.
+ for tname, t in self.custom_targets.items():
+ generator_id = 0
+ for genlist in t.sources:
+ if not isinstance(genlist, build.GeneratedList):
+ continue
+ self.gen_single_target_map(genlist, tname, t, generator_id)
+ generator_id += 1
+
+ def gen_single_target_map(self, genlist, tname, t, generator_id):
+ k = (tname, generator_id)
+ assert k not in self.shell_targets
+ self.shell_targets[k] = self.gen_id()
+ ofile_abs = []
+ for i in genlist.get_inputs():
+ for o_base in genlist.get_outputs_for(i):
+ o = os.path.join(self.get_target_private_dir(t), o_base)
+ ofile_abs.append(os.path.join(self.environment.get_build_dir(), o))
+ assert k not in self.generator_outputs
+ self.generator_outputs[k] = ofile_abs
+ buildfile_ids = []
+ fileref_ids = []
+ for i in range(len(ofile_abs)):
+ buildfile_ids.append(self.gen_id())
+ fileref_ids.append(self.gen_id())
+ self.generator_buildfile_ids[k] = buildfile_ids
+ self.generator_fileref_ids[k] = fileref_ids
+
+ def generate_native_frameworks_map(self):
+ self.native_frameworks = {}
+ self.native_frameworks_fileref = {}
+ for t in self.build_targets.values():
+ for dep in t.get_external_deps():
+ if isinstance(dep, dependencies.AppleFrameworks):
+ for f in dep.frameworks:
+ self.native_frameworks[f] = self.gen_id()
+ self.native_frameworks_fileref[f] = self.gen_id()
+
+ def generate_target_dependency_map(self):
+ self.target_dependency_map = {}
+ for tname, t in self.build_targets.items():
+ for target in t.link_targets:
+ if isinstance(target, build.CustomTargetIndex):
+ k = (tname, target.target.get_basename())
+ if k in self.target_dependency_map:
+ continue
+ else:
+ k = (tname, target.get_basename())
+ assert k not in self.target_dependency_map
+ self.target_dependency_map[k] = self.gen_id()
+ for tname, t in self.custom_targets.items():
+ k = tname
+ assert k not in self.target_dependency_map
+ self.target_dependency_map[k] = self.gen_id()
+
+ def generate_pbxdep_map(self):
+ self.pbx_dep_map = {}
+ self.pbx_custom_dep_map = {}
+ for t in self.build_targets:
+ self.pbx_dep_map[t] = self.gen_id()
+ for t in self.custom_targets:
+ self.pbx_custom_dep_map[t] = self.gen_id()
+
+ def generate_containerproxy_map(self):
+ self.containerproxy_map = {}
+ for t in self.build_targets:
+ self.containerproxy_map[t] = self.gen_id()
+
+ def generate_target_file_maps(self):
+ self.generate_target_file_maps_impl(self.build_targets)
+ self.generate_target_file_maps_impl(self.custom_targets)
+
+ def generate_target_file_maps_impl(self, targets):
+ for tname, t in targets.items():
+ for s in t.sources:
+ if isinstance(s, mesonlib.File):
+ s = os.path.join(s.subdir, s.fname)
+ if not isinstance(s, str):
+ continue
+ k = (tname, s)
+ assert k not in self.buildfile_ids
+ self.buildfile_ids[k] = self.gen_id()
+ assert k not in self.fileref_ids
+ self.fileref_ids[k] = self.gen_id()
+ if not hasattr(t, 'objects'):
+ continue
+ for o in t.objects:
+ if isinstance(o, build.ExtractedObjects):
+ # Extracted objects do not live in "the Xcode world".
+ continue
+ if isinstance(o, mesonlib.File):
+ o = os.path.join(o.subdir, o.fname)
+ if isinstance(o, str):
+ o = os.path.join(t.subdir, o)
+ k = (tname, o)
+ assert k not in self.buildfile_ids
+ self.buildfile_ids[k] = self.gen_id()
+ assert k not in self.fileref_ids
+ self.fileref_ids[k] = self.gen_id()
+ else:
+ raise RuntimeError('Unknown input type ' + str(o))
+
+ def generate_build_file_maps(self):
+ for buildfile in self.interpreter.get_build_def_files():
+ assert isinstance(buildfile, str)
+ self.buildfile_ids[buildfile] = self.gen_id()
+ self.fileref_ids[buildfile] = self.gen_id()
+
+ def generate_source_phase_map(self):
+ self.source_phase = {}
+ for t in self.build_targets:
+ self.source_phase[t] = self.gen_id()
+
+ def generate_pbx_aggregate_target(self, objects_dict):
+ self.custom_aggregate_targets = {}
+ self.build_all_tdep_id = self.gen_id()
+ # FIXME: filter out targets that are not built by default.
+ target_dependencies = [self.pbx_dep_map[t] for t in self.build_targets]
+ custom_target_dependencies = [self.pbx_custom_dep_map[t] for t in self.custom_targets]
+ aggregated_targets = []
+ aggregated_targets.append((self.all_id, 'ALL_BUILD',
+ self.all_buildconf_id,
+ [],
+ [self.regen_dependency_id] + target_dependencies + custom_target_dependencies))
+ aggregated_targets.append((self.test_id,
+ 'RUN_TESTS',
+ self.test_buildconf_id,
+ [self.test_command_id],
+ [self.regen_dependency_id, self.build_all_tdep_id]))
+ aggregated_targets.append((self.regen_id,
+ 'REGENERATE',
+ self.regen_buildconf_id,
+ [self.regen_command_id],
+ []))
+ for tname, t in self.build.get_custom_targets().items():
+ ct_id = self.gen_id()
+ self.custom_aggregate_targets[tname] = ct_id
+ build_phases = []
+ dependencies = [self.regen_dependency_id]
+ generator_id = 0
+ for s in t.sources:
+ if not isinstance(s, build.GeneratedList):
+ continue
+ build_phases.append(self.shell_targets[(tname, generator_id)])
+ for d in s.depends:
+ dependencies.append(self.pbx_custom_dep_map[d.get_id()])
+ generator_id += 1
+ build_phases.append(self.shell_targets[tname])
+ aggregated_targets.append((ct_id, tname, self.buildconflistmap[tname], build_phases, dependencies))
+
+ # Sort objects by ID before writing
+ sorted_aggregated_targets = sorted(aggregated_targets, key=operator.itemgetter(0))
+ for t in sorted_aggregated_targets:
+ agt_dict = PbxDict()
+ name = t[1]
+ buildconf_id = t[2]
+ build_phases = t[3]
+ dependencies = t[4]
+ agt_dict.add_item('isa', 'PBXAggregateTarget')
+ agt_dict.add_item('buildConfigurationList', buildconf_id, f'Build configuration list for PBXAggregateTarget "{name}"')
+ bp_arr = PbxArray()
+ agt_dict.add_item('buildPhases', bp_arr)
+ for bp in build_phases:
+ bp_arr.add_item(bp, 'ShellScript')
+ dep_arr = PbxArray()
+ agt_dict.add_item('dependencies', dep_arr)
+ for td in dependencies:
+ dep_arr.add_item(td, 'PBXTargetDependency')
+ agt_dict.add_item('name', f'"{name}"')
+ agt_dict.add_item('productName', f'"{name}"')
+ objects_dict.add_item(t[0], agt_dict, name)
+
+ def generate_pbx_build_file(self, objects_dict):
+ for tname, t in self.build_targets.items():
+ for dep in t.get_external_deps():
+ if isinstance(dep, dependencies.AppleFrameworks):
+ for f in dep.frameworks:
+ fw_dict = PbxDict()
+ fwkey = self.native_frameworks[f]
+ if fwkey not in objects_dict.keys:
+ objects_dict.add_item(fwkey, fw_dict, f'{f}.framework in Frameworks')
+ fw_dict.add_item('isa', 'PBXBuildFile')
+ fw_dict.add_item('fileRef', self.native_frameworks_fileref[f], f)
+
+ for s in t.sources:
+ in_build_dir = False
+ if isinstance(s, mesonlib.File):
+ if s.is_built:
+ in_build_dir = True
+ s = os.path.join(s.subdir, s.fname)
+
+ if not isinstance(s, str):
+ continue
+ sdict = PbxDict()
+ k = (tname, s)
+ idval = self.buildfile_ids[k]
+ fileref = self.fileref_ids[k]
+ if in_build_dir:
+ fullpath = os.path.join(self.environment.get_build_dir(), s)
+ else:
+ fullpath = os.path.join(self.environment.get_source_dir(), s)
+ sdict.add_item('isa', 'PBXBuildFile')
+ sdict.add_item('fileRef', fileref, fullpath)
+ objects_dict.add_item(idval, sdict)
+
+ for o in t.objects:
+ if isinstance(o, build.ExtractedObjects):
+ # Object files are not source files as such. We add them
+ # by hand in linker flags. It is also not particularly
+ # clear how to define build files in Xcode's file format.
+ continue
+ if isinstance(o, mesonlib.File):
+ o = os.path.join(o.subdir, o.fname)
+ elif isinstance(o, str):
+ o = os.path.join(t.subdir, o)
+ idval = self.buildfile_ids[(tname, o)]
+ k = (tname, o)
+ fileref = self.fileref_ids[k]
+ assert o not in self.filemap
+ self.filemap[o] = idval
+ fullpath = os.path.join(self.environment.get_source_dir(), o)
+ fullpath2 = fullpath
+ o_dict = PbxDict()
+ objects_dict.add_item(idval, o_dict, fullpath)
+ o_dict.add_item('isa', 'PBXBuildFile')
+ o_dict.add_item('fileRef', fileref, fullpath2)
+
+ generator_id = 0
+ for g in t.generated:
+ if not isinstance(g, build.GeneratedList):
+ continue
+ self.create_generator_shellphase(objects_dict, tname, generator_id)
+ generator_id += 1
+
+ # Custom targets are shell build phases in Xcode terminology.
+ for tname, t in self.custom_targets.items():
+ if not isinstance(t, build.CustomTarget):
+ continue
+ (srcs, ofilenames, cmd) = self.eval_custom_target_command(t)
+ for o in ofilenames:
+ custom_dict = PbxDict()
+ objects_dict.add_item(self.custom_target_output_buildfile[o], custom_dict, f'/* {o} */')
+ custom_dict.add_item('isa', 'PBXBuildFile')
+ custom_dict.add_item('fileRef', self.custom_target_output_fileref[o])
+ generator_id = 0
+ for g in t.sources:
+ if not isinstance(g, build.GeneratedList):
+ continue
+ self.create_generator_shellphase(objects_dict, tname, generator_id)
+ generator_id += 1
+
+ def create_generator_shellphase(self, objects_dict, tname, generator_id):
+ file_ids = self.generator_buildfile_ids[(tname, generator_id)]
+ ref_ids = self.generator_fileref_ids[(tname, generator_id)]
+ assert len(ref_ids) == len(file_ids)
+ for file_o, ref_id in zip(file_ids, ref_ids):
+ odict = PbxDict()
+ objects_dict.add_item(file_o, odict)
+ odict.add_item('isa', 'PBXBuildFile')
+ odict.add_item('fileRef', ref_id)
+
+ def generate_pbx_build_style(self, objects_dict):
+ # FIXME: Xcode 9 and later does not uses PBXBuildStyle and it gets removed. Maybe we can remove this part.
+ for name, idval in self.buildstylemap.items():
+ styledict = PbxDict()
+ objects_dict.add_item(idval, styledict, name)
+ styledict.add_item('isa', 'PBXBuildStyle')
+ settings_dict = PbxDict()
+ styledict.add_item('buildSettings', settings_dict)
+ settings_dict.add_item('COPY_PHASE_STRIP', 'NO')
+ styledict.add_item('name', f'"{name}"')
+
+ def generate_pbx_container_item_proxy(self, objects_dict):
+ for t in self.build_targets:
+ proxy_dict = PbxDict()
+ objects_dict.add_item(self.containerproxy_map[t], proxy_dict, 'PBXContainerItemProxy')
+ proxy_dict.add_item('isa', 'PBXContainerItemProxy')
+ proxy_dict.add_item('containerPortal', self.project_uid, 'Project object')
+ proxy_dict.add_item('proxyType', '1')
+ proxy_dict.add_item('remoteGlobalIDString', self.native_targets[t])
+ proxy_dict.add_item('remoteInfo', '"' + t + '"')
+
+ def generate_pbx_file_reference(self, objects_dict):
+ for tname, t in self.build_targets.items():
+ for dep in t.get_external_deps():
+ if isinstance(dep, dependencies.AppleFrameworks):
+ for f in dep.frameworks:
+ fw_dict = PbxDict()
+ framework_fileref = self.native_frameworks_fileref[f]
+ if objects_dict.has_item(framework_fileref):
+ continue
+ objects_dict.add_item(framework_fileref, fw_dict, f)
+ fw_dict.add_item('isa', 'PBXFileReference')
+ fw_dict.add_item('lastKnownFileType', 'wrapper.framework')
+ fw_dict.add_item('name', f'{f}.framework')
+ fw_dict.add_item('path', f'System/Library/Frameworks/{f}.framework')
+ fw_dict.add_item('sourceTree', 'SDKROOT')
+ for s in t.sources:
+ in_build_dir = False
+ if isinstance(s, mesonlib.File):
+ if s.is_built:
+ in_build_dir = True
+ s = os.path.join(s.subdir, s.fname)
+ if not isinstance(s, str):
+ continue
+ idval = self.fileref_ids[(tname, s)]
+ fullpath = os.path.join(self.environment.get_source_dir(), s)
+ src_dict = PbxDict()
+ xcodetype = self.get_xcodetype(s)
+ name = os.path.basename(s)
+ path = s
+ objects_dict.add_item(idval, src_dict, fullpath)
+ src_dict.add_item('isa', 'PBXFileReference')
+ src_dict.add_item('explicitFileType', '"' + xcodetype + '"')
+ src_dict.add_item('fileEncoding', '4')
+ if in_build_dir:
+ src_dict.add_item('name', '"' + name + '"')
+ # This makes no sense. This should say path instead of name
+ # but then the path gets added twice.
+ src_dict.add_item('path', '"' + name + '"')
+ src_dict.add_item('sourceTree', 'BUILD_ROOT')
+ else:
+ src_dict.add_item('name', '"' + name + '"')
+ src_dict.add_item('path', '"' + path + '"')
+ src_dict.add_item('sourceTree', 'SOURCE_ROOT')
+
+ generator_id = 0
+ for g in t.generated:
+ if not isinstance(g, build.GeneratedList):
+ continue
+ outputs = self.generator_outputs[(tname, generator_id)]
+ ref_ids = self.generator_fileref_ids[tname, generator_id]
+ assert len(ref_ids) == len(outputs)
+ for o, ref_id in zip(outputs, ref_ids):
+ odict = PbxDict()
+ name = os.path.basename(o)
+ objects_dict.add_item(ref_id, odict, o)
+ xcodetype = self.get_xcodetype(o)
+ rel_name = mesonlib.relpath(o, self.environment.get_source_dir())
+ odict.add_item('isa', 'PBXFileReference')
+ odict.add_item('explicitFileType', '"' + xcodetype + '"')
+ odict.add_item('fileEncoding', '4')
+ odict.add_item('name', f'"{name}"')
+ odict.add_item('path', f'"{rel_name}"')
+ odict.add_item('sourceTree', 'SOURCE_ROOT')
+
+ generator_id += 1
+
+ for o in t.objects:
+ if isinstance(o, build.ExtractedObjects):
+ # Same as with pbxbuildfile.
+ continue
+ if isinstance(o, mesonlib.File):
+ fullpath = o.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir())
+ o = os.path.join(o.subdir, o.fname)
+ else:
+ o = os.path.join(t.subdir, o)
+ fullpath = os.path.join(self.environment.get_source_dir(), o)
+ idval = self.fileref_ids[(tname, o)]
+ rel_name = mesonlib.relpath(fullpath, self.environment.get_source_dir())
+ o_dict = PbxDict()
+ name = os.path.basename(o)
+ objects_dict.add_item(idval, o_dict, fullpath)
+ o_dict.add_item('isa', 'PBXFileReference')
+ o_dict.add_item('explicitFileType', '"' + self.get_xcodetype(o) + '"')
+ o_dict.add_item('fileEncoding', '4')
+ o_dict.add_item('name', f'"{name}"')
+ o_dict.add_item('path', f'"{rel_name}"')
+ o_dict.add_item('sourceTree', 'SOURCE_ROOT')
+ for tname, idval in self.target_filemap.items():
+ target_dict = PbxDict()
+ objects_dict.add_item(idval, target_dict, tname)
+ t = self.build_targets[tname]
+ fname = t.get_filename()
+ reftype = 0
+ if isinstance(t, build.Executable):
+ typestr = 'compiled.mach-o.executable'
+ path = fname
+ elif isinstance(t, build.SharedLibrary):
+ typestr = self.get_xcodetype('dummy.dylib')
+ path = fname
+ else:
+ typestr = self.get_xcodetype(fname)
+ path = '"%s"' % t.get_filename()
+ target_dict.add_item('isa', 'PBXFileReference')
+ target_dict.add_item('explicitFileType', '"' + typestr + '"')
+ if ' ' in path and path[0] != '"':
+ target_dict.add_item('path', f'"{path}"')
+ else:
+ target_dict.add_item('path', path)
+ target_dict.add_item('refType', reftype)
+ target_dict.add_item('sourceTree', 'BUILT_PRODUCTS_DIR')
+
+ for tname, t in self.custom_targets.items():
+ if not isinstance(t, build.CustomTarget):
+ continue
+ (srcs, ofilenames, cmd) = self.eval_custom_target_command(t)
+ for s in t.sources:
+ if isinstance(s, mesonlib.File):
+ s = os.path.join(s.subdir, s.fname)
+ elif isinstance(s, str):
+ s = os.path.join(t.subdir, s)
+ else:
+ continue
+ custom_dict = PbxDict()
+ typestr = self.get_xcodetype(s)
+ custom_dict.add_item('isa', 'PBXFileReference')
+ custom_dict.add_item('explicitFileType', '"' + typestr + '"')
+ custom_dict.add_item('name', f'"{s}"')
+ custom_dict.add_item('path', f'"{s}"')
+ custom_dict.add_item('refType', 0)
+ custom_dict.add_item('sourceTree', 'SOURCE_ROOT')
+ objects_dict.add_item(self.fileref_ids[(tname, s)], custom_dict)
+ for o in ofilenames:
+ custom_dict = PbxDict()
+ typestr = self.get_xcodetype(o)
+ custom_dict.add_item('isa', 'PBXFileReference')
+ custom_dict.add_item('explicitFileType', '"' + typestr + '"')
+ custom_dict.add_item('name', o)
+ custom_dict.add_item('path', os.path.join(self.src_to_build, o))
+ custom_dict.add_item('refType', 0)
+ custom_dict.add_item('sourceTree', 'SOURCE_ROOT')
+ objects_dict.add_item(self.custom_target_output_fileref[o], custom_dict)
+
+ for buildfile in self.interpreter.get_build_def_files():
+ basename = os.path.split(buildfile)[1]
+ buildfile_dict = PbxDict()
+ typestr = self.get_xcodetype(buildfile)
+ buildfile_dict.add_item('isa', 'PBXFileReference')
+ buildfile_dict.add_item('explicitFileType', '"' + typestr + '"')
+ buildfile_dict.add_item('name', f'"{basename}"')
+ buildfile_dict.add_item('path', f'"{buildfile}"')
+ buildfile_dict.add_item('refType', 0)
+ buildfile_dict.add_item('sourceTree', 'SOURCE_ROOT')
+ objects_dict.add_item(self.fileref_ids[buildfile], buildfile_dict)
+
+ def generate_pbx_frameworks_buildphase(self, objects_dict):
+ for t in self.build_targets.values():
+ bt_dict = PbxDict()
+ objects_dict.add_item(t.buildphasemap['Frameworks'], bt_dict, 'Frameworks')
+ bt_dict.add_item('isa', 'PBXFrameworksBuildPhase')
+ bt_dict.add_item('buildActionMask', 2147483647)
+ file_list = PbxArray()
+ bt_dict.add_item('files', file_list)
+ for dep in t.get_external_deps():
+ if isinstance(dep, dependencies.AppleFrameworks):
+ for f in dep.frameworks:
+ file_list.add_item(self.native_frameworks[f], f'{f}.framework in Frameworks')
+ bt_dict.add_item('runOnlyForDeploymentPostprocessing', 0)
+
+ def generate_pbx_group(self, objects_dict):
+ groupmap = {}
+ target_src_map = {}
+ for t in self.build_targets:
+ groupmap[t] = self.gen_id()
+ target_src_map[t] = self.gen_id()
+ for t in self.custom_targets:
+ groupmap[t] = self.gen_id()
+ target_src_map[t] = self.gen_id()
+ projecttree_id = self.gen_id()
+ resources_id = self.gen_id()
+ products_id = self.gen_id()
+ frameworks_id = self.gen_id()
+ main_dict = PbxDict()
+ objects_dict.add_item(self.maingroup_id, main_dict)
+ main_dict.add_item('isa', 'PBXGroup')
+ main_children = PbxArray()
+ main_dict.add_item('children', main_children)
+ main_children.add_item(projecttree_id, 'Project tree')
+ main_children.add_item(resources_id, 'Resources')
+ main_children.add_item(products_id, 'Products')
+ main_children.add_item(frameworks_id, 'Frameworks')
+ main_dict.add_item('sourceTree', '"<group>"')
+
+ self.add_projecttree(objects_dict, projecttree_id)
+
+ resource_dict = PbxDict()
+ objects_dict.add_item(resources_id, resource_dict, 'Resources')
+ resource_dict.add_item('isa', 'PBXGroup')
+ resource_children = PbxArray()
+ resource_dict.add_item('children', resource_children)
+ resource_dict.add_item('name', 'Resources')
+ resource_dict.add_item('sourceTree', '"<group>"')
+
+ frameworks_dict = PbxDict()
+ objects_dict.add_item(frameworks_id, frameworks_dict, 'Frameworks')
+ frameworks_dict.add_item('isa', 'PBXGroup')
+ frameworks_children = PbxArray()
+ frameworks_dict.add_item('children', frameworks_children)
+ # write frameworks
+
+ for t in self.build_targets.values():
+ for dep in t.get_external_deps():
+ if isinstance(dep, dependencies.AppleFrameworks):
+ for f in dep.frameworks:
+ frameworks_children.add_item(self.native_frameworks_fileref[f], f)
+
+ frameworks_dict.add_item('name', 'Frameworks')
+ frameworks_dict.add_item('sourceTree', '"<group>"')
+
+ for tname, t in self.custom_targets.items():
+ target_dict = PbxDict()
+ objects_dict.add_item(groupmap[tname], target_dict, tname)
+ target_dict.add_item('isa', 'PBXGroup')
+ target_children = PbxArray()
+ target_dict.add_item('children', target_children)
+ target_children.add_item(target_src_map[tname], 'Source files')
+ if t.subproject:
+ target_dict.add_item('name', f'"{t.subproject} • {t.name}"')
+ else:
+ target_dict.add_item('name', f'"{t.name}"')
+ target_dict.add_item('sourceTree', '"<group>"')
+ source_files_dict = PbxDict()
+ objects_dict.add_item(target_src_map[tname], source_files_dict, 'Source files')
+ source_files_dict.add_item('isa', 'PBXGroup')
+ source_file_children = PbxArray()
+ source_files_dict.add_item('children', source_file_children)
+ for s in t.sources:
+ if isinstance(s, mesonlib.File):
+ s = os.path.join(s.subdir, s.fname)
+ elif isinstance(s, str):
+ s = os.path.join(t.subdir, s)
+ else:
+ continue
+ source_file_children.add_item(self.fileref_ids[(tname, s)], s)
+ source_files_dict.add_item('name', '"Source files"')
+ source_files_dict.add_item('sourceTree', '"<group>"')
+
+ # And finally products
+ product_dict = PbxDict()
+ objects_dict.add_item(products_id, product_dict, 'Products')
+ product_dict.add_item('isa', 'PBXGroup')
+ product_children = PbxArray()
+ product_dict.add_item('children', product_children)
+ for t in self.build_targets:
+ product_children.add_item(self.target_filemap[t], t)
+ product_dict.add_item('name', 'Products')
+ product_dict.add_item('sourceTree', '"<group>"')
+
+ def write_group_target_entry(self, objects_dict, t):
+ tid = t.get_id()
+ group_id = self.gen_id()
+ target_dict = PbxDict()
+ objects_dict.add_item(group_id, target_dict, tid)
+ target_dict.add_item('isa', 'PBXGroup')
+ target_children = PbxArray()
+ target_dict.add_item('children', target_children)
+ target_dict.add_item('name', f'"{t} · target"')
+ target_dict.add_item('sourceTree', '"<group>"')
+ source_files_dict = PbxDict()
+ for s in t.sources:
+ if isinstance(s, mesonlib.File):
+ s = os.path.join(s.subdir, s.fname)
+ elif isinstance(s, str):
+ s = os.path.join(t.subdir, s)
+ else:
+ continue
+ target_children.add_item(self.fileref_ids[(tid, s)], s)
+ for o in t.objects:
+ if isinstance(o, build.ExtractedObjects):
+ # Do not show built object files in the project tree.
+ continue
+ if isinstance(o, mesonlib.File):
+ o = os.path.join(o.subdir, o.fname)
+ else:
+ o = os.path.join(t.subdir, o)
+ target_children.add_item(self.fileref_ids[(tid, o)], o)
+ source_files_dict.add_item('name', '"Source files"')
+ source_files_dict.add_item('sourceTree', '"<group>"')
+ return group_id
+
+ def add_projecttree(self, objects_dict, projecttree_id):
+ root_dict = PbxDict()
+ objects_dict.add_item(projecttree_id, root_dict, "Root of project tree")
+ root_dict.add_item('isa', 'PBXGroup')
+ target_children = PbxArray()
+ root_dict.add_item('children', target_children)
+ root_dict.add_item('name', '"Project root"')
+ root_dict.add_item('sourceTree', '"<group>"')
+
+ project_tree = self.generate_project_tree()
+ self.write_tree(objects_dict, project_tree, target_children, '')
+
+ def write_tree(self, objects_dict, tree_node, children_array, current_subdir):
+ for subdir_name, subdir_node in tree_node.subdirs.items():
+ subdir_dict = PbxDict()
+ subdir_children = PbxArray()
+ subdir_id = self.gen_id()
+ objects_dict.add_item(subdir_id, subdir_dict)
+ children_array.add_item(subdir_id)
+ subdir_dict.add_item('isa', 'PBXGroup')
+ subdir_dict.add_item('children', subdir_children)
+ subdir_dict.add_item('name', f'"{subdir_name}"')
+ subdir_dict.add_item('sourceTree', '"<group>"')
+ self.write_tree(objects_dict, subdir_node, subdir_children, os.path.join(current_subdir, subdir_name))
+ for target in tree_node.targets:
+ group_id = self.write_group_target_entry(objects_dict, target)
+ children_array.add_item(group_id)
+ potentials = [os.path.join(current_subdir, 'meson.build'),
+ os.path.join(current_subdir, 'meson_options.txt')]
+ for bf in potentials:
+ i = self.fileref_ids.get(bf, None)
+ if i:
+ children_array.add_item(i)
+
+ def generate_project_tree(self):
+ tree_info = FileTreeEntry()
+ for tname, t in self.build_targets.items():
+ self.add_target_to_tree(tree_info, t)
+ return tree_info
+
+ def add_target_to_tree(self, tree_root, t):
+ current_node = tree_root
+ path_segments = t.subdir.split('/')
+ for s in path_segments:
+ if not s:
+ continue
+ if s not in current_node.subdirs:
+ current_node.subdirs[s] = FileTreeEntry()
+ current_node = current_node.subdirs[s]
+ current_node.targets.append(t)
+
+ def generate_pbx_native_target(self, objects_dict):
+ for tname, idval in self.native_targets.items():
+ ntarget_dict = PbxDict()
+ t = self.build_targets[tname]
+ objects_dict.add_item(idval, ntarget_dict, tname)
+ ntarget_dict.add_item('isa', 'PBXNativeTarget')
+ ntarget_dict.add_item('buildConfigurationList', self.buildconflistmap[tname], f'Build configuration list for PBXNativeTarget "{tname}"')
+ buildphases_array = PbxArray()
+ ntarget_dict.add_item('buildPhases', buildphases_array)
+ generator_id = 0
+ for g in t.generated:
+ # Custom target are handled via inter-target dependencies.
+ # Generators are built as a shellscriptbuildphase.
+ if isinstance(g, build.GeneratedList):
+ buildphases_array.add_item(self.shell_targets[(tname, generator_id)], f'Generator {generator_id}/{tname}')
+ generator_id += 1
+ for bpname, bpval in t.buildphasemap.items():
+ buildphases_array.add_item(bpval, f'{bpname} yyy')
+ ntarget_dict.add_item('buildRules', PbxArray())
+ dep_array = PbxArray()
+ ntarget_dict.add_item('dependencies', dep_array)
+ dep_array.add_item(self.regen_dependency_id)
+ # These dependencies only tell Xcode that the deps must be built
+ # before this one. They don't set up linkage or anything
+ # like that. Those are set up in the XCBuildConfiguration.
+ for lt in self.build_targets[tname].link_targets:
+ # NOT DOCUMENTED, may need to make different links
+ # to same target have different targetdependency item.
+ if isinstance(lt, build.CustomTarget):
+ dep_array.add_item(self.pbx_custom_dep_map[lt.get_id()], lt.name)
+ elif isinstance(lt, build.CustomTargetIndex):
+ dep_array.add_item(self.pbx_custom_dep_map[lt.target.get_id()], lt.target.name)
+ else:
+ idval = self.pbx_dep_map[lt.get_id()]
+ dep_array.add_item(idval, 'PBXTargetDependency')
+ for o in t.objects:
+ if isinstance(o, build.ExtractedObjects):
+ source_target_id = o.target.get_id()
+ idval = self.pbx_dep_map[source_target_id]
+ dep_array.add_item(idval, 'PBXTargetDependency')
+ generator_id = 0
+ for o in t.generated:
+ if isinstance(o, build.CustomTarget):
+ dep_array.add_item(self.pbx_custom_dep_map[o.get_id()], o.name)
+ elif isinstance(o, build.CustomTargetIndex):
+ dep_array.add_item(self.pbx_custom_dep_map[o.target.get_id()], o.target.name)
+
+ generator_id += 1
+
+ ntarget_dict.add_item('name', f'"{tname}"')
+ ntarget_dict.add_item('productName', f'"{tname}"')
+ ntarget_dict.add_item('productReference', self.target_filemap[tname], tname)
+ if isinstance(t, build.Executable):
+ typestr = 'com.apple.product-type.tool'
+ elif isinstance(t, build.StaticLibrary):
+ typestr = 'com.apple.product-type.library.static'
+ elif isinstance(t, build.SharedLibrary):
+ typestr = 'com.apple.product-type.library.dynamic'
+ else:
+ raise MesonException('Unknown target type for %s' % tname)
+ ntarget_dict.add_item('productType', f'"{typestr}"')
+
+ def generate_pbx_project(self, objects_dict):
+ project_dict = PbxDict()
+ objects_dict.add_item(self.project_uid, project_dict, 'Project object')
+ project_dict.add_item('isa', 'PBXProject')
+ attr_dict = PbxDict()
+ project_dict.add_item('attributes', attr_dict)
+ attr_dict.add_item('BuildIndependentTargetsInParallel', 'YES')
+ project_dict.add_item('buildConfigurationList', self.project_conflist, f'Build configuration list for PBXProject "{self.build.project_name}"')
+ project_dict.add_item('buildSettings', PbxDict())
+ style_arr = PbxArray()
+ project_dict.add_item('buildStyles', style_arr)
+ for name, idval in self.buildstylemap.items():
+ style_arr.add_item(idval, name)
+ project_dict.add_item('compatibilityVersion', '"Xcode 3.2"')
+ project_dict.add_item('hasScannedForEncodings', 0)
+ project_dict.add_item('mainGroup', self.maingroup_id)
+ project_dict.add_item('projectDirPath', '"' + self.environment.get_source_dir() + '"')
+ project_dict.add_item('projectRoot', '""')
+ targets_arr = PbxArray()
+ project_dict.add_item('targets', targets_arr)
+ targets_arr.add_item(self.all_id, 'ALL_BUILD')
+ targets_arr.add_item(self.test_id, 'RUN_TESTS')
+ targets_arr.add_item(self.regen_id, 'REGENERATE')
+ for t in self.build_targets:
+ targets_arr.add_item(self.native_targets[t], t)
+ for t in self.custom_targets:
+ targets_arr.add_item(self.custom_aggregate_targets[t], t)
+
+ def generate_pbx_shell_build_phase(self, objects_dict):
+ self.generate_test_shell_build_phase(objects_dict)
+ self.generate_regen_shell_build_phase(objects_dict)
+ self.generate_custom_target_shell_build_phases(objects_dict)
+ self.generate_generator_target_shell_build_phases(objects_dict)
+
+ def generate_test_shell_build_phase(self, objects_dict):
+ shell_dict = PbxDict()
+ objects_dict.add_item(self.test_command_id, shell_dict, 'ShellScript')
+ shell_dict.add_item('isa', 'PBXShellScriptBuildPhase')
+ shell_dict.add_item('buildActionMask', 2147483647)
+ shell_dict.add_item('files', PbxArray())
+ shell_dict.add_item('inputPaths', PbxArray())
+ shell_dict.add_item('outputPaths', PbxArray())
+ shell_dict.add_item('runOnlyForDeploymentPostprocessing', 0)
+ shell_dict.add_item('shellPath', '/bin/sh')
+ cmd = mesonlib.get_meson_command() + ['test', '--no-rebuild', '-C', self.environment.get_build_dir()]
+ cmdstr = ' '.join(["'%s'" % i for i in cmd])
+ shell_dict.add_item('shellScript', f'"{cmdstr}"')
+ shell_dict.add_item('showEnvVarsInLog', 0)
+
+ def generate_regen_shell_build_phase(self, objects_dict):
+ shell_dict = PbxDict()
+ objects_dict.add_item(self.regen_command_id, shell_dict, 'ShellScript')
+ shell_dict.add_item('isa', 'PBXShellScriptBuildPhase')
+ shell_dict.add_item('buildActionMask', 2147483647)
+ shell_dict.add_item('files', PbxArray())
+ shell_dict.add_item('inputPaths', PbxArray())
+ shell_dict.add_item('outputPaths', PbxArray())
+ shell_dict.add_item('runOnlyForDeploymentPostprocessing', 0)
+ shell_dict.add_item('shellPath', '/bin/sh')
+ cmd = mesonlib.get_meson_command() + ['--internal', 'regencheck', os.path.join(self.environment.get_build_dir(), 'meson-private')]
+ cmdstr = ' '.join(["'%s'" % i for i in cmd])
+ shell_dict.add_item('shellScript', f'"{cmdstr}"')
+ shell_dict.add_item('showEnvVarsInLog', 0)
+
+ def generate_custom_target_shell_build_phases(self, objects_dict):
+ # Custom targets are shell build phases in Xcode terminology.
+ for tname, t in self.custom_targets.items():
+ if not isinstance(t, build.CustomTarget):
+ continue
+ (srcs, ofilenames, cmd) = self.eval_custom_target_command(t, absolute_outputs=True)
+ fixed_cmd, _ = self.as_meson_exe_cmdline(cmd[0],
+ cmd[1:],
+ capture=ofilenames[0] if t.capture else None,
+ feed=srcs[0] if t.feed else None,
+ env=t.env)
+ custom_dict = PbxDict()
+ objects_dict.add_item(self.shell_targets[tname], custom_dict, f'/* Custom target {tname} */')
+ custom_dict.add_item('isa', 'PBXShellScriptBuildPhase')
+ custom_dict.add_item('buildActionMask', 2147483647)
+ custom_dict.add_item('files', PbxArray())
+ custom_dict.add_item('inputPaths', PbxArray())
+ outarray = PbxArray()
+ custom_dict.add_item('name', '"Generate {}."'.format(ofilenames[0]))
+ custom_dict.add_item('outputPaths', outarray)
+ for o in ofilenames:
+ outarray.add_item(os.path.join(self.environment.get_build_dir(), o))
+ custom_dict.add_item('runOnlyForDeploymentPostprocessing', 0)
+ custom_dict.add_item('shellPath', '/bin/sh')
+ workdir = self.environment.get_build_dir()
+ quoted_cmd = []
+ for c in fixed_cmd:
+ quoted_cmd.append(c.replace('"', chr(92) + '"'))
+ cmdstr = ' '.join([f"\\'{x}\\'" for x in quoted_cmd])
+ custom_dict.add_item('shellScript', f'"cd {workdir}; {cmdstr}"')
+ custom_dict.add_item('showEnvVarsInLog', 0)
+
+ def generate_generator_target_shell_build_phases(self, objects_dict):
+ for tname, t in self.build_targets.items():
+ generator_id = 0
+ for genlist in t.generated:
+ if isinstance(genlist, build.GeneratedList):
+ self.generate_single_generator_phase(tname, t, genlist, generator_id, objects_dict)
+ generator_id += 1
+ for tname, t in self.custom_targets.items():
+ generator_id = 0
+ for genlist in t.sources:
+ if isinstance(genlist, build.GeneratedList):
+ self.generate_single_generator_phase(tname, t, genlist, generator_id, objects_dict)
+ generator_id += 1
+
+ def generate_single_generator_phase(self, tname, t, genlist, generator_id, objects_dict):
+ # TODO: this should be rewritten to use the meson wrapper, like the other generators do
+ # Currently it doesn't handle a host binary that requires an exe wrapper correctly.
+ generator = genlist.get_generator()
+ exe = generator.get_exe()
+ exe_arr = self.build_target_to_cmd_array(exe)
+ workdir = self.environment.get_build_dir()
+ gen_dict = PbxDict()
+ objects_dict.add_item(self.shell_targets[(tname, generator_id)], gen_dict, f'"Generator {generator_id}/{tname}"')
+ infilelist = genlist.get_inputs()
+ outfilelist = genlist.get_outputs()
+ gen_dict.add_item('isa', 'PBXShellScriptBuildPhase')
+ gen_dict.add_item('buildActionMask', 2147483647)
+ gen_dict.add_item('files', PbxArray())
+ gen_dict.add_item('inputPaths', PbxArray())
+ gen_dict.add_item('name', f'"Generator {generator_id}/{tname}"')
+ commands = [["cd", workdir]] # Array of arrays, each one a single command, will get concatenated below.
+ k = (tname, generator_id)
+ ofile_abs = self.generator_outputs[k]
+ outarray = PbxArray()
+ gen_dict.add_item('outputPaths', outarray)
+ for of in ofile_abs:
+ outarray.add_item(of)
+ for i in infilelist:
+ # This might be needed to be added to inputPaths. It's not done yet as it is
+ # unclear whether it is necessary, what actually happens when it is defined
+ # and currently the build works without it.
+ #infile_abs = i.absolute_path(self.environment.get_source_dir(), self.environment.get_build_dir())
+ infilename = i.rel_to_builddir(self.build_to_src)
+ base_args = generator.get_arglist(infilename)
+ for o_base in genlist.get_outputs_for(i):
+ o = os.path.join(self.get_target_private_dir(t), o_base)
+ args = []
+ for arg in base_args:
+ arg = arg.replace("@INPUT@", infilename)
+ arg = arg.replace('@OUTPUT@', o).replace('@BUILD_DIR@', self.get_target_private_dir(t))
+ arg = arg.replace("@CURRENT_SOURCE_DIR@", os.path.join(self.build_to_src, t.subdir))
+ args.append(arg)
+ args = self.replace_outputs(args, self.get_target_private_dir(t), outfilelist)
+ args = self.replace_extra_args(args, genlist)
+ if generator.capture:
+ # When capturing, stdout is the output. Forward it with the shell.
+ full_command = ['('] + exe_arr + args + ['>', o, ')']
+ else:
+ full_command = exe_arr + args
+ commands.append(full_command)
+ gen_dict.add_item('runOnlyForDeploymentPostprocessing', 0)
+ gen_dict.add_item('shellPath', '/bin/sh')
+ quoted_cmds = []
+ for cmnd in commands:
+ q = []
+ for c in cmnd:
+ if ' ' in c:
+ q.append(f'\\"{c}\\"')
+ else:
+ q.append(c)
+ quoted_cmds.append(' '.join(q))
+ cmdstr = '"' + ' && '.join(quoted_cmds) + '"'
+ gen_dict.add_item('shellScript', cmdstr)
+ gen_dict.add_item('showEnvVarsInLog', 0)
+
+ def generate_pbx_sources_build_phase(self, objects_dict):
+ for name in self.source_phase:
+ phase_dict = PbxDict()
+ t = self.build_targets[name]
+ objects_dict.add_item(t.buildphasemap[name], phase_dict, 'Sources')
+ phase_dict.add_item('isa', 'PBXSourcesBuildPhase')
+ phase_dict.add_item('buildActionMask', 2147483647)
+ file_arr = PbxArray()
+ phase_dict.add_item('files', file_arr)
+ for s in self.build_targets[name].sources:
+ s = os.path.join(s.subdir, s.fname)
+ if not self.environment.is_header(s):
+ file_arr.add_item(self.buildfile_ids[(name, s)], os.path.join(self.environment.get_source_dir(), s))
+ generator_id = 0
+ for gt in t.generated:
+ if isinstance(gt, build.CustomTarget):
+ (srcs, ofilenames, cmd) = self.eval_custom_target_command(gt)
+ for o in ofilenames:
+ file_arr.add_item(self.custom_target_output_buildfile[o],
+ os.path.join(self.environment.get_build_dir(), o))
+ elif isinstance(gt, build.CustomTargetIndex):
+ for o in gt.get_outputs():
+ file_arr.add_item(self.custom_target_output_buildfile[o],
+ os.path.join(self.environment.get_build_dir(), o))
+ elif isinstance(gt, build.GeneratedList):
+ genfiles = self.generator_buildfile_ids[(name, generator_id)]
+ generator_id += 1
+ for o in genfiles:
+ file_arr.add_item(o)
+ else:
+ raise RuntimeError('Unknown input type: ' + str(gt))
+ phase_dict.add_item('runOnlyForDeploymentPostprocessing', 0)
+
+ def generate_pbx_target_dependency(self, objects_dict):
+ all_dict = PbxDict()
+ objects_dict.add_item(self.build_all_tdep_id, all_dict, 'ALL_BUILD')
+ all_dict.add_item('isa', 'PBXTargetDependency')
+ all_dict.add_item('target', self.all_id)
+ targets = []
+ targets.append((self.regen_dependency_id, self.regen_id, 'REGEN', None))
+ for t in self.build_targets:
+ idval = self.pbx_dep_map[t] # VERIFY: is this correct?
+ targets.append((idval, self.native_targets[t], t, self.containerproxy_map[t]))
+
+ for t in self.custom_targets:
+ idval = self.pbx_custom_dep_map[t]
+ targets.append((idval, self.custom_aggregate_targets[t], t, None)) # self.containerproxy_map[t]))
+
+ # Sort object by ID
+ sorted_targets = sorted(targets, key=operator.itemgetter(0))
+ for t in sorted_targets:
+ t_dict = PbxDict()
+ objects_dict.add_item(t[0], t_dict, 'PBXTargetDependency')
+ t_dict.add_item('isa', 'PBXTargetDependency')
+ t_dict.add_item('target', t[1], t[2])
+ if t[3] is not None:
+ t_dict.add_item('targetProxy', t[3], 'PBXContainerItemProxy')
+
+ def generate_xc_build_configuration(self, objects_dict):
+ # First the setup for the toplevel project.
+ for buildtype in self.buildtypes:
+ bt_dict = PbxDict()
+ objects_dict.add_item(self.project_configurations[buildtype], bt_dict, buildtype)
+ bt_dict.add_item('isa', 'XCBuildConfiguration')
+ settings_dict = PbxDict()
+ bt_dict.add_item('buildSettings', settings_dict)
+ settings_dict.add_item('ARCHS', '"$(NATIVE_ARCH_ACTUAL)"')
+ settings_dict.add_item('ONLY_ACTIVE_ARCH', 'YES')
+ settings_dict.add_item('SWIFT_VERSION', '5.0')
+ settings_dict.add_item('SDKROOT', '"macosx"')
+ settings_dict.add_item('SYMROOT', '"%s/build"' % self.environment.get_build_dir())
+ bt_dict.add_item('name', f'"{buildtype}"')
+
+ # Then the all target.
+ for buildtype in self.buildtypes:
+ bt_dict = PbxDict()
+ objects_dict.add_item(self.buildall_configurations[buildtype], bt_dict, buildtype)
+ bt_dict.add_item('isa', 'XCBuildConfiguration')
+ settings_dict = PbxDict()
+ bt_dict.add_item('buildSettings', settings_dict)
+ settings_dict.add_item('SYMROOT', '"%s"' % self.environment.get_build_dir())
+ warn_array = PbxArray()
+ warn_array.add_item('"$(inherited)"')
+ settings_dict.add_item('WARNING_CFLAGS', warn_array)
+
+ bt_dict.add_item('name', f'"{buildtype}"')
+
+ # Then the test target.
+ for buildtype in self.buildtypes:
+ bt_dict = PbxDict()
+ objects_dict.add_item(self.test_configurations[buildtype], bt_dict, buildtype)
+ bt_dict.add_item('isa', 'XCBuildConfiguration')
+ settings_dict = PbxDict()
+ bt_dict.add_item('buildSettings', settings_dict)
+ settings_dict.add_item('SYMROOT', '"%s"' % self.environment.get_build_dir())
+ warn_array = PbxArray()
+ settings_dict.add_item('WARNING_CFLAGS', warn_array)
+ warn_array.add_item('"$(inherited)"')
+ bt_dict.add_item('name', f'"{buildtype}"')
+
+ # Now finally targets.
+ for target_name, target in self.build_targets.items():
+ self.generate_single_build_target(objects_dict, target_name, target)
+
+ for target_name, target in self.custom_targets.items():
+ bt_dict = PbxDict()
+ objects_dict.add_item(self.buildconfmap[target_name][buildtype], bt_dict, buildtype)
+ bt_dict.add_item('isa', 'XCBuildConfiguration')
+ settings_dict = PbxDict()
+ bt_dict.add_item('buildSettings', settings_dict)
+ settings_dict.add_item('ARCHS', '"$(NATIVE_ARCH_ACTUAL)"')
+ settings_dict.add_item('ONLY_ACTIVE_ARCH', 'YES')
+ settings_dict.add_item('SDKROOT', '"macosx"')
+ settings_dict.add_item('SYMROOT', '"%s/build"' % self.environment.get_build_dir())
+ bt_dict.add_item('name', f'"{buildtype}"')
+
+ def determine_internal_dep_link_args(self, target, buildtype):
+ links_dylib = False
+ dep_libs = []
+ for l in target.link_targets:
+ if isinstance(target, build.SharedModule) and isinstance(l, build.Executable):
+ continue
+ if isinstance(l, build.CustomTargetIndex):
+ rel_dir = self.get_custom_target_output_dir(l.target)
+ libname = l.get_filename()
+ elif isinstance(l, build.CustomTarget):
+ rel_dir = self.get_custom_target_output_dir(l)
+ libname = l.get_filename()
+ else:
+ rel_dir = self.get_target_dir(l)
+ libname = l.get_filename()
+ abs_path = os.path.join(self.environment.get_build_dir(), rel_dir, libname)
+ dep_libs.append("'%s'" % abs_path)
+ if isinstance(l, build.SharedLibrary):
+ links_dylib = True
+ if isinstance(l, build.StaticLibrary):
+ (sub_libs, sub_links_dylib) = self.determine_internal_dep_link_args(l, buildtype)
+ dep_libs += sub_libs
+ links_dylib = links_dylib or sub_links_dylib
+ return (dep_libs, links_dylib)
+
+ def generate_single_build_target(self, objects_dict, target_name, target):
+ for buildtype in self.buildtypes:
+ dep_libs = []
+ links_dylib = False
+ headerdirs = []
+ for d in target.include_dirs:
+ for sd in d.incdirs:
+ cd = os.path.join(d.curdir, sd)
+ headerdirs.append(os.path.join(self.environment.get_source_dir(), cd))
+ headerdirs.append(os.path.join(self.environment.get_build_dir(), cd))
+ for extra in d.extra_build_dirs:
+ headerdirs.append(os.path.join(self.environment.get_build_dir(), extra))
+ (dep_libs, links_dylib) = self.determine_internal_dep_link_args(target, buildtype)
+ if links_dylib:
+ dep_libs = ['-Wl,-search_paths_first', '-Wl,-headerpad_max_install_names'] + dep_libs
+ dylib_version = None
+ if isinstance(target, build.SharedLibrary):
+ if isinstance(target, build.SharedModule):
+ ldargs = []
+ else:
+ ldargs = ['-dynamiclib']
+ ldargs += ['-Wl,-headerpad_max_install_names'] + dep_libs
+ install_path = os.path.join(self.environment.get_build_dir(), target.subdir, buildtype)
+ dylib_version = target.soversion
+ else:
+ ldargs = dep_libs
+ install_path = ''
+ if dylib_version is not None:
+ product_name = target.get_basename() + '.' + dylib_version
+ else:
+ product_name = target.get_basename()
+ ldargs += target.link_args
+ # Swift is special. Again. You can't mix Swift with other languages
+ # in the same target. Thus for Swift we only use
+ if self.is_swift_target(target):
+ linker, stdlib_args = target.compilers['swift'], []
+ else:
+ linker, stdlib_args = self.determine_linker_and_stdlib_args(target)
+ if not isinstance(target, build.StaticLibrary):
+ ldargs += self.build.get_project_link_args(linker, target.subproject, target.for_machine)
+ ldargs += self.build.get_global_link_args(linker, target.for_machine)
+ cargs = []
+ for dep in target.get_external_deps():
+ cargs += dep.get_compile_args()
+ ldargs += dep.get_link_args()
+ for o in target.objects:
+ # Add extracted objects to the link line by hand.
+ if isinstance(o, build.ExtractedObjects):
+ added_objs = set()
+ for objname_rel in self.determine_ext_objs(o):
+ objname_abs = os.path.join(self.environment.get_build_dir(), o.target.subdir, objname_rel)
+ if objname_abs not in added_objs:
+ added_objs.add(objname_abs)
+ ldargs += [r'\"' + objname_abs + r'\"']
+ generator_id = 0
+ for o in target.generated:
+ if isinstance(o, build.GeneratedList):
+ outputs = self.generator_outputs[target_name, generator_id]
+ generator_id += 1
+ for o_abs in outputs:
+ if o_abs.endswith('.o') or o_abs.endswith('.obj'):
+ ldargs += [r'\"' + o_abs + r'\"']
+ else:
+ if isinstance(o, build.CustomTarget):
+ (srcs, ofilenames, cmd) = self.eval_custom_target_command(o)
+ for ofname in ofilenames:
+ if os.path.splitext(ofname)[-1] in LINKABLE_EXTENSIONS:
+ ldargs += [r'\"' + os.path.join(self.environment.get_build_dir(), ofname) + r'\"']
+ elif isinstance(o, build.CustomTargetIndex):
+ for ofname in o.get_outputs():
+ if os.path.splitext(ofname)[-1] in LINKABLE_EXTENSIONS:
+ ldargs += [r'\"' + os.path.join(self.environment.get_build_dir(), ofname) + r'\"']
+ else:
+ raise RuntimeError(o)
+ if isinstance(target, build.SharedModule):
+ options = self.environment.coredata.options
+ ldargs += linker.get_std_shared_module_link_args(options)
+ elif isinstance(target, build.SharedLibrary):
+ ldargs += linker.get_std_shared_lib_link_args()
+ ldstr = ' '.join(ldargs)
+ valid = self.buildconfmap[target_name][buildtype]
+ langargs = {}
+ for lang in self.environment.coredata.compilers[target.for_machine]:
+ if lang not in LANGNAMEMAP:
+ continue
+ compiler = target.compilers.get(lang)
+ if compiler is None:
+ continue
+ # Start with warning args
+ warn_args = compiler.get_warn_args(target.get_option(OptionKey('warning_level')))
+ copt_proxy = target.get_options()
+ std_args = compiler.get_option_compile_args(copt_proxy)
+ # Add compile args added using add_project_arguments()
+ pargs = self.build.projects_args[target.for_machine].get(target.subproject, {}).get(lang, [])
+ # Add compile args added using add_global_arguments()
+ # These override per-project arguments
+ gargs = self.build.global_args[target.for_machine].get(lang, [])
+ targs = target.get_extra_args(lang)
+ args = warn_args + std_args + pargs + gargs + targs
+ if lang == 'swift':
+ # For some reason putting Swift module dirs in HEADER_SEARCH_PATHS does not work,
+ # but adding -I/path to manual args does work.
+ swift_dep_dirs = self.determine_swift_dep_dirs(target)
+ for d in swift_dep_dirs:
+ args += compiler.get_include_args(d, False)
+ if args:
+ lang_cargs = cargs
+ if compiler and target.implicit_include_directories:
+ # It is unclear what is the cwd when xcode runs. -I. does not seem to
+ # add the root build dir to the search path. So add an absolute path instead.
+ # This may break reproducible builds, in which case patches are welcome.
+ lang_cargs += self.get_custom_target_dir_include_args(target, compiler, absolute_path=True)
+ # Xcode can not handle separate compilation flags for C and ObjectiveC. They are both
+ # put in OTHER_CFLAGS. Same with C++ and ObjectiveC++.
+ if lang == 'objc':
+ lang = 'c'
+ elif lang == 'objcpp':
+ lang = 'cpp'
+ langname = LANGNAMEMAP[lang]
+ if langname in langargs:
+ langargs[langname] += args
+ else:
+ langargs[langname] = args
+ langargs[langname] += lang_cargs
+ symroot = os.path.join(self.environment.get_build_dir(), target.subdir)
+ bt_dict = PbxDict()
+ objects_dict.add_item(valid, bt_dict, buildtype)
+ bt_dict.add_item('isa', 'XCBuildConfiguration')
+ settings_dict = PbxDict()
+ bt_dict.add_item('buildSettings', settings_dict)
+ settings_dict.add_item('COMBINE_HIDPI_IMAGES', 'YES')
+ if isinstance(target, build.SharedModule):
+ settings_dict.add_item('DYLIB_CURRENT_VERSION', '""')
+ settings_dict.add_item('DYLIB_COMPATIBILITY_VERSION', '""')
+ else:
+ if dylib_version is not None:
+ settings_dict.add_item('DYLIB_CURRENT_VERSION', f'"{dylib_version}"')
+ if target.prefix:
+ settings_dict.add_item('EXECUTABLE_PREFIX', target.prefix)
+ if target.suffix:
+ suffix = '.' + target.suffix
+ settings_dict.add_item('EXECUTABLE_SUFFIX', suffix)
+ settings_dict.add_item('GCC_GENERATE_DEBUGGING_SYMBOLS', BOOL2XCODEBOOL[target.get_option(OptionKey('debug'))])
+ settings_dict.add_item('GCC_INLINES_ARE_PRIVATE_EXTERN', 'NO')
+ opt_flag = OPT2XCODEOPT[target.get_option(OptionKey('optimization'))]
+ if opt_flag is not None:
+ settings_dict.add_item('GCC_OPTIMIZATION_LEVEL', opt_flag)
+ if target.has_pch:
+ # Xcode uses GCC_PREFIX_HEADER which only allows one file per target/executable. Precompiling various header files and
+ # applying a particular pch to each source file will require custom scripts (as a build phase) and build flags per each
+ # file. Since Xcode itself already discourages precompiled headers in favor of modules we don't try much harder here.
+ pchs = target.get_pch('c') + target.get_pch('cpp') + target.get_pch('objc') + target.get_pch('objcpp')
+ # Make sure to use headers (other backends require implementation files like *.c *.cpp, etc; these should not be used here)
+ pchs = [pch for pch in pchs if pch.endswith('.h') or pch.endswith('.hh') or pch.endswith('hpp')]
+ if pchs:
+ if len(pchs) > 1:
+ mlog.warning(f'Unsupported Xcode configuration: More than 1 precompiled header found "{pchs!s}". Target "{target.name}" might not compile correctly.')
+ relative_pch_path = os.path.join(target.get_subdir(), pchs[0]) # Path relative to target so it can be used with "$(PROJECT_DIR)"
+ settings_dict.add_item('GCC_PRECOMPILE_PREFIX_HEADER', 'YES')
+ settings_dict.add_item('GCC_PREFIX_HEADER', f'"$(PROJECT_DIR)/{relative_pch_path}"')
+ settings_dict.add_item('GCC_PREPROCESSOR_DEFINITIONS', '""')
+ settings_dict.add_item('GCC_SYMBOLS_PRIVATE_EXTERN', 'NO')
+ header_arr = PbxArray()
+ unquoted_headers = []
+ unquoted_headers.append(self.get_target_private_dir_abs(target))
+ if target.implicit_include_directories:
+ unquoted_headers.append(os.path.join(self.environment.get_build_dir(), target.get_subdir()))
+ unquoted_headers.append(os.path.join(self.environment.get_source_dir(), target.get_subdir()))
+ if headerdirs:
+ for i in headerdirs:
+ i = os.path.normpath(i)
+ unquoted_headers.append(i)
+ for i in unquoted_headers:
+ header_arr.add_item(f'"\\"{i}\\""')
+ settings_dict.add_item('HEADER_SEARCH_PATHS', header_arr)
+ settings_dict.add_item('INSTALL_PATH', f'"{install_path}"')
+ settings_dict.add_item('LIBRARY_SEARCH_PATHS', '""')
+ if isinstance(target, build.SharedModule):
+ settings_dict.add_item('LIBRARY_STYLE', 'BUNDLE')
+ settings_dict.add_item('MACH_O_TYPE', 'mh_bundle')
+ elif isinstance(target, build.SharedLibrary):
+ settings_dict.add_item('LIBRARY_STYLE', 'DYNAMIC')
+ self.add_otherargs(settings_dict, langargs)
+ settings_dict.add_item('OTHER_LDFLAGS', f'"{ldstr}"')
+ settings_dict.add_item('OTHER_REZFLAGS', '""')
+ if ' ' in product_name:
+ settings_dict.add_item('PRODUCT_NAME', f'"{product_name}"')
+ else:
+ settings_dict.add_item('PRODUCT_NAME', product_name)
+ settings_dict.add_item('SECTORDER_FLAGS', '""')
+ settings_dict.add_item('SYMROOT', f'"{symroot}"')
+ sysheader_arr = PbxArray()
+ # XCode will change every -I flag that points inside these directories
+ # to an -isystem. Thus set nothing in it since we control our own
+ # include flags.
+ settings_dict.add_item('SYSTEM_HEADER_SEARCH_PATHS', sysheader_arr)
+ settings_dict.add_item('USE_HEADERMAP', 'NO')
+ warn_array = PbxArray()
+ settings_dict.add_item('WARNING_CFLAGS', warn_array)
+ warn_array.add_item('"$(inherited)"')
+ bt_dict.add_item('name', buildtype)
+
+ def add_otherargs(self, settings_dict, langargs):
+ for langname, args in langargs.items():
+ if args:
+ quoted_args = []
+ for a in args:
+ # This works but
+ # a) it's ugly as sin
+ # b) I don't know why it works or why every backslash must be escaped into eight backslashes
+ a = a.replace(chr(92), 8*chr(92)) # chr(92) is backslash, this how we smuggle it in without Python's quoting grabbing it.
+ a = a.replace(r'"', r'\\\"')
+ if ' ' in a or "'" in a:
+ a = r'\"' + a + r'\"'
+ quoted_args.append(a)
+ settings_dict.add_item(f'OTHER_{langname}FLAGS', '"' + ' '.join(quoted_args) + '"')
+
+ def generate_xc_configurationList(self, objects_dict):
+ # FIXME: sort items
+ conf_dict = PbxDict()
+ objects_dict.add_item(self.project_conflist, conf_dict, f'Build configuration list for PBXProject "{self.build.project_name}"')
+ conf_dict.add_item('isa', 'XCConfigurationList')
+ confs_arr = PbxArray()
+ conf_dict.add_item('buildConfigurations', confs_arr)
+ for buildtype in self.buildtypes:
+ confs_arr.add_item(self.project_configurations[buildtype], buildtype)
+ conf_dict.add_item('defaultConfigurationIsVisible', 0)
+ conf_dict.add_item('defaultConfigurationName', self.buildtype)
+
+ # Now the all target
+ all_dict = PbxDict()
+ objects_dict.add_item(self.all_buildconf_id, all_dict, 'Build configuration list for PBXAggregateTarget "ALL_BUILD"')
+ all_dict.add_item('isa', 'XCConfigurationList')
+ conf_arr = PbxArray()
+ all_dict.add_item('buildConfigurations', conf_arr)
+ for buildtype in self.buildtypes:
+ conf_arr.add_item(self.buildall_configurations[buildtype], buildtype)
+ all_dict.add_item('defaultConfigurationIsVisible', 0)
+ all_dict.add_item('defaultConfigurationName', self.buildtype)
+
+ # Test target
+ test_dict = PbxDict()
+ objects_dict.add_item(self.test_buildconf_id, test_dict, 'Build configuration list for PBXAggregateTarget "RUN_TEST"')
+ test_dict.add_item('isa', 'XCConfigurationList')
+ conf_arr = PbxArray()
+ test_dict.add_item('buildConfigurations', conf_arr)
+ for buildtype in self.buildtypes:
+ conf_arr.add_item(self.test_configurations[buildtype], buildtype)
+ test_dict.add_item('defaultConfigurationIsVisible', 0)
+ test_dict.add_item('defaultConfigurationName', self.buildtype)
+
+ # Regen target
+ regen_dict = PbxDict()
+ objects_dict.add_item(self.regen_buildconf_id, test_dict, 'Build configuration list for PBXAggregateTarget "REGENERATE"')
+ regen_dict.add_item('isa', 'XCConfigurationList')
+ conf_arr = PbxArray()
+ regen_dict.add_item('buildConfigurations', conf_arr)
+ for buildtype in self.buildtypes:
+ conf_arr.add_item(self.test_configurations[buildtype], buildtype)
+ regen_dict.add_item('defaultConfigurationIsVisible', 0)
+ regen_dict.add_item('defaultConfigurationName', self.buildtype)
+
+ for target_name in self.build_targets:
+ t_dict = PbxDict()
+ listid = self.buildconflistmap[target_name]
+ objects_dict.add_item(listid, t_dict, f'Build configuration list for PBXNativeTarget "{target_name}"')
+ t_dict.add_item('isa', 'XCConfigurationList')
+ conf_arr = PbxArray()
+ t_dict.add_item('buildConfigurations', conf_arr)
+ idval = self.buildconfmap[target_name][self.buildtype]
+ conf_arr.add_item(idval, self.buildtype)
+ t_dict.add_item('defaultConfigurationIsVisible', 0)
+ t_dict.add_item('defaultConfigurationName', self.buildtype)
+
+ for target_name in self.custom_targets:
+ t_dict = PbxDict()
+ listid = self.buildconflistmap[target_name]
+ objects_dict.add_item(listid, t_dict, f'Build configuration list for PBXAggregateTarget "{target_name}"')
+ t_dict.add_item('isa', 'XCConfigurationList')
+ conf_arr = PbxArray()
+ t_dict.add_item('buildConfigurations', conf_arr)
+ idval = self.buildconfmap[target_name][self.buildtype]
+ conf_arr.add_item(idval, self.buildtype)
+ t_dict.add_item('defaultConfigurationIsVisible', 0)
+ t_dict.add_item('defaultConfigurationName', self.buildtype)
+
+ def generate_prefix(self, pbxdict):
+ pbxdict.add_item('archiveVersion', '1')
+ pbxdict.add_item('classes', PbxDict())
+ pbxdict.add_item('objectVersion', '46')
+ objects_dict = PbxDict()
+ pbxdict.add_item('objects', objects_dict)
+
+ return objects_dict
+
+ def generate_suffix(self, pbxdict):
+ pbxdict.add_item('rootObject', self.project_uid, 'Project object')