Set RPATH for all non-system libs with absolute paths

If a pkg-config dependency has multiple libraries in it, which is the
most common case when it has a Requires: directive, or when it has
multiple -l args in Libs: (rare), then we don't add -Wl,-rpath
directives to it when linking.

The existing test wasn't catching it because it was linking to
a pkgconfig file with a single library in it. Update the test to
demonstrate this.

This function was originally added for shared libraries in the source
directory, which explains the name:
https://github.com/mesonbuild/meson/pull/2397

However, since now it is also used for linking to *all* non-system
shared libraries that we link to with absolute paths:
https://github.com/mesonbuild/meson/pull/3092

But that PR is incomplete / wrong, because only adding RPATHs for
dependencies that specify a single library, which is simply
inconsistent. Things will work for some dependencies and not work for
others, with no logical reason for it.

We should add RPATHs for *all* libraries. There are no special length
limits for RPATHs that I can find.

For ELF, DT_RPATH or DT_RUNPATH are used, which are just stored in
a string table (DT_STRTAB). The maximum length is only a problem when
editing pre-existing tags.

For Mach-O, each RPATH is stored in a separate LC_RPATH entry so there
are no length issues there either.

Fixes https://github.com/mesonbuild/meson/issues/9543

Fixes https://github.com/mesonbuild/meson/issues/4372
pull/8973/merge
Nirbheek Chauhan 3 years ago committed by Nirbheek Chauhan
parent 95a4c6a62a
commit 06b1132f82
  1. 55
      mesonbuild/backend/backends.py
  2. 3
      test cases/common/44 pkgconfig-gen/answer.c
  3. 7
      test cases/common/44 pkgconfig-gen/foo.c
  4. 14
      test cases/common/44 pkgconfig-gen/meson.build
  5. 1
      test cases/common/44 pkgconfig-gen/test.json
  6. 2
      unittests/allplatformstests.py

@ -751,37 +751,36 @@ class Backend:
return dirs return dirs
@lru_cache(maxsize=None) @lru_cache(maxsize=None)
def rpaths_for_bundled_shared_libraries(self, target: build.BuildTarget, exclude_system: bool = True) -> 'ImmutableListProtocol[str]': def rpaths_for_non_system_absolute_shared_libraries(self, target: build.BuildTarget, exclude_system: bool = True) -> 'ImmutableListProtocol[str]':
paths: T.List[str] = [] paths: OrderedSet[str] = OrderedSet()
for dep in target.external_deps: for dep in target.external_deps:
if not isinstance(dep, (dependencies.ExternalLibrary, dependencies.PkgConfigDependency)): if not isinstance(dep, (dependencies.ExternalLibrary, dependencies.PkgConfigDependency)):
continue continue
la = dep.link_args for libpath in dep.link_args:
if len(la) != 1 or not os.path.isabs(la[0]): # For all link args that are absolute paths to a library file, add RPATH args
continue if not os.path.isabs(libpath):
# The only link argument is an absolute path to a library file. continue
libpath = la[0] libdir = os.path.dirname(libpath)
libdir = os.path.dirname(libpath) if exclude_system and self._libdir_is_system(libdir, target.compilers, self.environment):
if exclude_system and self._libdir_is_system(libdir, target.compilers, self.environment): # No point in adding system paths.
# No point in adding system paths. continue
continue # Don't remove rpaths specified in LDFLAGS.
# Don't remove rpaths specified in LDFLAGS. if libdir in self.get_external_rpath_dirs(target):
if libdir in self.get_external_rpath_dirs(target): continue
continue # Windows doesn't support rpaths, but we use this function to
# Windows doesn't support rpaths, but we use this function to # emulate rpaths by setting PATH, so also accept DLLs here
# emulate rpaths by setting PATH, so also accept DLLs here if os.path.splitext(libpath)[1] not in ['.dll', '.lib', '.so', '.dylib']:
if os.path.splitext(libpath)[1] not in ['.dll', '.lib', '.so', '.dylib']: continue
continue if libdir.startswith(self.environment.get_source_dir()):
if libdir.startswith(self.environment.get_source_dir()): rel_to_src = libdir[len(self.environment.get_source_dir()) + 1:]
rel_to_src = libdir[len(self.environment.get_source_dir()) + 1:] assert not os.path.isabs(rel_to_src), f'rel_to_src: {rel_to_src} is absolute'
assert not os.path.isabs(rel_to_src), f'rel_to_src: {rel_to_src} is absolute' paths.add(os.path.join(self.build_to_src, rel_to_src))
paths.append(os.path.join(self.build_to_src, rel_to_src)) else:
else: paths.add(libdir)
paths.append(libdir)
for i in chain(target.link_targets, target.link_whole_targets): for i in chain(target.link_targets, target.link_whole_targets):
if isinstance(i, build.BuildTarget): if isinstance(i, build.BuildTarget):
paths.extend(self.rpaths_for_bundled_shared_libraries(i, exclude_system)) paths.update(self.rpaths_for_non_system_absolute_shared_libraries(i, exclude_system))
return paths return list(paths)
# This may take other types # This may take other types
def determine_rpath_dirs(self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex] def determine_rpath_dirs(self, target: T.Union[build.BuildTarget, build.CustomTarget, build.CustomTargetIndex]
@ -794,7 +793,7 @@ class Backend:
result = OrderedSet() result = OrderedSet()
result.add('meson-out') result.add('meson-out')
if isinstance(target, build.BuildTarget): if isinstance(target, build.BuildTarget):
result.update(self.rpaths_for_bundled_shared_libraries(target)) result.update(self.rpaths_for_non_system_absolute_shared_libraries(target))
target.rpath_dirs_to_remove.update([d.encode('utf-8') for d in result]) target.rpath_dirs_to_remove.update([d.encode('utf-8') for d in result])
return tuple(result) return tuple(result)
@ -1072,7 +1071,7 @@ class Backend:
if isinstance(target, build.BuildTarget): if isinstance(target, build.BuildTarget):
prospectives.update(target.get_transitive_link_deps()) prospectives.update(target.get_transitive_link_deps())
# External deps # External deps
for deppath in self.rpaths_for_bundled_shared_libraries(target, exclude_system=False): for deppath in self.rpaths_for_non_system_absolute_shared_libraries(target, exclude_system=False):
result.add(os.path.normpath(os.path.join(self.environment.get_build_dir(), deppath))) result.add(os.path.normpath(os.path.join(self.environment.get_build_dir(), deppath)))
for bdep in extra_bdeps: for bdep in extra_bdeps:
prospectives.add(bdep) prospectives.add(bdep)

@ -0,0 +1,3 @@
int answer_to_life_the_universe_and_everything(void) {
return 42;
}

@ -0,0 +1,7 @@
#include"simple.h"
int answer_to_life_the_universe_and_everything (void);
int simple_function(void) {
return answer_to_life_the_universe_and_everything();
}

@ -42,13 +42,21 @@ test('pkgconfig-validation', pkgconfig,
args: ['--validate', 'simple'], args: ['--validate', 'simple'],
env: [ 'PKG_CONFIG_PATH=' + meson.current_build_dir() + '/meson-private' ]) env: [ 'PKG_CONFIG_PATH=' + meson.current_build_dir() + '/meson-private' ])
answerlib = shared_library('answer', 'answer.c')
pkgg.generate(answerlib,
name : 'libanswer',
description : 'An answer library.',
)
# Test that name_prefix='' and name='libfoo' results in '-lfoo' # Test that name_prefix='' and name='libfoo' results in '-lfoo'
lib2 = shared_library('libfoo', 'simple.c', lib2 = shared_library('libfoo', 'foo.c',
link_with: answerlib,
name_prefix : '', name_prefix : '',
version : libver) version : libver)
pkgg.generate( pkgg.generate(lib2,
libraries : lib2, libraries : [lib2, answerlib],
name : 'libfoo', name : 'libfoo',
version : libver, version : libver,
description : 'A foo library.', description : 'A foo library.',

@ -3,6 +3,7 @@
{"type": "file", "file": "usr/include/simple.h"}, {"type": "file", "file": "usr/include/simple.h"},
{"type": "file", "file": "usr/lib/libstat2.a"}, {"type": "file", "file": "usr/lib/libstat2.a"},
{"type": "file", "file": "usr/lib/pkgconfig/simple.pc"}, {"type": "file", "file": "usr/lib/pkgconfig/simple.pc"},
{"type": "file", "file": "usr/lib/pkgconfig/libanswer.pc"},
{"type": "file", "file": "usr/lib/pkgconfig/libfoo.pc"}, {"type": "file", "file": "usr/lib/pkgconfig/libfoo.pc"},
{"type": "file", "file": "usr/lib/pkgconfig/libhello.pc"}, {"type": "file", "file": "usr/lib/pkgconfig/libhello.pc"},
{"type": "file", "file": "usr/lib/pkgconfig/libhello_nolib.pc"}, {"type": "file", "file": "usr/lib/pkgconfig/libhello_nolib.pc"},

@ -1595,7 +1595,7 @@ class AllPlatformTests(BasePlatformTests):
foo_dep = PkgConfigDependency('libfoo', env, kwargs) foo_dep = PkgConfigDependency('libfoo', env, kwargs)
# Ensure link_args are properly quoted # Ensure link_args are properly quoted
libdir = PurePath(prefix) / PurePath(libdir) libdir = PurePath(prefix) / PurePath(libdir)
link_args = ['-L' + libdir.as_posix(), '-lfoo'] link_args = ['-L' + libdir.as_posix(), '-lfoo', '-lanswer']
self.assertEqual(foo_dep.get_link_args(), link_args) self.assertEqual(foo_dep.get_link_args(), link_args)
# Ensure include args are properly quoted # Ensure include args are properly quoted
incdir = PurePath(prefix) / PurePath('include') incdir = PurePath(prefix) / PurePath('include')

Loading…
Cancel
Save