Meson: Please provide a user-configurable, global way to specify RPATH/RUNPATH for the whole project.

Created on 1 Nov 2017  路  53Comments  路  Source: mesonbuild/meson

Currently meson does not provide a way to specify a custom rpath, it removes rpath in install step even if it was specified via LDFLAGS.
https://github.com/NixOS/nixpkgs/pull/28444#issuecomment-324033323

Please note: I'm aware about {build,install}_rpath, these are project or target options.
I'm talking about something like cmake's CMAKE_INSTALL_RPATH which can be easily passed to cmake while configuring. Even autohell behaves fine with such use case.
What I'm asking for is an option like --meson-install-rpath (like --prefix) which would allow users (project consumers, not developers) to build and install packages into a custom prefix _without_ messing with meson.build .

This is needed when you want to install something into an arbitrary prefix to avoid overwriting system stuff, eg. you want to build something with new shiny features straight from git, but you don't want to break your OS.

Please consider adding such a feature.

PS: LD_RUN_PATH et al is not an option, the rest of the system should not know anything "prefixed" installs.

compilers enhancement

Most helpful comment

This bit me today when I tried to switch to meson. Please, for the love of $deity, be kind to people who have an ecosystem that installs into /opt/foobar. LD_LIBRARY_PATH is not an option because users don't want to be setting stuff like that when they run apps installed in /opt/foobar. They just want the apps to work.

All 53 comments

The established standard way of doing this is to set LD_LIBRARY_PATH when running these programs. It is simple and guaranteed to work with any build system, even handwritten makefiles.

If you really want to create an executable to run withouth changin envvars, it can be accomplished by defining the rpath with $ORIGIN. The added bonus here is that your executables become fully relocatable.

That's not what I'm asking for.
As I said "LD_RUN_PATH et al is not an option" and meson strips any rpath even if it's $ORIGIN,
and using patchelf to set rpath back is very dirty hack.
I can achieve what I need with any other build system _except_ meson.
So far I hacked depfixer.py to do what I need but it's still a hack too.

This https://github.com/mesonbuild/meson/issues/1411#issuecomment-281244672 would be a much better solution.

I need this as well. I install custom versions of software and dependencies to custom directories to assist with development. For example I install the latest git of a project and git versions of key dependencies to one directory for testing. I install the latest stable of that project and stable dependencies to another directory so I have a known good version for checking regressions. When working on a feature that requires API additions to a dependency I will install that to another directory so avoid having to recompiling everything when switching branches.

I rely on rpath so that I can run .../latest/bin/foo and know that it will correctly use the libraries in .../latest/lib. At the same time I can compare results with another version eg .../stable/bin/foo and know that it will correctly use the libraries in .../stable/lib.

This all works perfectly with autotools. LD_LIBRARY_PATH does not work for this use case. I have wasted countless hours debugging non existent bugs due to programs not linking with shared library I thought it was using because I ran it in a different terminal from the one I set LD_LIBRARY_PATH in.

Related to issue #314.

You will also need this when building MacOS app bundles.

I'm evaluating this for building an app bundle on MacOS. It looks like the only way to do this is to put it back within the install script.

I made simple project to learn about using meson to build a MacOS+Qt app bundle.

https://github.com/kdart/qt-meson-example

I would greatly appreciate it if anyone could critique it. Thanks!

I'd like to add another voice to this. The Yocto Project/OpenEmbedded pass in -rpath-link options into the linker and we expect our binaries to have these. There is no good way to allow us to preserve these right now other than hacking meson itself much like every other build system seem to be doing.

The Yocto Project/OpenEmbedded pass in -rpath-link options into the linker and we expect our binaries to have these.

Why do you do this? What is the exact problem that you are trying to solve?

The Yocto Project/OpenEmbedded pass in -rpath-link options into the linker and we expect our binaries to have these.

Why do you do this? What is the exact problem that you are trying to solve?

We build (cross compile) complete Linux software stacks but only have normal user permissions so we never install anything into the main system. Whilst we're cross compiling we also build tools we need to run to build things, in OE terms, "native" versions of the software. Since we don't install these but place them in our own native sysroot, we include RPATHs in the binaries to make sure they run correctly. As such our native LDFLAGS include -L, -Wl,-rpath-link, and -Wl,-rpath options.

We do post process the binaries later to make the rpaths relative using $ORIGIN so the binaries are relocatable although getting that right in the first place is something we need to look into.

Since you are using this for every executable and library, would it not be simpler to set LD_LIBRARY_PATH to point to your sysroot dirs? It is specifically documented as having the highest precedence, even over system libraries.

@jpakkane While this is a possible solution I must ask why it's possible to pass any *flags we want to the other buildsystems and everything will just work and why meson has to mangle a legitimate flag set by the user?

Ultimately what that boils down to is the UX we want to provide to our end users. Specifically one that is noticeably higher level than "bunch of files and random strings". This affords us to provide a better experience where all the different parts and options work together as opposed to fighting with each other all the time (and failing randomly at that).

The core of this is that we have an internal representation that is based on needs and requirements, as opposed to command line strings from random origins. That is how we can do all the things we do and provide to end users.

Injecting random strings in the mix makes _everything_ difficult because you can't rely on anything any more. All the assumptions we have and use to generate the final build steps can be broken by compiler and linker flag injection, as happens here. More specifically there are two different ways of specifying the install time rpath:

  1. The install_rpath kwarg on each target
  2. A flag injected by LDFLAGS.

Here we have a case where the two say different things. So what should we do? There is no one correct answer here. Especially since we can't know if the user intended the LDFLAGS one to be used in the build dir, after install or both. This is why having two things to do the same thing is bad: sooner or later they will conflict and things break leading to tears an flame wars on mailing lists.

Thanks for the explanation.
However while your reason and design goals and yada-yada are valid you are breaking functionality some of us rely on.
RPATH/RUNPATH exist for a reason and they existed before first line of meson (which I actually like a lot) was written.
Maybe provide a cmdline switch, something like --no-strip-rpath, or cmake style --install-rpath= for people who know what they want and what they are doing, so we don't have to hack your beautiful UX to suit our goals and needs.

Perhaps. The followup to that is that it would probably require a new option (unless people are happy with editing build definitions to get that, but I'm guessing no) and we already have a plethora of those. We will probably need to have some sort of an "advanced options" thingy to filter out options that are not of interest to most people most of the time.

What's easier to maintain a patch which may break after meson version bump or just add a new option to the build script/Makefile/whatever ?

Since you are using this for every executable and library, would it not be simpler to set LD_LIBRARY_PATH to point to your sysroot dirs? It is specifically documented as having the highest precedence, even over system libraries.

No, we've tried this and it doesn't work. We're building a ton of different software and doing this either relies on all those build systems preserving the value of LD_LIBRARY_PATH (doesn't happen) or we have to wrap all binaries with a wrapper script which sets LD_LIBRARY_PATH.

To us, it makes sense that when we call our native tools they should work, they shouldn't need magic environment variables set. Setting RPATHs in the binaries does exactly that and is the right way to solve this.

relies on all those build systems preserving the value of LD_LIBRARY_PATH (doesn't happen) or

Wait, what? I don't understand. It's just an environment variable. The only way it would not be preserved if someone goes out of their way to unset it. Are there really programs that do that?

they shouldn't need magic environment variables set. Setting RPATHs in the binaries does exactly that

It's more of a question of where you want your magic to be in: a magic envvar or magic elf entries.

relies on all those build systems preserving the value of LD_LIBRARY_PATH (doesn't happen) or

Wait, what? I don't understand. It's just an environment variable. The only way it would not be preserved if someone goes out of their way to unset it. Are there really programs that do that?

Yes, its surprisingly common. Bottom line is build systems like to control the environment of the things they're building.

they shouldn't need magic environment variables set. Setting RPATHs in the binaries does exactly that

It's more of a question of where you want your magic to be in: a magic envvar or magic elf entries.

Yes. We decided we quite liked the binaries to just work when we run them and that has been working well for us.

It does mean we could do with having a way to pass in these flags. I appreciate that complicates things from a user experience side in meson though and figuring out how different options interact.

It's more of a question of where you want your magic to be in: a magic envvar or magic elf entries.

Just my opinion: I prefer magic elf entries, basically it's build the "native" toolset once and forget.
Dealing with environment vars in complex environments specially with cross-compilation, atleast a half dozen of buildsystems, qemu-user wrappers and that kind of stuff is painful enough.

PS: almost forgot this.
Another point of why LD_LIBRARY_PATH is undesirable and even may be evil.
Imagine you have a prefix let's say /my/prefix.
Which already has some stuff built, lets say there is "libfoo.so" to which my binaries from /my/prefix/bin link.
The host os also happen to have "libfoo.so" , but it's different like another version, different configure options, distro-specific patches and etc.
Now I have to run a binary from host OS that happens to link to libfoo.so.
If LD_LIBRARY_PATH is set then runtime linker would pickup the library from my prefix and the host binary may crash (eg. missing symbols), been there ...
The solution in that case would be to build the needed binary and put in the prefix, but it may require a truckload of dependencies and add more unnecessary work.
That's one of the reasons to rely on rpath.

The established standard way of doing this is to set LD_LIBRARY_PATH when running these programs. It is simple and guaranteed to work with any build system, even handwritten makefiles.

The second comment missed the words "On Linux" to be prepended to the first paragraph. (I though meson was meant to be cross-platform.)

I maintain a lot of software where I work. Putting the "magic" into the elf files is far preferable to using LD_LIBRARY_PATH. In our system, a subset of our users eventually need incompatible software to be used at the same time, and in a runpath configuration it works and in an LD_LIBRARY_PATH configuration it will not.

There are plenty of reasons that the knowledgeable experts are advocating for either no RUNPATH and also no LD_LIBRARY_PATH (e.g. use ldconfig for everything), or to use RUNPATH without LD_LIBRARY_PATH.

I'm not really using meson (I play with it every once in a while), but this was the first issue I hit with it when I tried.

This bit me today when I tried to switch to meson. Please, for the love of $deity, be kind to people who have an ecosystem that installs into /opt/foobar. LD_LIBRARY_PATH is not an option because users don't want to be setting stuff like that when they run apps installed in /opt/foobar. They just want the apps to work.

The established standard way of doing this is to set LD_LIBRARY_PATH when running these programs. It is simple and guaranteed to work with any build system, even handwritten makefiles.

No, it only works with build systems which know how to modify LD_LIBRARY_PATH. It works with autotools because libtool can prepend required paths to LD_LIBRARY_PATH in its wrapper. In fact, LD_LIBRARY_PATH can cause build failure with meson: https://github.com/mesonbuild/meson/issues/1635. I tried to workaround it with a shell script in colord, but the maintainer doesn't like it: https://github.com/hughsie/colord/pull/68.

An issue reported on FreeBSD Bugzilla referenced this issue:
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=238128

This is a different use case from the custom prefix one mentioned above. On FreeBSD, most architectures have switched to use Clang as the default compiler, so GCC libraries, such as libstdc++.so.6, are not shipped with the base system. Therefore, if you compile a C++ program with GCC (available as packages) instead of Clang shipped with the base system, you have to find a way to ask the runtime linker to use libstdc++.so.6 in a non-standard directory such as /usr/local/lib/gcc8. FreeBSD Ports currently handle this by using -Wl,-rpath in environment variables, and it is affected by the issue.

$ make -V CFLAGS
-O2 -pipe  -fstack-protector-strong -Wl,-rpath=/usr/local/lib/gcc8 -fno-strict-aliasing
$ make -V CXXFLAGS
-O2 -pipe  -fstack-protector-strong -Wl,-rpath=/usr/local/lib/gcc8  -Wl,-rpath=/usr/local/lib/gcc8
$ make -V LDFLAGS
 -lpthread -fstack-protector-strong -Wl,-rpath=/usr/local/lib/gcc8 -L/usr/local/lib/gcc8

An issue reported on FreeBSD Bugzilla referenced this issue:
https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=238128

This is a different use case from the custom prefix one mentioned above. On FreeBSD, most architectures have switched to use Clang as the default compiler, so GCC libraries, such as libstdc++.so.6, are not shipped with the base system. Therefore, if you compile a C++ program with GCC (available as packages) instead of Clang shipped with the base system, you have to find a way to ask the runtime linker to use libstdc++.so.6 in a non-standard directory such as /usr/local/lib/gcc8. FreeBSD Ports currently handle this by using -Wl,-rpath in environment variables, and it is affected by the issue.

$ make -V CFLAGS
-O2 -pipe  -fstack-protector-strong -Wl,-rpath=/usr/local/lib/gcc8 -fno-strict-aliasing
$ make -V CXXFLAGS
-O2 -pipe  -fstack-protector-strong -Wl,-rpath=/usr/local/lib/gcc8  -Wl,-rpath=/usr/local/lib/gcc8
$ make -V LDFLAGS
 -lpthread -fstack-protector-strong -Wl,-rpath=/usr/local/lib/gcc8 -L/usr/local/lib/gcc8

You are wrong here in one thing.

Some architectures have switched to Clang but others have not. They still use GCC 4.2 in base. The Bugzilla issue was discovered on powerpc64, which still uses GCC 4.2 (but will switch to Clang in FreeBSD 13.0).

-Wl,-rpath is needed so that libstdc++ from GCC8 (from ports) is used instead of libstdc++ from GCC 4.2 (from base).

LDFLAGS="-Wl,-dynamic-linker,/different/ld-linux-x86-64.so.2  -Wl,-rpath,/other/path"

Is there a way to do this with meson and if not yet, is it a possibility? There's no reason that the actual LDFLAGS variable has to be used. Starting with a new clean system, I can totally see why you would want to distance yourself from that ball of mud.

The thing is, if you want to package something with many dependencies into /opt, or for a different filesystem layout like guix or NixOS, it's really difficult to do that without a way to globally set your rpath and dynamic-linker as you build.

For now is everybody manually patching every meson built binary with patchelf or does somebody have a robust script to add build-rpath and install-rpath into meson.build on the fly, or is everyone needing a global rpath patching scripts/depfixer.py?

For reference here is the patch used in NixOS:

https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/tools/build-managers/meson/fix-rpath.patch

We don't disable the entire RPATH handling, but just make it keep all of the paths starting with /nix/store (@storeDir@).

This is the one the Yocto Project is using:

http://git.yoctoproject.org/cgit/cgit.cgi/poky/tree/meta/recipes-devtools/meson/meson/disable-rpath-handling.patch

The name is misleading as its not totally disabled, we just don't modify it at install time and use the compile time one since we're cross compiling and not running things at build time.

For added justification, LD_LIBRARY_PATH doesn't work for setuid binaries - for security reasons, when setuid binaries are exec'd, LD_LIBRARY_PATH is ignored by the run-time linker. RPATH is necessary if the required library is not in the system library path. There are other cases where a user doesn't have a good opportunity to add LD_LIBRARY_PATH (gui tools forked in a desktop environment). Relying on LD_LIBRARY_PATH is a weak case against supporting RPATH in meson.

The bottom line is that meson should not be stripping out rpath entries that it itself didn't set.

Here we have a case where the two say different things. So what should we do? There is no one correct answer here.

If the user has externally specified their own -Wl,-rpath then they are quite obviously an expert user and they should be expected to live with their own expert settings that they presumably wanted for some good reason. Do you have any good reason to think that anyone other than the people vehemently protesting meson's behavior, are actually running around with -Wl,-rpath in the wild? This is not exactly common behavior to begin with.

Especially since we can't know if the user intended the LDFLAGS one to be used in the build dir, after install or both.

One assumes that the purpose of a custom rpath is to refer to existing system libraries as used by all the people discussing it here. The logical conclusion is that they are needed in both places.

Of course, meson's own internal build rpath should take precedence, and likewise project-specific install_rpath() should be prioritized (over vague, generic system-defined rpath) at install time.

This is why having two things to do the same thing is bad: sooner or later they will conflict and things break leading to tears an flame wars on mailing lists.

They don't have to conflict unless the person writing the UI designs them to conflict just so they can say "I told you so" and have an excuse to delete the functionality.

You might just as well claim that the default /usr/lib is "two ways to do the same thing" as compared to using rpath in the first place. But of course this is a solved problem: define an order of precedence (like, "the more specific it is, the higher value it has").

The bottom line is that meson should not be stripping out rpath entries that it itself didn't set.

We discussed this last year at GUADEC, and the consensus was that yes, if we can reliably detect which RPATH entries were added by Meson and only remove those, that's fine. If you or someone else could come up with a PR that does that, I'll be happy to review and merge it.

Note that 'not stripping unknown RPATHs' is a separate issue and 'a global option to set RPATH' is a separate issue. For the former, there's already an issue open: https://github.com/mesonbuild/meson/issues/4027 and this exact thing was discussed there too.

An alternative approach that I came up with yesterday is to add build_rpath and install_rpath properties to the dependency object. Then when we scan for external dependencies we search for rpath command line arguments and convert those from plain command line arguments to properties (for both build and install rpath, presumably).

@jpakkane looking at the command-line arguments is insufficient for some use-cases:

In PTXdist (a embedded Linux distribution) the "compiler" is actually a script that adds all the necessary extra arguments, including the rpath. It is done like this because it's the only reliable way to make sure that arguments are not dropped somewhere when dealing with a lot of different build-systems.

So meson will not actually see the rpath in the command-line.

If we make sure to have the correct rpaths in our .pc files, and meson's pkgconfig does the right thing, it is a shame for meson to strip those same rpaths out on install. (So rpaths not on meson's command line currently disappear.)

And if we use meson to generate the .pc file for a package that someone else has written, there has to be a convenient way, by command line or environment variable, to set the rpath for the build, for the rpath to survive the install, and for it to make it into the .pc file.

Any news on this? I see a patch for the Meson included in homebrew at https://github.com/Homebrew/linuxbrew-core/pull/16054 that adds a install_rpath option. The spack package manager also patches Meson to fully disable any rpath stripping. But for the official Meson here, on other platforms, it seems no such option is available?

I'm running into the same issue in yet another different scenario: the EasyBuild build framework includes some packages that are built with Meson+Ninja and there RPATHs in the binaries also should not be touched by Meson as EasyBuild sets up the RPATHs. This is specifically so that using LD_LIBRARY_PATH does not break the packages made available through EasyBuild.

Hi,

FWIW, here is a code snippet I'm using that emulates a global rpath using patchelf and the output of meson introspect:

import subprocess                                                                                                                           
import json                                                                                                                                 
import os                                                                                                                                   
import sys                                                                                                                                  

def set_rpath (rpath):                                                                                                                      
    if not rpath:                                                                                                                           
        return                                                                                                                              

    print ("Set shared library and executable rpath to '{}':".format(rpath))                                                                
    try:                                                                                                                                    
        output = subprocess.run(["meson", "introspect", "--targets", "--indent"], stdout=subprocess.PIPE, check=True)                       
        data = json.loads(output.stdout)                                                                                                    
        for target in data:                                                                                                                 
            if target['installed']:                                                                                                         
                if target['type'] in ['executable', 'shared library', 'shared module']:                                                     
                    for filename in target['install_filename']:                                                                             
                        try:                                                                                                                
                            subprocess.run(["patchelf", "--set-rpath", rpath, filename], check=True)                                        
                            subprocess.run(["patchelf", "--shrink-rpath", filename], check=True)                                            
                            print (filename)                                                                                                
                        except:                                                                                                             
                            pass                                                                                                            
                else:                                                                                                                       
                    try:                                                                                                                    
                        print("Ignore '{0}' with type '{1}'".format(target['name'], target['type']))                                        
                    except:                                                                                                                 
                        pass                                                                                                                

    except Exception as error:                                                                                                              
        print(error)                                                                                                                        
        sys.exit(1)  

I use slightly different and simpler approach - I patch meson to check if environment variable is present and set RPATH to the value of that variable if it is.

--- a/mesonbuild/scripts/depfixer.py    2018-05-04 15:04:41.842845916 +0300
+++ b/mesonbuild/scripts/depfixer.py    2018-05-04 15:05:41.122107624 +0300
@@ -13,7 +13,7 @@
 # limitations under the License.


-import sys, struct
+import sys, struct, os
 import shutil, subprocess

 SHT_STRTAB = 3
@@ -288,6 +288,14 @@
     def fix_rpath(self, new_rpath):
         # The path to search for can be either rpath or runpath.
         # Fix both of them to be sure.
+        if os.getenv('MESON_FORCE_RPATH'):
+            if isinstance(new_rpath, str) and new_rpath != "":
+                new_rpath = "{}:{}".format(os.getenv('MESON_FORCE_RPATH'), new_rpath)
+            else:
+                new_rpath = os.path.join(os.getenv('MESON_FORCE_RPATH'))
+
+            print("HACK: applying meson rpath hack, rpath: {}".format(new_rpath))
+
         self.fix_rpathtype_entry(new_rpath, DT_RPATH)
         self.fix_rpathtype_entry(new_rpath, DT_RUNPATH)

Thanks, but I'd much rather Meson would simply not touch any RPATH set outside of its control, instead of applying one after the fact.

Yep, thats exactly why I opened this issue.
Every other buildsystem known to me respects user set stuff.
The people who set rpath/runpath are supposed to know what they are doing and we don't need meson babysitting us, but hey, there are UX, design goals and all that involved.
Obviously that crap is more important to the developers than actual convenience.

It's up to us users who need it to design the feature and offer a patch that the meson maintainers can live with.

A built-in option is probably preferable to an environment variable. strip-rpaths, with default false, comes to mind.

But meson does need to strip the rpaths it itself adds pointing to /home/username/.../projectname/builddir/lib/ or wherever, which allows the binaries to be executed when uninstalled (e.g. for the tests) but must not be present when installed to /usr as it is a security vulnerability.

The correct solution is that meson should only remove the rpath components which it itself has added.

I think creating those paths should be optional. In many cases nothing is ever executed in the build directory.

IIRC the maintainers have stated they'd accept a patch which only removes the rpath components that meson synthesized.

See also https://github.com/mesonbuild/meson/issues/4027

meson can (and should) do whatever the devs consider appropriate with meson internal stuff.
The question is: if the user explicitly sets LDFLAGS=-Wl,-rpath=/some/path why the hell it is not honored || removed ?

IMO it's clearly a design flaw.

Proof of concept: apply the following hunk atop https://github.com/mesonbuild/meson/pull/7103 :

--- a/mesonbuild/backend/backends.py
+++ b/mesonbuild/backend/backends.py
@@ -458,6 +458,14 @@ class Backend:
             if exclude_system and self._libdir_is_system(libdir, target.compilers, self.environment):
                 # No point in adding system paths.
                 continue
+            # Don't remove rpaths specified in LDFLAGS?
+            ext_rpaths = set()
+            for arg in self.environment.coredata.get_external_link_args(target.for_machine, 'c'):
+                if arg.startswith('-Wl,-rpath='):
+                    for dir in arg.replace('-Wl,-rpath=','').split(':'):
+                        ext_rpaths.add(dir)
+            if libdir in ext_rpaths:
+                continue

@dankegel Thanks. Will do and report back as soon as I can get my dev box online again, waiting for a spare PSU to arrive, probably tomorrow (

https://github.com/mesonbuild/meson/pull/7103 now has an LDFLAGS fix and test case, so you don't need that hunk anymore.

Review/testing welcome. There might be a better way to do it.

@dankegel finally I was able to run a few quick tests with #7103 - looking awesome so far,
RPATH/RUNPATH from LDFLAGS are finally working.

#this is from glib
 readelf -d gdbus | grep PATH
 0x000000000000001d (RUNPATH)            Library runpath: [/opt/pfx/lib]

If I manage to find something odd I'll report back in #7103
Huge thanks.

Was this page helpful?
0 / 5 - 0 ratings