Export: Expand CPS default targets

CPS provides "default targets" as a user convenience which can be linked
by specifying only the package name. CMake implements this via a utility
`INTERFACE` target. However, because it is not namespaced, this is not a
valid requirement, which prevents exporting anything linking to this
target to CPS.

Work around this by introducing a new (internal) target property which
replaces a target with its link interface targets at export time.
This commit is contained in:
Matthew Woehlke
2026-07-30 15:01:47 -04:00
parent 42da7a30a9
commit 4c6b437265
16 changed files with 188 additions and 0 deletions

View File

@@ -398,6 +398,21 @@ bool cmExportFileGenerator::AddTargetNamespace(std::string& input,
return false;
}
std::vector<std::string> replace = tgt->Target->GetExportTargets();
if (!replace.empty()) {
std::vector<std::string> 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")) {

View File

@@ -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;

View File

@@ -631,6 +631,7 @@ public:
bool PerConfig = false;
bool IsSymbolic = false;
bool IsForTryCompile = false;
bool IsExportPassthrough = false;
cmTarget::Visibility TargetVisibility;
std::set<BT<std::pair<std::string, bool>>> Utilities;
std::set<std::string> 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<std::string> cmTarget::GetExportTargets() const
{
if (this->impl->IsExportPassthrough) {
return cm::remove_BT(this->impl->InterfaceLinkLibraries.Entries);
}
return {};
}
char const* cmTarget::GetSuffixVariableInternal(
cmStateEnums::ArtifactType artifact) const
{

View File

@@ -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<std::string> GetExportTargets() const;
bool GetMappedConfig(std::string const& desiredConfig, cmValue& loc,
cmValue& imp, std::string& suffix) const;

View File

@@ -1515,6 +1515,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)

View File

@@ -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()

View File

@@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 4.4.0)
project(${RunCMake_TEST} NONE)
include(${RunCMake_TEST}.cmake)

View File

@@ -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()

View File

@@ -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)

View File

@@ -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\.

View File

@@ -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)

View File

@@ -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()

View File

@@ -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)

View File

@@ -0,0 +1,8 @@
include(RunCMake)
# Test normal usage
run_cmake(NormalExport)
# Test usage via generator expression
run_cmake(GenExExportCMake)
run_cmake(GenExExportCps)

View File

@@ -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"
}
}
}