From d79bdb9b6bc915f146ce2ab2cfdf17d077953f5d Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 5 Jun 2017 02:11:07 +0530 Subject: [PATCH 1/5] unit tests: Use only implementation of get_soname --- run_unittests.py | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/run_unittests.py b/run_unittests.py index a610e6b85..34a1a158e 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -36,16 +36,19 @@ 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], +def get_dynamic_section_entry(fname, entry): + raw_out = subprocess.check_output(['readelf', '-d', fname], universal_newlines=True) - pattern = re.compile('soname: \[(.*?)\]') + pattern = re.compile(entry + ': \[(.*?)\]') 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') + class InternalTests(unittest.TestCase): @@ -1319,14 +1322,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 +1333,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 +1362,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): From 264ce6c0bc27d2998368b7652b1c12729f088d3a Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 5 Jun 2017 02:11:43 +0530 Subject: [PATCH 2/5] Use absolute RPATHs while linking due to a binutils bug Use -rpath-link with the absolute paths to the respective build dirs to work around a binutils bug that causes $ORIGIN to not be used while linking. Includes a unit test that manually checks the RPATH value written out to ensure that it uses $ORIGIN. See: https://sourceware.org/bugzilla/show_bug.cgi?id=16936 Closes https://github.com/mesonbuild/meson/issues/1897 --- mesonbuild/compilers.py | 7 ++++++- run_unittests.py | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index 06b0a5904..595be6099 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -337,7 +337,12 @@ def build_unix_rpath_args(build_dir, from_dir, rpath_paths, install_rpath): paths = padding else: paths = paths + ':' + padding - return ['-Wl,-rpath,' + paths] + # Rpaths to use while linking must be absolute. These are not used + # while running and are not written to the binary. + # https://github.com/mesonbuild/meson/issues/1897 + # https://sourceware.org/bugzilla/show_bug.cgi?id=16936 + linkpaths = ':'.join([os.path.join(build_dir, p) for p in rpath_paths]) + return ['-Wl,-rpath,' + paths, '-Wl,-rpath-link,' + linkpaths] class CrossNoRunException(MesonException): pass diff --git a/run_unittests.py b/run_unittests.py index 34a1a158e..6d81b00ff 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -49,6 +49,9 @@ def get_dynamic_section_entry(fname, entry): def get_soname(fname): return get_dynamic_section_entry(fname, 'soname') +def get_rpath(fname): + return get_dynamic_section_entry(fname, 'rpath') + class InternalTests(unittest.TestCase): @@ -1133,6 +1136,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 From 41f8f1a53b0dbf9f7858dbb122bac707e48edfb7 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 5 Jun 2017 03:09:17 +0530 Subject: [PATCH 3/5] unit tests: Also check RUNPATH when fetching RPATH --- run_unittests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/run_unittests.py b/run_unittests.py index 6d81b00ff..21b1109c1 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -39,7 +39,7 @@ from run_tests import ensure_backend_detects_changes def get_dynamic_section_entry(fname, entry): raw_out = subprocess.check_output(['readelf', '-d', fname], universal_newlines=True) - pattern = re.compile(entry + ': \[(.*?)\]') + pattern = re.compile(entry + r': \[(.*?)\]') for line in raw_out.split('\n'): m = pattern.search(line) if m is not None: @@ -50,7 +50,7 @@ def get_soname(fname): return get_dynamic_section_entry(fname, 'soname') def get_rpath(fname): - return get_dynamic_section_entry(fname, 'rpath') + return get_dynamic_section_entry(fname, r'(?:rpath|runpath)') class InternalTests(unittest.TestCase): From 7c0d1242aad40c3424b8541c6c0ef1a53ca1bb04 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 5 Jun 2017 04:05:48 +0530 Subject: [PATCH 4/5] compilers: Only set -rpath-link on GNU ld and linuxlike It's an invalid option with Apple ld and leads to a build error. --- mesonbuild/compilers.py | 67 +++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/mesonbuild/compilers.py b/mesonbuild/compilers.py index 595be6099..c9cfb4641 100644 --- a/mesonbuild/compilers.py +++ b/mesonbuild/compilers.py @@ -317,33 +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 - # Rpaths to use while linking must be absolute. These are not used - # while running and are not written to the binary. - # https://github.com/mesonbuild/meson/issues/1897 - # https://sourceware.org/bugzilla/show_bug.cgi?id=16936 - linkpaths = ':'.join([os.path.join(build_dir, p) for p in rpath_paths]) - return ['-Wl,-rpath,' + paths, '-Wl,-rpath-link,' + linkpaths] - class CrossNoRunException(MesonException): pass @@ -757,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 @@ -806,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] @@ -2071,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'] @@ -3029,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' From 198c2f2d555b8e9e08fcf545f56540af718ae026 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 5 Jun 2017 13:20:04 +0530 Subject: [PATCH 5/5] unit tests: Skip tests if no readelf found --- run_unittests.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/run_unittests.py b/run_unittests.py index 21b1109c1..63f6def3a 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -37,8 +37,12 @@ from run_tests import ensure_backend_detects_changes def get_dynamic_section_entry(fname, entry): - raw_out = subprocess.check_output(['readelf', '-d', fname], - universal_newlines=True) + 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)