Use relative build-tree RPATHs on macOS

* This helps with reproducibility on macOS in the same way
  `$ORIGIN` improves reproducibility on Linux-like systems.
* This makes the build-tree more resilient to users injecting
  rpaths via `LDFLAGS`. Currently Meson on macOS crashes when
  a build-tree rpath and a user-provided `-Wl,-rpath` in
  LDFLAGS collide, leading to `install_name_tool` failures.
  While this still does not solve the root cause, it makes
  the occurrence much less likely, as users will generally
  pass absolute `-Wl,-rpath` arguments into Meson.
pull/4368/head
David Seifert 6 years ago committed by Jussi Pakkanen
parent 3a8911a07f
commit a28eddb822
  1. 5
      mesonbuild/compilers/c.py
  2. 56
      mesonbuild/compilers/compilers.py

@ -121,10 +121,7 @@ class CCompiler(Compiler):
# The default behavior is this, override in MSVC # The default behavior is this, override in MSVC
@functools.lru_cache(maxsize=None) @functools.lru_cache(maxsize=None)
def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath): def build_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath):
if getattr(self, 'compiler_type', False) and self.compiler_type.is_osx_compiler: if self.compiler_type.is_windows_compiler:
# Clang, GCC and ICC on macOS all use the same rpath arguments
return self.build_osx_rpath_args(build_dir, rpath_paths, build_rpath)
elif self.compiler_type.is_windows_compiler:
return [] return []
return self.build_unix_rpath_args(build_dir, from_dir, rpath_paths, build_rpath, install_rpath) return self.build_unix_rpath_args(build_dir, from_dir, rpath_paths, build_rpath, install_rpath)

@ -1088,23 +1088,18 @@ class Compiler:
def get_instruction_set_args(self, instruction_set): def get_instruction_set_args(self, instruction_set):
return None return None
def build_osx_rpath_args(self, build_dir, rpath_paths, build_rpath):
# Ensure that there is enough space for large RPATHs and install_name
args = ['-Wl,-headerpad_max_install_names']
if not rpath_paths and not build_rpath:
return args
# On OSX, rpaths must be absolute.
abs_rpaths = [os.path.join(build_dir, p) for p in rpath_paths]
if build_rpath != '':
abs_rpaths.append(build_rpath)
# Need to deduplicate abs_rpaths, as rpath_paths and
# build_rpath are not guaranteed to be disjoint sets
args += ['-Wl,-rpath,' + rp for rp in OrderedSet(abs_rpaths)]
return args
def build_unix_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath): def build_unix_rpath_args(self, build_dir, from_dir, rpath_paths, build_rpath, install_rpath):
if not rpath_paths and not install_rpath and not build_rpath: if not rpath_paths and not install_rpath and not build_rpath:
return [] return []
args = []
if mesonlib.is_osx():
# Ensure that there is enough space for install_name_tool in-place editing of large RPATHs
args.append('-Wl,-headerpad_max_install_names')
# @loader_path is the equivalent of $ORIGIN on macOS
# https://stackoverflow.com/q/26280738
origin_placeholder = '@loader_path'
else:
origin_placeholder = '$ORIGIN'
# The rpaths we write must be relative, because otherwise # The rpaths we write must be relative, because otherwise
# they have different length depending on the build # they have different length depending on the build
# directory. This breaks reproducible builds. # directory. This breaks reproducible builds.
@ -1115,19 +1110,14 @@ class Compiler:
else: else:
relative = os.path.relpath(os.path.join(build_dir, p), os.path.join(build_dir, from_dir)) relative = os.path.relpath(os.path.join(build_dir, p), os.path.join(build_dir, from_dir))
rel_rpaths.append(relative) rel_rpaths.append(relative)
paths = ':'.join([os.path.join('$ORIGIN', p) for p in rel_rpaths]) # 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 = OrderedSet([os.path.join(origin_placeholder, p) for p in rel_rpaths])
# Build_rpath is used as-is (it is usually absolute). # Build_rpath is used as-is (it is usually absolute).
if build_rpath != '': if build_rpath != '':
if paths != '': all_paths.add(build_rpath)
paths += ':'
paths += build_rpath
if len(paths) < len(install_rpath):
padding = 'X' * (len(install_rpath) - len(paths))
if not paths:
paths = padding
else:
paths = paths + ':' + padding
args = []
if mesonlib.is_dragonflybsd() or mesonlib.is_openbsd(): if mesonlib.is_dragonflybsd() or mesonlib.is_openbsd():
# This argument instructs the compiler to record the value of # This argument instructs the compiler to record the value of
# ORIGIN in the .dynamic section of the elf. On Linux this is done # ORIGIN in the .dynamic section of the elf. On Linux this is done
@ -1135,7 +1125,23 @@ class Compiler:
# $ORIGIN in the runtime path will be undefined and any binaries # $ORIGIN in the runtime path will be undefined and any binaries
# linked against local libraries will fail to resolve them. # linked against local libraries will fail to resolve them.
args.append('-Wl,-z,origin') args.append('-Wl,-z,origin')
if mesonlib.is_osx():
# macOS does not support colon-separated strings in LC_RPATH,
# hence we have to pass each path component individually
args += ['-Wl,-rpath,' + rp for rp in all_paths]
else:
# 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) args.append('-Wl,-rpath,' + paths)
if get_compiler_is_linuxlike(self): if get_compiler_is_linuxlike(self):
# Rpaths to use while linking must be absolute. These are not # Rpaths to use while linking must be absolute. These are not
# written to the binary. Needed only with GNU ld: # written to the binary. Needed only with GNU ld:

Loading…
Cancel
Save