mesonbuild / mesonbuild/meson

Add an option to have @rpath install_name instead of an absolute path

Open
#2,121 18 comments 3 reactions 1 assignee View on GitHub

@nirbheek is already working on this.

Since Jun 18, 2018.

bug compilers OS:macos
Dominant language
Python
Stars
6.6k
Forks
1.9k
Avg merge
2d 6h
Merged PRs (30d)
33

Description

Description

Meson's RPATH and install_name handling are currently broken on macOS. Here's a toy example. Let foo be the library (aka the provider) and let bar be the executable (aka the consumer). For foo we have:
foo.cpp:

#include <iostream>

void foo_func()
{
	std::cout << "Hello World!\n";
}

foo.hpp:

void foo_func();

meson.build:

project('libfoo', 'cpp')

foo_lib = library('foo', 'foo.cpp', install : true)
install_headers('foo.hpp', subdir : 'foo')

import('pkgconfig').generate(
  libraries : foo_lib,
  version : '1.0',
  name : 'foo',
  filebase : 'foo',
  subdirs : 'foo',
  description : 'A foo library')

For bar we have:
bar.cpp:

#include <foo.hpp>

int main()
{
	foo_func();
	return 0;
}

meson.build:

project('barapp', 'cpp')

foo_dep = dependency('foo')

# build_rpath is used here for test dependencies
executable('bar', 'bar.cpp', dependencies : foo_dep, build_rpath : foo_dep.get_pkgconfig_variable('libdir'))

How to reproduce

in foo (the rm -r build at the end is crucial to reproduce the issue):

meson.py --prefix /Users/Soap/Desktop/PREFIX/ build . && ninja -C build -v install && rm -r build

in bar:

export PKG_CONFIG_PATH=/Users/Soap/Desktop/PREFIX/lib/pkgconfig
meson.py --prefix /Users/Soap/Desktop/PREFIX/ build . && ninja -C build -v
./build/bar

which then greets you with a message:

dyld: Library not loaded: /Users/Soap/Desktop/foo/build/libfoo.dylib
  Referenced from: /Users/Soap/Desktop/bar/./build/bar
  Reason: image not found

The actual problem

The culprit is how meson builds libraries on macOS. The linking line is currently:

c++  -o libfoo.dylib 'foo@sha/foo.cpp.o' -shared -install_name /Users/Soap/Desktop/foo/build/libfoo.dylib '-Wl,-rpath,$ORIGIN/'

which is incorrect. MacOS has a peculiar and very unusual way of linking, namely that consumers of a library inherit a provider's install_name. If you run otool -l on the library you get

          cmd LC_ID_DYLIB
      cmdsize 72
         name /Users/Soap/Desktop/foo/build/libfoo.dylib (offset 24)

where the name is clearly wrong, as it uses the builddir name. As an absolute minimum this should have been /Users/Soap/Desktop/PREFIX/lib/libfoo.dylib (but hold on, you still don't want this). This is why, when not running rm -r build in foo, the linking seems to work, because bar is still using the build dir lib, not the installed one.

Furthermore, another peculiarity of macOS is the fact that for RPATH of consumers to work, the provider has to play along. This is different from Linux, where RPATH/RUNPATH are purely a consumer problem, the provider does not have to know about consumers' RPATH behaviour. Thus, in order for RPATH to work for consumers, the install_name has to be @rpath/<libname>.dylib. In a quick and hacky way, doing this in foo:

c++  -o libfoo.dylib 'foo@sha/foo.cpp.o' -shared -install_name @rpath/libfoo.dylib '-Wl,-rpath,$ORIGIN/'

And then in bar (notice the missing colon : after -Wl,-rpath,):

c++  -o bar 'bar@exe/bar.cpp.o' -L/Users/Soap/Desktop/PREFIX/lib -lfoo -Wl,-rpath,/Users/Soap/Desktop/PREFIX/lib

and then ./bar gives me

Hello World!

Conclusion

This issue is caused by macOS' unconventional linking behaviour, and the fact that library providers have to be RPATH aware, which is unduly burdensome and complicates things. I believe the following to be the best line of operation:

  1. all (shared) libraries would use -install_name @rpath/<libname>.dylib unconditionally, purely to be consumed in an RPATH-capable way. While using an absolute install_name would make things a bit easier in the short run, this is short-sighted and a path full of pain. There are two reasons to avoid absolute install_names:
    1. reproducible builds, as this would code path/layout-dependent information into libraries
    2. it makes libraries non-relocatable, whereas relative RPATH-based builds are relocatable.
  2. all consumers (that is, other libraries and executables) should specify RPATHs in a relative way, where @loader_path is the proper analogue to $ORIGIN on Linux.

References

https://github.com/conda/conda-build/issues/279
https://www.mikeash.com/pyblog/friday-qa-2009-11-06-linking-and-install-names.html
https://wincent.com/wiki/%40executable_path%2C_%40load_path_and_%40rpath
http://jorgen.tjer.no/post/2014/05/20/dt-rpath-ld-and-at-rpath-dyld/

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.