Files
cmake/Source/cmStateSnapshot.cxx
T
Arha Gatram 86759dce81 enable_language: add support for usage in subdirectories
Propagates variables set in enable_language() logic up variable scopes
so that the language can be used by targets in the ancestor directories.
Works through block() and function() scopes as well. The
add_subdirectory() call can be thought of as the effective call site for
enabling the language. The same holds for endblock() and the actual
function call.

Adds a policy CMP0220 to control this behavior. As the language is
"enabled" at each ancestor scope, the policy is checked. If OLD, the
language is not enabled at this scope and the ones above.

Closes: #27881 #27508 #27389
Related: #26751 #27564
2026-08-07 11:05:57 -04:00

701 lines
22 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmStateSnapshot.h"
#include <algorithm>
#include <array>
#include <cassert>
#include <set>
#include <string>
#include <unordered_map>
#include <cm/iterator>
#include <cmext/algorithm>
#include "cmDefinitions.h"
#include "cmLinkedTree.h"
#include "cmListFileCache.h"
#include "cmPackageState.h"
#include "cmPropertyMap.h"
#include "cmState.h"
#include "cmStateDirectory.h"
#include "cmStatePrivate.h"
#include "cmSystemTools.h"
#include "cmValue.h"
#include "cmVersion.h"
#if defined(__CYGWIN__)
# include "cmStringAlgorithms.h"
#endif
cmStateSnapshot::cmStateSnapshot(cmState* state)
: State(state)
{
}
std::vector<cmStateSnapshot> cmStateSnapshot::GetChildren()
{
return this->Position->BuildSystemDirectory->Children;
}
cmStateSnapshot::cmStateSnapshot(cmState* state,
cmStateDetail::PositionType position)
: State(state)
, Position(position)
{
}
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;
}
std::string const& cmStateSnapshot::GetExecutionListFile() const
{
return *this->Position->ExecutionListFile;
}
bool cmStateSnapshot::IsValid() const
{
return this->State && this->Position.IsValid()
? this->Position != this->State->SnapshotData.Root()
: false;
}
cmStateSnapshot cmStateSnapshot::GetBuildsystemDirectory() const
{
return { this->State, this->Position->BuildSystemDirectory->CurrentScope };
}
cmStateSnapshot cmStateSnapshot::GetBuildsystemDirectoryParent() const
{
cmStateSnapshot snapshot;
if (!this->State || this->Position == this->State->SnapshotData.Root()) {
return snapshot;
}
cmStateDetail::PositionType parentPos = this->Position->DirectoryParent;
if (parentPos != this->State->SnapshotData.Root()) {
snapshot = cmStateSnapshot(this->State,
parentPos->BuildSystemDirectory->CurrentScope);
}
return snapshot;
}
cmStateSnapshot cmStateSnapshot::GetCallStackParent() const
{
assert(this->State);
assert(this->Position != this->State->SnapshotData.Root());
cmStateSnapshot snapshot;
cmStateDetail::PositionType parentPos = this->Position;
while (parentPos->SnapshotType == cmStateEnums::PolicyScopeType ||
parentPos->SnapshotType == cmStateEnums::VariableScopeType) {
++parentPos;
}
if (parentPos->SnapshotType == cmStateEnums::BuildsystemDirectoryType ||
parentPos->SnapshotType == cmStateEnums::BaseType) {
return snapshot;
}
++parentPos;
while (parentPos->SnapshotType == cmStateEnums::PolicyScopeType ||
parentPos->SnapshotType == cmStateEnums::VariableScopeType) {
++parentPos;
}
if (parentPos == this->State->SnapshotData.Root()) {
return snapshot;
}
snapshot = cmStateSnapshot(this->State, parentPos);
return snapshot;
}
cmStateSnapshot cmStateSnapshot::GetCallStackBottom() const
{
assert(this->State);
assert(this->Position != this->State->SnapshotData.Root());
cmStateDetail::PositionType pos = this->Position;
while (pos->SnapshotType != cmStateEnums::BaseType &&
pos->SnapshotType != cmStateEnums::BuildsystemDirectoryType &&
pos != this->State->SnapshotData.Root()) {
++pos;
}
return { this->State, pos };
}
void cmStateSnapshot::PushPolicy(cmPolicies::PolicyMap const& entry, bool weak)
{
cmStateDetail::PositionType pos = this->Position;
pos->Policies = this->State->PolicyStack.Push(
pos->Policies, cmStateDetail::PolicyStackEntry(entry, weak));
}
bool cmStateSnapshot::PopPolicy()
{
cmStateDetail::PositionType pos = this->Position;
if (pos->Policies == pos->PolicyScope) {
return false;
}
pos->Policies = this->State->PolicyStack.Pop(pos->Policies);
return true;
}
bool cmStateSnapshot::CanPopPolicyScope() const
{
return this->Position->Policies != this->Position->PolicyScope;
}
void cmStateSnapshot::SetPolicy(cmPolicies::PolicyID id,
cmPolicies::PolicyStatus status)
{
// Update the policy stack from the top to the top-most strong entry.
bool previous_was_weak = true;
for (cmLinkedTree<cmStateDetail::PolicyStackEntry>::iterator psi =
this->Position->Policies;
previous_was_weak && psi != this->Position->PolicyRoot; ++psi) {
psi->Set(id, status);
previous_was_weak = psi->Weak;
}
}
cmPolicies::PolicyStatus cmStateSnapshot::GetPolicy(cmPolicies::PolicyID id,
bool parent_scope) const
{
if (cmPolicies::IsRemoved(id)) {
return cmPolicies::NEW;
}
cmPolicies::PolicyStatus status = cmPolicies::WARN;
cmLinkedTree<cmStateDetail::BuildsystemDirectoryStateType>::iterator dir =
this->Position->BuildSystemDirectory;
while (true) {
assert(dir.IsValid());
cmLinkedTree<cmStateDetail::PolicyStackEntry>::iterator leaf =
dir->CurrentScope->Policies;
cmLinkedTree<cmStateDetail::PolicyStackEntry>::iterator root =
dir->CurrentScope->PolicyRoot;
for (; leaf != root; ++leaf) {
if (parent_scope) {
parent_scope = false;
continue;
}
if (leaf->IsDefined(id)) {
status = leaf->Get(id);
return status;
}
}
cmStateDetail::PositionType e = dir->CurrentScope;
cmStateDetail::PositionType p = e->DirectoryParent;
if (p == this->State->SnapshotData.Root()) {
break;
}
dir = p->BuildSystemDirectory;
}
return status;
}
cmPolicies::PolicyStatus cmStateSnapshot::GetScopePolicy(
cmPolicies::PolicyID id) const
{
if (cmPolicies::IsRemoved(id)) {
return cmPolicies::NEW;
}
cmPolicies::PolicyStatus status = cmPolicies::WARN;
cmLinkedTree<cmStateDetail::BuildsystemDirectoryStateType>::iterator dir =
this->Position->BuildSystemDirectory;
// Logic mirrors GetPolicy(), except that the search starts at this
// snapshot's exact scope rather than the directory's current top scope.
cmLinkedTree<cmStateDetail::PolicyStackEntry>::iterator leaf =
this->Position->Policies;
cmLinkedTree<cmStateDetail::PolicyStackEntry>::iterator root =
this->Position->PolicyRoot;
while (true) {
assert(dir.IsValid());
for (; leaf != root; ++leaf) {
if (leaf->IsDefined(id)) {
return leaf->Get(id);
}
}
cmStateDetail::PositionType e = dir->CurrentScope;
cmStateDetail::PositionType p = e->DirectoryParent;
if (p == this->State->SnapshotData.Root()) {
break;
}
dir = p->BuildSystemDirectory;
leaf = dir->CurrentScope->Policies;
root = dir->CurrentScope->PolicyRoot;
}
return status;
}
void cmStateSnapshot::PushDiagnostic(cmDiagnostics::DiagnosticMap entry,
bool weak)
{
cmStateDetail::PositionType pos = this->Position;
pos->Diagnostics = this->State->DiagnosticStack.Push(
pos->Diagnostics, cmStateDetail::DiagnosticStackEntry(entry, weak));
}
bool cmStateSnapshot::PopDiagnostic()
{
cmStateDetail::PositionType pos = this->Position;
if (pos->Diagnostics == pos->DiagnosticScope) {
return false;
}
pos->Diagnostics = this->State->DiagnosticStack.Pop(pos->Diagnostics);
return true;
}
bool cmStateSnapshot::CanPopDiagnosticScope() const
{
return this->Position->Diagnostics != this->Position->DiagnosticScope;
}
void cmStateSnapshot::SetDiagnostic(cmDiagnosticCategory category,
cmDiagnosticAction action, bool recursive)
{
assert(action != cmDiagnostics::Undefined);
auto function = [](cmDiagnosticAction, cmDiagnosticAction) -> bool {
return true;
};
this->AlterDiagnostic(category, action, function, recursive);
}
void cmStateSnapshot::PromoteDiagnostic(cmDiagnosticCategory category,
cmDiagnosticAction action,
bool recursive)
{
assert(action != cmDiagnostics::Undefined);
auto function = [](cmDiagnosticAction current,
cmDiagnosticAction desired) -> bool {
return (current < desired);
};
this->AlterDiagnostic(category, action, function, recursive);
}
void cmStateSnapshot::DemoteDiagnostic(cmDiagnosticCategory category,
cmDiagnosticAction action,
bool recursive)
{
assert(action != cmDiagnostics::Undefined);
auto function = [](cmDiagnosticAction current,
cmDiagnosticAction desired) -> bool {
return (current > desired);
};
this->AlterDiagnostic(category, action, function, recursive);
}
void cmStateSnapshot::AlterDiagnostic(cmDiagnosticCategory category,
cmDiagnosticAction action,
AlterDiagnosticFunction function,
bool recursive)
{
if (recursive) {
unsigned i = category;
for (;;) {
this->AlterDiagnostic(static_cast<cmDiagnosticCategory>(i), action,
function, false);
if (++i >= cmDiagnostics::CategoryCount) {
break;
}
if (cmDiagnostics::CategoryInfo[i].Parent < category) {
break;
}
}
} else {
cmDiagnosticAction const oldAction = this->GetDiagnostic(category);
if (function(oldAction, action)) {
// Update the policy stack from the top to the top-most strong entry.
bool previous_was_weak = true;
for (cmLinkedTree<cmStateDetail::DiagnosticStackEntry>::iterator dsi =
this->Position->Diagnostics;
previous_was_weak && dsi != this->Position->DiagnosticRoot; ++dsi) {
(*dsi)[category] = action;
previous_was_weak = dsi->Weak;
}
}
}
}
cmDiagnosticAction cmStateSnapshot::GetDiagnostic(
cmDiagnosticCategory category, cmDiagnosticAction defaultAction) const
{
cmLinkedTree<cmStateDetail::BuildsystemDirectoryStateType>::iterator dir =
this->Position->BuildSystemDirectory;
while (true) {
assert(dir.IsValid());
cmLinkedTree<cmStateDetail::DiagnosticStackEntry>::iterator leaf =
dir->CurrentScope->Diagnostics;
cmLinkedTree<cmStateDetail::DiagnosticStackEntry>::iterator root =
dir->CurrentScope->DiagnosticRoot;
for (; leaf != root; ++leaf) {
cmDiagnosticAction const action = (*leaf)[category];
if (action != cmDiagnostics::Undefined) {
return action;
}
}
cmStateDetail::PositionType e = dir->CurrentScope;
cmStateDetail::PositionType p = e->DirectoryParent;
if (p == this->State->SnapshotData.Root()) {
break;
}
dir = p->BuildSystemDirectory;
}
return defaultAction;
}
cmValue cmStateSnapshot::GetDefinition(std::string const& name) const
{
assert(this->Position->Vars.IsValid());
return cmDefinitions::Get(name, this->Position->Vars, this->Position->Root);
}
bool cmStateSnapshot::IsInitialized(std::string const& name) const
{
return cmDefinitions::HasKey(name, this->Position->Vars,
this->Position->Root);
}
void cmStateSnapshot::SetDefinition(std::string const& name,
cm::string_view value)
{
this->Position->Vars->Set(name, value);
}
void cmStateSnapshot::RemoveDefinition(std::string const& name)
{
this->Position->Vars->Unset(name);
}
std::vector<std::string> cmStateSnapshot::ClosureKeys() const
{
return cmDefinitions::ClosureKeys(this->Position->Vars,
this->Position->Root);
}
std::vector<std::string> cmStateSnapshot::LocalKeys() const
{
std::vector<std::string> keys = cmDefinitions::ClosureKeys(
this->Position->Vars, this->Position->ScopeParent->Vars);
// Remove keys that are the same in the parent scope to avoid propagating
// logic meant to restore variables back to their original value.
keys.erase(std::remove_if(
keys.begin(), keys.end(),
[this](std::string const& key) {
return cmDefinitions::Get(key, this->Position->Vars,
this->Position->Root) ==
cmDefinitions::Get(key, this->Position->ScopeParent->Vars,
this->Position->ScopeParent->Root);
}),
keys.end());
std::sort(keys.begin(), keys.end());
return keys;
}
bool cmStateSnapshot::RaiseScope(std::string const& var, char const* varDef)
{
if (this->Position->ScopeParent == this->Position->DirectoryParent) {
cmStateSnapshot parentDir = this->GetBuildsystemDirectoryParent();
if (!parentDir.IsValid()) {
return false;
}
// Update the definition in the parent directory top scope. This
// directory's scope was initialized by the closure of the parent
// scope, so we do not need to localize the definition first.
if (varDef) {
parentDir.SetDefinition(var, varDef);
} else {
parentDir.RemoveDefinition(var);
}
return true;
}
// First localize the definition in the current scope.
cmDefinitions::Raise(var, this->Position->Vars, this->Position->Root);
// Now update the definition in the parent scope.
if (varDef) {
this->Position->Parent->Set(var, varDef);
} else {
this->Position->Parent->Unset(var);
}
return true;
}
cmStateSnapshot::WarnCMP0220 cmStateSnapshot::RaiseToRoot(
std::string const& var, char const* varDef, CheckCMP0220 checkCMP0220)
{
// If we are at the top of a directory, propagate to the parent directory (if
// any).
cmStateSnapshot parentScope =
this->Position->ScopeParent == this->Position->DirectoryParent
? this->GetBuildsystemDirectoryParent()
: cmStateSnapshot(this->State, this->Position->ScopeParent);
if (!parentScope.IsValid()) {
return WarnCMP0220::No;
}
// First raise always occurs to propagate out of capturing variable scope.
if (checkCMP0220 == CheckCMP0220::Yes) {
switch (parentScope.GetScopePolicy(cmPolicies::CMP0220)) {
case cmPolicies::NEW:
break;
case cmPolicies::OLD:
return WarnCMP0220::No;
case cmPolicies::WARN:
return WarnCMP0220::Yes;
}
}
if (varDef) {
parentScope.SetDefinition(var, varDef);
} else {
parentScope.RemoveDefinition(var);
}
return parentScope.RaiseToRoot(var, varDef, CheckCMP0220::Yes);
}
template <typename T, typename U>
void InitializeContentFromParent(T& parentContent, T& thisContent,
U& contentEndPosition)
{
auto parentEnd = parentContent.end();
auto parentRbegin = cm::make_reverse_iterator(parentEnd);
auto parentRend = parentContent.rend();
parentRbegin =
std::find(parentRbegin, parentRend, cmStateDetail::PropertySentinel);
auto parentIt = parentRbegin.base();
thisContent = std::vector<BT<std::string>>(parentIt, parentEnd);
contentEndPosition = thisContent.size();
}
void cmStateSnapshot::SetDefaultDefinitions()
{
/* Up to CMake 2.4 here only WIN32, UNIX and APPLE were set.
With CMake must separate between target and host platform. In most cases
the tests for WIN32, UNIX and APPLE will be for the target system, so an
additional set of variables for the host system is required ->
CMAKE_HOST_WIN32, CMAKE_HOST_UNIX, CMAKE_HOST_APPLE.
WIN32, UNIX and APPLE are now set in the platform files in
Modules/Platforms/.
To keep cmake scripts (-P) and custom language and compiler modules
working, these variables are still also set here in this place, but they
will be reset in CMakeSystemSpecificInformation.cmake before the platform
files are executed. */
cm::string_view hostSystemName = cmSystemTools::GetSystemName();
this->SetDefinition("CMAKE_HOST_SYSTEM_NAME", hostSystemName);
if (hostSystemName == "Windows") {
this->SetDefinition("WIN32", "1");
this->SetDefinition("CMAKE_HOST_WIN32", "1");
this->SetDefinition("CMAKE_HOST_EXECUTABLE_SUFFIX", ".exe");
} else {
this->SetDefinition("UNIX", "1");
this->SetDefinition("CMAKE_HOST_UNIX", "1");
this->SetDefinition("CMAKE_HOST_EXECUTABLE_SUFFIX", "");
}
#if defined(__APPLE__)
this->SetDefinition("APPLE", "1");
this->SetDefinition("CMAKE_HOST_APPLE", "1");
#endif
#if defined(__sun__)
this->SetDefinition("CMAKE_HOST_SOLARIS", "1");
#endif
#if defined(__OpenBSD__)
this->SetDefinition("BSD", "OpenBSD");
this->SetDefinition("CMAKE_HOST_BSD", "OpenBSD");
#elif defined(__FreeBSD__)
this->SetDefinition("BSD", "FreeBSD");
this->SetDefinition("CMAKE_HOST_BSD", "FreeBSD");
#elif defined(__NetBSD__)
this->SetDefinition("BSD", "NetBSD");
this->SetDefinition("CMAKE_HOST_BSD", "NetBSD");
#elif defined(__DragonFly__)
this->SetDefinition("BSD", "DragonFlyBSD");
this->SetDefinition("CMAKE_HOST_BSD", "DragonFlyBSD");
#endif
#if defined(__linux__)
this->SetDefinition("LINUX", "1");
this->SetDefinition("CMAKE_HOST_LINUX", "1");
#endif
#if defined(_AIX)
this->SetDefinition("AIX", "1");
this->SetDefinition("CMAKE_HOST_AIX", "1");
#endif
this->SetDefinition("CMAKE_MAJOR_VERSION",
std::to_string(cmVersion::GetMajorVersion()));
this->SetDefinition("CMAKE_MINOR_VERSION",
std::to_string(cmVersion::GetMinorVersion()));
this->SetDefinition("CMAKE_PATCH_VERSION",
std::to_string(cmVersion::GetPatchVersion()));
this->SetDefinition("CMAKE_TWEAK_VERSION",
std::to_string(cmVersion::GetTweakVersion()));
this->SetDefinition("CMAKE_VERSION", cmVersion::GetCMakeVersion());
this->SetDefinition("CMAKE_FILES_DIRECTORY", "/CMakeFiles");
// Setup the default include file regular expression (match everything).
this->Position->BuildSystemDirectory->Properties.SetProperty(
"INCLUDE_REGULAR_EXPRESSION", "^.*$");
}
void cmStateSnapshot::SetDirectoryDefinitions()
{
this->SetDefinition("CMAKE_SOURCE_DIR", this->State->GetSourceDirectory());
this->SetDefinition("CMAKE_CURRENT_SOURCE_DIR",
this->State->GetSourceDirectory());
this->SetDefinition("CMAKE_BINARY_DIR", this->State->GetBinaryDirectory());
this->SetDefinition("CMAKE_CURRENT_BINARY_DIR",
this->State->GetBinaryDirectory());
}
void cmStateSnapshot::InitializeFromParent()
{
cmStateDetail::PositionType parent = this->Position->DirectoryParent;
assert(this->Position->Vars.IsValid());
assert(parent->Vars.IsValid());
*this->Position->Vars =
cmDefinitions::MakeClosure(parent->Vars, parent->Root);
InitializeContentFromParent(
parent->BuildSystemDirectory->IncludeDirectories,
this->Position->BuildSystemDirectory->IncludeDirectories,
this->Position->IncludeDirectoryPosition);
InitializeContentFromParent(
parent->BuildSystemDirectory->CompileDefinitions,
this->Position->BuildSystemDirectory->CompileDefinitions,
this->Position->CompileDefinitionsPosition);
InitializeContentFromParent(
parent->BuildSystemDirectory->CompileOptions,
this->Position->BuildSystemDirectory->CompileOptions,
this->Position->CompileOptionsPosition);
InitializeContentFromParent(
parent->BuildSystemDirectory->LinkOptions,
this->Position->BuildSystemDirectory->LinkOptions,
this->Position->LinkOptionsPosition);
InitializeContentFromParent(
parent->BuildSystemDirectory->LinkDirectories,
this->Position->BuildSystemDirectory->LinkDirectories,
this->Position->LinkDirectoriesPosition);
cmValue include_regex =
parent->BuildSystemDirectory->Properties.GetPropertyValue(
"INCLUDE_REGULAR_EXPRESSION");
this->Position->BuildSystemDirectory->Properties.SetProperty(
"INCLUDE_REGULAR_EXPRESSION", include_regex);
}
cmState* cmStateSnapshot::GetState() const
{
return this->State;
}
cmStateDirectory cmStateSnapshot::GetDirectory() const
{
return { this->Position->BuildSystemDirectory, *this };
}
void cmStateSnapshot::SetProjectName(std::string const& name)
{
this->Position->BuildSystemDirectory->ProjectName = name;
this->Position->BuildSystemDirectory->Projects.insert(name);
}
std::string cmStateSnapshot::GetProjectName() const
{
return this->Position->BuildSystemDirectory->ProjectName;
}
bool cmStateSnapshot::CheckProjectName(std::string const& name) const
{
return cm::contains(this->Position->BuildSystemDirectory->Projects, name);
}
cmPackageState& cmStateSnapshot::GetPackageState(
std::string const& packagePath)
{
return this->Position->BuildSystemDirectory->Packages[packagePath];
}
void cmStateSnapshot::InitializeFromParent_ForSubdirsCommand()
{
std::string currentSrcDir = *this->GetDefinition("CMAKE_CURRENT_SOURCE_DIR");
std::string currentBinDir = *this->GetDefinition("CMAKE_CURRENT_BINARY_DIR");
this->InitializeFromParent();
this->SetDefinition("CMAKE_SOURCE_DIR", this->State->GetSourceDirectory());
this->SetDefinition("CMAKE_BINARY_DIR", this->State->GetBinaryDirectory());
this->SetDefinition("CMAKE_CURRENT_SOURCE_DIR", currentSrcDir);
this->SetDefinition("CMAKE_CURRENT_BINARY_DIR", currentBinDir);
}
bool cmStateSnapshot::StrictWeakOrder::operator()(
cmStateSnapshot const& lhs, cmStateSnapshot const& rhs) const
{
return lhs.Position.StrictWeakOrdered(rhs.Position);
}
bool operator==(cmStateSnapshot const& lhs, cmStateSnapshot const& rhs)
{
return lhs.Position == rhs.Position;
}
bool operator!=(cmStateSnapshot const& lhs, cmStateSnapshot const& rhs)
{
return lhs.Position != rhs.Position;
}