cmGeneratorExpressionNode: implement COMPILE_ONLY genex

This generator expression is the inverse of `LINK_ONLY` and only coveys
usage requirements for the purposes of compilation. Its intended use is
to avoid needing to export targets that do not have link usage
requirements (e.g., header-only libraries) when used by another target.

It will also be used to represent private usage requirements on exported
C++ module-containing targets in the future.

Eventually there should be logic to collapse nesting of
`$<COMPILE_ONLY>` and `$<LINK_ONLY>` when generating instances of
either. A TODO is left in the code for this case.

See: #15415
This commit is contained in:
Ben Boeckel 2023-01-14 23:58:28 -05:00 committed by Robert Maynard
parent 6c11f7e4a8
commit 0fb923c460
9 changed files with 76 additions and 1 deletions

View File

@ -959,6 +959,22 @@ Compile Features
:manual:`cmake-compile-features(7)` manual for information on
compile features and a list of supported compilers.
Compile Context
^^^^^^^^^^^^^^^
.. genex:: $<COMPILE_ONLY:...>
.. versionadded:: 3.27
Content of ``...``, except while collecting :ref:`Target Usage Requirements`,
in which case it is the empty string. This is intended for use in an
:prop_tgt:`INTERFACE_LINK_LIBRARIES` target property, typically populated
via the :command:`target_link_libraries` command, to specify private
compilation requirements without other usage requirements.
Use cases include header-only usage where all usages are known to not have
linking requirements (e.g., all-``inline`` or C++ template libraries).
Linker Language And ID
^^^^^^^^^^^^^^^^^^^^^^
@ -1339,7 +1355,8 @@ Link Context
in which case it is the empty string. This is intended for use in an
:prop_tgt:`INTERFACE_LINK_LIBRARIES` target property, typically populated
via the :command:`target_link_libraries` command, to specify private link
dependencies without other usage requirements.
dependencies without other usage requirements such as include directories or
compile options.
.. versionadded:: 3.24
``LINK_ONLY`` may also be used in a :prop_tgt:`LINK_LIBRARIES` target

View File

@ -0,0 +1,5 @@
genex-compile-only
------------------
* The :genex:`COMPILE_ONLY` generator expression has been added which provides
compilation usage requirements without any linking requirements.

View File

@ -734,6 +734,22 @@ void cmExportFileGenerator::ResolveTargetsInGeneratorExpression(
lastPos = nameStartPos + libName.size() + 1;
}
while (errorString.empty() &&
(pos = input.find("$<COMPILE_ONLY:", lastPos)) != std::string::npos) {
std::string::size_type nameStartPos = pos + cmStrLen("$<COMPILE_ONLY:");
std::string::size_type endPos = input.find('>', nameStartPos);
if (endPos == std::string::npos) {
errorString = "$<COMPILE_ONLY:...> expression incomplete";
break;
}
std::string libName = input.substr(nameStartPos, endPos - nameStartPos);
if (cmGeneratorExpression::IsValidTargetName(libName) &&
this->AddTargetNamespace(libName, target, lg)) {
input.replace(nameStartPos, endPos - nameStartPos, libName);
}
lastPos = nameStartPos + libName.size() + 1;
}
this->ReplaceInstallPrefix(input);
if (!errorString.empty()) {

View File

@ -1351,6 +1351,29 @@ static const VersionNode<cmSystemTools::OP_LESS> versionLessNode;
static const VersionNode<cmSystemTools::OP_LESS_EQUAL> versionLessEqNode;
static const VersionNode<cmSystemTools::OP_EQUAL> versionEqualNode;
static const struct CompileOnlyNode : public cmGeneratorExpressionNode
{
CompileOnlyNode() {} // NOLINT(modernize-use-equals-default)
std::string Evaluate(
const std::vector<std::string>& parameters,
cmGeneratorExpressionContext* context,
const GeneratorExpressionContent* content,
cmGeneratorExpressionDAGChecker* dagChecker) const override
{
if (!dagChecker) {
reportError(context, content->GetOriginalExpression(),
"$<COMPILE_ONLY:...> may only be used via linking");
return std::string();
}
// Linking checks for the inverse, so compiling is the opposite.
if (dagChecker->GetTransitivePropertiesOnly()) {
return parameters.front();
}
return std::string();
}
} compileOnlyNode;
static const struct LinkOnlyNode : public cmGeneratorExpressionNode
{
LinkOnlyNode() {} // NOLINT(modernize-use-equals-default)
@ -1366,6 +1389,7 @@ static const struct LinkOnlyNode : public cmGeneratorExpressionNode
"$<LINK_ONLY:...> may only be used for linking");
return std::string();
}
// Compile-only checks for the inverse, so linking is the opposite.
if (!dagChecker->GetTransitivePropertiesOnly()) {
return parameters.front();
}
@ -3805,6 +3829,7 @@ const cmGeneratorExpressionNode* cmGeneratorExpressionNode::GetNode(
{ "BUILD_LOCAL_INTERFACE", &buildLocalInterfaceNode },
{ "INSTALL_PREFIX", &installPrefixNode },
{ "JOIN", &joinNode },
{ "COMPILE_ONLY", &compileOnlyNode },
{ "LINK_ONLY", &linkOnlyNode },
{ "COMPILE_LANG_AND_ID", &languageAndIdNode },
{ "COMPILE_LANGUAGE", &languageNode },

View File

@ -552,6 +552,7 @@ bool TLL::HandleLibrary(ProcessingState currentProcessingState,
currentProcessingState == ProcessingPlainPrivateInterface) {
if (this->Target->GetType() == cmStateEnums::STATIC_LIBRARY ||
this->Target->GetType() == cmStateEnums::OBJECT_LIBRARY) {
// TODO: Detect and no-op `$<COMPILE_ONLY>` genexes here.
std::string configLib =
this->Target->GetDebugGeneratorExpressions(lib, llt);
if (cmGeneratorExpression::IsValidTargetName(lib) ||

View File

@ -0,0 +1,8 @@
CMake Error at COMPILE_ONLY-not-compiling.cmake:1 \(add_custom_target\):
Error evaluating generator expression:
\$<COMPILE_ONLY:something>
\$<COMPILE_ONLY:...> may only be used via linking
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@ -0,0 +1 @@
add_custom_target(Custom ALL COMMAND ${CMAKE_COMMAND} -E echo $<COMPILE_ONLY:something>)

View File

@ -22,6 +22,7 @@ run_cmake(NonValidTarget-CXX_COMPILER_VERSION)
run_cmake(NonValidTarget-Fortran_COMPILER_VERSION)
run_cmake(NonValidTarget-TARGET_PROPERTY)
run_cmake(NonValidTarget-TARGET_POLICY)
run_cmake(COMPILE_ONLY-not-compiling)
run_cmake(LINK_ONLY-not-linking)
run_cmake(TARGET_EXISTS-no-arg)
run_cmake(TARGET_EXISTS-empty-arg)