Skip to content

Conversation

@hpkfft
Copy link
Contributor

@hpkfft hpkfft commented Mar 30, 2025

Prefix library paths specified by -L are converted to canonical paths to improve the library runpath stored in the binary output. For example, if pkg-config --libs add returns the following:

-L/usr/local/lib/pkgconfig/../../lib -ladd

we convert the path so that it is as if it had returned:

-L/usr/local/lib -ladd

Motivation

Consider a third-party math library that is distributed as two packages for installation: libadd and libadd-devel.
The first installs the following files (under /opt or /usr/local as the system administrator chooses):

lib/libadd.so.0 -> libadd.so.0.1.0
lib/libadd.so.0.1.0

The second, which is the development package, installs the following files:

include/add/add.h
lib/libadd.so -> libadd.so.0
lib/pkgconfig/add.pc

The file add.pc is as follows:

prefix=${pcfiledir}/../..
includedir=${prefix}/include
libdir=${prefix}/lib

Name: add
Description: addition: add
Version: 0.1.0
Libs: -L${libdir} -ladd
Cflags: -I${includedir}

On a build machine, the sysadmin has installed both packages. But the application that uses this math library will be deployed on hundreds of machines (virtual machines in the cloud, nodes on a supercomputer, docker containers, etc.). The system administrator only installs libadd on those machines, not libadd-devel, nor meson, ninja, etc.

The problem is that, without this patch, a meson.build file containing

add_dep = dependency('add', method: 'pkg-config')
executable('myapp', 'myapp.c', dependencies: add_dep)

will create an application binary with the following library runpath:

$ ldd myapp
    ...
    libadd.so.0 => /usr/local/lib/pkgconfig/../../lib/libadd.so.0
    ...

and this won't run on the deployment nodes since the directory /usr/local/lib/pkgconfig does not exist there.

With this patch:

$ ldd myapp
    ...
    libadd.so.0 => /usr/local/lib/libadd.so.0
    ...

and all is well.

@hpkfft hpkfft requested a review from jpakkane as a code owner March 30, 2025 01:32
@eli-schwartz
Copy link
Member

The motivation here sounds quite wrong. If you're distributing libadd to hundreds of machines and the administrator of each machine can choose where to install it, because it's a relocatable package and uses ${pcfiledir}, then it would be factually incorrect to use /usr/local/lib as the rpath. It would also be factually incorrect to use /usr/local/lib/pkgconfig/../../lib.

The only correct option is to evaluate the pcfiledir pkg-config variable in the text used for rpath, with the rpath token ${ORIGIN} (which is evaluated at runtime by ld.so)

@hpkfft
Copy link
Contributor Author

hpkfft commented Mar 30, 2025

In this scenario, there's only one administrator, who creates two disk images (one for the build machine and one for the deployment machines). The only difference between the two images is whether or not libadd-devel is installed. The choice of installation location (e.g., /usr/local) is made once and is applied to both the build and deployment nodes. The idea is to save time and bandwidth when spinning up the deployment nodes by having a smaller image for them.

Sorry, I'm not really understanding your suggestion. The ${ORIGIN} would refer to the location of myapp, which does not seem helpful for locating libadd.so.0.
The ${pcfiledir} in the third-party math library's pc file is evaluated by pkg-config at the time meson setup is run for myapp. Meson just receives the output string -L/usr/local/lib/pkgconfig/../../lib -ladd

As the developer of myapp, I would like my source code to build correctly on a supercomputer that has installed the math library at any arbitrary (known) location. I hope to use the meson flag --pkg-config-path to allow the same source code to work regardless of the sysadmin's choice.

The dependency code for pkgconfig already converts relative paths to absolute paths.
Is there a reason not to use the canonical path returned by os.path.realpath(path)?
Even without the deployment motivation I have described, isn't it nicer to set the RUNPATH to /usr/local/lib?

@hpkfft
Copy link
Contributor Author

hpkfft commented Mar 30, 2025

With this PR:

$ readelf -d myapp

Dynamic section at offset 0x2dc0 contains 28 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libadd.so.0]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000001d (RUNPATH)            Library runpath: [/usr/local/lib]
....

The executable myapp is built on a build machine provided by (and dedicated for) a given compute cluster and is to be run on that same cluster's many deployment machines.
The executable was built using meson setup --pkg-config-path /usr/local/lib/pkgconfig

@virtuald
Copy link

virtuald commented Apr 4, 2025

Even without the deployment motivation I have described, isn't it nicer to set the RUNPATH to /usr/local/lib?

No, if the pkg-config path is relocatable, then the library is intended to be moved around. Turning that into an absolute path would break lots of usages. Instead, your app should make sure that it lives where the library expects you to be (by looking at $ORIGIN). If you can't do that, then you need to use LD_LIBRARY_PATH.

@hpkfft
Copy link
Contributor Author

hpkfft commented Apr 5, 2025

The meson code in pkgconfig.py already converts the path provided by pkg-config into an absolute path as follows:

if not os.path.isabs(path):
    path = os.path.join(self.env.get_build_dir(), path)

According to the comments, it does this because, "We need the full path of the library to calculate RPATH values" and because, "De-dup of libraries is easier when we have absolute paths."
My patch is to also canonicalize the path using Path(path).resolve().as_posix() before adding the string to the set of library paths.
For example, this will convert /something/lib/pkgconfig/../../lib to /something/lib.

If the library moves around, it seems to me that my patch doesn't make the problem any worse. Do you see a use case for which the absolute path with the ../ works but the canonicalized path without it does not? (In fact, my patch seems like it might help with library path de-duplication.)

Certainly, if the library is moved around from one machine to the next, then whoever is running the application would need to use LD_LIBRARY_PATH.

I do not see how ${ORIGIN} is useful here. The code I wish to build with meson, myapp, is made available as source code. It needs to use a third-party math library, say libadd.so.0. Some computing facilities install libadd.so.0 in a directory based on the math library vendor, e.g., /opt/intel/, others install it based on the department that requested it, e.g., /opt/astrophysics/, others may use /usr/local/, etc.

your app should make sure that it lives where the library expects you to be

The third-party math library has no idea that myapp exists. The myapp binary would be somewhere like /home/paul/cosmology/myapp. So, ${ORIGIN} is /home/paul/cosmology/. I am not a system admin, and I cannot install myapp in /opt/ or /usr/local/.

It seems common for third-party libraries to ship their product in two pieces, e.g., libadd_0.1.2 and libadd-dev_0.1.2.
For example, on stackoverflow: Linux dev packages
It seems desirable that I should be able to build myapp on a machine with both libadd_0.1.2 and libadd-dev_0.1.2 installed and be able to run it on a machine with only the former installed, assuming of course that libadd_0.1.2 was installed into the same location on both machines. It seems that is the whole point of the packaging convention.

I would like to run

meson setup --pkg-config-path=/opt/vendor/lib/pkgconfig:/opt/astrophysics/lib/pkgconfig:/usr/local/lib/pkdconfig mybuild

on the build machine to have pkg-config tell meson setup where to find libadd.so.0.
Then, I would like to build myapp and be able to run it on any of the compute nodes that have installed libadd_0.1.2 but not libadd-dev_0.1.2 .

I think the same thing happens with Kubernetes deployments--a minimized container image without -dev packages installed is used for deployment to save time and money.

@hpkfft
Copy link
Contributor Author

hpkfft commented Apr 19, 2025

Rebased on top of master.
Note that the masos failures are unrelated to this PR: "ERROR: Could not install packages due to an OSError: [Errno 13] Permission denied"

@hpkfft
Copy link
Contributor Author

hpkfft commented May 20, 2025

Rebased on top of master so the CI checks pass.
No other changes made.

@hpkfft
Copy link
Contributor Author

hpkfft commented Jun 21, 2025

Rebased on top of master.
No other changes made.

@hpkfft hpkfft changed the title dependencies/pkgconfig: Canonicalize libpaths Bugfix: Generated binaries should not depend on devel package paths Sep 4, 2025
# Resolve the path as a compiler in the build directory would
path = os.path.join(self.env.get_build_dir(), path)
prefix_libpaths.add(path)
prefix_libpaths.add(Path(path).resolve().as_posix())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why .as_posix()?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On windows, Meson chooses to keep prefix_libpaths as posix. I don't know why. Sorry, I don't know much about windows. The honest answer is that the windows tests in the CI failed without this, and looking at the difference from what was expected, this fix was obvious.
If it's worth anything, I see .as_posix() used in line 276 of the same file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's also the following comment on line 219: # pkg-config paths follow Unix conventions, even on Windows

@hpkfft
Copy link
Contributor Author

hpkfft commented Dec 11, 2025

Rebased.

@hpkfft
Copy link
Contributor Author

hpkfft commented Jan 16, 2026

Rebased.

Prefix library paths specified by -L are converted to canonical paths
to improve the library runpath stored in the binary output.
For example, if `pkg-config --libs add` returns the following:

    -L/usr/local/lib/pkgconfig/../../lib -ladd

we convert the path so that it is as if it had returned:

    -L/usr/local/lib -ladd
@hpkfft
Copy link
Contributor Author

hpkfft commented Jan 23, 2026

Rebased.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants