Per-config sources in object libraries broke with Ninja Multi-Config + cross config mode in 3.21.0
Attaching sample project.
cmake_minimum_required(VERSION 3.20)
get_property(is_multi GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(is_multi)
# Activate default cross config mode. Required to reproduce the issue.
set(CMAKE_CROSS_CONFIGS "all" CACHE STRING "")
set(CMAKE_DEFAULT_BUILD_TYPE "Release" CACHE STRING "")
set(CMAKE_DEFAULT_CONFIGS "all" CACHE STRING "")
endif()
project(proj LANGUAGES CXX)
# Make a regular library.
set(target "corelib")
set(file_path "${CMAKE_CURRENT_BINARY_DIR}/core_kernel.cpp")
file(GENERATE OUTPUT "${file_path}" CONTENT "int foo() {return 0;}")
add_library(${target} SHARED ${file_path})
# Prepare cpp source file name and location, to allow for config specific files.
set(cpp_file_output "${CMAKE_BINARY_DIR}/")
if(is_multi)
string(APPEND cpp_file_output "$<CONFIG>/")
endif()
string(APPEND cpp_file_output "generated_file_impl.cpp")
# Prepare CPP contents. Contains config specific value.
set(contents "
#include <string>
std::string name_of_target() { return \"$<TARGET_FILE_NAME:${target}\";}")
file(GENERATE OUTPUT "${cpp_file_output}"
CONTENT "${contents}"
)
# When using NMC, expand $<CONFIG> in the file name into a list of explict config-specific files.
if(is_multi)
set(configs ${CMAKE_CONFIGURATION_TYPES})
set(cpp_files "")
foreach(cfg ${configs})
string(REPLACE "$<CONFIG>" "${cfg}" expanded_cpp_file_output "${cpp_file_output}")
list(APPEND cpp_files "${expanded_cpp_file_output}")
endforeach()
else()
set(configs "${CMAKE_BUILD_TYPE}")
set(cpp_files "${cpp_file_output}")
endif()
# Change this to see different behavior for per-sources for obj libs vs lib
set(use_obj_lib TRUE)
# set(use_obj_lib FALSE)
set(end_target "${target}")
# Create a separate object lib to hold the compiled cpp files and link the compiled files to the
# library.
if(use_obj_lib)
add_library(${target}_objs OBJECT)
target_link_libraries(${target} PRIVATE $<TARGET_OBJECTS:${target}_objs>)
set(end_target "${target}_objs")
endif()
# Add the list of config specific cpp files to the obj's libs source.
while(cpp_files)
list(POP_FRONT configs cfg)
list(POP_FRONT cpp_files cpp_file)
target_sources(${end_target} PRIVATE "$<$<CONFIG:${cfg}>:${cpp_file}>")
endwhile()
Configure with
cmake -DCMAKE_CONFIGURATION_TYPES="Release;Debug" -G"Ninja Multi-Config" ..
Error output:
ninja: error: 'CMakeFiles/corelib_objs.dir/Release/Debug/generated_file_impl.cpp.o', needed by 'Debug/libcorelib.dylib', missing and no known rule to make it
Note the Release/Debug
part.
The project above works fine with CMake 3.20.5
and earlier.
The issue only reproduces if cross config mode is enabled (as done in the project) and when the per-config sources are added to the object library.
If they are added directly to the target, the build succeeds. That can be tested by flipping the use_obj_lib
variable in the project.
The difference in the generated CMakefiles/impl-Release.ninja
is in the linker line
# 3.20.5
# Link the shared library Debug/libcorelib.dylib
build Debug/libcorelib.dylib: CXX_SHARED_LIBRARY_LINKER__corelib_Debug CMakeFiles/corelib.dir/Debug/core_kernel.cpp.o | CMakeFiles/corelib_objs.dir/Debug/Debug/generated_file_impl.cpp.o
# 3.21.0
# Link the shared library Debug/libcorelib.dylib
build Debug/libcorelib.dylib: CXX_SHARED_LIBRARY_LINKER__corelib_Debug CMakeFiles/corelib.dir/Debug/core_kernel.cpp.o | CMakeFiles/corelib_objs.dir/${CONFIGURATION}/Debug/generated_file_impl.cpp.o || corelib_objs$:Debug
Note the older versions has Debug/Debug
whereas the new version is config specific ${CONFIGURATION}/Debug
.