ExternalProject: Default CMAKE_INSTALL_PREFIX to INSTALL_DIR

The default CMake configure command never set an install prefix, so
external projects installed to /usr/local rather than the INSTALL_DIR
the module already reserves. Add policy CMP0225 to pass it by default;
an explicit prefix from the caller still takes precedence.

Fixes: #27050
This commit is contained in:
15daksh-2003 committed 2026-09-23 18:41:33 +05:30
1 parent de8a69ce2d
commit 2b07afa1a1
35 files changed
+296 -4

No files matched your search

+1
View File
@@ -100,6 +100,7 @@ Policies Introduced by CMake 4.5
.. toctree::
:maxdepth: 1
CMP0225: ExternalProject sets CMAKE_INSTALL_PREFIX to <INSTALL_DIR> in the default configure command. </policy/CMP0225>
CMP0224: Test fixtures use AROUND_EACH_REPEAT mode by default with ctest --repeat. </policy/CMP0224>
CMP0223: An empty path is not a prefix of any path. </policy/CMP0223>
CMP0222: The if() command supports path prefix tests using PATH_IS_PREFIX operator. </policy/CMP0222>
+52
View File
@@ -0,0 +1,52 @@
CMP0225
-------
.. versionadded:: 4.5
:command:`ExternalProject_Add` sets :variable:`CMAKE_INSTALL_PREFIX` to
``<INSTALL_DIR>`` in the default configure command.
When no ``CONFIGURE_COMMAND`` is given, :command:`ExternalProject_Add` drives
the external project as a CMake project with a default configure command.
CMake 4.4 and below do not pass :variable:`CMAKE_INSTALL_PREFIX` in that
command, so the external project's install step uses CMake's built-in default
prefix, such as ``/usr/local`` on Unix or ``C:/Program Files`` on Windows,
rather than the ``INSTALL_DIR`` that the module reserves for it inside the
build tree. Installing to a shared system prefix as a side effect of a build
is surprising and often fails for lack of permission.
CMake 4.5 and above prefer to set :variable:`CMAKE_INSTALL_PREFIX` to the
external project's ``INSTALL_DIR``, so that it installs to the location the
module reserves for it.
The ``OLD`` behavior of this policy does not set
:variable:`CMAKE_INSTALL_PREFIX` in the default configure command. The
``NEW`` behavior sets it to the external project's ``INSTALL_DIR``.
This policy affects only the default configure command; a custom
``CONFIGURE_COMMAND``, including an empty one, is unaffected.
A :variable:`CMAKE_INSTALL_PREFIX` given by the caller in ``CMAKE_ARGS`` (for
example, through :option:`cmake --install-prefix`) or in ``CMAKE_CACHE_ARGS``
still takes precedence. A prefix given in ``CMAKE_CACHE_DEFAULT_ARGS`` does
not, matching that option's non-overriding-default semantics.
Setting :variable:`CMAKE_INSTALL_PREFIX` in the external project also has
effects beyond the install location:
:variable:`CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT` is left false, and the
prefix is prepended to :variable:`CMAKE_SYSTEM_PREFIX_PATH`, which can change
``find_*()`` results. These effects apply even when the install step is
disabled with ``INSTALL_COMMAND ""``.
Adopting the ``NEW`` behavior in an existing build tree reconfigures the
external project and overwrites its cached :variable:`CMAKE_INSTALL_PREFIX`;
returning to the ``OLD`` behavior does not restore the previous cached value.
As with the other directory placeholders, changing ``INSTALL_DIR`` later may
not reconfigure the external project with every generator, so its build tree
may need to be cleaned for the cached prefix to update.
.. |INTRODUCED_IN_CMAKE_VERSION| replace:: 4.5
.. |WARNS_OR_DOES_NOT_WARN| replace:: warns
.. include:: include/STANDARD_ADVICE.rst
.. include:: include/DEPRECATED.rst
@@ -0,0 +1,9 @@
externalproject-install-prefix
------------------------------
* The :command:`ExternalProject_Add` command now sets
:variable:`CMAKE_INSTALL_PREFIX` to the external project's ``INSTALL_DIR`` in
its default configure command, so a CMake external project installs into the
prefix the module reserves for it rather than the built-in default (e.g.
``/usr/local``). A caller-supplied prefix still takes precedence. See policy
:policy:`CMP0225`.
+34 -4
View File
@@ -96,9 +96,14 @@ external project.
``INSTALL_DIR <dir>``
Installation prefix to be placed in the ``<INSTALL_DIR>`` placeholder.
This does not actually configure the external project to install to
the given prefix. That must be done by passing appropriate arguments
to the external project configuration step, e.g. using ``<INSTALL_DIR>``.
When the default configure command is used (i.e. no ``CONFIGURE_COMMAND``
is given) and policy :policy:`CMP0225` is ``NEW``, the external project's
:variable:`CMAKE_INSTALL_PREFIX` defaults to ``<INSTALL_DIR>``. Otherwise,
or for a custom configure command, setting ``INSTALL_DIR`` does not by itself
configure the external project to install to the given prefix; that must be
done by passing appropriate arguments to the external project configuration
step, e.g. using ``<INSTALL_DIR>``.
If any of the above ``..._DIR`` options are not specified, their defaults
are computed as follows. If the ``PREFIX`` option is given or the
@@ -586,6 +591,10 @@ overridden if required.
reuse from the main project or otherwise specify (see ``CMAKE_ARGS``,
``CMAKE_CACHE_ARGS`` and ``CMAKE_CACHE_DEFAULT_ARGS`` below).
When policy :policy:`CMP0225` is set to ``NEW``, the default configure
command also sets :variable:`CMAKE_INSTALL_PREFIX` to ``<INSTALL_DIR>``,
unless the caller specifies a prefix of their own.
For non-CMake external projects, the ``CONFIGURE_COMMAND`` option must
be used to override the default configure command
(:manual:`generator expressions <cmake-generator-expressions(7)>` are
@@ -2656,7 +2665,6 @@ function(_ep_extract_configure_command var name)
TARGET ${name}
PROPERTY _EP_CMAKE_ARGS
)
list(APPEND cmd ${cmake_args})
# If there are any CMAKE_CACHE_ARGS or CMAKE_CACHE_DEFAULT_ARGS,
# write an initial cache and use it
@@ -2669,6 +2677,25 @@ function(_ep_extract_configure_command var name)
PROPERTY _EP_CMAKE_CACHE_DEFAULT_ARGS
)
# Inject the <INSTALL_DIR> prefix ahead of the caller's arguments so an
# explicit CMAKE_INSTALL_PREFIX still wins. See policy CMP0225.
if(_EP_CMP0225 STREQUAL "NEW")
list(APPEND cmd "-DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>")
elseif(_EP_CMP0225 STREQUAL ""
AND NOT "${cmake_args};${cmake_cache_args};${cmake_cache_default_args}"
MATCHES "CMAKE_INSTALL_PREFIX|install-prefix")
cmake_policy(GET_WARNING CMP0225 _ep_cmp0225_warning)
message(AUTHOR_WARNING
"${_ep_cmp0225_warning}\n"
"ExternalProject_Add(${name}) uses the default configure command "
"without a CMAKE_INSTALL_PREFIX. The OLD behavior leaves the external "
"project's install prefix at the CMake default; the NEW behavior sets "
"it to <INSTALL_DIR>."
)
endif()
list(APPEND cmd ${cmake_args})
set(has_cmake_cache_args 0)
if(NOT "${cmake_cache_args}" STREQUAL "")
set(has_cmake_cache_args 1)
@@ -3182,6 +3209,9 @@ function(ExternalProject_Add name)
cmake_policy(GET CMP0135 _EP_CMP0135
PARENT_SCOPE # undocumented, do not use outside of CMake
)
cmake_policy(GET CMP0225 _EP_CMP0225
PARENT_SCOPE # undocumented, do not use outside of CMake
)
_ep_get_configuration_subdir_genex(cfgdir)
+4
View File
@@ -676,6 +676,10 @@ class cmMakefile;
SELECT(POLICY, CMP0224, \
"Test fixtures use AROUND_EACH_REPEAT mode by default with " \
"ctest --repeat.", \
4, 5, 0, WARN) \
SELECT(POLICY, CMP0225, \
"ExternalProject sets CMAKE_INSTALL_PREFIX to <INSTALL_DIR> in the " \
"default configure command.", \
4, 5, 0, WARN)
#define CM_SELECT_ID(F, A1, A2, A3, A4, A5, A6) F(A1)
+1
View File
@@ -1,3 +1,4 @@
cmake_minimum_required(VERSION 3.23)
project(${RunCMake_TEST} NONE)
cmake_policy(SET CMP0225 NEW)
include(${RunCMake_TEST}.cmake)
+1
View File
@@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.25)
project(@projName@ LANGUAGES NONE)
@policyCommand@
cmake_policy(SET CMP0225 NEW)
include(ExternalProject)
ExternalProject_Add(ep-@depName@
+1
View File
@@ -1251,6 +1251,7 @@ add_RunCMake_test(ExternalProject -DDETECT_JOBSERVER=$<TARGET_FILE:detect_jobser
if(CMake_TEST_RunCMake_ExternalProject_RUN_SERIAL)
set_property(TEST RunCMake.ExternalProject PROPERTY RUN_SERIAL TRUE)
endif()
add_RunCMake_test(ExternalProject-CMP0225)
add_RunCMake_test(FetchContent)
add_RunCMake_test(FetchContent_find_package)
if(CMake_TEST_CLANG_COVERAGE)
@@ -0,0 +1,11 @@
set(cache "${RunCMake_TEST_BINARY_DIR}/Sub-build/CMakeCache.txt")
if(NOT EXISTS "${cache}")
set(RunCMake_TEST_FAILED "External project was not configured: ${cache}")
return()
endif()
file(STRINGS "${cache}" prefix_line REGEX "^CMAKE_INSTALL_PREFIX:" ENCODING UTF-8)
set(expected "CMAKE_INSTALL_PREFIX:PATH=${RunCMake_TEST_BINARY_DIR}/override")
if(NOT prefix_line STREQUAL expected)
set(RunCMake_TEST_FAILED
"Caller CMAKE_ARGS prefix should win.\n expected: ${expected}\n actual: ${prefix_line}")
endif()
@@ -0,0 +1,12 @@
cmake_policy(SET CMP0225 NEW)
include(ExternalProject)
ExternalProject_Add(Sub
SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/Sub"
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/Sub-build"
INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/install dir"
CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/override"
DOWNLOAD_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
)
@@ -0,0 +1,11 @@
set(cache "${RunCMake_TEST_BINARY_DIR}/Sub-build/CMakeCache.txt")
if(NOT EXISTS "${cache}")
set(RunCMake_TEST_FAILED "External project was not configured: ${cache}")
return()
endif()
file(STRINGS "${cache}" prefix_line REGEX "^CMAKE_INSTALL_PREFIX:" ENCODING UTF-8)
set(expected "CMAKE_INSTALL_PREFIX:PATH=${RunCMake_TEST_BINARY_DIR}/override")
if(NOT prefix_line STREQUAL expected)
set(RunCMake_TEST_FAILED
"Caller --install-prefix should win.\n expected: ${expected}\n actual: ${prefix_line}")
endif()
@@ -0,0 +1,12 @@
cmake_policy(SET CMP0225 NEW)
include(ExternalProject)
ExternalProject_Add(Sub
SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/Sub"
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/Sub-build"
INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/install dir"
CMAKE_ARGS "--install-prefix=${CMAKE_CURRENT_BINARY_DIR}/override"
DOWNLOAD_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
)
@@ -0,0 +1,11 @@
set(cache "${RunCMake_TEST_BINARY_DIR}/Sub-build/CMakeCache.txt")
if(NOT EXISTS "${cache}")
set(RunCMake_TEST_FAILED "External project was not configured: ${cache}")
return()
endif()
file(STRINGS "${cache}" prefix_line REGEX "^CMAKE_INSTALL_PREFIX:" ENCODING UTF-8)
set(expected "CMAKE_INSTALL_PREFIX:PATH=${RunCMake_TEST_BINARY_DIR}/install dir")
if(NOT prefix_line STREQUAL expected)
set(RunCMake_TEST_FAILED
"External project cache has unexpected CMAKE_INSTALL_PREFIX.\n expected: ${expected}\n actual: ${prefix_line}")
endif()
@@ -0,0 +1,11 @@
cmake_policy(SET CMP0225 NEW)
include(ExternalProject)
ExternalProject_Add(Sub
SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/Sub"
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/Sub-build"
INSTALL_DIR "${CMAKE_CURRENT_BINARY_DIR}/install dir"
DOWNLOAD_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
)
@@ -0,0 +1,5 @@
file(READ "${RunCMake_TEST_BINARY_DIR}/tmp/Sub-cfgcmd.txt" cfgcmd)
if(NOT cfgcmd MATCHES "CMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>")
set(RunCMake_TEST_FAILED
"Default configure command did not inject <INSTALL_DIR> prefix:\n${cfgcmd}")
endif()
@@ -0,0 +1,5 @@
file(READ "${RunCMake_TEST_BINARY_DIR}/tmp/Sub-cfgcmd.txt" cfgcmd)
if(cfgcmd MATCHES "CMAKE_INSTALL_PREFIX")
set(RunCMake_TEST_FAILED
"Custom configure command should not receive a prefix:\n${cfgcmd}")
endif()
@@ -0,0 +1,8 @@
cmake_policy(SET CMP0225 NEW)
include(ExternalProject)
ExternalProject_Add(Sub
SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/Sub"
TMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/tmp"
CONFIGURE_COMMAND "${CMAKE_COMMAND}" -E echo "custom configure"
DOWNLOAD_COMMAND ""
)
@@ -0,0 +1,5 @@
file(READ "${RunCMake_TEST_BINARY_DIR}/tmp/Sub-cfgcmd.txt" cfgcmd)
if(cfgcmd MATCHES "CMAKE_INSTALL_PREFIX")
set(RunCMake_TEST_FAILED
"Empty configure command should not receive a prefix:\n${cfgcmd}")
endif()
@@ -0,0 +1,8 @@
cmake_policy(SET CMP0225 NEW)
include(ExternalProject)
ExternalProject_Add(Sub
SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/Sub"
TMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/tmp"
CONFIGURE_COMMAND ""
DOWNLOAD_COMMAND ""
)
@@ -0,0 +1,7 @@
cmake_policy(SET CMP0225 NEW)
include(ExternalProject)
ExternalProject_Add(Sub
SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/Sub"
TMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/tmp"
DOWNLOAD_COMMAND ""
)
@@ -0,0 +1,5 @@
file(READ "${RunCMake_TEST_BINARY_DIR}/tmp/Sub-cfgcmd.txt" cfgcmd)
if(cfgcmd MATCHES "CMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>")
set(RunCMake_TEST_FAILED
"OLD behavior unexpectedly injected <INSTALL_DIR> prefix:\n${cfgcmd}")
endif()
@@ -0,0 +1,7 @@
cmake_policy(SET CMP0225 OLD)
include(ExternalProject)
ExternalProject_Add(Sub
SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/Sub"
TMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/tmp"
DOWNLOAD_COMMAND ""
)
@@ -0,0 +1,5 @@
file(READ "${RunCMake_TEST_BINARY_DIR}/tmp/Sub-cfgcmd.txt" cfgcmd)
if(cfgcmd MATCHES "CMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>")
set(RunCMake_TEST_FAILED
"Unset policy unexpectedly injected <INSTALL_DIR> prefix:\n${cfgcmd}")
endif()
@@ -0,0 +1,5 @@
file(READ "${RunCMake_TEST_BINARY_DIR}/tmp/Sub-cfgcmd.txt" cfgcmd)
if(cfgcmd MATCHES "CMAKE_INSTALL_PREFIX")
set(RunCMake_TEST_FAILED
"Empty configure command should not receive a prefix:\n${cfgcmd}")
endif()
@@ -0,0 +1,7 @@
include(ExternalProject)
ExternalProject_Add(Sub
SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/Sub"
TMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/tmp"
CONFIGURE_COMMAND ""
DOWNLOAD_COMMAND ""
)
@@ -0,0 +1,5 @@
file(READ "${RunCMake_TEST_BINARY_DIR}/tmp/Sub-cfgcmd.txt" cfgcmd)
if(cfgcmd MATCHES "CMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>")
set(RunCMake_TEST_FAILED
"Caller prefix should not trigger <INSTALL_DIR> injection:\n${cfgcmd}")
endif()
@@ -0,0 +1,7 @@
include(ExternalProject)
ExternalProject_Add(Sub
SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/Sub"
TMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/tmp"
CMAKE_ARGS "-DCMAKE_INSTALL_PREFIX:PATH=${CMAKE_CURRENT_BINARY_DIR}/caller"
DOWNLOAD_COMMAND ""
)
@@ -0,0 +1,10 @@
CMake Warning \(author\) at .*/Modules/ExternalProject\.cmake:[0-9]+ \(message\):
Policy CMP0225 is not set: ExternalProject sets CMAKE_INSTALL_PREFIX to
<INSTALL_DIR> in the default configure command\. Run "cmake --help-policy
CMP0225" for policy details\. Use the cmake_policy command to set the
policy and suppress this warning\.
ExternalProject_Add\(Sub\) uses the default configure command without a
CMAKE_INSTALL_PREFIX\. The OLD behavior leaves the external project's
install prefix at the CMake default; the NEW behavior sets it to
<INSTALL_DIR>\.
@@ -0,0 +1,6 @@
include(ExternalProject)
ExternalProject_Add(Sub
SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/Sub"
TMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/tmp"
DOWNLOAD_COMMAND ""
)
@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.19)
project(${RunCMake_TEST} NONE)
include(${RunCMake_TEST}.cmake)
@@ -0,0 +1,22 @@
include(RunCMake)
run_cmake(CMP0225-NEW)
run_cmake(CMP0225-OLD)
run_cmake(CMP0225-WARN)
run_cmake(CMP0225-WARN-prefix)
run_cmake(CMP0225-NEW-custom)
run_cmake(CMP0225-NEW-empty)
run_cmake(CMP0225-WARN-empty)
function(run_CMP0225_effective case)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/${case}-build)
set(RunCMake_TEST_NO_CLEAN 1)
file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
run_cmake(${case})
run_cmake_command(${case}-build ${CMAKE_COMMAND} --build .)
endfunction()
run_CMP0225_effective(CMP0225-Effective-NEW)
run_CMP0225_effective(CMP0225-Effective-Args)
run_CMP0225_effective(CMP0225-Effective-Flag)
@@ -0,0 +1,2 @@
cmake_minimum_required(VERSION 3.18)
project(Sub NONE)
@@ -5,6 +5,7 @@ else()
cmake_policy(SET CMP0114 OLD) # Test deprecated behavior.
endif()
cmake_policy(SET CMP0135 NEW)
cmake_policy(SET CMP0225 NEW)
include(ExternalProject)
@@ -5,6 +5,7 @@ else()
cmake_policy(SET CMP0114 OLD) # Test deprecated behavior.
endif()
cmake_policy(SET CMP0135 NEW)
cmake_policy(SET CMP0225 NEW)
include(ExternalProject)
@@ -4,4 +4,5 @@ if(CMAKE_XCODE_BUILD_SYSTEM VERSION_GREATER_EQUAL 12 AND NOT RunCMake_TEST STREQ
cmake_policy(SET CMP0114 NEW)
endif()
cmake_policy(SET CMP0135 NEW)
cmake_policy(SET CMP0225 NEW)
include(${RunCMake_TEST}.cmake)