diff --git a/Source/cmExportFileGenerator.cxx b/Source/cmExportFileGenerator.cxx index 456fcefcbd..fbedbff36f 100644 --- a/Source/cmExportFileGenerator.cxx +++ b/Source/cmExportFileGenerator.cxx @@ -398,6 +398,21 @@ bool cmExportFileGenerator::AddTargetNamespace(std::string& input, return false; } + std::vector replace = tgt->Target->GetExportTargets(); + if (!replace.empty()) { + std::vector out; + out.reserve(replace.size()); + + bool result = true; + for (std::string& t : replace) { + result = this->AddTargetNamespace(t, target, lg) && result; + out.emplace_back(std::move(t)); + } + + input = cmJoin(out, ";"_s); + return result; + } + cmFindPackageStack const& pkgStack = tgt->Target->GetFindPackageStack(); if (!pkgStack.Empty() || tgt->Target->GetProperty("EXPORT_FIND_PACKAGE_NAME")) { diff --git a/Source/cmPackageInfoReader.cxx b/Source/cmPackageInfoReader.cxx index a51eafa159..a97a31fe51 100644 --- a/Source/cmPackageInfoReader.cxx +++ b/Source/cmPackageInfoReader.cxx @@ -1005,6 +1005,7 @@ bool cmPackageInfoReader::ImportTargets(cmMakefile* makefile, std::string const& fullName = cmStrCat(package, "::"_s, name); AppendProperty(makefile, target, "LINK_LIBRARIES"_s, {}, fullName); } + target->SetExportPassthrough(true); } return true; diff --git a/Source/cmTarget.cxx b/Source/cmTarget.cxx index 23bb847c96..7cf8760c60 100644 --- a/Source/cmTarget.cxx +++ b/Source/cmTarget.cxx @@ -631,6 +631,7 @@ public: bool PerConfig = false; bool IsSymbolic = false; bool IsForTryCompile = false; + bool IsExportPassthrough = false; cmTarget::Visibility TargetVisibility; std::set>> Utilities; std::set CodegenDependencies; @@ -2150,6 +2151,12 @@ void cmTarget::SetSymbolic(bool const value) this->impl->IsSymbolic = value; } +void cmTarget::SetExportPassthrough(bool value) +{ + assert(this->impl->TargetType == cm::TargetType::INTERFACE_LIBRARY); + this->impl->IsExportPassthrough = value; +} + void cmTarget::SetProperty(std::string const& prop, cmValue value) { if (!IsSettableProperty(this->impl->Makefile, this, prop)) { @@ -3007,6 +3014,14 @@ bool cmTarget::IsForTryCompile() const return this->impl->IsForTryCompile; } +std::vector cmTarget::GetExportTargets() const +{ + if (this->impl->IsExportPassthrough) { + return cm::remove_BT(this->impl->InterfaceLinkLibraries.Entries); + } + return {}; +} + char const* cmTarget::GetSuffixVariableInternal( cmStateEnums::ArtifactType artifact) const { diff --git a/Source/cmTarget.h b/Source/cmTarget.h index 58d8911b65..73c0bfdc8a 100644 --- a/Source/cmTarget.h +++ b/Source/cmTarget.h @@ -210,6 +210,8 @@ public: void SetSymbolic(bool value); + void SetExportPassthrough(bool value); + //! Set/Get a property of this target file void SetProperty(std::string const& prop, cmValue value); void SetProperty(std::string const& prop, std::nullptr_t) @@ -254,6 +256,13 @@ public: void SetIsForTryCompile(); bool IsForTryCompile() const; + /** + * Get the list of targets which should replace this target on export. This + * equals the target's interface link libraries iff the target is an export + * passthrough target. For all other targets, this returns an empty list. + */ + std::vector GetExportTargets() const; + bool GetMappedConfig(std::string const& desiredConfig, cmValue& loc, cmValue& imp, std::string& suffix) const; diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt index 7b5d6af869..1406ca291a 100644 --- a/Tests/RunCMake/CMakeLists.txt +++ b/Tests/RunCMake/CMakeLists.txt @@ -1530,6 +1530,7 @@ add_RunCMake_test(AutoExportDll ) add_RunCMake_test(AndroidMK) +add_RunCMake_test(ExportCpsDefaultComponents) add_RunCMake_test(ExportPackageInfo) add_RunCMake_test(ExportSbom) add_RunCMake_test(EnvSbom) diff --git a/Tests/RunCMake/ExportCpsDefaultComponents/Assertions.cmake b/Tests/RunCMake/ExportCpsDefaultComponents/Assertions.cmake new file mode 100644 index 0000000000..068074da2a --- /dev/null +++ b/Tests/RunCMake/ExportCpsDefaultComponents/Assertions.cmake @@ -0,0 +1,39 @@ +macro(_expect entity op actual expected) + if(NOT "${actual}" ${op} "${expected}") + list(JOIN ARGN "." name) + set(RunCMake_TEST_FAILED + "Attribute '${name}' ${entity} '${actual}' does not match expected ${entity} '${expected}'" PARENT_SCOPE) + return() + endif() +endmacro() + +function(expect_value content expected_value) + string(JSON actual_value GET "${content}" ${ARGN}) + _expect("value" STREQUAL "${actual_value}" "${expected_value}" ${ARGN}) +endfunction() + +function(expect_array content expected_length) + string(JSON actual_type TYPE "${content}" ${ARGN}) + _expect("type" STREQUAL "${actual_type}" "ARRAY" ${ARGN}) + + string(JSON actual_length LENGTH "${content}" ${ARGN}) + _expect("length" EQUAL "${actual_length}" "${expected_length}" ${ARGN}) +endfunction() + +function(expect_object content) + string(JSON actual_type TYPE "${content}" ${ARGN}) + _expect("type" STREQUAL "${actual_type}" "OBJECT" ${ARGN}) +endfunction() + +function(expect_null content) + string(JSON actual_type TYPE "${content}" ${ARGN}) + _expect("type" STREQUAL "${actual_type}" "NULL" ${ARGN}) +endfunction() + +function(expect_missing content) + string(JSON value ERROR_VARIABLE error GET "${content}" ${ARGN}) + if(NOT value MATCHES "^(.*-)?NOTFOUND$") + set(RunCMake_TEST_FAILED + "Attribute '${ARGN}' is unexpectedly present" PARENT_SCOPE) + endif() +endfunction() diff --git a/Tests/RunCMake/ExportCpsDefaultComponents/CMakeLists.txt b/Tests/RunCMake/ExportCpsDefaultComponents/CMakeLists.txt new file mode 100644 index 0000000000..1cd096a377 --- /dev/null +++ b/Tests/RunCMake/ExportCpsDefaultComponents/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 4.4.0) +project(${RunCMake_TEST} NONE) +include(${RunCMake_TEST}.cmake) diff --git a/Tests/RunCMake/ExportCpsDefaultComponents/GenExExportCMake-check.cmake b/Tests/RunCMake/ExportCpsDefaultComponents/GenExExportCMake-check.cmake new file mode 100644 index 0000000000..303501354a --- /dev/null +++ b/Tests/RunCMake/ExportCpsDefaultComponents/GenExExportCMake-check.cmake @@ -0,0 +1,13 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake) + +set(out_dir "${RunCMake_BINARY_DIR}/GenExExportCMake-build") + +file(READ "${out_dir}/bar.cmake" bar_cmake) +if(NOT "${bar_cmake}" MATCHES "add_library\\(bar INTERFACE IMPORTED\\)") + string(APPEND RunCMake_TEST_FAILED + "Interface library 'bar' was not exported\n") +endif() +if(NOT "${bar_cmake}" MATCHES "set_target_properties\\(bar PROPERTIES[ \n]+INTERFACE_LINK_LIBRARIES \"[\\][\$]<1:foo>\"[ \n]*\\)") + string(APPEND RunCMake_TEST_FAILED + "Interface library 'bar' has wrong link libraries\n") +endif() diff --git a/Tests/RunCMake/ExportCpsDefaultComponents/GenExExportCMake.cmake b/Tests/RunCMake/ExportCpsDefaultComponents/GenExExportCMake.cmake new file mode 100644 index 0000000000..469e8d3b4b --- /dev/null +++ b/Tests/RunCMake/ExportCpsDefaultComponents/GenExExportCMake.cmake @@ -0,0 +1,11 @@ +find_package( + foo REQUIRED CONFIG + NO_DEFAULT_PATH + PATHS ${CMAKE_CURRENT_LIST_DIR} + ) + +add_library(bar INTERFACE) +target_link_libraries(bar INTERFACE $<1:foo>) + +install(TARGETS bar EXPORT bar) +export(EXPORT bar) diff --git a/Tests/RunCMake/ExportCpsDefaultComponents/GenExExportCps-result.txt b/Tests/RunCMake/ExportCpsDefaultComponents/GenExExportCps-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/ExportCpsDefaultComponents/GenExExportCps-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/ExportCpsDefaultComponents/GenExExportCps-stderr.txt b/Tests/RunCMake/ExportCpsDefaultComponents/GenExExportCps-stderr.txt new file mode 100644 index 0000000000..7489725ad4 --- /dev/null +++ b/Tests/RunCMake/ExportCpsDefaultComponents/GenExExportCps-stderr.txt @@ -0,0 +1,5 @@ +CMake Error in CMakeLists\.txt: + Property "INTERFACE_LINK_LIBRARIES" of target "bar" contains a generator + expression\. This is not allowed\.[ +]+ +CMake Generate step failed\. Build files cannot be regenerated correctly\. diff --git a/Tests/RunCMake/ExportCpsDefaultComponents/GenExExportCps.cmake b/Tests/RunCMake/ExportCpsDefaultComponents/GenExExportCps.cmake new file mode 100644 index 0000000000..78d33e681b --- /dev/null +++ b/Tests/RunCMake/ExportCpsDefaultComponents/GenExExportCps.cmake @@ -0,0 +1,11 @@ +find_package( + foo REQUIRED CONFIG + NO_DEFAULT_PATH + PATHS ${CMAKE_CURRENT_LIST_DIR} + ) + +add_library(bar INTERFACE) +target_link_libraries(bar INTERFACE $<1:foo>) + +install(TARGETS bar EXPORT bar) +export(PACKAGE_INFO bar EXPORT bar) diff --git a/Tests/RunCMake/ExportCpsDefaultComponents/NormalExport-check.cmake b/Tests/RunCMake/ExportCpsDefaultComponents/NormalExport-check.cmake new file mode 100644 index 0000000000..84a47db200 --- /dev/null +++ b/Tests/RunCMake/ExportCpsDefaultComponents/NormalExport-check.cmake @@ -0,0 +1,27 @@ +include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake) + +set(out_dir "${RunCMake_BINARY_DIR}/NormalExport-build") + +file(READ "${out_dir}/cps/bar/bar.cps" content) +expect_value("${content}" "bar" "name") +expect_value("${content}" "interface" "components" "bar" "type") + +expect_value("${content}" "bar" "name") +expect_array("${content}" 2 "requires" "foo" "components") +expect_value("${content}" "foo1" "requires" "foo" "components" 0) +expect_value("${content}" "foo2" "requires" "foo" "components" 1) + +string(JSON component GET "${content}" "components" "bar") +expect_array("${component}" 2 "requires") +expect_value("${component}" "foo:foo1" "requires" 0) +expect_value("${component}" "foo:foo2" "requires" 1) + +file(READ "${out_dir}/bar.cmake" bar_cmake) +if(NOT "${bar_cmake}" MATCHES "add_library\\(bar INTERFACE IMPORTED\\)") + string(APPEND RunCMake_TEST_FAILED + "Interface library 'bar' was not exported\n") +endif() +if(NOT "${bar_cmake}" MATCHES "set_target_properties\\(bar PROPERTIES[ \n]+INTERFACE_LINK_LIBRARIES \"foo::foo1;foo::foo2\"[ \n]*\\)") + string(APPEND RunCMake_TEST_FAILED + "Interface library 'bar' has wrong link libraries\n") +endif() diff --git a/Tests/RunCMake/ExportCpsDefaultComponents/NormalExport.cmake b/Tests/RunCMake/ExportCpsDefaultComponents/NormalExport.cmake new file mode 100644 index 0000000000..2362ffad14 --- /dev/null +++ b/Tests/RunCMake/ExportCpsDefaultComponents/NormalExport.cmake @@ -0,0 +1,12 @@ +find_package( + foo REQUIRED CONFIG + NO_DEFAULT_PATH + PATHS ${CMAKE_CURRENT_LIST_DIR} + ) + +add_library(bar INTERFACE) +target_link_libraries(bar INTERFACE foo) + +install(TARGETS bar EXPORT bar) +export(EXPORT bar) +export(PACKAGE_INFO bar EXPORT bar) diff --git a/Tests/RunCMake/ExportCpsDefaultComponents/RunCMakeTest.cmake b/Tests/RunCMake/ExportCpsDefaultComponents/RunCMakeTest.cmake new file mode 100644 index 0000000000..cd6df67719 --- /dev/null +++ b/Tests/RunCMake/ExportCpsDefaultComponents/RunCMakeTest.cmake @@ -0,0 +1,8 @@ +include(RunCMake) + +# Test normal usage +run_cmake(NormalExport) + +# Test usage via generator expression +run_cmake(GenExExportCMake) +run_cmake(GenExExportCps) diff --git a/Tests/RunCMake/ExportCpsDefaultComponents/cps/foo.cps b/Tests/RunCMake/ExportCpsDefaultComponents/cps/foo.cps new file mode 100644 index 0000000000..c56049fab3 --- /dev/null +++ b/Tests/RunCMake/ExportCpsDefaultComponents/cps/foo.cps @@ -0,0 +1,17 @@ +{ + "cps_version" : "0.13.0", + "name" : "foo", + "cps_path" : "@prefix@/cps", + "default_components" : [ "foo1", "foo2" ], + "components" : + { + "foo1" : + { + "type" : "interface" + }, + "foo2" : + { + "type" : "interface" + } + } +}