aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/build.py
diff options
context:
space:
mode:
Diffstat (limited to 'mesonbuild/build.py')
-rw-r--r--mesonbuild/build.py223
1 files changed, 187 insertions, 36 deletions
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
index 7320b88..2adfb98 100644
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -24,16 +24,16 @@ from .mesonlib import (
File, MesonException, MachineChoice, PerMachine, OrderedSet, listify,
extract_as_list, typeslistify, stringlistify, classify_unity_sources,
get_filenames_templates_dict, substitute_values, has_path_sep,
- is_parent_path, PerMachineDefaultable,
+ is_parent_path, relpath, PerMachineDefaultable,
MesonBugException, EnvironmentVariables, pickle_load, lazy_property,
)
from .options import OptionKey
from .compilers import (
is_header, is_object, is_source, clink_langs, sort_clink, all_languages,
- is_known_suffix, detect_static_linker
+ is_known_suffix, detect_static_linker, LANGUAGES_USING_LDFLAGS
)
-from .interpreterbase import FeatureNew, FeatureDeprecated
+from .interpreterbase import FeatureNew, FeatureDeprecated, UnknownValue
if T.TYPE_CHECKING:
from typing_extensions import Literal, TypedDict
@@ -75,6 +75,7 @@ lang_arg_kwargs |= {
vala_kwargs = {'vala_header', 'vala_gir', 'vala_vapi'}
rust_kwargs = {'rust_crate_type', 'rust_dependency_map'}
cs_kwargs = {'resources', 'cs_args'}
+swift_kwargs = {'swift_interoperability_mode', 'swift_module_name'}
buildtarget_kwargs = {
'build_by_default',
@@ -110,7 +111,8 @@ known_build_target_kwargs = (
pch_kwargs |
vala_kwargs |
rust_kwargs |
- cs_kwargs)
+ cs_kwargs |
+ swift_kwargs)
known_exe_kwargs = known_build_target_kwargs | {'implib', 'export_dynamic', 'pie', 'vs_module_defs', 'android_exe_type'}
known_shlib_kwargs = known_build_target_kwargs | {'version', 'soversion', 'vs_module_defs', 'darwin_versions', 'rust_abi'}
@@ -275,7 +277,7 @@ class Build:
self.stdlibs = PerMachine({}, {})
self.test_setups: T.Dict[str, TestSetup] = {}
self.test_setup_default_name = None
- self.find_overrides: T.Dict[str, T.Union['Executable', programs.ExternalProgram, programs.OverrideProgram]] = {}
+ self.find_overrides: T.Dict[str, T.Union['OverrideExecutable', programs.ExternalProgram, programs.OverrideProgram]] = {}
self.searched_programs: T.Set[str] = set() # The list of all programs that have been searched for.
# If we are doing a cross build we need two caches, if we're doing a
@@ -648,7 +650,7 @@ class Target(HoldableObject, metaclass=abc.ABCMeta):
def process_kwargs_base(self, kwargs: T.Dict[str, T.Any]) -> None:
if 'build_by_default' in kwargs:
self.build_by_default = kwargs['build_by_default']
- if not isinstance(self.build_by_default, bool):
+ if not isinstance(self.build_by_default, (bool, UnknownValue)):
raise InvalidArguments('build_by_default must be a boolean value.')
if not self.build_by_default and kwargs.get('install', False):
@@ -656,29 +658,11 @@ class Target(HoldableObject, metaclass=abc.ABCMeta):
# set, use the value of 'install' if it's enabled.
self.build_by_default = True
- self.raw_overrides = self.parse_overrides(kwargs)
+ self.raw_overrides = kwargs.get('override_options', {})
def get_override(self, name: str) -> T.Optional[str]:
return self.raw_overrides.get(name, None)
- @staticmethod
- def parse_overrides(kwargs: T.Dict[str, T.Any]) -> T.Dict[str, str]:
- opts = kwargs.get('override_options', [])
-
- # In this case we have an already parsed and ready to go dictionary
- # provided by typed_kwargs
- if isinstance(opts, dict):
- return T.cast('T.Dict[OptionKey, str]', opts)
-
- result: T.Dict[str, str] = {}
- overrides = stringlistify(opts)
- for o in overrides:
- if '=' not in o:
- raise InvalidArguments('Overrides must be of form "key=value"')
- k, v = o.split('=', 1)
- result[k] = v
- return result
-
def is_linkable_target(self) -> bool:
return False
@@ -787,14 +771,23 @@ class BuildTarget(Target):
''' Initialisations and checks requiring the final list of compilers to be known
'''
self.validate_sources()
- if self.structured_sources and any([self.sources, self.generated]):
- raise MesonException('cannot mix structured sources and unstructured sources')
- if self.structured_sources and 'rust' not in self.compilers:
- raise MesonException('structured sources are only supported in Rust targets')
if self.uses_rust():
+ if self.link_language and self.link_language != 'rust':
+ raise MesonException('cannot build Rust sources with a different link_language')
+ if self.structured_sources:
+ # TODO: the interpreter should be able to generate a better error message?
+ if any((s.endswith('.rs') for s in self.sources)) or \
+ any(any((s.endswith('.rs') for s in g.get_outputs())) for g in self.generated):
+ raise MesonException('cannot mix Rust structured sources and unstructured sources')
+
# relocation-model=pic is rustc's default and Meson does not
# currently have a way to disable PIC.
self.pic = True
+ self.pie = True
+ else:
+ if self.structured_sources:
+ raise MesonException('structured sources are only supported in Rust targets')
+
if 'vala' in self.compilers and self.is_linkable_target():
self.outputs += [self.vala_header, self.vala_vapi]
self.install_tag += ['devel', 'devel']
@@ -896,6 +889,10 @@ class BuildTarget(Target):
if isinstance(t, (CustomTarget, CustomTargetIndex)):
continue # We can't know anything about these.
for name, compiler in t.compilers.items():
+ if name == 'rust':
+ # Rust is always linked through a C-ABI target, so do not add
+ # the compiler here
+ continue
if name in link_langs and name not in self.compilers:
self.compilers[name] = compiler
@@ -981,7 +978,7 @@ class BuildTarget(Target):
self.compilers[lang] = compiler
break
else:
- if is_known_suffix(s):
+ if is_known_suffix(s) and not is_header(s):
path = pathlib.Path(str(s)).as_posix()
m = f'No {self.for_machine.get_lower_case_name()} machine compiler for {path!r}'
raise MesonException(m)
@@ -1278,6 +1275,12 @@ class BuildTarget(Target):
raise InvalidArguments(f'Invalid rust_dependency_map "{rust_dependency_map}": must be a dictionary with string values.')
self.rust_dependency_map = rust_dependency_map
+ self.swift_interoperability_mode = kwargs.get('swift_interoperability_mode')
+
+ self.swift_module_name = kwargs.get('swift_module_name')
+ if self.swift_module_name == '':
+ self.swift_module_name = self.name
+
def _extract_pic_pie(self, kwargs: T.Dict[str, T.Any], arg: str, option: str) -> bool:
# Check if we have -fPIC, -fpic, -fPIE, or -fpie in cflags
all_flags = self.extra_args['c'] + self.extra_args['cpp']
@@ -1375,6 +1378,10 @@ class BuildTarget(Target):
deps = listify(deps)
for dep in deps:
if dep in self.added_deps:
+ # Prefer to add dependencies to added_deps which have a name
+ if dep.is_named():
+ self.added_deps.remove(dep)
+ self.added_deps.add(dep)
continue
if isinstance(dep, dependencies.InternalDependency):
@@ -1603,6 +1610,9 @@ class BuildTarget(Target):
if isinstance(link_target, (CustomTarget, CustomTargetIndex)):
continue
for language in link_target.compilers:
+ if language == 'rust' and not link_target.uses_rust_abi():
+ # All Rust dependencies must go through a C-ABI dependency, so ignore it
+ continue
if language not in langs:
langs.append(language)
@@ -1694,6 +1704,9 @@ class BuildTarget(Target):
def uses_fortran(self) -> bool:
return 'fortran' in self.compilers
+ def uses_swift_cpp_interop(self) -> bool:
+ return self.swift_interoperability_mode == 'cpp' and 'swift' in self.compilers
+
def get_using_msvc(self) -> bool:
'''
Check if the dynamic linker is MSVC. Used by Executable, StaticLibrary,
@@ -1788,6 +1801,121 @@ class BuildTarget(Target):
"""Base case used by BothLibraries"""
return self
+ def determine_rpath_dirs(self) -> T.Tuple[str, ...]:
+ result: OrderedSet[str]
+ if self.environment.coredata.optstore.get_value_for(OptionKey('layout')) == 'mirror':
+ # Need a copy here
+ result = OrderedSet(self.get_link_dep_subdirs())
+ else:
+ result = OrderedSet()
+ result.add('meson-out')
+ result.update(self.rpaths_for_non_system_absolute_shared_libraries())
+ self.rpath_dirs_to_remove.update([d.encode('utf-8') for d in result])
+ return tuple(result)
+
+ @lru_cache(maxsize=None)
+ def rpaths_for_non_system_absolute_shared_libraries(self, exclude_system: bool = True) -> ImmutableListProtocol[str]:
+ paths: OrderedSet[str] = OrderedSet()
+ srcdir = self.environment.get_source_dir()
+
+ system_dirs = set()
+ if exclude_system:
+ for cc in self.compilers.values():
+ system_dirs.update(cc.get_library_dirs(self.environment))
+
+ external_rpaths = self.get_external_rpath_dirs()
+ build_to_src = relpath(self.environment.get_source_dir(),
+ self.environment.get_build_dir())
+
+ for dep in self.external_deps:
+ if dep.type_name not in {'library', 'pkgconfig', 'cmake'}:
+ continue
+ for libpath in dep.link_args:
+ if libpath.startswith('-'):
+ continue
+ # For all link args that are absolute paths to a library file, add RPATH args
+ if not os.path.isabs(libpath):
+ continue
+ libdir, libname = os.path.split(libpath)
+ # Windows doesn't support rpaths, but we use this function to
+ # emulate rpaths by setting PATH
+ # .dll is there for mingw gcc
+ # .so's may be extended with version information, e.g. libxyz.so.1.2.3
+ if not (
+ libname.endswith(('.dll', '.lib', '.so', '.dylib'))
+ or '.so.' in libname
+ ):
+ continue
+
+ # Don't remove rpaths specified in LDFLAGS.
+ if libdir in external_rpaths:
+ continue
+ if system_dirs and os.path.normpath(libdir) in system_dirs:
+ # No point in adding system paths.
+ continue
+
+ if is_parent_path(srcdir, libdir):
+ rel_to_src = libdir[len(srcdir) + 1:]
+ assert not os.path.isabs(rel_to_src), f'rel_to_src: {rel_to_src} is absolute'
+ paths.add(os.path.join(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 itertools.chain(self.link_targets, self.link_whole_targets):
+ if isinstance(i, BuildTarget):
+ paths.update(i.rpaths_for_non_system_absolute_shared_libraries(exclude_system))
+ return list(paths)
+
+ def get_external_rpath_dirs(self) -> T.Set[str]:
+ args: T.List[str] = []
+ for lang in LANGUAGES_USING_LDFLAGS:
+ try:
+ args += self.environment.coredata.get_external_link_args(self.for_machine, lang)
+ except KeyError:
+ pass
+ return self.get_rpath_dirs_from_link_args(args)
+
+ # 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[=,]([^,]+)')
+
+ @classmethod
+ def get_rpath_dirs_from_link_args(cls, args: T.List[str]) -> T.Set[str]:
+ dirs: T.Set[str] = set()
+
+ for arg in args:
+ if not arg.startswith('-Wl,'):
+ continue
+
+ rpath_match = cls._rpath_regex.match(arg)
+ if rpath_match:
+ for dir in rpath_match.group(1).split(':'):
+ dirs.add(dir)
+ runpath_match = cls._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 os.path.isdir(dir):
+ dirs.add(dir)
+ symbols_match = cls._symbols_regex.match(arg)
+ if symbols_match:
+ for dir in symbols_match.group(1).split(':'):
+ # Prevent usage of --just-symbols to specify rpath
+ if os.path.isdir(dir):
+ raise MesonException(f'Invalid arg for --just-symbols, {dir} is a directory.')
+ return dirs
+
+
class FileInTargetPrivateDir:
"""Represents a file with the path '/path/to/build/target_private_dir/fname'.
target_private_dir is the return value of get_target_private_dir which is e.g. 'subdir/target.p'.
@@ -2199,10 +2327,16 @@ class StaticLibrary(BuildTarget):
elif self.rust_crate_type == 'staticlib':
self.suffix = 'a'
else:
- if 'c' in self.compilers and self.compilers['c'].get_id() == 'tasking':
- self.suffix = 'ma' if self.options.get_value('b_lto') and not self.prelink else 'a'
- else:
- self.suffix = 'a'
+ self.suffix = 'a'
+ if 'c' in self.compilers and self.compilers['c'].get_id() == 'tasking' and not self.prelink:
+ key = OptionKey('b_lto', self.subproject, self.for_machine)
+ try:
+ v = self.environment.coredata.get_option_for_target(self, key)
+ except KeyError:
+ v = self.environment.coredata.optstore.get_value_for(key)
+ assert isinstance(v, bool), 'for mypy'
+ if v:
+ self.suffix = 'ma'
self.filename = self.prefix + self.name + '.' + self.suffix
self.outputs[0] = self.filename
@@ -2594,7 +2728,7 @@ class CommandBase:
subproject: str
def flatten_command(self, cmd: T.Sequence[T.Union[str, File, programs.ExternalProgram, BuildTargetTypes]]) -> \
- T.List[T.Union[str, File, BuildTarget, 'CustomTarget']]:
+ T.List[T.Union[str, File, BuildTarget, CustomTarget, programs.ExternalProgram]]:
cmd = listify(cmd)
final_cmd: T.List[T.Union[str, File, BuildTarget, 'CustomTarget']] = []
for c in cmd:
@@ -2611,7 +2745,8 @@ class CommandBase:
# Can only add a dependency on an external program which we
# know the absolute path of
self.depend_files.append(File.from_absolute_file(path))
- final_cmd += c.get_command()
+ # Do NOT flatten -- it is needed for later parsing
+ final_cmd.append(c)
elif isinstance(c, (BuildTarget, CustomTarget)):
self.dependencies.append(c)
final_cmd.append(c)
@@ -2681,6 +2816,7 @@ class CustomTarget(Target, CustomTargetBase, CommandBase):
install_dir: T.Optional[T.List[T.Union[str, Literal[False]]]] = None,
install_mode: T.Optional[FileMode] = None,
install_tag: T.Optional[T.List[T.Optional[str]]] = None,
+ rspable: bool = False,
absolute_paths: bool = False,
backend: T.Optional['Backend'] = None,
description: str = 'Generating {} with a custom command',
@@ -2713,6 +2849,9 @@ class CustomTarget(Target, CustomTargetBase, CommandBase):
# Whether to use absolute paths for all files on the commandline
self.absolute_paths = absolute_paths
+ # Whether to enable using response files for the underlying tool
+ self.rspable = rspable
+
def get_default_install_dir(self) -> T.Union[T.Tuple[str, str], T.Tuple[None, None]]:
return None, None
@@ -3129,6 +3268,18 @@ class ConfigurationData(HoldableObject):
def keys(self) -> T.Iterator[str]:
return self.values.keys()
+class OverrideExecutable(Executable):
+ def __init__(self, executable: Executable, version: str):
+ self._executable = executable
+ self._version = version
+
+ def __getattr__(self, name: str) -> T.Any:
+ _executable = object.__getattribute__(self, '_executable')
+ return getattr(_executable, name)
+
+ def get_version(self, interpreter: T.Optional[Interpreter] = None) -> str:
+ return self._version
+
# A bit poorly named, but this represents plain data files to copy
# during install.
@dataclass(eq=False)