1
0
mirror of https://github.com/Kitware/CMake.git synced 2025-10-18 08:51:52 +08:00

find_package: Implement UNWIND_INCLUDE

This implements a limited exception mechanism for find_package() via
the UNWIND_INCLUDE keyword.

When package discovery via find_package(UNWIND_INCLUDE) fails the
StateSnapshot is updated to an UNWINDING state. In this state further
calls to find_package() and include() are forbidden. While in the
UNWINDING state, the include() command immediately calls
SetReturnInvoked() whenever it is reached.

The UNWINDING state is reset when a parent call to find_package() is
reached.

Fixes: #26897
This commit is contained in:
Vito Gamberini
2025-07-10 12:42:55 -04:00
parent a4d82a5a6d
commit f61768107e
40 changed files with 404 additions and 8 deletions

View File

@@ -141,7 +141,8 @@ Basic Signature
[REGISTRY_VIEW (64|32|64_32|32_64|HOST|TARGET|BOTH)]
[GLOBAL]
[NO_POLICY_SCOPE]
[BYPASS_PROVIDER])
[BYPASS_PROVIDER]
[UNWIND_INCLUDE])
The basic signature is supported by both Module and Config modes.
The ``MODULE`` keyword implies that only Module mode can be used to find
@@ -249,6 +250,21 @@ of the ``NO_POLICY_SCOPE`` option.
itself. Future versions of CMake may detect attempts to use this keyword
from places other than a dependency provider and halt with a fatal error.
.. versionadded:: 4.2
The ``UNWIND_INCLUDE`` keyword is only allowed when ``find_package()`` is
being called within a parent call to ``find_package()``. When a call to
``find_package(UNWIND_INCLUDE)`` fails to find the desired package, it begins
an "unwind" state. In this state further calls to ``find_package()`` and
:command:`include()` are forbidden, and all parent :command:`include()`
commands will immediately invoke :command:`return()` when their scope is
reached. This "unwinding" will continue until the parent ``find_package()``
is returned to.
``UNWIND_INCLUDE`` is only intended to be used by calls to ``find_package()``
generated by :command:`install(EXPORT_PACKAGE_DEPENDENCIES)`, but may be
useful to those who wish to manually manage their dependencies in a similar
manner.
.. _`full signature`:
Full Signature

View File

@@ -0,0 +1,6 @@
find-package-UNWIND_INCLUDE
---------------------------
* The :command:`find_package()` command gained a new ``UNWIND_INCLUDE`` option
to enable immediate :command:`return` from :command:`include()` commands
after a failure to discover a transitive dependency.

View File

@@ -630,6 +630,12 @@ bool cmFindPackageCommand::InitialPass(std::vector<std::string> const& args)
return false;
}
if (this->Makefile->GetStateSnapshot().GetUnwindState() ==
cmStateEnums::UNWINDING) {
this->SetError("called while already in an UNWIND state");
return false;
}
// Lookup required version of CMake.
if (cmValue const rv =
this->Makefile->GetDefinition("CMAKE_MINIMUM_REQUIRED_VERSION")) {
@@ -839,6 +845,14 @@ bool cmFindPackageCommand::InitialPass(std::vector<std::string> const& args)
cmStrCat("given invalid value for REGISTRY_VIEW: ", args[i]));
return false;
}
} else if (args[i] == "UNWIND_INCLUDE") {
if (this->Makefile->GetStateSnapshot().GetUnwindType() !=
cmStateEnums::CAN_UNWIND) {
this->SetError("called with UNWIND_INCLUDE in an invalid context");
return false;
}
this->ScopeUnwind = true;
doing = DoingNone;
} else if (this->CheckCommonArgument(args[i])) {
configArgs.push_back(i);
doing = DoingNone;
@@ -1053,8 +1067,18 @@ bool cmFindPackageCommand::InitialPass(std::vector<std::string> const& args)
this->VersionMaxPatch, this->VersionMaxTweak);
}
return this->FindPackage(this->BypassProvider ? std::vector<std::string>{}
: args);
bool result = this->FindPackage(
this->BypassProvider ? std::vector<std::string>{} : args);
std::string const foundVar = cmStrCat(this->Name, "_FOUND");
bool const isFound = this->Makefile->IsOn(foundVar) ||
this->Makefile->IsOn(cmSystemTools::UpperCase(foundVar));
if (this->ScopeUnwind && (!result || !isFound)) {
this->Makefile->GetStateSnapshot().SetUnwindState(cmStateEnums::UNWINDING);
}
return result;
}
bool cmFindPackageCommand::FindPackage(
@@ -2047,12 +2071,24 @@ bool cmFindPackageCommand::ReadListFile(std::string const& f,
ITScope scope = this->GlobalScope ? ITScope::Global : ITScope::Local;
cmMakefile::SetGlobalTargetImportScope globScope(this->Makefile, scope);
if (this->Makefile->ReadDependentFile(f, noPolicyScope)) {
return true;
auto oldUnwind = this->Makefile->GetStateSnapshot().GetUnwindType();
// This allows child snapshots to inherit the CAN_UNWIND state from us, we'll
// reset it immediately after the dependent file is done
this->Makefile->GetStateSnapshot().SetUnwindType(cmStateEnums::CAN_UNWIND);
bool result = this->Makefile->ReadDependentFile(f, noPolicyScope);
this->Makefile->GetStateSnapshot().SetUnwindType(oldUnwind);
this->Makefile->GetStateSnapshot().SetUnwindState(
cmStateEnums::NOT_UNWINDING);
if (!result) {
std::string const e =
cmStrCat("Error reading CMake code from \"", f, "\".");
this->SetError(e);
}
std::string const e = cmStrCat("Error reading CMake code from \"", f, "\".");
this->SetError(e);
return false;
return result;
}
bool cmFindPackageCommand::ReadPackage()

View File

@@ -276,6 +276,7 @@ private:
bool PolicyScope = true;
bool GlobalScope = false;
bool RegistryViewDefined = false;
bool ScopeUnwind = false;
std::string LibraryArchitecture;
std::vector<std::string> Names;
std::set<std::string> IgnoredPaths;

View File

@@ -10,6 +10,8 @@
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmPolicies.h"
#include "cmStateSnapshot.h"
#include "cmStateTypes.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
@@ -38,6 +40,13 @@ bool cmIncludeCommand(std::vector<std::string> const& args,
"include() only takes one file.");
return false;
}
if (status.GetMakefile().GetStateSnapshot().GetUnwindState() ==
cmStateEnums::UNWINDING) {
status.SetError("called while already in an UNWIND state");
return false;
}
bool optional = false;
bool noPolicyScope = false;
std::string fname = args[0];
@@ -166,5 +175,22 @@ bool cmIncludeCommand(std::vector<std::string> const& args,
status.SetError(m);
return false;
}
if (status.GetMakefile().GetStateSnapshot().GetUnwindState() ==
cmStateEnums::UNWINDING) {
if (status.GetMakefile().GetStateSnapshot().GetUnwindType() !=
cmStateEnums::CAN_UNWIND) {
std::string m = cmStrCat("requested file is attempting to unwind the "
"stack in an invalid context:\n ",
fname);
status.SetError(m);
cmSystemTools::SetFatalErrorOccurred();
return false;
}
status.SetReturnInvoked();
}
return true;
}

View File

@@ -1030,6 +1030,7 @@ cmStateSnapshot cmState::Pop(cmStateSnapshot const& originSnapshot)
prevPos->LinkDirectoriesPosition =
prevPos->BuildSystemDirectory->LinkDirectories.size();
prevPos->BuildSystemDirectory->CurrentScope = prevPos;
prevPos->UnwindState = pos->UnwindState;
if (!pos->Keep && this->SnapshotData.IsLast(pos)) {
if (pos->Vars != prevPos->Vars) {

View File

@@ -34,6 +34,8 @@ struct cmStateDetail::SnapshotDataType
cmLinkedTree<cmStateDetail::PolicyStackEntry>::iterator PolicyRoot;
cmLinkedTree<cmStateDetail::PolicyStackEntry>::iterator PolicyScope;
cmStateEnums::SnapshotType SnapshotType;
cmStateEnums::SnapshotUnwindType UnwindType = cmStateEnums::NO_UNWIND;
cmStateEnums::SnapshotUnwindState UnwindState = cmStateEnums::NOT_UNWINDING;
bool Keep;
cmLinkedTree<std::string>::iterator ExecutionListFile;
cmLinkedTree<cmStateDetail::BuildsystemDirectoryStateType>::iterator

View File

@@ -50,6 +50,26 @@ cmStateEnums::SnapshotType cmStateSnapshot::GetType() const
return this->Position->SnapshotType;
}
cmStateEnums::SnapshotUnwindType cmStateSnapshot::GetUnwindType() const
{
return this->Position->UnwindType;
}
void cmStateSnapshot::SetUnwindType(cmStateEnums::SnapshotUnwindType type)
{
this->Position->UnwindType = type;
}
cmStateEnums::SnapshotUnwindState cmStateSnapshot::GetUnwindState() const
{
return this->Position->UnwindState;
}
void cmStateSnapshot::SetUnwindState(cmStateEnums::SnapshotUnwindState state)
{
this->Position->UnwindState = state;
}
void cmStateSnapshot::SetListFile(std::string const& listfile)
{
*this->Position->ExecutionListFile = listfile;

View File

@@ -44,6 +44,12 @@ public:
cmStateSnapshot GetCallStackBottom() const;
cmStateEnums::SnapshotType GetType() const;
cmStateEnums::SnapshotUnwindType GetUnwindType() const;
void SetUnwindType(cmStateEnums::SnapshotUnwindType type);
cmStateEnums::SnapshotUnwindState GetUnwindState() const;
void SetUnwindState(cmStateEnums::SnapshotUnwindState state);
void SetPolicy(cmPolicies::PolicyID id, cmPolicies::PolicyStatus status);
cmPolicies::PolicyStatus GetPolicy(cmPolicies::PolicyID id,
bool parent_scope = false) const;

View File

@@ -29,6 +29,18 @@ enum SnapshotType
VariableScopeType
};
enum SnapshotUnwindType
{
NO_UNWIND,
CAN_UNWIND
};
enum SnapshotUnwindState
{
NOT_UNWINDING,
UNWINDING
};
// There are multiple overlapping ranges represented here. Be aware that adding
// a value to this enumeration may cause failures in numerous places which
// assume details about the ordering.

View File

@@ -48,6 +48,14 @@ run_cmake(RequiredVarOptional)
run_cmake(RequiredVarNested)
run_cmake(FindRootPathAndPrefixPathAreEqual)
run_cmake(SetFoundFALSE)
run_cmake(UnwindIncludeBlock)
run_cmake(UnwindIncludeFunction)
run_cmake(UnwindIncludeInvalidContext)
run_cmake(UnwindIncludeInvalidFindPackage)
run_cmake(UnwindIncludeInvalidInclude)
run_cmake(UnwindIncludeNoUnwind)
run_cmake(UnwindIncludeOnly)
run_cmake(UnwindIncludeSecondary)
run_cmake(WrongVersion)
run_cmake(WrongVersionConfig)
run_cmake(CMP0084-OLD)

View File

@@ -0,0 +1,2 @@
find_package(SecondarySuccessful UNWIND_INCLUDE)
include(${CMAKE_CURRENT_LIST_DIR}/NoOp.cmake)

View File

@@ -0,0 +1,2 @@
include(${CMAKE_CURRENT_LIST_DIR}/${UNWIND_TARGET}.cmake)
set(PrimaryUnwind_FOUND true)

View File

@@ -0,0 +1 @@
set(SecondarySuccessful_FOUND true)

View File

@@ -0,0 +1 @@
set(SecondaryUnwind_FOUND false)

View File

@@ -0,0 +1,14 @@
set(RunCMake_TEST_FAILED "Failed to observe side effects of block() scopes during unwind")
block()
find_package(foo UNWIND_INCLUDE)
endblock()
block(PROPAGATE BLOCK_RUN PrimaryUnwind_FOUND)
set(BLOCK_RUN true)
set(PrimaryUnwind_FOUND false)
endblock()
if(BLOCK_RUN)
set(RunCMake_TEST_FAILED)
endif()

View File

@@ -0,0 +1,20 @@
cmake_policy(SET CMP0140 NEW)
function(f)
find_package(foo UNWIND_INCLUDE)
endfunction()
function(g)
set(FUNC_CALLED true)
set(PrimaryUnwind_FOUND false)
return(PROPAGATE func_called PrimaryUnwind_FOUND)
endfunction()
set(RunCMake_TEST_FAILED "Failed to observe side effects of function() calls during unwind")
f()
g()
if(FUNC_CALLED)
set(RunCMake_TEST_FAILED)
endif()

View File

@@ -0,0 +1,2 @@
find_package(foo UNWIND_INCLUDE)
find_package(bar)

View File

@@ -0,0 +1,2 @@
find_package(foo UNWIND_INCLUDE)
include(${CMAKE_CURRENT_LIST_DIR}/NoOp.cmake)

View File

@@ -0,0 +1,2 @@
find_package(foo UNWIND_INCLUDE)
set(PrimaryUnwind_FOUND false)

View File

@@ -0,0 +1,2 @@
find_package(SecondaryUnwind UNWIND_INCLUDE)
set(PrimaryUnwind_FOUND false)

View File

@@ -0,0 +1,30 @@
CMake Warning at UnwindInclude/UnwindBlock.cmake:4 \(find_package\):
By not providing "Findfoo.cmake" in CMAKE_MODULE_PATH this project has
asked CMake to find a package configuration file provided by "foo", but
CMake did not find one.
Could not find a package configuration file provided by "foo" with any of
the following names:
fooConfig.cmake
foo-config.cmake
Add the installation prefix of "foo" to CMAKE_PREFIX_PATH or set "foo_DIR"
to a directory containing one of the above files. If "foo" provides a
separate development package or SDK, be sure it has been installed.
Call Stack \(most recent call first\):
UnwindInclude/PrimaryUnwindConfig.cmake:1 \(include\)
UnwindIncludeBlock.cmake:5 \(find_package\)
CMakeLists.txt:3 \(include\)
CMake Warning at UnwindIncludeBlock.cmake:5 \(find_package\):
Found package configuration file:
[^
]*/Tests/RunCMake/find_package/UnwindInclude/PrimaryUnwindConfig.cmake
but it set PrimaryUnwind_FOUND to FALSE so package "PrimaryUnwind" is
considered to be NOT FOUND.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@@ -0,0 +1,5 @@
cmake_policy(SET CMP0074 NEW)
set(PrimaryUnwind_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/UnwindInclude)
set(UNWIND_TARGET UnwindBlock)
find_package(PrimaryUnwind)

View File

@@ -0,0 +1,31 @@
CMake Warning at UnwindInclude/UnwindFunction.cmake:4 \(find_package\):
By not providing "Findfoo.cmake" in CMAKE_MODULE_PATH this project has
asked CMake to find a package configuration file provided by "foo", but
CMake did not find one.
Could not find a package configuration file provided by "foo" with any of
the following names:
fooConfig.cmake
foo-config.cmake
Add the installation prefix of "foo" to CMAKE_PREFIX_PATH or set "foo_DIR"
to a directory containing one of the above files. If "foo" provides a
separate development package or SDK, be sure it has been installed.
Call Stack \(most recent call first\):
UnwindInclude/UnwindFunction.cmake:15 \(f\)
UnwindInclude/PrimaryUnwindConfig.cmake:1 \(include\)
UnwindIncludeFunction.cmake:5 \(find_package\)
CMakeLists.txt:3 \(include\)
CMake Warning at UnwindIncludeFunction.cmake:5 \(find_package\):
Found package configuration file:
[^
]*/Tests/RunCMake/find_package/UnwindInclude/PrimaryUnwindConfig.cmake
but it set PrimaryUnwind_FOUND to FALSE so package "PrimaryUnwind" is
considered to be NOT FOUND.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@@ -0,0 +1,5 @@
cmake_policy(SET CMP0074 NEW)
set(PrimaryUnwind_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/UnwindInclude)
set(UNWIND_TARGET UnwindFunction)
find_package(PrimaryUnwind)

View File

@@ -0,0 +1,4 @@
CMake Error at UnwindIncludeInvalidContext.cmake:5 \(find_package\):
find_package called with UNWIND_INCLUDE in an invalid context
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@@ -0,0 +1,5 @@
cmake_policy(SET CMP0074 NEW)
set(PrimaryUnwind_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/UnwindInclude)
set(UNWIND_TARGET NoUnwind)
find_package(PrimaryUnwind UNWIND_INCLUDE)

View File

@@ -0,0 +1,26 @@
CMake Warning at UnwindInclude/UnwindInvalidFindPackage.cmake:1 \(find_package\):
By not providing "Findfoo.cmake" in CMAKE_MODULE_PATH this project has
asked CMake to find a package configuration file provided by "foo", but
CMake did not find one.
Could not find a package configuration file provided by "foo" with any of
the following names:
fooConfig.cmake
foo-config.cmake
Add the installation prefix of "foo" to CMAKE_PREFIX_PATH or set "foo_DIR"
to a directory containing one of the above files. If "foo" provides a
separate development package or SDK, be sure it has been installed.
Call Stack \(most recent call first\):
UnwindInclude/PrimaryUnwindConfig.cmake:1 \(include\)
UnwindIncludeInvalidFindPackage.cmake:5 \(find_package\)
CMakeLists.txt:3 \(include\)
CMake Error at UnwindInclude/UnwindInvalidFindPackage.cmake:2 \(find_package\):
find_package called while already in an UNWIND state
Call Stack \(most recent call first\):
UnwindInclude/PrimaryUnwindConfig.cmake:1 \(include\)
UnwindIncludeInvalidFindPackage.cmake:5 \(find_package\)
CMakeLists.txt:3 \(include\)

View File

@@ -0,0 +1,5 @@
cmake_policy(SET CMP0074 NEW)
set(PrimaryUnwind_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/UnwindInclude)
set(UNWIND_TARGET UnwindInvalidFindPackage)
find_package(PrimaryUnwind)

View File

@@ -0,0 +1,26 @@
CMake Warning at UnwindInclude/UnwindInvalidInclude.cmake:1 \(find_package\):
By not providing "Findfoo.cmake" in CMAKE_MODULE_PATH this project has
asked CMake to find a package configuration file provided by "foo", but
CMake did not find one.
Could not find a package configuration file provided by "foo" with any of
the following names:
fooConfig.cmake
foo-config.cmake
Add the installation prefix of "foo" to CMAKE_PREFIX_PATH or set "foo_DIR"
to a directory containing one of the above files. If "foo" provides a
separate development package or SDK, be sure it has been installed.
Call Stack \(most recent call first\):
UnwindInclude/PrimaryUnwindConfig.cmake:1 \(include\)
UnwindIncludeInvalidInclude.cmake:5 \(find_package\)
CMakeLists.txt:3 \(include\)
CMake Error at UnwindInclude/UnwindInvalidInclude.cmake:2 \(include\):
include called while already in an UNWIND state
Call Stack \(most recent call first\):
UnwindInclude/PrimaryUnwindConfig.cmake:1 \(include\)
UnwindIncludeInvalidInclude.cmake:5 \(find_package\)
CMakeLists.txt:3 \(include\)

View File

@@ -0,0 +1,5 @@
cmake_policy(SET CMP0074 NEW)
set(PrimaryUnwind_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/UnwindInclude)
set(UNWIND_TARGET UnwindInvalidInclude)
find_package(PrimaryUnwind)

View File

@@ -0,0 +1,5 @@
cmake_policy(SET CMP0074 NEW)
set(PrimaryUnwind_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/UnwindInclude)
set(UNWIND_TARGET NoUnwind)
find_package(PrimaryUnwind)

View File

@@ -0,0 +1,30 @@
CMake Warning at UnwindInclude/UnwindOnly.cmake:1 \(find_package\):
By not providing "Findfoo.cmake" in CMAKE_MODULE_PATH this project has
asked CMake to find a package configuration file provided by "foo", but
CMake did not find one.
Could not find a package configuration file provided by "foo" with any of
the following names:
fooConfig.cmake
foo-config.cmake
Add the installation prefix of "foo" to CMAKE_PREFIX_PATH or set "foo_DIR"
to a directory containing one of the above files. If "foo" provides a
separate development package or SDK, be sure it has been installed.
Call Stack \(most recent call first\):
UnwindInclude/PrimaryUnwindConfig.cmake:1 \(include\)
UnwindIncludeOnly.cmake:5 \(find_package\)
CMakeLists.txt:3 \(include\)
CMake Warning at UnwindIncludeOnly.cmake:5 \(find_package\):
Found package configuration file:
[^
]*/Tests/RunCMake/find_package/UnwindInclude/PrimaryUnwindConfig.cmake
but it set PrimaryUnwind_FOUND to FALSE so package "PrimaryUnwind" is
considered to be NOT FOUND.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@@ -0,0 +1,5 @@
cmake_policy(SET CMP0074 NEW)
set(PrimaryUnwind_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/UnwindInclude)
set(UNWIND_TARGET UnwindOnly)
find_package(PrimaryUnwind)

View File

@@ -0,0 +1,24 @@
CMake Warning at UnwindInclude/UnwindSecondary.cmake:1 \(find_package\):
Found package configuration file:
[^
]*/Tests/RunCMake/find_package/UnwindInclude/SecondaryUnwindConfig.cmake
but it set SecondaryUnwind_FOUND to FALSE so package "SecondaryUnwind" is
considered to be NOT FOUND.
Call Stack \(most recent call first\):
UnwindInclude/PrimaryUnwindConfig.cmake:1 \(include\)
UnwindIncludeSecondary.cmake:5 \(find_package\)
CMakeLists.txt:3 \(include\)
CMake Warning at UnwindIncludeSecondary.cmake:5 \(find_package\):
Found package configuration file:
[^
]*/Tests/RunCMake/find_package/UnwindInclude/PrimaryUnwindConfig.cmake
but it set PrimaryUnwind_FOUND to FALSE so package "PrimaryUnwind" is
considered to be NOT FOUND.
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)

View File

@@ -0,0 +1,5 @@
cmake_policy(SET CMP0074 NEW)
set(PrimaryUnwind_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/UnwindInclude)
set(UNWIND_TARGET UnwindSecondary)
find_package(PrimaryUnwind)