diff --git a/Help/manual/cmake-policies.7.rst b/Help/manual/cmake-policies.7.rst
index 8f28cf6042..821adc930f 100644
--- a/Help/manual/cmake-policies.7.rst
+++ b/Help/manual/cmake-policies.7.rst
@@ -98,6 +98,7 @@ Policies Introduced by CMake 4.1
.. toctree::
:maxdepth: 1
+ CMP0193: GNUInstallDirs caches CMAKE_INSTALL_* with leading 'usr/' for install prefix '/'.
CMP0192: GNUInstallDirs uses absolute SYSCONFDIR, LOCALSTATEDIR, and RUNSTATEDIR in special prefixes.
CMP0191: The FindCABLE module is removed.
CMP0190: FindPython enforce consistency in cross-compiling mode.
diff --git a/Help/policy/CMP0193.rst b/Help/policy/CMP0193.rst
new file mode 100644
index 0000000000..633291e41b
--- /dev/null
+++ b/Help/policy/CMP0193.rst
@@ -0,0 +1,31 @@
+CMP0193
+-------
+
+.. versionadded:: 4.1
+
+:module:`GNUInstallDirs` caches ``CMAKE_INSTALL_*`` with leading ``usr/`` for
+install prefix ``/``.
+
+When :variable:`CMAKE_INSTALL_PREFIX` is ``/``, the ``CMAKE_INSTALL_
``
+variables, for ```` equal to ``BINDIR``, ``SBINDIR``, ``LIBEXECDIR``,
+``SHAREDSTATEDIR``, ``INCLUDEDIR``, ``OLDINCLUDEDIR``, ``DATAROOTDIR``, and
+``LIBDIR``, are prepended with a leading ``usr/`` as documented among the
+:ref:`special cases `.
+In CMake 4.0 and below, these ``CMAKE_INSTALL_`` variables were cached
+without their leading ``usr/``, and it was prepended in normal variables that
+shadow their cache entries.
+CMake 4.1 and above prefer to cache ``CMAKE_INSTALL_`` with their leading
+``usr/``. Consequently, the :command:`GNUInstallDirs_get_absolute_install_dir`
+command no longer alters the relative ``var`` input variable. This policy
+provides compatibility for projects that have not been updated to expect
+the new behavior.
+
+The ``OLD`` behavior for this policy is to cache ``CMAKE_INSTALL_``
+variables without the leading ``usr/``. The ``NEW`` behavior for this
+policy is to cache ``CMAKE_INSTALL_`` variables with the leading ``usr/``.
+
+.. |INTRODUCED_IN_CMAKE_VERSION| replace:: 4.1
+.. |WARNS_OR_DOES_NOT_WARN| replace:: does *not* warn
+.. include:: include/STANDARD_ADVICE.rst
+
+.. include:: include/DEPRECATED.rst
diff --git a/Help/release/dev/GNUInstallDirs-special-cases.rst b/Help/release/dev/GNUInstallDirs-special-cases.rst
index 05c1a740ed..b21841038f 100644
--- a/Help/release/dev/GNUInstallDirs-special-cases.rst
+++ b/Help/release/dev/GNUInstallDirs-special-cases.rst
@@ -5,3 +5,7 @@ GNUInstallDirs-special-cases
``SYSCONFDIR``, ``LOCALSTATEDIR``, and ``RUNSTATEDIR`` to
absolute paths when installing to special prefixes.
See policy :policy:`CMP0192`.
+
+* The :module:`GNUInstallDirs` module now caches ``CMAKE_INSTALL_*``
+ variables with their leading ``usr/`` for install prefix ``/``.
+ See policy :policy:`CMP0193`.
diff --git a/Modules/GNUInstallDirs.cmake b/Modules/GNUInstallDirs.cmake
index ef9b530cf4..a730764713 100644
--- a/Modules/GNUInstallDirs.cmake
+++ b/Modules/GNUInstallDirs.cmake
@@ -129,6 +129,10 @@ The following values of :variable:`CMAKE_INSTALL_PREFIX` are special:
When building the complete GNU system, the prefix will be empty
and ``/usr`` will be a symbolic link to ``/``.
+ .. versionchanged:: 4.1
+ The ``CMAKE_INSTALL_`` variables are cached with the ``usr/`` prefix.
+ See policy :policy:`CMP0193`.
+
``/usr``
For ```` equal to ``SYSCONFDIR``, ``LOCALSTATEDIR`` or
@@ -186,6 +190,9 @@ Functions
.. versionchanged:: 3.20
Added the ```` parameter. Previous versions of CMake passed
this value through the variable ``${dir}``.
+
+ .. versionchanged:: 4.1
+ The ``var`` variable is no longer altered. See policy :policy:`CMP0193`.
#]=======================================================================]
cmake_policy(SET CMP0140 NEW)
@@ -330,6 +337,20 @@ function(__GNUInstallDirs_default_in_root out_var original_path install_prefix)
return(PROPAGATE ${out_var})
endfunction()
+# Common handler for defaults that should be in usr/
+function(__GNUInstallDirs_default_in_usr out_var initial_value install_prefix)
+ set(${out_var} "${initial_value}")
+ if(install_prefix STREQUAL "/")
+ cmake_policy(GET CMP0193 cmp0193
+ PARENT_SCOPE # undocumented, do not use outside of CMake
+ )
+ if(cmp0193 STREQUAL "NEW")
+ set(${out_var} "usr/${${out_var}}")
+ endif()
+ endif()
+ return(PROPAGATE ${out_var})
+endfunction()
+
# Installation directories
#
@@ -388,6 +409,7 @@ function(_GNUInstallDirs_LIBDIR_get_default out_var install_prefix)
endif()
endif()
endif()
+ __GNUInstallDirs_default_in_usr(${out_var} "${${out_var}}" "${install_prefix}")
return(PROPAGATE ${out_var})
endfunction()
@@ -423,6 +445,31 @@ function(_GNUInstallDirs_RUNSTATEDIR_get_default out_var install_prefix)
return(PROPAGATE ${out_var})
endfunction()
+# All of the other (primitive) dirs are typically in usr/.
+# A special handling is needed for the `/` install_prefix
+foreach(dir IN ITEMS
+ BINDIR
+ SBINDIR
+ LIBEXECDIR
+ SHAREDSTATEDIR
+ INCLUDEDIR
+ OLDINCLUDEDIR
+ DATAROOTDIR
+ # Except all the previous ones that had a special handling:
+ # LIBDIR, SYSCONFDIR, LOCALSTATEDIR, OLDINCLUDEDIR
+)
+ # Cannot call function() directly because `dir` would not be accessible inside the function
+ # Using cmake_language(EVAL) to call a short wrapper function instead
+ cmake_language(EVAL CODE "
+ function(_GNUInstallDirs_${dir}_get_default out_var install_prefix)
+ set(\${out_var} \"\${_GNUInstallDirs_${dir}_DEFAULT}\")
+ __GNUInstallDirs_default_in_usr(\${out_var} \"\${\${out_var}}\" \"\${install_prefix}\")
+ return(PROPAGATE \${out_var})
+ endfunction()
+ "
+ )
+endforeach()
+
_GNUInstallDirs_cache_path(BINDIR
"User executables")
_GNUInstallDirs_cache_path(SBINDIR
@@ -571,7 +618,14 @@ function(GNUInstallDirs_get_absolute_install_dir absvar var)
set(${absvar} "${${var}}")
endif()
- return(PROPAGATE ${var} ${absvar})
+ set(return_vars ${absvar})
+ cmake_policy(GET CMP0193 cmp0193
+ PARENT_SCOPE # undocumented, do not use outside of CMake
+ )
+ if(NOT cmp0193 STREQUAL "NEW")
+ list(APPEND return_vars ${var})
+ endif()
+ return(PROPAGATE ${return_vars})
endfunction()
# Result directories
diff --git a/Source/cmPolicies.h b/Source/cmPolicies.h
index c91fec6410..0d65c49f89 100644
--- a/Source/cmPolicies.h
+++ b/Source/cmPolicies.h
@@ -574,6 +574,10 @@ class cmMakefile;
SELECT(POLICY, CMP0192, \
"GNUInstallDirs uses absolute SYSCONFDIR, LOCALSTATEDIR, and " \
"RUNSTATEDIR in special prefixes.", \
+ 4, 1, 0, WARN) \
+ SELECT(POLICY, CMP0193, \
+ "GNUInstallDirs caches CMAKE_INSTALL_* with leading 'usr/' for " \
+ "install prefix '/'.", \
4, 1, 0, WARN)
#define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
diff --git a/Tests/RunCMake/GNUInstallDirs/CMakeLists.txt b/Tests/RunCMake/GNUInstallDirs/CMakeLists.txt
index 0f0de84a83..38ff9b3fb3 100644
--- a/Tests/RunCMake/GNUInstallDirs/CMakeLists.txt
+++ b/Tests/RunCMake/GNUInstallDirs/CMakeLists.txt
@@ -1,4 +1,5 @@
cmake_minimum_required(VERSION 3.10)
cmake_policy(SET CMP0192 NEW)
+cmake_policy(SET CMP0193 NEW)
project(${RunCMake_TEST} NONE)
include(${RunCMake_TEST}.cmake)
diff --git a/Tests/RunCMake/GNUInstallDirs/GetAbs-stderr.txt b/Tests/RunCMake/GNUInstallDirs/GetAbs-stderr.txt
index 1c2f0846eb..f0378c49f9 100644
--- a/Tests/RunCMake/GNUInstallDirs/GetAbs-stderr.txt
+++ b/Tests/RunCMake/GNUInstallDirs/GetAbs-stderr.txt
@@ -6,7 +6,7 @@ CMake Warning \(dev\) at [^
below.
Call Stack \(most recent call first\):
GetAbs.cmake:10 \(GNUInstallDirs_get_absolute_install_dir\)
- CMakeLists.txt:4 \(include\)
+ CMakeLists.txt:5 \(include\)
This warning is for project developers. Use -Wno-dev to suppress it.
+
PROJ2_FULL_BINDIR='/usr/bin'$