Merge pull request #1898 from centricular/fix-rpath-linking

Use absolute RPATHs while linking due to a binutils bug
pull/1917/head
Jussi Pakkanen 8 years ago committed by GitHub
commit f397db6db3
  1. 62
      mesonbuild/compilers.py
  2. 59
      run_unittests.py

@ -317,28 +317,6 @@ def get_base_link_args(options, linker, is_shared_module):
pass
return args
def build_unix_rpath_args(build_dir, from_dir, rpath_paths, install_rpath):
if not rpath_paths and not install_rpath:
return []
# The rpaths we write must be relative, because otherwise
# they have different length depending on the build
# directory. This breaks reproducible builds.
rel_rpaths = []
for p in rpath_paths:
if p == from_dir:
relative = '' # relpath errors out in this case
else:
relative = os.path.relpath(p, from_dir)
rel_rpaths.append(relative)
paths = ':'.join([os.path.join('$ORIGIN', p) for p in rel_rpaths])
if len(paths) < len(install_rpath):
padding = 'X' * (len(install_rpath) - len(paths))
if not paths:
paths = padding
else:
paths = paths + ':' + padding
return ['-Wl,-rpath,' + paths]
class CrossNoRunException(MesonException):
pass
@ -752,6 +730,37 @@ class Compiler:
return []
raise EnvironmentException('Language %s does not support linking whole archives.' % self.language)
def build_unix_rpath_args(self, build_dir, from_dir, rpath_paths, install_rpath):
if not rpath_paths and not install_rpath:
return []
# The rpaths we write must be relative, because otherwise
# they have different length depending on the build
# directory. This breaks reproducible builds.
rel_rpaths = []
for p in rpath_paths:
if p == from_dir:
relative = '' # relpath errors out in this case
else:
relative = os.path.relpath(p, from_dir)
rel_rpaths.append(relative)
paths = ':'.join([os.path.join('$ORIGIN', p) for p in rel_rpaths])
if len(paths) < len(install_rpath):
padding = 'X' * (len(install_rpath) - len(paths))
if not paths:
paths = padding
else:
paths = paths + ':' + padding
args = ['-Wl,-rpath,' + paths]
if get_compiler_is_linuxlike(self):
# 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
lpaths = ':'.join([os.path.join(build_dir, p) for p in rpath_paths])
args += ['-Wl,-rpath-link,' + lpaths]
return args
class CCompiler(Compiler):
def __init__(self, exelist, version, is_cross, exe_wrapper=None):
# If a child ObjC or CPP class has already set it, don't set it ourselves
@ -801,10 +810,9 @@ class CCompiler(Compiler):
def split_shlib_to_parts(self, fname):
return None, fname
# The default behavior is this, override in
# OSX and MSVC.
# The default behavior is this, override in MSVC
def build_rpath_args(self, build_dir, from_dir, rpath_paths, install_rpath):
return build_unix_rpath_args(build_dir, from_dir, rpath_paths, install_rpath)
return self.build_unix_rpath_args(build_dir, from_dir, rpath_paths, install_rpath)
def get_dependency_gen_args(self, outtarget, outfile):
return ['-MMD', '-MQ', outtarget, '-MF', outfile]
@ -2066,7 +2074,7 @@ class GnuDCompiler(DCompiler):
return d_gdc_buildtype_args[buildtype]
def build_rpath_args(self, build_dir, from_dir, rpath_paths, install_rpath):
return build_unix_rpath_args(build_dir, from_dir, rpath_paths, install_rpath)
return self.build_unix_rpath_args(build_dir, from_dir, rpath_paths, install_rpath)
def get_unittest_args(self):
return ['-funittest']
@ -3024,7 +3032,7 @@ end program prog
return []
def build_rpath_args(self, build_dir, from_dir, rpath_paths, install_rpath):
return build_unix_rpath_args(build_dir, from_dir, rpath_paths, install_rpath)
return self.build_unix_rpath_args(build_dir, from_dir, rpath_paths, install_rpath)
def module_name_to_filename(self, module_name):
return module_name.lower() + '.mod'

@ -36,16 +36,26 @@ from run_tests import get_builddir_target_args, get_backend_commands, Backend
from run_tests import ensure_backend_detects_changes
def get_soname(fname):
# HACK, fix to not use shell.
raw_out = subprocess.check_output(['readelf', '-a', fname],
universal_newlines=True)
pattern = re.compile('soname: \[(.*?)\]')
def get_dynamic_section_entry(fname, entry):
try:
raw_out = subprocess.check_output(['readelf', '-d', fname],
universal_newlines=True)
except FileNotFoundError:
# FIXME: Try using depfixer.py:Elf() as a fallback
raise unittest.SkipTest('readelf not found')
pattern = re.compile(entry + r': \[(.*?)\]')
for line in raw_out.split('\n'):
m = pattern.search(line)
if m is not None:
return m.group(1)
raise RuntimeError('Could not determine soname:\n\n' + raw_out)
raise RuntimeError('Could not determine {}:\n\n'.format(entry) + raw_out)
def get_soname(fname):
return get_dynamic_section_entry(fname, 'soname')
def get_rpath(fname):
return get_dynamic_section_entry(fname, r'(?:rpath|runpath)')
class InternalTests(unittest.TestCase):
@ -1130,6 +1140,25 @@ int main(int argc, char **argv) {
self.assertTrue(os.path.exists(distfile))
self.assertTrue(os.path.exists(checksumfile))
def test_rpath_uses_ORIGIN(self):
'''
Test that built targets use $ORIGIN in rpath, which ensures that they
are relocatable and ensures that builds are reproducible since the
build directory won't get embedded into the built binaries.
'''
if is_windows() or is_cygwin():
raise unittest.SkipTest('Windows PE/COFF binaries do not use RPATH')
testdir = os.path.join(self.common_test_dir, '46 library chain')
self.init(testdir)
self.build()
for each in ('prog', 'subdir/liblib1.so', 'subdir/subdir2/liblib2.so',
'subdir/subdir3/liblib3.so'):
rpath = get_rpath(os.path.join(self.builddir, each))
self.assertTrue(rpath)
for path in rpath.split(':'):
self.assertTrue(path.startswith('$ORIGIN'), msg=(each, path))
class WindowsTests(BasePlatformTests):
'''
Tests that should run on Cygwin, MinGW, and MSVC
@ -1319,14 +1348,6 @@ class LinuxlikeTests(BasePlatformTests):
mesonlog = self.get_meson_log()
self.assertTrue(msg in mesonlog or msg2 in mesonlog)
def get_soname(self, fname):
output = subprocess.check_output(['readelf', '-a', fname],
universal_newlines=True)
for line in output.split('\n'):
if 'SONAME' in line:
return line.split('[')[1].split(']')[0]
raise RuntimeError('Readelf gave no SONAME.')
def _test_soname_impl(self, libpath, install):
testdir = os.path.join(self.unit_test_dir, '1 soname')
self.init(testdir)
@ -1338,28 +1359,28 @@ class LinuxlikeTests(BasePlatformTests):
nover = os.path.join(libpath, 'libnover.so')
self.assertTrue(os.path.exists(nover))
self.assertFalse(os.path.islink(nover))
self.assertEqual(self.get_soname(nover), 'libnover.so')
self.assertEqual(get_soname(nover), 'libnover.so')
self.assertEqual(len(glob(nover[:-3] + '*')), 1)
# File with version set
verset = os.path.join(libpath, 'libverset.so')
self.assertTrue(os.path.exists(verset + '.4.5.6'))
self.assertEqual(os.readlink(verset), 'libverset.so.4')
self.assertEqual(self.get_soname(verset), 'libverset.so.4')
self.assertEqual(get_soname(verset), 'libverset.so.4')
self.assertEqual(len(glob(verset[:-3] + '*')), 3)
# File with soversion set
soverset = os.path.join(libpath, 'libsoverset.so')
self.assertTrue(os.path.exists(soverset + '.1.2.3'))
self.assertEqual(os.readlink(soverset), 'libsoverset.so.1.2.3')
self.assertEqual(self.get_soname(soverset), 'libsoverset.so.1.2.3')
self.assertEqual(get_soname(soverset), 'libsoverset.so.1.2.3')
self.assertEqual(len(glob(soverset[:-3] + '*')), 2)
# File with version and soversion set to same values
settosame = os.path.join(libpath, 'libsettosame.so')
self.assertTrue(os.path.exists(settosame + '.7.8.9'))
self.assertEqual(os.readlink(settosame), 'libsettosame.so.7.8.9')
self.assertEqual(self.get_soname(settosame), 'libsettosame.so.7.8.9')
self.assertEqual(get_soname(settosame), 'libsettosame.so.7.8.9')
self.assertEqual(len(glob(settosame[:-3] + '*')), 2)
# File with version and soversion set to different values
@ -1367,7 +1388,7 @@ class LinuxlikeTests(BasePlatformTests):
self.assertTrue(os.path.exists(bothset + '.1.2.3'))
self.assertEqual(os.readlink(bothset), 'libbothset.so.1.2.3')
self.assertEqual(os.readlink(bothset + '.1.2.3'), 'libbothset.so.4.5.6')
self.assertEqual(self.get_soname(bothset), 'libbothset.so.1.2.3')
self.assertEqual(get_soname(bothset), 'libbothset.so.1.2.3')
self.assertEqual(len(glob(bothset[:-3] + '*')), 3)
def test_soname(self):

Loading…
Cancel
Save