Consider the following short project:
cmake_minimum_required(VERSION 3.16)
set(CMAKE_CONFIGURATION_TYPES "RelWithDebInfo;Debug")
set(CMAKE_DEFAULT_BUILD_TYPE "RelWithDebInfo")
set(CMAKE_DEFAULT_CONFIGS "all")
set(CMAKE_CROSS_CONFIGS "all")
project(ExternalProjectExample NONE)
include(ExternalProject)
# Assignment of config specific binary dir that causes build failure
set(ep_binary_dir "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>/mysub")
ExternalProject_Add(
mysub
#BINARY_DIR "${ep_binary_dir}"
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/mysub
INSTALL_COMMAND ""
)
ExternalProject_Get_Property(mysub BINARY_DIR)
message(">> BINARY_DIR ${BINARY_DIR}")
# Workaround
#set_property(
#TARGET "mysub"
#PROPERTY EXCLUDE_FROM_ALL "$<NOT:$<CONFIG:RelWithDebInfo>>")
So it’s a multi-config cross-config project building one EP. Running ninja will configure and build the EP in both configurations, RelWithDebInfo
and Debug
.
The external project it configures
# $ cat mysub/CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(mysub LANGUAGES CXX)
add_executable(app main.cpp)
// $ cat main.cpp
int main() {return 0;}
If you run ninja
after configuring with cmake .. -G"Ninja Multi-Config"
then CMake will configure the external project twice in the exact same build directory, overriding CMakeCache.txt
and other files.
...
cd /Volumes/T3/Dev/projects/cmake/general/external_project_multi_config/build/special/RelWithDebInfo && /usr/local/Cellar/cmake/3.26.4/bin/cmake "-GNinja Multi-Config" -S /Volumes/T3/Dev/projects/cmake/general/external_project_multi_config/mysub -B /Volumes/T3/Dev/projects/cmake/general/external_project_multi_config/build/special/RelWithDebInfo && /usr/local/Cellar/cmake/3.26.4/bin/cmake -E touch /Volumes/T3/Dev/projects/cmake/general/external_project_multi_config/build/mysub-prefix/src/mysub-stamp/RelWithDebInfo/mysub-configure
...
cd /Volumes/T3/Dev/projects/cmake/general/external_project_multi_config/build/special/Debug && /usr/local/Cellar/cmake/3.26.4/bin/cmake "-GNinja Multi-Config" -S /Volumes/T3/Dev/projects/cmake/general/external_project_multi_config/mysub -B /Volumes/T3/Dev/projects/cmake/general/external_project_multi_config/build/special/Debug && /usr/local/Cellar/cmake/3.26.4/bin/cmake -E touch /Volumes/T3/Dev/projects/cmake/general/external_project_multi_config/build/mysub-prefix/src/mysub-stamp/Debug/mysub-configure
This is because BINARY_DIR
does not contain any config-specific subdirectory, despite other metadata files (timestamp files) being config-specific.
This happens starting with CMake 3.24.0 up to 3.27.1.
If I change the BINARY_DIR
to contain a $<CONFIG>
subdirectory, then at build time ninja fails with
/bin/sh: line 0: cd: external_project_multi_config/build/RelWithDebInfo/mysub: No such file or directory
I can work around the issue of the overridden CMakeCache.txt
by explicitly excluding the Debug configuration from ALL
, but that is not entirely safe, because one could still run ninja mysub:Debug
explicitly and override the files.
I can also manually pre-create the subdirectories with something like:
set(ep_binary_dir "${CMAKE_CURRENT_BINARY_DIR}/mysub/$<CONFIG>")
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/mysub/RelWithDebInfo")
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/mysub/Debug")
And the build appears to work, but i also end up with an actuall directory called \$\<CONFIG\>
on the file system.
So what’s the intended way to assign unique BINARY_DIR
s for each config in such a case? Is it just not possible with the current release CMake versions?