Skip to content

dependencies/pkgconfig: Canonicalize libpaths #14426

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

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.

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 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.

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.

3 participants