diff --git a/mesonbuild/linkers.py b/mesonbuild/linkers.py index c813e3e89..c0a07ec93 100644 --- a/mesonbuild/linkers.py +++ b/mesonbuild/linkers.py @@ -194,6 +194,36 @@ class CcrxLinker(StaticLinker): return ['-nologo', '-form=library'] +def prepare_rpaths(raw_rpaths: str, build_dir: str, from_dir: str) -> typing.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: typing.List[str]) -> typing.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 DynamicLinker(metaclass=abc.ABCMeta): """Base class for dynamic linkers.""" @@ -368,3 +398,149 @@ class PosixDynamicLinkerMixin: def get_search_args(self, dirname: str) -> typing.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), the Intel Xild + (which comes with ICC), LLVM's lld, and other linkers like GNU-ld. + """ + + _BUILDTYPE_ARGS = { + 'plain': [], + 'debug': [], + 'debugoptimized': [], + 'release': ['-Wl,-O1'], + 'minsize': [], + 'custom': [], + } # type: typing.Dict[str, typing.List[str]] + + def get_pie_args(self) -> typing.List[str]: + return ['-pie'] + + def get_asneeded_args(self) -> typing.List[str]: + return ['-Wl,--as-needed'] + + def get_link_whole_for(self, args: typing.List[str]) -> typing.List[str]: + if not args: + return args + return ['-Wl,--whole-archive'] + args + ['-Wl,--no-whole-archive'] + + def get_allow_undefined_args(self) -> typing.List[str]: + return ['-Wl,--allow-shlib-undefined'] + + def get_lto_args(self) -> typing.List[str]: + return ['-flto'] + + def sanitizer_args(self, value: str) -> typing.List[str]: + if value == 'none': + return [] + return ['-fsanitize=' + value] + + def invoked_by_compiler(self) -> bool: + """True if meson uses the compiler to invoke the linker.""" + return True + + def get_coverage_args(self) -> typing.List[str]: + return ['--coverage'] + + def export_dynamic_args(self, env: 'Environment') -> typing.List[str]: + m = env.machines[self.for_machine] + if m.is_windows() or m.is_cygwin(): + return ['-Wl,--export-all-symbols'] + return ['-Wl,-export-dynamic'] + + def import_library_args(self, implibname: str) -> typing.List[str]: + return ['-Wl,--out-implib=' + implibname] + + def thread_flags(self, env: 'Environment') -> typing.List[str]: + if env.machines[self.for_machine].is_haiku(): + return [] + return ['-pthread'] + + def no_undefined_args(self) -> typing.List[str]: + return ['-Wl,--no-undefined'] + + def fatal_warnings(self) -> typing.List[str]: + return ['-Wl,--fatal-warnings'] + + def get_soname_args(self, env: 'Environment', prefix: str, shlib_name: str, + suffix: str, soversion: str, darwin_versions: typing.Tuple[str, str], + is_shared_module: bool) -> typing.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 ['-Wl,-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) -> typing.List[str]: + m = env.machines[self.for_machine] + if m.is_windows() or m.is_cygwin(): + return [] + if not rpath_paths and not install_rpath and not build_rpath: + return [] + 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]) + # Build_rpath is used as-is (it is usually absolute). + if build_rpath != '': + all_paths.add(build_rpath) + + # 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.append('-Wl,-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.append('-Wl,-rpath,' + paths) + + # TODO: should this actually be "for solaris/sunos"? + if mesonlib.is_sunos(): + return args + + # 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:... + args.extend(['-Wl,-rpath-link,' + os.path.join(build_dir, p) for p in rpath_paths]) + + return args + + +class GnuDynamicLinker(GnuLikeDynamicLinkerMixin, PosixDynamicLinkerMixin, DynamicLinker): + + """Representation of GNU ld.bfd and ld.gold.""" + + pass