Inconsistencies with install() and relative path to GNUInstallDirs

Hello,

I’m just reading and adapting a project for the installation phase which uses the GNUInstallDirs module:
https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html

The module documentation mentions some special cases, where, for example, when the install prefix is /usr, the CMAKE_INSTALL_FULL_SYSCONFDIR becomes /etc instead of the /usr/etc, and similar.

However, the CMake’s install() command doesn’t adjust the destination according to these special cases.

Is this considered a CMake bug, or is this something that projects should adapt by themselves, or is this something that the installation phase should adapt via the CMAKE_INSTALL_* variables by themselves?

There is also a bug opened here
https://gitlab.kitware.com/cmake/cmake/-/issues/25852

What would be the correct approach here at this point?

Thank you much in advance for any pointers here.

See special-cases

I did. Exactly these special cases aren’t taken into consideration when using the install() command and the installation prefix is set at the install phase (cmake --install ... --prefix /usr). In this case the libraries will be installed to “lib” instead of “lib/x86_64-linux-gnu”. If the install prefix is set at the configuration phase (-DCMAKE_INSTALL_PREFIX=/usr) then they are installed to “lib/x86_64-linux-gnu”, which makes the entire special cases inconsistent in CMake.

see too CPack empty archive with absolute install DESTINATION path

I do not know for what the CMAKE_INSTALL_FULL_XXX vars are, but I would not use them!

For what it’s worth, the CMAKE_INSTALL_LIBDIR is relative path (not related here to the CMAKE_INSTALL_FULL_LIBDIR) and it gets changed based on the install prefix. But only if the install prefix is set during the configuration phase. It is not changed if the install prefix is set at the install phase. Meaning, I don’t use these CMAKE_INSTALL_FULL_* variables either. But I do get different but inconsistent CMAKE_INSTALL_* relative path variables.

Do you mean this strange behaviour:

bash-5.2$ cmake --preset default --fresh
Preset CMake variables:

  BUILD_SHARED_LIBS="NO"
  CMAKE_BUILD_TYPE="Release"
  CMAKE_CXX_STANDARD="20"
  CMAKE_DEBUG_POSTFIX="D"
  CMAKE_INSTALL_PREFIX="/usr"
  CMAKE_PREFIX_PATH:STRING="/Users/clausklein/Workspace/cpp/ModernCmakeStarter/stagedir"

Preset environment variables:

  CPM_USE_LOCAL_PACKAGES="YES"
  
-- The CXX compiler identification is AppleClang 16.0.0.16000026
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found Git: /usr/local/bin/git (found version "2.46.2")
-- CPM: Adding package project_options@0.30.0 (v0.30.0 at /Users/clausklein/.cache/CPM/project_options/2de06ed3a4c11a5a7ebda8b90d2dd05882bbb095)
-- CPM: Adding package PackageProject.cmake@1.10.0 (v1.10.0 at /Users/clausklein/.cache/CPM/packageproject.cmake/41b1a5028ad2c8d2c6bb64eb6beff8b090d304e5)
-- CPM: Using local package fmt@10.0.0
-- Developer mode is OFF. For developement, use `-DENABLE_DEVELOPER_MODE:BOOL=ON`. Building the project for the end-user...
-- The default CMAKE_C_STANDARD used by external targets and tools is not set yet. Using the latest supported C standard that is 90
-- /usr/local/bin/ccache found and enabled
-- Configuring done (0.5s)
-- Generating done (0.0s)
-- Build files have been written to: /Users/clausklein/Workspace/cpp/ModernCmakeStarter/build/user
bash-5.2$ DESTDIR=/tmp/test cmake --install build/user              
-- Install configuration: "Release"
-- Up-to-date: /tmp/test/usr/etc/test.cfg
-- Up-to-date: /tmp/test/usr/include/greeter
-- Up-to-date: /tmp/test/usr/include/greeter/greeter
-- Up-to-date: /tmp/test/usr/include/greeter/greeter/version.h
-- Installing: /tmp/test/usr/lib/greeter/libgreeter.a
-- Installing: /tmp/test/usr/lib/cmake/greeter/greeterTargets.cmake
-- Installing: /tmp/test/usr/lib/cmake/greeter/greeterTargets-release.cmake
-- Up-to-date: /tmp/test/usr/lib/cmake/greeter/greeterConfigVersion.cmake
-- Up-to-date: /tmp/test/usr/lib/cmake/greeter/greeterConfig.cmake
-- Up-to-date: /tmp/test/usr/include/greeter
-- Up-to-date: /tmp/test/usr/include/greeter/greeter
-- Up-to-date: /tmp/test/usr/include/greeter/greeter/greeter.h
bash-5.2$ sudo cmake --install build/user 
Password:
-- Install configuration: "Release"
CMake Error at build/user/cmake_install.cmake:41 (file):
  file cannot create directory: /usr/etc.  Maybe need administrative
  privileges.

bash-5.2$ 

A very simple reproducible example:

# CMakeLists.txt
cmake_minimum_required(VERSION 3.25 FATAL_ERROR)

project(TestingInstallation LANGUAGES C)

include(GNUInstallDirs)

add_library(testinginstallation SHARED lib.c)

include(GNUInstallDirs)

install(
  TARGETS testinginstallation
)
/* lib.c */
int foo(void) { return 0; }

Then here the first installation sets the installation prefix to /usr in the configuration step, which sets the CMAKE_INSTALL_LIBDIR to lib/x86_64-linux-gnu on Debian-based machines (Ubuntu included):

# Shell commands
cmake -S . -B build-1 --install-prefix /usr
cmake --build build-1 -j
DESTDIR=/home/user/testing-installation/1 cmake --install build-1
/home/user/testing-installation/1
└── usr
    └── lib
        └── x86_64-linux-gnu
            └── libtestinginstallation.so

The 2nd installation sets the installation prefix /usr (same as above) but in the installation phase using the --prefix option:

# Shell commands
cmake -S . -B build-2
cmake --build build-2 -j
DESTDIR=/home/user/testing-installation-2 cmake --install build-2 --prefix /usr

Difference in the installation directory structure here is that the libdir is the lib without the machine/architecture subdirectory:

/home/user/testing-installation/2
└── usr
    └── lib
        └── libtestinginstallation.so

Same issue happens with all these mentioned special cases in the GNUInstallDirs module.

I think I’ll start resolving this for now by creating a custom install module that overrides the CMake’s install commands that I use and have all the CMAKE_INSTALL_* variables available in their adjusted form in the install() scripts. Another option would be to not use the --prefix option in the cmake --install phase and warn about this in the documentation. But I would probably miss something then here for the CPack etc.

It would be kind of good for CMake to address these issues in the CMake code directly instead of users needing to do this kind of adjustments or that the packager needs to set all the CMAKE_INSTALL_* variables when packaging the software.

Because this is too difficult to get right and it makes the installation phase too cumbersome to use for the projects.

1 Like

Yes, that is true, but CMAKE_INSTALL_SYSCONFDIR is usr/etc, if the installation is redirected with DESTDIR=/tmp/stagedir cmake --install ...

@ben.boeckel this seeam a CMake bug or stange feature?

see Inconsistencies with install() and relative path to GNUInstallDirs - #6 by ClausKlein

1 Like