pixi icon indicating copy to clipboard operation
pixi copied to clipboard

RPATH not properly set in installed CMake/C++ executable

Open villapx-path opened this issue 8 months ago • 6 comments

Checks

  • [x] I have checked that this issue has not already been reported.

  • [x] I have confirmed this bug exists on the latest version of pixi, using pixi --version.

Reproducible example

pixi.toml:

[workspace]
authors = ["Jeremy Villa <[email protected]>"]
channels = [
  "conda-forge",
]
platforms = ["linux-64"]

[tasks.how-to-scan-images]
cmd = "./.build/how-to-scan-images"
depends-on = ["build"]


[feature.build.dependencies]
cmake = ">=4,<5"
gxx = ">=14"
ninja = ">=1.12.1,<2"
libopencv = ">=4.11,<5"

[feature.build.tasks.configure]
cmd = [
    "cmake",
    "-GNinja",
    "-S.",
    "-B.build",
]
inputs = ["CMakeLists.txt"]
outputs = [".build/CMakeFiles/"]

[feature.build.tasks.build]
cmd = ["cmake", "--build", ".build"]
depends-on = ["configure"]
inputs = ["CMakeLists.txt", "src/*"]
outputs = [".build/how-to-scan-images"]

[feature.build.tasks.clean]
cmd = ["cmake", "--build", ".build", "--target", "clean"]
depends-on = ["configure"]
inputs = ["CMakeLists.txt"]

[feature.build.tasks.install]
cmd = ["cmake", "--install", ".build"]
depends-on = ["build"]

[environments]
build = ["build"]

CMakeLists.txt:

cmake_minimum_required(VERSION 3.27)  # required for TARGET_RUNTIME_DLL_DIRS
project(opencv-example)

find_package(OpenCV REQUIRED)

set(EXE how-to-scan-images)

add_executable(${EXE}
    src/how_to_scan_images.cpp
)
set_property(TARGET ${EXE} PROPERTY CXX_STANDARD 23)
target_link_libraries(${EXE}
    PRIVATE
    ${OpenCV_LIBS}
)

install(
    TARGETS ${EXE}
    RUNTIME_DEPENDENCY_SET deps
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

install(
    RUNTIME_DEPENDENCY_SET deps
    DIRECTORIES
    "$<TARGET_RUNTIME_DLL_DIRS:${EXE}>"
)

The how_to_scan_images.cpp source file is a direct copy+paste from the OpenCV "how to scan images" example.

Issue description

When I install my executable using pixi run install --prefix /tmp/app, the executable installed at /tmp/app/bin/how-to-scan-images has an incorrect RPATH embedded in it:

$ pixi run install --prefix /tmp/app
✨ Pixi task (configure in build): cmake -GNinja -S. -B.build
-- The C compiler identification is GNU 14.2.0
-- The CXX compiler identification is GNU 14.2.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /home/jvilla/projects/pixi/opencv-example/.pixi/envs/build/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /home/jvilla/projects/pixi/opencv-example/.pixi/envs/build/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found OpenCV: /home/jvilla/projects/pixi/opencv-example/.pixi/envs/build (found version "4.11.0")
-- Configuring done (2.3s)
-- Generating done (0.0s)
-- Build files have been written to: /home/jvilla/projects/pixi/opencv-example/.build

✨ Pixi task (build in build): cmake --build .build
[2/2] Linking CXX executable how-to-scan-images

✨ Pixi task (install in build): cmake --install .build --prefix /tmp/app
-- Install configuration: ""
-- Installing: /tmp/app/bin/how-to-scan-images
-- Installing: /tmp/app/lib/ld-linux-x86-64.so.2
-- ...
-- ... installing other libs ...
-- ...
$
$
$ chrpath -l /tmp/app/bin/how-to-scan-images
/tmp/app/bin/how-to-scan-images: RPATH=/home/jvilla/projects/pixi/opencv-example/.pixi/envs/build/lib

Per the CMake documentation, the INSTALL_RPATH variable is empty by default, so no RPATH should be written into the executable. So the fact that any RPATH is set at all appears to be a bug.


What I am actually trying to achieve is to set the RPATH to either /tmp/app/lib or ${ORIGIN}/../lib (the literal string ${ORIGIN} is understood by the Linux linker), but any attempts to do so -- such as using -DCMAKE_INSTALL_RPATH=/tmp/app/lib in my configure task, or using set(INSTALL_RPATH /tmp/app/lib) in CMakeLists.txt -- do not work, and the RPATH in both of these cases is instead the same string /home/jvilla/projects/pixi/opencv-example/.pixi/envs/build/lib.

Expected behavior

The expected behavior is that the installed executable should have no RPATH in it.

Executing that same CMakeLists.txt outside of any pixi environment (I am on an Ubuntu 20.04 system with CMake 3.31.6 and the libopencv-dev v4.2.0 apt package installed):

$ cmake -GNinja -S. -B.build
-- The C compiler identification is GNU 9.4.0
-- The CXX compiler identification is GNU 9.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /home/jvilla/.local/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /home/jvilla/.local/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found OpenCV: /usr (found version "4.2.0")
-- Configuring done (0.2s)
-- Generating done (0.0s)
-- Build files have been written to: /home/jvilla/projects/opencv-example/.build
$
$
$ cmake --build .build
[2/2] Linking CXX executable how-to-scan-images
$
$
$ cmake --install .build --prefix /tmp/app
-- Install configuration: ""
-- Installing: /tmp/app/bin/how-to-scan-images
-- Installing: /tmp/app/lib/ld-linux-x86-64.so.2
-- ...
-- ... installing other libs ...
-- ...
$
$
$ chrpath -l /tmp/app/bin/how-to-scan-images
/tmp/app/bin/how-to-scan-images: no rpath or runpath tag found.

In my "actual" desired case, if I add -DCMAKE_INSTALL_RPATH='${ORIGIN}/../lib' to my configure command, that value is correctly written into the RPATH of the executable:

$ cmake -GNinja -S. -B.build -DCMAKE_INSTALL_RPATH='${ORIGIN}/../lib'
-- The C compiler identification is GNU 9.4.0
-- The CXX compiler identification is GNU 9.4.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /home/jvilla/.local/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /home/jvilla/.local/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found OpenCV: /usr (found version "4.2.0")
-- Configuring done (0.2s)
-- Generating done (0.0s)
-- Build files have been written to: /home/jvilla/projects/opencv-example/.build
$
$
$ cmake --build .build
[2/2] Linking CXX executable how-to-scan-images
$
$
$ cmake --install .build --prefix /tmp/app | head -n5
-- Install configuration: ""
-- Installing: /tmp/app/bin/how-to-scan-images
-- Set non-toolchain portion of runtime path of "/tmp/app/bin/how-to-scan-images" to "${ORIGIN}/../lib"
-- Installing: /tmp/app/lib/ld-linux-x86-64.so.2
-- Installing: /tmp/app/lib/ld-2.31.so
$
$
$ chrpath -l /tmp/app/bin/how-to-scan-images
/tmp/app/bin/how-to-scan-images: RUNPATH=${ORIGIN}/../lib

villapx-path avatar Apr 24 '25 16:04 villapx-path

Thanks in advance for your attention!

If this issue instead belongs in https://github.com/prefix-dev/pixi-build-backends, please let me know, and I can re-open it there.

Cheers!

villapx-path avatar Apr 24 '25 16:04 villapx-path

Hey @villapx-path - You are not yet using the pixi build feature and neither the pixi-build-cmake backend because you are not actually defining a package.

I think it could be nice for you to try to use the build backend if you are up for it, but you have to restructure your pixi.toml a bit and add a [package] section.

You can find our example here: https://github.com/prefix-dev/pixi/blob/main/examples/pixi-build/cpp-sdl/pixi.toml

wolfv avatar Apr 24 '25 19:04 wolfv

Hi @wolfv! Thank you for your response.

That example seems to be incomplete, unfortunately. I restructured my pixi.toml to be the following:

Expand to view
[workspace]
channels = [
  "conda-forge",
]
platforms = ["linux-64"]
preview = ["pixi-build"]

[package]
authors = ["Jeremy Villa <[email protected]>"]
description = "OpenCV tutorial project"
name = "opencv-example"
version = "0.0.1"

[dependencies]
libopencv = ">=4.11,<5"

[package.build]
backend = { name = "pixi-build-cmake", version = "0.1.*" }
channels = [
  "https://prefix.dev/pixi-build-backends",
  "https://prefix.dev/conda-forge",
]

[tasks.how-to-scan-images]
cmd = "how-to-scan-images"

And following the example in the README.md, I tried pixi run configure, but given that there are no tasks configure nor build defined, it fails:

Output
$ pixi run configure
 WARN Skipped running the post-link scripts because `run-post-link-scripts` = `false`
        - bin/.librsvg-pre-unlink.sh

To enable them, run:
        pixi config set --local run-post-link-scripts insecure

More info:
        https://pixi.sh/latest/reference/pixi_configuration/#run-post-link-scripts

configure: command not found

Available tasks:
        how-to-scan-images

So I'm just not sure how to use this build backend.


I am particularly interested in not using any build backend, though... I would like to be able to just run regular CMake and Ninja commands, and also choose my own versions of build tools (gcc vs. clang, newer vs. older versions, etc). So if possible, I would love to drill down and find what's manipulating the RPATH in the "non build backend" case

villapx-path avatar Apr 24 '25 20:04 villapx-path

I should also mention, the Tasks section of the documentation was the main basis of my original pixi.toml, which does not define any build backend and does define the configure, build, etc tasks.

villapx-path avatar Apr 24 '25 20:04 villapx-path

Hmm, so I have made some progress here.

If I add the CMake argument -DCMAKE_INSTALL_RPATH='${ORIGIN}/../lib' to tasks.build.configure.cmd, it does add that value to the RPATH:

$ chrpath -l /tmp/app/bin/how-to-scan-images
/tmp/app/bin/how-to-scan-images: RPATH=${ORIGIN}/../lib:/home/jvilla/projects/pixi-sandbox/opencv-example/.pixi/envs/build/lib

So now, I am just hoping I can remove the environment-specific lib path from the RPATH... Although, I have tried a couple different CMake options, -DCMAKE_INSTALL_REMOVE_ENVIRONMENT_RPATH=1 and -DCMAKE_SKIP_INSTALL_RPATH=1, neither of which removes that environment-specific path. At this point, I am unsure if my issue is indeed that pixi is adding some voodoo RPATH path, or if my problem is purely CMake-related 😅

villapx-path avatar Apr 24 '25 20:04 villapx-path

Hmm, so I have made some progress here.

If I add the CMake argument -DCMAKE_INSTALL_RPATH='${ORIGIN}/../lib' to tasks.build.configure.cmd, it does add that value to the RPATH:

$ chrpath -l /tmp/app/bin/how-to-scan-images /tmp/app/bin/how-to-scan-images: RPATH=${ORIGIN}/../lib:/home/jvilla/projects/pixi-sandbox/opencv-example/.pixi/envs/build/lib So now, I am just hoping I can remove the environment-specific lib path from the RPATH... Although, I have tried a couple different CMake options, -DCMAKE_INSTALL_REMOVE_ENVIRONMENT_RPATH=1 and -DCMAKE_SKIP_INSTALL_RPATH=1, neither of which removes that environment-specific path. At this point, I am unsure if my issue is indeed that pixi is adding some voodoo RPATH path, or if my problem is purely CMake-related 😅

Maybe it relates to the gcc in conda. The specs for conda gcc has a rule that the link command always appends $PREFIX/lib at the end of rpath, see the install script of the package. After cmake build, the result rpath on the target .so file is <user-rpaths>::$PREFIX/lib, then cmake install cannot remove the :$PREFIX/lib suffix even if INSTALL_REMOVE_ENVIRONMENT_RPATH is set. In cmake code to change rpath, the generated OLD_PATH is <user-rpaths>: which will be replaced by ${INSTALL_RPATH}, left the toolchain suffix part :$PREFIX/lib unchanged.

In our projects, we prepend the $PREFIX/lib to rpath first, and the result rpath turns to $PREFIX/lib:<user-rpaths>:. Then cmake install can correctly replace it by ${INSTALL_RPATH} if set INSTALL_REMOVE_ENVIRONMENT_RPATH.

target_link_options(some_lib PRIVATE LINKER:-rpath,$ENV{CONDA_PREFIX}/lib)

gzm55 avatar Apr 25 '25 05:04 gzm55

Sorry for the delay, I was on paternity leave 😀

Thank you for that info @gzm55. So this may be a "feature" of conda-forge's packaging of GCC, rather than anything to do with pixi?

villapx-path avatar May 27 '25 19:05 villapx-path

@gzm55 That workaround did work, by the way -- thank you!

Do you know why it works?

Is it working because we are basically making the conda gcc specs "ignore" the RPATH, since we have already set it to the same value that it wants to set?

villapx-path avatar May 27 '25 20:05 villapx-path

@villapx-path

So this may be a "feature" of conda-forge's packaging of GCC, rather than anything to do with pixi?

Yes.

Is it working because we are basically making the conda gcc specs "ignore" the RPATH, since we have already set it to the same value that it wants to set?

ld: deduplicates the RPATH, keeping the first position of a path. cmake: INSTALL_REMOVE_ENVIRONMENT_RPATH does not go through each the rpath and detect the toochain lib dir, but just replace the toolchain lib added by cmake list file; also cmake does not aware the patch action of conda GCC.

The workaround use the deduplication of ld to disable the patch of conda GCC.

PS: this conflict should also appears when using micromamba or original conda, therefore cannot be improved in a package manager. I cannot say this is an issue of GCC conda spec or cmake, since they work fine on their own but only together. Maybe cmake could improve a little by using list substracting over rpath list instread of string replacement.

gzm55 avatar May 28 '25 00:05 gzm55

@gzm55 Awesome, thank you for the information, and the help

villapx-path avatar May 28 '25 13:05 villapx-path