Hi all,
I believe I’ve run into a small bug in GNUInstallDirs and wanted to check here before opening an issue.
When CMAKE_INSTALL_PREFIX is under /opt/ and CMP0192 is OLD or unset, the FHS special case produces a doubled separator. CMAKE_INSTALL_FULL_SYSCONFDIR comes out as /etc//opt/foo instead of /etc/opt/foo, and CMAKE_INSTALL_FULL_LOCALSTATEDIR and CMAKE_INSTALL_FULL_RUNSTATEDIR have the same problem. The special cases documentation says “the SYSCONFDIR value etc becomes /etc/opt/...”, so I don’t think the // is intended. I noticed it because installing to CMAKE_INSTALL_FULL_SYSCONFDIR triggers the CMP0177 warning, since normalizing would change the path.
cmake_minimum_required(VERSION 3.25)
project(Foo C)
set(CMAKE_INSTALL_PREFIX /opt/foo)
include(GNUInstallDirs)
message("SYSCONFDIR: ${CMAKE_INSTALL_FULL_SYSCONFDIR}")
message("LOCALSTATEDIR: ${CMAKE_INSTALL_FULL_LOCALSTATEDIR}")
message("RUNSTATEDIR: ${CMAKE_INSTALL_FULL_RUNSTATEDIR}")
install(FILES CMakeLists.txt DESTINATION ${CMAKE_INSTALL_FULL_SYSCONFDIR})
Output:
SYSCONFDIR: /etc//opt/foo
LOCALSTATEDIR: /var//opt/foo
RUNSTATEDIR: /var/run//opt/foo
CMake Warning (policy) at CMakeLists.txt:8 (install):
Policy CMP0177 is not set: install() DESTINATION paths are normalized. Run
"cmake --help-policy CMP0177" for policy details. Use the cmake_policy
command to set the policy and suppress this warning.
This warning is for project developers. Use -Wno-author or -Wno-policy to
suppress it.
Setting CMP0177 silences the warning, but the // is still there. Only CMP0192 NEW gives clean values. Results with a fresh build directory each time:
| CMP0177 | CMP0192 | CMP0177 warning | FULL_<dir> values |
|---|---|---|---|
| unset | unset | yes | contain // |
| unset | OLD | yes | contain // |
| unset | NEW | no | normalized |
| OLD or NEW | unset or OLD | no | contain // |
| OLD or NEW | NEW | no | normalized |
From reading Modules/GNUInstallDirs.cmake, I think the cause is _GNUInstallDirs_special_absolute, which builds the path as "/${original_path}/${install_prefix}". The prefix already starts with /, so the result is /etc//opt/foo. Both policy settings go through this helper. The difference is whether the result gets normalized afterwards:
- CMP0192 NEW:
__GNUInstallDirs_default_in_rootapplies the special case when computing the defaultCMAKE_INSTALL_SYSCONFDIR, then runscmake_path(NORMAL_PATH), and/etc/opt/foois cached. That value is already absolute, soGNUInstallDirs_get_absolute_install_dircopies it intoCMAKE_INSTALL_FULL_SYSCONFDIRunchanged. - CMP0192 OLD: the cached default stays relative (
etc), andGNUInstallDirs_get_absolute_install_dircalls_GNUInstallDirs_special_absoluteitself without normalizing, so the//ends up inCMAKE_INSTALL_FULL_SYSCONFDIR.
So as far as I can tell, CMP0192 behaves as documented. Its NEW behavior just happens to hide the doubled separator. The RunCMake GNUInstallDirs tests set CMP0192 NEW for every case, which may be why the OLD path wasn’t caught.
I’m using CMake 4.4.4 on Ubuntu 26.04.1 LTS, and the same code looks unchanged on current master commit (5856adc). I think the cleanest fix is to normalize the result in GNUInstallDirs_get_absolute_install_dir, either just for the special-case directories or for every FULL_<dir>, depending on how much of a behavior change is acceptable. Normalizing inside _GNUInstallDirs_special_absolute would also work.
Does this look like a bug worth an issue, or am I missing something? I’m happy to open an issue or put together a merge request, including a test for the CMP0192 OLD case.