# 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. import abc import os import typing as T from . import mesonlib from .arglist import CompilerArgs from .envconfig import get_env_var if T.TYPE_CHECKING: from .coredata import OptionDictType from .envconfig import MachineChoice from .environment import Environment class StaticLinker: def __init__(self, exelist: T.List[str]): self.exelist = exelist def compiler_args(self, args: T.Optional[T.Iterable[str]] = None) -> CompilerArgs: return CompilerArgs(self, args) def can_linker_accept_rsp(self) -> bool: """ Determines whether the linker can accept arguments using the @rsp syntax. """ return mesonlib.is_windows() def get_base_link_args(self, options: 'OptionDictType') -> T.List[str]: """Like compilers.get_base_link_args, but for the static linker.""" return [] def get_exelist(self) -> T.List[str]: return self.exelist.copy() def get_std_link_args(self) -> T.List[str]: return [] def get_buildtype_linker_args(self, buildtype: str) -> T.List[str]: return [] def get_output_args(self, target: str) -> T.List[str]: return[] def get_coverage_link_args(self) -> T.List[str]: return [] def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, rpath_paths: str, build_rpath: str, install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: return ([], set()) def thread_link_flags(self, env: 'Environment') -> T.List[str]: return [] def openmp_flags(self) -> T.List[str]: return [] def get_option_link_args(self, options: 'OptionDictType') -> T.List[str]: return [] @classmethod def unix_args_to_native(cls, args: T.List[str]) -> T.List[str]: return args[:] @classmethod def native_args_to_unix(cls, args: T.List[str]) -> T.List[str]: return args[:] def get_link_debugfile_name(self, targetfile: str) -> str: return None def get_link_debugfile_args(self, targetfile: str) -> T.List[str]: # Static libraries do not have PDB files return [] def get_always_args(self) -> T.List[str]: return [] def get_linker_always_args(self) -> T.List[str]: return [] class VisualStudioLikeLinker: always_args = ['/NOLOGO'] def __init__(self, machine: str): self.machine = machine def get_always_args(self) -> T.List[str]: return self.always_args.copy() def get_linker_always_args(self) -> T.List[str]: return self.always_args.copy() def get_output_args(self, target: str) -> T.List[str]: args = [] # type: T.List[str] if self.machine: args += ['/MACHINE:' + self.machine] args += ['/OUT:' + target] return args @classmethod def unix_args_to_native(cls, args: T.List[str]) -> T.List[str]: from .compilers import VisualStudioCCompiler return VisualStudioCCompiler.unix_args_to_native(args) @classmethod def native_args_to_unix(cls, args: T.List[str]) -> T.List[str]: from .compilers import VisualStudioCCompiler return VisualStudioCCompiler.native_args_to_unix(args) class VisualStudioLinker(VisualStudioLikeLinker, StaticLinker): """Microsoft's lib static linker.""" def __init__(self, exelist: T.List[str], machine: str): StaticLinker.__init__(self, exelist) VisualStudioLikeLinker.__init__(self, machine) class IntelVisualStudioLinker(VisualStudioLikeLinker, StaticLinker): """Intel's xilib static linker.""" def __init__(self, exelist: T.List[str], machine: str): StaticLinker.__init__(self, exelist) VisualStudioLikeLinker.__init__(self, machine) class ArLinker(StaticLinker): def __init__(self, exelist: T.List[str]): super().__init__(exelist) self.id = 'ar' pc, stdo = mesonlib.Popen_safe(self.exelist + ['-h'])[0:2] # Enable deterministic builds if they are available. if '[D]' in stdo: self.std_args = ['csrD'] else: self.std_args = ['csr'] self.can_rsp = '@<' in stdo def can_linker_accept_rsp(self) -> bool: return self.can_rsp def get_std_link_args(self) -> T.List[str]: return self.std_args def get_output_args(self, target: str) -> T.List[str]: return [target] class ArmarLinker(ArLinker): # lgtm [py/missing-call-to-init] def __init__(self, exelist: T.List[str]): StaticLinker.__init__(self, exelist) self.id = 'armar' self.std_args = ['-csr'] def can_linker_accept_rsp(self) -> bool: # armar can't accept arguments using the @rsp syntax return False class DLinker(StaticLinker): def __init__(self, exelist: T.List[str], arch: str): super().__init__(exelist) self.id = exelist[0] self.arch = arch def get_std_link_args(self) -> T.List[str]: return ['-lib'] def get_output_args(self, target: str) -> T.List[str]: return ['-of=' + target] def get_linker_always_args(self) -> T.List[str]: if mesonlib.is_windows(): if self.arch == 'x86_64': return ['-m64'] elif self.arch == 'x86_mscoff' and self.id == 'dmd': return ['-m32mscoff'] return ['-m32'] return [] class CcrxLinker(StaticLinker): def __init__(self, exelist: T.List[str]): super().__init__(exelist) self.id = 'rlink' def can_linker_accept_rsp(self) -> bool: return False def get_output_args(self, target: str) -> T.List[str]: return ['-output={}'.format(target)] def get_linker_always_args(self) -> T.List[str]: return ['-nologo', '-form=library'] class Xc16Linker(StaticLinker): def __init__(self, exelist: T.List[str]): super().__init__(exelist) self.id = 'xc16-ar' def can_linker_accept_rsp(self) -> bool: return False def get_output_args(self, target: str) -> T.List[str]: return ['{}'.format(target)] def get_linker_always_args(self) -> T.List[str]: return ['rcs'] class CompCertLinker(StaticLinker): def __init__(self, exelist: T.List[str]): super().__init__(exelist) self.id = 'ccomp' def can_linker_accept_rsp(self) -> bool: return False def get_output_args(self, target: str) -> T.List[str]: return ['-o{}'.format(target)] class C2000Linker(StaticLinker): def __init__(self, exelist: T.List[str]): super().__init__(exelist) self.id = 'ar2000' def can_linker_accept_rsp(self) -> bool: return False def get_output_args(self, target: str) -> T.List[str]: return ['{}'.format(target)] def get_linker_always_args(self) -> T.List[str]: return ['-r'] class AIXArLinker(ArLinker): def __init__(self, exelist: T.List[str]): StaticLinker.__init__(self, exelist) self.id = 'aixar' self.std_args = ['-csr', '-Xany'] def can_linker_accept_rsp(self) -> bool: # AIXAr can't accept arguments using the @rsp syntax return False def prepare_rpaths(raw_rpaths: str, build_dir: str, from_dir: str) -> T.List[str]: # The rpaths we write must be relative if they point to the build dir, # because otherwise they have different length depending on the build # directory. This breaks reproducible builds. internal_format_rpaths = [evaluate_rpath(p, build_dir, from_dir) for p in raw_rpaths] ordered_rpaths = order_rpaths(internal_format_rpaths) return ordered_rpaths def order_rpaths(rpath_list: T.List[str]) -> T.List[str]: # We want rpaths that point inside our build dir to always override # those pointing to other places in the file system. This is so built # binaries prefer our libraries to the ones that may lie somewhere # in the file system, such as /lib/x86_64-linux-gnu. # # The correct thing to do here would be C++'s std::stable_partition. # Python standard library does not have it, so replicate it with # sort, which is guaranteed to be stable. return sorted(rpath_list, key=os.path.isabs) def evaluate_rpath(p: str, build_dir: str, from_dir: str) -> str: if p == from_dir: return '' # relpath errors out in this case elif os.path.isabs(p): return p # These can be outside of build dir. else: return os.path.relpath(os.path.join(build_dir, p), os.path.join(build_dir, from_dir)) class LinkerEnvVarsMixin(metaclass=abc.ABCMeta): """Mixin reading LDFLAGS from the environment.""" @staticmethod def get_args_from_envvars(for_machine: mesonlib.MachineChoice, is_cross: bool) -> T.List[str]: raw_value = get_env_var(for_machine, is_cross, 'LDFLAGS') if raw_value is not None: return mesonlib.split_args(raw_value) else: return [] class DynamicLinker(LinkerEnvVarsMixin, metaclass=abc.ABCMeta): """Base class for dynamic linkers.""" _BUILDTYPE_ARGS = { 'plain': [], 'debug': [], 'debugoptimized': [], 'release': [], 'minsize': [], 'custom': [], } # type: T.Dict[str, T.List[str]] @abc.abstractproperty def id(self) -> str: pass def _apply_prefix(self, arg: T.Union[str, T.List[str]]) -> T.List[str]: args = [arg] if isinstance(arg, str) else arg if self.prefix_arg is None: return args elif isinstance(self.prefix_arg, str): return [self.prefix_arg + arg for arg in args] ret = [] for arg in args: ret += self.prefix_arg + [arg] return ret def __init__(self, exelist: T.List[str], for_machine: mesonlib.MachineChoice, prefix_arg: T.Union[str, T.List[str]], always_args: T.List[str], *, version: str = 'unknown version'): self.exelist = exelist self.for_machine = for_machine self.version = version self.prefix_arg = prefix_arg self.always_args = always_args self.machine = None # type: T.Optional[str] def __repr__(self) -> str: return '<{}: v{} `{}`>'.format(type(self).__name__, self.version, ' '.join(self.exelist)) def get_id(self) -> str: return self.id def get_version_string(self) -> str: return '({} {})'.format(self.id, self.version) def get_exelist(self) -> T.List[str]: return self.exelist.copy() def get_accepts_rsp(self) -> bool: # rsp files are only used when building on Windows because we want to # avoid issues with quoting and max argument length return mesonlib.is_windows() def get_always_args(self) -> T.List[str]: return self.always_args.copy() def get_lib_prefix(self) -> str: return '' # XXX: is use_ldflags a compiler or a linker attribute? def get_option_args(self, options: 'OptionDictType') -> T.List[str]: return [] def has_multi_arguments(self, args: T.List[str], env: 'Environment') -> T.Tuple[bool, bool]: m = 'Language {} does not support has_multi_link_arguments.' raise mesonlib.EnvironmentException(m.format(self.id)) def get_debugfile_name(self, targetfile: str) -> str: '''Name of debug file written out (see below)''' return None def get_debugfile_args(self, targetfile: str) -> T.List[str]: """Some compilers (MSVC) write debug into a separate file. This method takes the target object path and returns a list of commands to append to the linker invocation to control where that file is written. """ return [] def get_std_shared_lib_args(self) -> T.List[str]: return [] def get_std_shared_module_args(self, options: 'OptionDictType') -> T.List[str]: return self.get_std_shared_lib_args() def get_pie_args(self) -> T.List[str]: # TODO: this really needs to take a boolean and return the args to # disable pie, otherwise it only acts to enable pie if pie *isn't* the # default. m = 'Linker {} does not support position-independent executable' raise mesonlib.EnvironmentException(m.format(self.id)) def get_lto_args(self) -> T.List[str]: return [] def sanitizer_args(self, value: str) -> T.List[str]: return [] def get_buildtype_args(self, buildtype: str) -> T.List[str]: # We can override these in children by just overriding the # _BUILDTYPE_ARGS value. return self._BUILDTYPE_ARGS[buildtype] def get_asneeded_args(self) -> T.List[str]: return [] def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: raise mesonlib.EnvironmentException( 'Linker {} does not support link_whole'.format(self.id)) def get_allow_undefined_args(self) -> T.List[str]: raise mesonlib.EnvironmentException( 'Linker {} does not support allow undefined'.format(self.id)) @abc.abstractmethod def get_output_args(self, outname: str) -> T.List[str]: pass def get_coverage_args(self) -> T.List[str]: m = "Linker {} doesn't implement coverage data generation.".format(self.id) raise mesonlib.EnvironmentException(m) @abc.abstractmethod def get_search_args(self, dirname: str) -> T.List[str]: pass def export_dynamic_args(self, env: 'Environment') -> T.List[str]: return [] def import_library_args(self, implibname: str) -> T.List[str]: """The name of the outputted import library. This implementation is used only on Windows by compilers that use GNU ld """ return [] def thread_flags(self, env: 'Environment') -> T.List[str]: return [] def no_undefined_args(self) -> T.List[str]: """Arguments to error if there are any undefined symbols at link time. This is the inverse of get_allow_undefined_args(). TODO: A future cleanup might merge this and get_allow_undefined_args() into a single method taking a boolean """ return [] def fatal_warnings(self) -> T.List[str]: """Arguments to make all warnings errors.""" return [] def headerpad_args(self) -> T.List[str]: # Only used by the Apple linker return [] def get_gui_app_args(self, value: bool) -> T.List[str]: # Only used by VisualStudioLikeLinkers return [] def get_win_subsystem_args(self, value: str) -> T.List[str]: # Only used if supported by the dynamic linker and # only when targeting Windows return [] def bitcode_args(self) -> T.List[str]: raise mesonlib.MesonException('This linker does not support bitcode bundles') def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, rpath_paths: str, build_rpath: str, install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: return ([], set()) def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, suffix: str, soversion: str, darwin_versions: T.Tuple[str, str], is_shared_module: bool) -> T.List[str]: return [] class PosixDynamicLinkerMixin: """Mixin class for POSIX-ish linkers. This is obviously a pretty small subset of the linker interface, but enough dynamic linkers that meson supports are POSIX-like but not GNU-like that it makes sense to split this out. """ def get_output_args(self, outname: str) -> T.List[str]: return ['-o', outname] def get_std_shared_lib_args(self) -> T.List[str]: return ['-shared'] def get_search_args(self, dirname: str) -> T.List[str]: return ['-L' + dirname] class GnuLikeDynamicLinkerMixin: """Mixin class for dynamic linkers that provides gnu-like interface. This acts as a base for the GNU linkers (bfd and gold), LLVM's lld, and other linkers like GNU-ld. """ if T.TYPE_CHECKING: for_machine = MachineChoice.HOST def _apply_prefix(self, arg: T.Union[str, T.List[str]]) -> T.List[str]: ... _BUILDTYPE_ARGS = { 'plain': [], 'debug': [], 'debugoptimized': [], 'release': ['-O1'], 'minsize': [], 'custom': [], } # type: T.Dict[str, T.List[str]] def get_buildtype_args(self, buildtype: str) -> T.List[str]: # We can override these in children by just overriding the # _BUILDTYPE_ARGS value. return mesonlib.listify([self._apply_prefix(a) for a in self._BUILDTYPE_ARGS[buildtype]]) def get_pie_args(self) -> T.List[str]: return ['-pie'] def get_asneeded_args(self) -> T.List[str]: return self._apply_prefix('--as-needed') def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: if not args: return args return self._apply_prefix('--whole-archive') + args + self._apply_prefix('--no-whole-archive') def get_allow_undefined_args(self) -> T.List[str]: return self._apply_prefix('--allow-shlib-undefined') def get_lto_args(self) -> T.List[str]: return ['-flto'] def sanitizer_args(self, value: str) -> T.List[str]: if value == 'none': return [] return ['-fsanitize=' + value] def get_coverage_args(self) -> T.List[str]: return ['--coverage'] def export_dynamic_args(self, env: 'Environment') -> T.List[str]: m = env.machines[self.for_machine] if m.is_windows() or m.is_cygwin(): return self._apply_prefix('--export-all-symbols') return self._apply_prefix('-export-dynamic') def import_library_args(self, implibname: str) -> T.List[str]: return self._apply_prefix('--out-implib=' + implibname) def thread_flags(self, env: 'Environment') -> T.List[str]: if env.machines[self.for_machine].is_haiku(): return [] return ['-pthread'] def no_undefined_args(self) -> T.List[str]: return self._apply_prefix('--no-undefined') def fatal_warnings(self) -> T.List[str]: return self._apply_prefix('--fatal-warnings') def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, suffix: str, soversion: str, darwin_versions: T.Tuple[str, str], is_shared_module: bool) -> T.List[str]: m = env.machines[self.for_machine] if m.is_windows() or m.is_cygwin(): # For PE/COFF the soname argument has no effect return [] sostr = '' if soversion is None else '.' + soversion return self._apply_prefix('-soname,{}{}.{}{}'.format(prefix, shlib_name, suffix, sostr)) def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, rpath_paths: str, build_rpath: str, install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: m = env.machines[self.for_machine] if m.is_windows() or m.is_cygwin(): return ([], set()) if not rpath_paths and not install_rpath and not build_rpath: return ([], set()) args = [] origin_placeholder = '$ORIGIN' processed_rpaths = prepare_rpaths(rpath_paths, build_dir, from_dir) # Need to deduplicate rpaths, as macOS's install_name_tool # is *very* allergic to duplicate -delete_rpath arguments # when calling depfixer on installation. all_paths = mesonlib.OrderedSet([os.path.join(origin_placeholder, p) for p in processed_rpaths]) rpath_dirs_to_remove = set() for p in all_paths: rpath_dirs_to_remove.add(p.encode('utf8')) # Build_rpath is used as-is (it is usually absolute). if build_rpath != '': all_paths.add(build_rpath) for p in build_rpath.split(':'): rpath_dirs_to_remove.add(p.encode('utf8')) # TODO: should this actually be "for (dragonfly|open)bsd"? if mesonlib.is_dragonflybsd() or mesonlib.is_openbsd(): # This argument instructs the compiler to record the value of # ORIGIN in the .dynamic section of the elf. On Linux this is done # by default, but is not on dragonfly/openbsd for some reason. Without this # $ORIGIN in the runtime path will be undefined and any binaries # linked against local libraries will fail to resolve them. args.extend(self._apply_prefix('-z,origin')) # In order to avoid relinking for RPATH removal, the binary needs to contain just # enough space in the ELF header to hold the final installation RPATH. paths = ':'.join(all_paths) if len(paths) < len(install_rpath): padding = 'X' * (len(install_rpath) - len(paths)) if not paths: paths = padding else: paths = paths + ':' + padding args.extend(self._apply_prefix('-rpath,' + paths)) # TODO: should this actually be "for solaris/sunos"? if mesonlib.is_sunos(): return (args, rpath_dirs_to_remove) # Rpaths to use while linking must be absolute. These are not # written to the binary. Needed only with GNU ld: # https://sourceware.org/bugzilla/show_bug.cgi?id=16936 # Not needed on Windows or other platforms that don't use RPATH # https://github.com/mesonbuild/meson/issues/1897 # # In addition, this linker option tends to be quite long and some # compilers have trouble dealing with it. That's why we will include # one option per folder, like this: # # -Wl,-rpath-link,/path/to/folder1 -Wl,-rpath,/path/to/folder2 ... # # ...instead of just one single looooong option, like this: # # -Wl,-rpath-link,/path/to/folder1:/path/to/folder2:... for p in rpath_paths: args.extend(self._apply_prefix('-rpath-link,' + os.path.join(build_dir, p))) return (args, rpath_dirs_to_remove) def get_win_subsystem_args(self, value: str) -> T.List[str]: if 'windows' in value: args = ['--subsystem,windows'] elif 'console' in value: args = ['--subsystem,console'] else: raise mesonlib.MesonException(f'Only "windows" and "console" are supported for win_subsystem with MinGW, not "{value}".') if ',' in value: args[-1] = args[-1] + ':' + value.split(',')[1] return self._apply_prefix(args) class AppleDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): """Apple's ld implementation.""" id = 'ld64' def get_asneeded_args(self) -> T.List[str]: return self._apply_prefix('-dead_strip_dylibs') def get_allow_undefined_args(self) -> T.List[str]: return self._apply_prefix('-undefined,dynamic_lookup') def get_std_shared_module_args(self, options: 'OptionDictType') -> T.List[str]: return ['-bundle'] + self._apply_prefix('-undefined,dynamic_lookup') def get_pie_args(self) -> T.List[str]: return [] def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: result = [] # type: T.List[str] for a in args: result.extend(self._apply_prefix('-force_load')) result.append(a) return result def get_coverage_args(self) -> T.List[str]: return ['--coverage'] def sanitizer_args(self, value: str) -> T.List[str]: if value == 'none': return [] return ['-fsanitize=' + value] def no_undefined_args(self) -> T.List[str]: return self._apply_prefix('-undefined,error') def headerpad_args(self) -> T.List[str]: return self._apply_prefix('-headerpad_max_install_names') def bitcode_args(self) -> T.List[str]: return self._apply_prefix('-bitcode_bundle') def fatal_warnings(self) -> T.List[str]: return self._apply_prefix('-fatal_warnings') def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, suffix: str, soversion: str, darwin_versions: T.Tuple[str, str], is_shared_module: bool) -> T.List[str]: if is_shared_module: return [] install_name = ['@rpath/', prefix, shlib_name] if soversion is not None: install_name.append('.' + soversion) install_name.append('.dylib') args = ['-install_name', ''.join(install_name)] if darwin_versions: args.extend(['-compatibility_version', darwin_versions[0], '-current_version', darwin_versions[1]]) return args def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, rpath_paths: str, build_rpath: str, install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: if not rpath_paths and not install_rpath and not build_rpath: return ([], set()) args = [] # @loader_path is the equivalent of $ORIGIN on macOS # https://stackoverflow.com/q/26280738 origin_placeholder = '@loader_path' processed_rpaths = prepare_rpaths(rpath_paths, build_dir, from_dir) all_paths = mesonlib.OrderedSet([os.path.join(origin_placeholder, p) for p in processed_rpaths]) if build_rpath != '': all_paths.add(build_rpath) for rp in all_paths: args.extend(self._apply_prefix('-rpath,' + rp)) return (args, set()) class GnuDynamicLinker(GnuLikeDynamicLinkerMixin, PosixDynamicLinkerMixin, DynamicLinker): """Representation of GNU ld.bfd and ld.gold.""" def get_accepts_rsp(self) -> bool: return True class GnuGoldDynamicLinker(GnuDynamicLinker): id = 'ld.gold' class GnuBFDDynamicLinker(GnuDynamicLinker): id = 'ld.bfd' class LLVMDynamicLinker(GnuLikeDynamicLinkerMixin, PosixDynamicLinkerMixin, DynamicLinker): """Representation of LLVM's ld.lld linker. This is only the gnu-like linker, not the apple like or link.exe like linkers. """ id = 'ld.lld' def __init__(self, exelist: T.List[str], for_machine: mesonlib.MachineChoice, prefix_arg: T.Union[str, T.List[str]], always_args: T.List[str], *, version: str = 'unknown version'): super().__init__(exelist, for_machine, prefix_arg, always_args, version=version) # Some targets don't seem to support this argument (windows, wasm, ...) _, _, e = mesonlib.Popen_safe(self.exelist + self._apply_prefix('--allow-shlib-undefined')) self.has_allow_shlib_undefined = not ('unknown argument: --allow-shlib-undefined' in e) def get_allow_undefined_args(self) -> T.List[str]: if self.has_allow_shlib_undefined: return self._apply_prefix('--allow-shlib-undefined') return [] class WASMDynamicLinker(GnuLikeDynamicLinkerMixin, PosixDynamicLinkerMixin, DynamicLinker): """Emscripten's wasm-ld.""" id = 'ld.wasm' def get_allow_undefined_args(self) -> T.List[str]: return ['-s', 'ERROR_ON_UNDEFINED_SYMBOLS=0'] def no_undefined_args(self) -> T.List[str]: return ['-s', 'ERROR_ON_UNDEFINED_SYMBOLS=1'] def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, suffix: str, soversion: str, darwin_versions: T.Tuple[str, str], is_shared_module: bool) -> T.List[str]: raise mesonlib.MesonException('{} does not support shared libraries.'.format(self.id)) def get_asneeded_args(self) -> T.List[str]: return [] def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, rpath_paths: str, build_rpath: str, install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: return ([], set()) class CcrxDynamicLinker(DynamicLinker): """Linker for Renesis CCrx compiler.""" id = 'rlink' def __init__(self, for_machine: mesonlib.MachineChoice, *, version: str = 'unknown version'): super().__init__(['rlink.exe'], for_machine, '', [], version=version) def get_accepts_rsp(self) -> bool: return False def get_lib_prefix(self) -> str: return '-lib=' def get_std_shared_lib_args(self) -> T.List[str]: return [] def get_output_args(self, outputname: str) -> T.List[str]: return ['-output={}'.format(outputname)] def get_search_args(self, dirname: str) -> 'T.NoReturn': raise EnvironmentError('rlink.exe does not have a search dir argument') def get_allow_undefined_args(self) -> T.List[str]: return [] def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, suffix: str, soversion: str, darwin_versions: T.Tuple[str, str], is_shared_module: bool) -> T.List[str]: return [] class Xc16DynamicLinker(DynamicLinker): """Linker for Microchip XC16 compiler.""" id = 'xc16-gcc' def __init__(self, for_machine: mesonlib.MachineChoice, *, version: str = 'unknown version'): super().__init__(['xc16-gcc.exe'], for_machine, '', [], version=version) def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: if not args: return args return self._apply_prefix('--start-group') + args + self._apply_prefix('--end-group') def get_accepts_rsp(self) -> bool: return False def get_lib_prefix(self) -> str: return '' def get_std_shared_lib_args(self) -> T.List[str]: return [] def get_output_args(self, outputname: str) -> T.List[str]: return ['-o{}'.format(outputname)] def get_search_args(self, dirname: str) -> 'T.NoReturn': raise EnvironmentError('xc16-gcc.exe does not have a search dir argument') def get_allow_undefined_args(self) -> T.List[str]: return [] def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, suffix: str, soversion: str, darwin_versions: T.Tuple[str, str], is_shared_module: bool) -> T.List[str]: return [] def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, rpath_paths: str, build_rpath: str, install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: return ([], set()) class CompCertDynamicLinker(DynamicLinker): """Linker for CompCert C compiler.""" id = 'ccomp' def __init__(self, for_machine: mesonlib.MachineChoice, *, version: str = 'unknown version'): super().__init__(['ccomp'], for_machine, '', [], version=version) def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: if not args: return args return self._apply_prefix('-Wl,--whole-archive') + args + self._apply_prefix('-Wl,--no-whole-archive') def get_accepts_rsp(self) -> bool: return False def get_lib_prefix(self) -> str: return '' def get_std_shared_lib_args(self) -> T.List[str]: return [] def get_output_args(self, outputname: str) -> T.List[str]: return ['-o{}'.format(outputname)] def get_search_args(self, dirname: str) -> T.List[str]: return ['-L{}'.format(dirname)] def get_allow_undefined_args(self) -> T.List[str]: return [] def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, suffix: str, soversion: str, darwin_versions: T.Tuple[str, str], is_shared_module: bool) -> T.List[str]: raise mesonlib.MesonException('{} does not support shared libraries.'.format(self.id)) def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, rpath_paths: str, build_rpath: str, install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: return ([], set()) class C2000DynamicLinker(DynamicLinker): """Linker for Texas Instruments C2000 compiler.""" id = 'cl2000' def __init__(self, for_machine: mesonlib.MachineChoice, *, version: str = 'unknown version'): super().__init__(['cl2000.exe'], for_machine, '', [], version=version) def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: if not args: return args return self._apply_prefix('--start-group') + args + self._apply_prefix('--end-group') def get_accepts_rsp(self) -> bool: return False def get_lib_prefix(self) -> str: return '-l=' def get_std_shared_lib_args(self) -> T.List[str]: return [] def get_output_args(self, outputname: str) -> T.List[str]: return ['-z', '--output_file={}'.format(outputname)] def get_search_args(self, dirname: str) -> 'T.NoReturn': raise EnvironmentError('cl2000.exe does not have a search dir argument') def get_allow_undefined_args(self) -> T.List[str]: return [] def get_always_args(self) -> T.List[str]: return [] class ArmDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): """Linker for the ARM compiler.""" id = 'armlink' def __init__(self, for_machine: mesonlib.MachineChoice, *, version: str = 'unknown version'): super().__init__(['armlink'], for_machine, '', [], version=version) def get_accepts_rsp(self) -> bool: return False def get_std_shared_lib_args(self) -> 'T.NoReturn': raise mesonlib.MesonException('The Arm Linkers do not support shared libraries') def get_allow_undefined_args(self) -> T.List[str]: return [] class ArmClangDynamicLinker(ArmDynamicLinker): """Linker used with ARM's clang fork. The interface is similar enough to the old ARM ld that it inherits and extends a few things as needed. """ def export_dynamic_args(self, env: 'Environment') -> T.List[str]: return ['--export_dynamic'] def import_library_args(self, implibname: str) -> T.List[str]: return ['--symdefs=' + implibname] class QualcommLLVMDynamicLinker(LLVMDynamicLinker): """ARM Linker from Snapdragon LLVM ARM Compiler.""" id = 'ld.qcld' class PGIDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): """PGI linker.""" id = 'pgi' def get_allow_undefined_args(self) -> T.List[str]: return [] def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, suffix: str, soversion: str, darwin_versions: T.Tuple[str, str], is_shared_module: bool) -> T.List[str]: return [] def get_std_shared_lib_args(self) -> T.List[str]: # PGI -shared is Linux only. if mesonlib.is_windows(): return ['-Bdynamic', '-Mmakedll'] elif mesonlib.is_linux(): return ['-shared'] return [] def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, rpath_paths: str, build_rpath: str, install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: if not env.machines[self.for_machine].is_windows(): return (['-R' + os.path.join(build_dir, p) for p in rpath_paths], set()) return ([], set()) NvidiaHPC_DynamicLinker = PGIDynamicLinker class PGIStaticLinker(StaticLinker): def __init__(self, exelist: T.List[str]): super().__init__(exelist) self.id = 'ar' self.std_args = ['-r'] def get_std_link_args(self) -> T.List[str]: return self.std_args def get_output_args(self, target: str) -> T.List[str]: return [target] NvidiaHPC_StaticLinker = PGIStaticLinker class VisualStudioLikeLinkerMixin: """Mixin class for for dynamic linkers that act like Microsoft's link.exe.""" if T.TYPE_CHECKING: for_machine = MachineChoice.HOST def _apply_prefix(self, arg: T.Union[str, T.List[str]]) -> T.List[str]: ... _BUILDTYPE_ARGS = { 'plain': [], 'debug': [], 'debugoptimized': [], # The otherwise implicit REF and ICF linker optimisations are disabled by # /DEBUG. REF implies ICF. 'release': ['/OPT:REF'], 'minsize': ['/INCREMENTAL:NO', '/OPT:REF'], 'custom': [], } # type: T.Dict[str, T.List[str]] def __init__(self, exelist: T.List[str], for_machine: mesonlib.MachineChoice, prefix_arg: T.Union[str, T.List[str]], always_args: T.List[str], *, version: str = 'unknown version', direct: bool = True, machine: str = 'x86'): # There's no way I can find to make mypy understand what's going on here super().__init__(exelist, for_machine, prefix_arg, always_args, version=version) # type: ignore self.machine = machine self.direct = direct def get_buildtype_args(self, buildtype: str) -> T.List[str]: return mesonlib.listify([self._apply_prefix(a) for a in self._BUILDTYPE_ARGS[buildtype]]) def invoked_by_compiler(self) -> bool: return not self.direct def get_output_args(self, outputname: str) -> T.List[str]: return self._apply_prefix(['/MACHINE:' + self.machine, '/OUT:' + outputname]) def get_always_args(self) -> T.List[str]: parent = super().get_always_args() # type: ignore return self._apply_prefix('/nologo') + T.cast(T.List[str], parent) def get_search_args(self, dirname: str) -> T.List[str]: return self._apply_prefix('/LIBPATH:' + dirname) def get_std_shared_lib_args(self) -> T.List[str]: return self._apply_prefix('/DLL') def get_debugfile_name(self, targetfile: str) -> str: basename = targetfile.rsplit('.', maxsplit=1)[0] return basename + '.pdb' def get_debugfile_args(self, targetfile: str) -> T.List[str]: return self._apply_prefix(['/DEBUG', '/PDB:' + self.get_debugfile_name(targetfile)]) def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: # Only since VS2015 args = mesonlib.listify(args) l = [] # T.List[str] for a in args: l.extend(self._apply_prefix('/WHOLEARCHIVE:' + a)) return l def get_allow_undefined_args(self) -> T.List[str]: return [] def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, suffix: str, soversion: str, darwin_versions: T.Tuple[str, str], is_shared_module: bool) -> T.List[str]: return [] def import_library_args(self, implibname: str) -> T.List[str]: """The command to generate the import library.""" return self._apply_prefix(['/IMPLIB:' + implibname]) class MSVCDynamicLinker(VisualStudioLikeLinkerMixin, DynamicLinker): """Microsoft's Link.exe.""" id = 'link' def __init__(self, for_machine: mesonlib.MachineChoice, always_args: T.List[str], *, exelist: T.Optional[T.List[str]] = None, prefix: T.Union[str, T.List[str]] = '', machine: str = 'x86', version: str = 'unknown version', direct: bool = True): super().__init__(exelist or ['link.exe'], for_machine, prefix, always_args, machine=machine, version=version, direct=direct) def get_always_args(self) -> T.List[str]: return self._apply_prefix(['/nologo', '/release']) + super().get_always_args() def get_gui_app_args(self, value: bool) -> T.List[str]: return self.get_win_subsystem_args("windows" if value else "console") def get_win_subsystem_args(self, value: str) -> T.List[str]: return self._apply_prefix([f'/SUBSYSTEM:{value.upper()}']) class ClangClDynamicLinker(VisualStudioLikeLinkerMixin, DynamicLinker): """Clang's lld-link.exe.""" id = 'lld-link' def __init__(self, for_machine: mesonlib.MachineChoice, always_args: T.List[str], *, exelist: T.Optional[T.List[str]] = None, prefix: T.Union[str, T.List[str]] = '', machine: str = 'x86', version: str = 'unknown version', direct: bool = True): super().__init__(exelist or ['lld-link.exe'], for_machine, prefix, always_args, machine=machine, version=version, direct=direct) def get_output_args(self, outputname: str) -> T.List[str]: # If we're being driven indirectly by clang just skip /MACHINE # as clang's target triple will handle the machine selection if self.machine is None: return self._apply_prefix([f"/OUT:{outputname}"]) return super().get_output_args(outputname) def get_gui_app_args(self, value: bool) -> T.List[str]: return self.get_win_subsystem_args("windows" if value else "console") def get_win_subsystem_args(self, value: str) -> T.List[str]: return self._apply_prefix([f'/SUBSYSTEM:{value.upper()}']) class XilinkDynamicLinker(VisualStudioLikeLinkerMixin, DynamicLinker): """Intel's Xilink.exe.""" id = 'xilink' def __init__(self, for_machine: mesonlib.MachineChoice, always_args: T.List[str], *, exelist: T.Optional[T.List[str]] = None, prefix: T.Union[str, T.List[str]] = '', machine: str = 'x86', version: str = 'unknown version', direct: bool = True): super().__init__(['xilink.exe'], for_machine, '', always_args, version=version) class SolarisDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): """Sys-V derived linker used on Solaris and OpenSolaris.""" id = 'ld.solaris' def get_link_whole_for(self, args: T.List[str]) -> T.List[str]: if not args: return args return self._apply_prefix('--whole-archive') + args + self._apply_prefix('--no-whole-archive') def get_pie_args(self) -> T.List[str]: # Available in Solaris 11.2 and later pc, stdo, stde = mesonlib.Popen_safe(self.exelist + self._apply_prefix('-zhelp')) for line in (stdo + stde).split('\n'): if '-z type' in line: if 'pie' in line: return ['-z', 'type=pie'] break return [] def get_asneeded_args(self) -> T.List[str]: return self._apply_prefix(['-z', 'ignore']) def no_undefined_args(self) -> T.List[str]: return ['-z', 'defs'] def get_allow_undefined_args(self) -> T.List[str]: return ['-z', 'nodefs'] def fatal_warnings(self) -> T.List[str]: return ['-z', 'fatal-warnings'] def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, rpath_paths: str, build_rpath: str, install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: if not rpath_paths and not install_rpath and not build_rpath: return ([], set()) processed_rpaths = prepare_rpaths(rpath_paths, build_dir, from_dir) all_paths = mesonlib.OrderedSet([os.path.join('$ORIGIN', p) for p in processed_rpaths]) if build_rpath != '': all_paths.add(build_rpath) # In order to avoid relinking for RPATH removal, the binary needs to contain just # enough space in the ELF header to hold the final installation RPATH. paths = ':'.join(all_paths) if len(paths) < len(install_rpath): padding = 'X' * (len(install_rpath) - len(paths)) if not paths: paths = padding else: paths = paths + ':' + padding return (self._apply_prefix('-rpath,{}'.format(paths)), set()) def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, suffix: str, soversion: str, darwin_versions: T.Tuple[str, str], is_shared_module: bool) -> T.List[str]: sostr = '' if soversion is None else '.' + soversion return self._apply_prefix('-soname,{}{}.{}{}'.format(prefix, shlib_name, suffix, sostr)) class AIXDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): """Sys-V derived linker used on AIX""" id = 'ld.aix' def get_always_args(self) -> T.List[str]: return self._apply_prefix(['-bsvr4', '-bnoipath', '-bbigtoc']) + super().get_always_args() def no_undefined_args(self) -> T.List[str]: return self._apply_prefix(['-z', 'defs']) def get_allow_undefined_args(self) -> T.List[str]: return self._apply_prefix(['-z', 'nodefs']) def build_rpath_args(self, env: 'Environment', build_dir: str, from_dir: str, rpath_paths: str, build_rpath: str, install_rpath: str) -> T.Tuple[T.List[str], T.Set[bytes]]: all_paths = mesonlib.OrderedSet(['/opt/freeware/lib']) # for libgcc_s.a for p in rpath_paths: all_paths.add(os.path.join(build_dir, p)) if build_rpath != '': all_paths.add(build_rpath) if install_rpath != '': all_paths.add(install_rpath) return (self._apply_prefix([x for p in all_paths for x in ('-R', p)]), set()) def thread_flags(self, env: 'Environment') -> T.List[str]: return ['-pthread'] class OptlinkDynamicLinker(VisualStudioLikeLinkerMixin, DynamicLinker): """Digital Mars dynamic linker for windows.""" id = 'optlink' def __init__(self, exelist: T.List[str], for_machine: mesonlib.MachineChoice, *, version: str = 'unknown version'): # Use optlink instead of link so we don't interfer with other link.exe # implementations. super().__init__(exelist, for_machine, '', [], version=version) def get_allow_undefined_args(self) -> T.List[str]: return [] def get_debugfile_args(self, targetfile: str) -> T.List[str]: # Optlink does not generate pdb files. return [] def get_always_args(self) -> T.List[str]: return [] class CudaLinker(PosixDynamicLinkerMixin, DynamicLinker): """Cuda linker (nvlink)""" id = 'nvlink' @staticmethod def parse_version() -> str: version_cmd = ['nvlink', '--version'] try: _, out, _ = mesonlib.Popen_safe(version_cmd) except OSError: return 'unknown version' # Output example: # nvlink: NVIDIA (R) Cuda linker # Copyright (c) 2005-2018 NVIDIA Corporation # Built on Sun_Sep_30_21:09:22_CDT_2018 # Cuda compilation tools, release 10.0, V10.0.166 # we need the most verbose version output. Luckily starting with V return out.strip().split('V')[-1] def get_accepts_rsp(self) -> bool: # nvcc does not support response files return False def get_lib_prefix(self) -> str: if not mesonlib.is_windows(): return '' # nvcc doesn't recognize Meson's default .a extension for static libraries on # Windows and passes it to cl as an object file, resulting in 'warning D9024 : # unrecognized source file type 'xxx.a', object file assumed'. # # nvcc's --library= option doesn't help: it takes the library name without the # extension and assumes that the extension on Windows is .lib; prefixing the # library with -Xlinker= seems to work. from .compilers import CudaCompiler return CudaCompiler.LINKER_PREFIX def fatal_warnings(self) -> T.List[str]: return ['--warning-as-error'] def get_allow_undefined_args(self) -> T.List[str]: return [] def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, suffix: str, soversion: str, darwin_versions: T.Tuple[str, str], is_shared_module: bool) -> T.List[str]: return []