export: Allow export with LINK_ONLY library dependencies

This commit is contained in:
Martin Duffy
2025-04-11 10:54:28 -04:00
parent 13c7bb5b0c
commit ebe487ea81
10 changed files with 126 additions and 23 deletions

View File

@@ -2,6 +2,7 @@
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmExportPackageInfoGenerator.h"
#include <cstddef>
#include <memory>
#include <set>
#include <utility>
@@ -210,13 +211,15 @@ bool cmExportPackageInfoGenerator::GenerateInterfaceProperties(
}
namespace {
bool forbidGeneratorExpressions(std::string const& propertyName,
std::string const& propertyValue,
cmGeneratorTarget const* target)
bool ForbidGeneratorExpressions(
cmGeneratorTarget const* target, std::string const& propertyName,
std::string const& propertyValue, std::string& evaluatedValue,
std::map<std::string, std::vector<std::string>>& allowList)
{
std::string const& evaluatedValue = cmGeneratorExpression::Preprocess(
propertyValue, cmGeneratorExpression::StripAllGeneratorExpressions);
if (evaluatedValue != propertyValue) {
size_t const allowedExpressions = allowList.size();
evaluatedValue = cmGeneratorExpression::Collect(propertyValue, allowList);
if (evaluatedValue != propertyValue &&
allowList.size() > allowedExpressions) {
target->Makefile->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat("Property \"", propertyName, "\" of target \"",
@@ -224,8 +227,32 @@ bool forbidGeneratorExpressions(std::string const& propertyName,
"\" contains a generator expression. This is not allowed."));
return false;
}
// Forbid Nested Generator Expressions
for (auto const& genexp : allowList) {
for (auto const& value : genexp.second) {
if (value.find("$<") != std::string::npos) {
target->Makefile->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat(
"$<", genexp.first, ":...> expression in \"", propertyName,
"\" of target \"", target->GetName(),
"\" contains a generator expression. This is not allowed."));
return false;
}
}
}
return true;
}
bool ForbidGeneratorExpressions(cmGeneratorTarget const* target,
std::string const& propertyName,
std::string const& propertyValue)
{
std::map<std::string, std::vector<std::string>> allowList;
std::string evaluatedValue;
return ForbidGeneratorExpressions(target, propertyName, propertyValue,
evaluatedValue, allowList);
}
}
bool cmExportPackageInfoGenerator::NoteLinkedTarget(
@@ -317,31 +344,43 @@ void cmExportPackageInfoGenerator::GenerateInterfaceLinkProperties(
return;
}
// TODO: Support $<LINK_ONLY>.
if (!forbidGeneratorExpressions(iter->first, iter->second, target)) {
// Extract any $<LINK_ONLY:...> from the link libraries, and assert that no
// other generator expressions are present.
std::map<std::string, std::vector<std::string>> allowList = { { "LINK_ONLY",
{} } };
std::string interfaceLinkLibraries;
if (!ForbidGeneratorExpressions(target, iter->first, iter->second,
interfaceLinkLibraries, allowList)) {
result = false;
return;
}
std::vector<std::string> buildRequires;
// std::vector<std::string> linkRequires; TODO
std::vector<std::string> linkLibraries;
std::vector<std::string> linkRequires;
std::vector<std::string> buildRequires;
for (auto const& name : cmList{ iter->second }) {
auto const& ti = this->LinkTargets.find(name);
if (ti != this->LinkTargets.end()) {
if (ti->second.empty()) {
result = false;
auto addLibraries = [this, &linkLibraries,
&result](std::vector<std::string> const& names,
std::vector<std::string>& output) -> void {
for (auto const& name : names) {
auto const& ti = this->LinkTargets.find(name);
if (ti != this->LinkTargets.end()) {
if (ti->second.empty()) {
result = false;
} else {
output.emplace_back(ti->second);
}
} else {
buildRequires.emplace_back(ti->second);
linkLibraries.emplace_back(name);
}
} else {
linkLibraries.emplace_back(name);
}
}
};
addLibraries(allowList["LINK_ONLY"], linkRequires);
addLibraries(cmList{ interfaceLinkLibraries }, buildRequires);
buildArray(component, "requires", buildRequires);
// buildArray(component, "link_requires", linkRequires); TODO
buildArray(component, "link_requires", linkRequires);
buildArray(component, "link_libraries", linkLibraries);
}
@@ -354,7 +393,7 @@ void cmExportPackageInfoGenerator::GenerateInterfaceCompileFeatures(
return;
}
if (!forbidGeneratorExpressions(iter->first, iter->second, target)) {
if (!ForbidGeneratorExpressions(target, iter->first, iter->second)) {
result = false;
return;
}
@@ -383,7 +422,7 @@ void cmExportPackageInfoGenerator::GenerateInterfaceCompileDefines(
}
// TODO: Support language-specific defines.
if (!forbidGeneratorExpressions(iter->first, iter->second, target)) {
if (!ForbidGeneratorExpressions(target, iter->first, iter->second)) {
result = false;
return;
}
@@ -414,7 +453,7 @@ void cmExportPackageInfoGenerator::GenerateInterfaceListProperty(
return;
}
if (!forbidGeneratorExpressions(prop, iter->second, target)) {
if (!ForbidGeneratorExpressions(target, prop, iter->second)) {
result = false;
return;
}

View File

@@ -0,0 +1,6 @@
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 @@
project(LinkInterfaceGeneratorExpression CXX)
add_library(foo foo.cxx)
add_library(bar foo.cxx)
target_link_libraries(bar $<1:foo>)
install(TARGETS foo EXPORT foo)
export(EXPORT foo PACKAGE_INFO foo)
install(TARGETS bar EXPORT bar)
export(EXPORT bar PACKAGE_INFO bar)

View File

@@ -0,0 +1,12 @@
include(${CMAKE_CURRENT_LIST_DIR}/Assertions.cmake)
set(out_dir "${RunCMake_BINARY_DIR}/LinkOnly-build")
file(READ "${out_dir}/bar.cps" content)
string(JSON component GET "${content}" "components" "bar")
expect_array("${component}" 2 "link_requires")
expect_value("${component}" "foo:linkOnlyOne" "link_requires" 0)
expect_value("${component}" "foo:linkOnlyTwo" "link_requires" 1)
expect_array("${component}" 1 "requires")
expect_value("${component}" "foo:foo" "requires" 0)
expect_missing("${component}" "foo:foo" "link_libraries")

View File

@@ -0,0 +1,13 @@
project(LinkOnly CXX)
add_library(linkOnlyOne foo.cxx)
add_library(linkOnlyTwo foo.cxx)
add_library(foo foo.cxx)
add_library(bar foo.cxx)
target_link_libraries(bar $<LINK_ONLY:linkOnlyOne> $<LINK_ONLY:linkOnlyTwo> foo)
install(TARGETS foo linkOnlyOne linkOnlyTwo EXPORT foo)
export(EXPORT foo PACKAGE_INFO foo)
install(TARGETS bar EXPORT bar)
export(EXPORT bar PACKAGE_INFO bar)

View File

@@ -0,0 +1 @@
1

View File

@@ -0,0 +1,6 @@
CMake Error in CMakeLists.txt:
\$<LINK_ONLY:...> expression in "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 @@
project(LinkOnly CXX)
add_library(foo foo.cxx)
add_library(bar foo.cxx)
target_link_libraries(bar $<LINK_ONLY:$<LINK_ONLY:foo>>)
install(TARGETS foo EXPORT foo)
export(EXPORT foo PACKAGE_INFO foo)
install(TARGETS bar EXPORT bar)
export(EXPORT bar PACKAGE_INFO bar)

View File

@@ -24,6 +24,8 @@ run_cmake(ReferencesWronglyImportedTarget)
run_cmake(ReferencesWronglyNamespacedTarget)
run_cmake(DependsMultipleDifferentNamespace)
run_cmake(DependsMultipleDifferentSets)
run_cmake(LinkInterfaceGeneratorExpression)
run_cmake(LinkOnlyRecursive)
# Test functionality
run_cmake(Appendix)
@@ -35,3 +37,4 @@ run_cmake(LowerCaseFile)
run_cmake(Requirements)
run_cmake(TargetTypes)
run_cmake(DependsMultiple)
run_cmake(LinkOnly)