1
0
mirror of https://github.com/Kitware/CMake.git synced 2025-06-12 08:42:47 +08:00
CMake/Source/cmGetPropertyCommand.cxx
Deniz Bahadir 071f0d6f98 CMP0163: Make GENERATED source file property globally visible
This was originally attempted by policy CMP0118, but its
implementation did not cover all intended use cases.  We fixed its
documentation in commit 1dabbbb5e0 (CMP0118: Revise documentation to
describe actual behavior, 2024-03-20).

Add new policy CMP0163 to cover the remaining use cases.  In particular,
make the `GENERATED` property visible to `get_property` calls in other
directories.  In order to capture the original intention of CMP0118,
define CMP0163's NEW behavior to also imply CMP0118's NEW behavior.

Fixes: #25437
Fixes: #25058
2024-03-29 08:54:11 -04:00

555 lines
19 KiB
C++

/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file Copyright.txt or https://cmake.org/licensing for details. */
#include "cmGetPropertyCommand.h"
#include <cstddef>
#include <functional>
#include <cm/string_view>
#include <cmext/string_view>
#include "cmExecutionStatus.h"
#include "cmGlobalGenerator.h"
#include "cmInstalledFile.h"
#include "cmMakefile.h"
#include "cmMessageType.h"
#include "cmPolicies.h"
#include "cmProperty.h"
#include "cmPropertyDefinition.h"
#include "cmSetPropertyCommand.h"
#include "cmSourceFile.h"
#include "cmState.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmTest.h"
#include "cmValue.h"
#include "cmake.h"
namespace {
enum OutType
{
OutValue,
OutDefined,
OutBriefDoc,
OutFullDoc,
OutSet
};
// Implementation of each property type.
bool HandleGlobalMode(cmExecutionStatus& status, const std::string& name,
OutType infoType, const std::string& variable,
const std::string& propertyName);
bool HandleDirectoryMode(cmExecutionStatus& status, const std::string& name,
OutType infoType, const std::string& variable,
const std::string& propertyName);
bool HandleTargetMode(cmExecutionStatus& status, const std::string& name,
OutType infoType, const std::string& variable,
const std::string& propertyName);
bool HandleSourceMode(cmExecutionStatus& status, const std::string& name,
OutType infoType, const std::string& variable,
const std::string& propertyName,
cmMakefile& directory_makefile,
bool source_file_paths_should_be_absolute);
bool HandleTestMode(cmExecutionStatus& status, const std::string& name,
OutType infoType, const std::string& variable,
const std::string& propertyName,
cmMakefile& directory_makefile);
bool HandleVariableMode(cmExecutionStatus& status, const std::string& name,
OutType infoType, const std::string& variable,
const std::string& propertyName);
bool HandleCacheMode(cmExecutionStatus& status, const std::string& name,
OutType infoType, const std::string& variable,
const std::string& propertyName);
bool HandleInstallMode(cmExecutionStatus& status, const std::string& name,
OutType infoType, const std::string& variable,
const std::string& propertyName);
}
bool cmGetPropertyCommand(std::vector<std::string> const& args,
cmExecutionStatus& status)
{
OutType infoType = OutValue;
if (args.size() < 3) {
status.SetError("called with incorrect number of arguments");
return false;
}
// The cmake variable in which to store the result.
std::string const& variable = args[0];
std::string name;
std::string propertyName;
std::vector<std::string> source_file_directories;
std::vector<std::string> source_file_target_directories;
bool source_file_directory_option_enabled = false;
bool source_file_target_option_enabled = false;
std::string test_directory;
bool test_directory_option_enabled = false;
// Get the scope from which to get the property.
cmProperty::ScopeType scope;
if (args[1] == "GLOBAL") {
scope = cmProperty::GLOBAL;
} else if (args[1] == "DIRECTORY") {
scope = cmProperty::DIRECTORY;
} else if (args[1] == "TARGET") {
scope = cmProperty::TARGET;
} else if (args[1] == "SOURCE") {
scope = cmProperty::SOURCE_FILE;
} else if (args[1] == "TEST") {
scope = cmProperty::TEST;
} else if (args[1] == "VARIABLE") {
scope = cmProperty::VARIABLE;
} else if (args[1] == "CACHE") {
scope = cmProperty::CACHE;
} else if (args[1] == "INSTALL") {
scope = cmProperty::INSTALL;
} else {
status.SetError(cmStrCat(
"given invalid scope ", args[1],
". "
"Valid scopes are "
"GLOBAL, DIRECTORY, TARGET, SOURCE, TEST, VARIABLE, CACHE, INSTALL."));
return false;
}
// Parse remaining arguments.
enum Doing
{
DoingNone,
DoingName,
DoingProperty,
DoingType,
DoingSourceDirectory,
DoingSourceTargetDirectory,
DoingTestDirectory,
};
Doing doing = DoingName;
for (unsigned int i = 2; i < args.size(); ++i) {
if (args[i] == "PROPERTY") {
doing = DoingProperty;
} else if (args[i] == "BRIEF_DOCS") {
doing = DoingNone;
infoType = OutBriefDoc;
} else if (args[i] == "FULL_DOCS") {
doing = DoingNone;
infoType = OutFullDoc;
} else if (args[i] == "SET") {
doing = DoingNone;
infoType = OutSet;
} else if (args[i] == "DEFINED") {
doing = DoingNone;
infoType = OutDefined;
} else if (doing == DoingName) {
doing = DoingNone;
name = args[i];
} else if (doing == DoingNone && scope == cmProperty::SOURCE_FILE &&
args[i] == "DIRECTORY") {
doing = DoingSourceDirectory;
source_file_directory_option_enabled = true;
} else if (doing == DoingNone && scope == cmProperty::SOURCE_FILE &&
args[i] == "TARGET_DIRECTORY") {
doing = DoingSourceTargetDirectory;
source_file_target_option_enabled = true;
} else if (doing == DoingNone && scope == cmProperty::TEST &&
args[i] == "DIRECTORY") {
doing = DoingTestDirectory;
test_directory_option_enabled = true;
} else if (doing == DoingSourceDirectory) {
source_file_directories.push_back(args[i]);
doing = DoingNone;
} else if (doing == DoingSourceTargetDirectory) {
source_file_target_directories.push_back(args[i]);
doing = DoingNone;
} else if (doing == DoingTestDirectory) {
test_directory = args[i];
doing = DoingNone;
} else if (doing == DoingProperty) {
doing = DoingNone;
propertyName = args[i];
} else {
status.SetError(cmStrCat("given invalid argument \"", args[i], "\"."));
return false;
}
}
// Make sure a property name was found.
if (propertyName.empty()) {
status.SetError("not given a PROPERTY <name> argument.");
return false;
}
std::vector<cmMakefile*> source_file_directory_makefiles;
bool source_file_scopes_handled =
SetPropertyCommand::HandleAndValidateSourceFileDirectoryScopes(
status, source_file_directory_option_enabled,
source_file_target_option_enabled, source_file_directories,
source_file_target_directories, source_file_directory_makefiles);
cmMakefile* test_directory_makefile;
bool test_scopes_handled =
SetPropertyCommand::HandleAndValidateTestDirectoryScopes(
status, test_directory_option_enabled, test_directory,
test_directory_makefile);
if (!(source_file_scopes_handled && test_scopes_handled)) {
return false;
}
// Compute requested output.
if (infoType == OutBriefDoc) {
// Lookup brief documentation.
std::string output;
if (cmPropertyDefinition const* def =
status.GetMakefile().GetState()->GetPropertyDefinition(propertyName,
scope)) {
output = def->GetShortDescription();
}
if (output.empty()) {
output = "NOTFOUND";
}
status.GetMakefile().AddDefinition(variable, output);
} else if (infoType == OutFullDoc) {
// Lookup full documentation.
std::string output;
if (cmPropertyDefinition const* def =
status.GetMakefile().GetState()->GetPropertyDefinition(propertyName,
scope)) {
output = def->GetFullDescription();
}
if (output.empty()) {
output = "NOTFOUND";
}
status.GetMakefile().AddDefinition(variable, output);
} else if (infoType == OutDefined) {
// Lookup if the property is defined
if (status.GetMakefile().GetState()->GetPropertyDefinition(propertyName,
scope)) {
status.GetMakefile().AddDefinition(variable, "1");
} else {
status.GetMakefile().AddDefinition(variable, "0");
}
} else {
// Dispatch property getting.
cmMakefile& directory_scope_mf = *(source_file_directory_makefiles[0]);
bool source_file_paths_should_be_absolute =
source_file_directory_option_enabled ||
source_file_target_option_enabled;
switch (scope) {
case cmProperty::GLOBAL:
return HandleGlobalMode(status, name, infoType, variable,
propertyName);
case cmProperty::DIRECTORY:
return HandleDirectoryMode(status, name, infoType, variable,
propertyName);
case cmProperty::TARGET:
return HandleTargetMode(status, name, infoType, variable,
propertyName);
case cmProperty::SOURCE_FILE:
return HandleSourceMode(status, name, infoType, variable, propertyName,
directory_scope_mf,
source_file_paths_should_be_absolute);
case cmProperty::TEST:
return HandleTestMode(status, name, infoType, variable, propertyName,
*test_directory_makefile);
case cmProperty::VARIABLE:
return HandleVariableMode(status, name, infoType, variable,
propertyName);
case cmProperty::CACHE:
return HandleCacheMode(status, name, infoType, variable, propertyName);
case cmProperty::INSTALL:
return HandleInstallMode(status, name, infoType, variable,
propertyName);
case cmProperty::CACHED_VARIABLE:
break; // should never happen
}
}
return true;
}
namespace GetPropertyCommand {
bool GetSourceFilePropertyGENERATED(
const std::string& name, cmMakefile& mf,
const std::function<bool(bool)>& storeResult)
{
// Globally set as generated?
// Note: If the given "name" only contains a filename or a relative path
// the file's location is ambiguous. In general, one would expect
// it in the source-directory, because that is where source files
// are located normally. However, generated files are normally
// generated in the build-directory. Therefore, we first check for
// a generated file in the build-directory before we check for a
// generated file in the source-directory.
{
auto file =
cmSystemTools::CollapseFullPath(name, mf.GetCurrentBinaryDirectory());
if (mf.GetGlobalGenerator()->IsGeneratedFile(file)) {
return storeResult(true);
}
}
{
auto file =
cmSystemTools::CollapseFullPath(name, mf.GetCurrentSourceDirectory());
if (mf.GetGlobalGenerator()->IsGeneratedFile(file)) {
return storeResult(true);
}
}
// Skip checking the traditional/local property.
return storeResult(false);
}
}
namespace {
// Implementation of result storage.
template <typename ValueType>
bool StoreResult(OutType infoType, cmMakefile& makefile,
const std::string& variable, ValueType value)
{
if (infoType == OutSet) {
makefile.AddDefinition(variable, value ? "1" : "0");
} else // if(infoType == OutValue)
{
if (value) {
makefile.AddDefinition(variable, value);
} else {
makefile.RemoveDefinition(variable);
}
}
return true;
}
template <>
bool StoreResult(OutType infoType, cmMakefile& makefile,
const std::string& variable, std::nullptr_t value)
{
return StoreResult(infoType, makefile, variable, cmValue(value));
}
bool HandleGlobalMode(cmExecutionStatus& status, const std::string& name,
OutType infoType, const std::string& variable,
const std::string& propertyName)
{
if (!name.empty()) {
status.SetError("given name for GLOBAL scope.");
return false;
}
// Get the property.
cmake* cm = status.GetMakefile().GetCMakeInstance();
return StoreResult(infoType, status.GetMakefile(), variable,
cm->GetState()->GetGlobalProperty(propertyName));
}
bool HandleDirectoryMode(cmExecutionStatus& status, const std::string& name,
OutType infoType, const std::string& variable,
const std::string& propertyName)
{
// Default to the current directory.
cmMakefile* mf = &status.GetMakefile();
// Lookup the directory if given.
if (!name.empty()) {
// Construct the directory name. Interpret relative paths with
// respect to the current directory.
std::string dir = cmSystemTools::CollapseFullPath(
name, status.GetMakefile().GetCurrentSourceDirectory());
// Lookup the generator.
mf = status.GetMakefile().GetGlobalGenerator()->FindMakefile(dir);
if (!mf) {
// Could not find the directory.
status.SetError(
"DIRECTORY scope provided but requested directory was not found. "
"This could be because the directory argument was invalid or, "
"it is valid but has not been processed yet.");
return false;
}
}
if (propertyName == "DEFINITIONS") {
switch (mf->GetPolicyStatus(cmPolicies::CMP0059)) {
case cmPolicies::WARN:
mf->IssueMessage(MessageType::AUTHOR_WARNING,
cmPolicies::GetPolicyWarning(cmPolicies::CMP0059));
CM_FALLTHROUGH;
case cmPolicies::OLD:
return StoreResult(infoType, status.GetMakefile(), variable,
mf->GetDefineFlagsCMP0059());
case cmPolicies::NEW:
case cmPolicies::REQUIRED_ALWAYS:
case cmPolicies::REQUIRED_IF_USED:
break;
}
}
// Get the property.
return StoreResult(infoType, status.GetMakefile(), variable,
mf->GetProperty(propertyName));
}
bool HandleTargetMode(cmExecutionStatus& status, const std::string& name,
OutType infoType, const std::string& variable,
const std::string& propertyName)
{
if (name.empty()) {
status.SetError("not given name for TARGET scope.");
return false;
}
if (cmTarget* target = status.GetMakefile().FindTargetToUse(name)) {
if (propertyName == "ALIASED_TARGET" || propertyName == "ALIAS_GLOBAL") {
if (status.GetMakefile().IsAlias(name)) {
if (propertyName == "ALIASED_TARGET") {
return StoreResult(infoType, status.GetMakefile(), variable,
target->GetName().c_str());
}
if (propertyName == "ALIAS_GLOBAL") {
return StoreResult(
infoType, status.GetMakefile(), variable,
status.GetMakefile().GetGlobalGenerator()->IsAlias(name)
? "TRUE"
: "FALSE");
}
}
return StoreResult(infoType, status.GetMakefile(), variable, nullptr);
}
cmValue prop =
target->GetComputedProperty(propertyName, status.GetMakefile());
if (!prop) {
prop = target->GetProperty(propertyName);
}
return StoreResult(infoType, status.GetMakefile(), variable, prop);
}
status.SetError(cmStrCat("could not find TARGET ", name,
". Perhaps it has not yet been created."));
return false;
}
bool HandleSourceMode(cmExecutionStatus& status, const std::string& name,
OutType infoType, const std::string& variable,
const std::string& propertyName,
cmMakefile& directory_makefile,
const bool source_file_paths_should_be_absolute)
{
if (name.empty()) {
status.SetError("not given name for SOURCE scope.");
return false;
}
// Special handling for GENERATED property.
// Note: Only, if CMP0163 is set to NEW!
if (propertyName == "GENERATED"_s) {
auto& mf = status.GetMakefile();
auto cmp0163 = directory_makefile.GetPolicyStatus(cmPolicies::CMP0163);
bool const cmp0163new =
cmp0163 != cmPolicies::OLD && cmp0163 != cmPolicies::WARN;
if (cmp0163new) {
return GetPropertyCommand::GetSourceFilePropertyGENERATED(
name, mf, [infoType, &variable, &mf](bool isGenerated) -> bool {
// Set the value on the original Makefile scope, not the scope of the
// requested directory.
return StoreResult(infoType, mf, variable,
(isGenerated) ? cmValue("1") : cmValue("0"));
});
}
}
// Get the source file.
const std::string source_file_absolute_path =
SetPropertyCommand::MakeSourceFilePathAbsoluteIfNeeded(
status, name, source_file_paths_should_be_absolute);
if (cmSourceFile* sf =
directory_makefile.GetOrCreateSource(source_file_absolute_path)) {
// Set the value on the original Makefile scope, not the scope of the
// requested directory.
return StoreResult(infoType, status.GetMakefile(), variable,
sf->GetPropertyForUser(propertyName));
}
status.SetError(
cmStrCat("given SOURCE name that could not be found or created: ",
source_file_absolute_path));
return false;
}
bool HandleTestMode(cmExecutionStatus& status, const std::string& name,
OutType infoType, const std::string& variable,
const std::string& propertyName, cmMakefile& test_makefile)
{
if (name.empty()) {
status.SetError("not given name for TEST scope.");
return false;
}
// Loop over all tests looking for matching names.
if (cmTest* test = test_makefile.GetTest(name)) {
return StoreResult(infoType, status.GetMakefile(), variable,
test->GetProperty(propertyName));
}
// If not found it is an error.
status.SetError(cmStrCat("given TEST name that does not exist: ", name));
return false;
}
bool HandleVariableMode(cmExecutionStatus& status, const std::string& name,
OutType infoType, const std::string& variable,
const std::string& propertyName)
{
if (!name.empty()) {
status.SetError("given name for VARIABLE scope.");
return false;
}
return StoreResult(infoType, status.GetMakefile(), variable,
status.GetMakefile().GetDefinition(propertyName));
}
bool HandleCacheMode(cmExecutionStatus& status, const std::string& name,
OutType infoType, const std::string& variable,
const std::string& propertyName)
{
if (name.empty()) {
status.SetError("not given name for CACHE scope.");
return false;
}
cmValue value = nullptr;
if (status.GetMakefile().GetState()->GetCacheEntryValue(name)) {
value = status.GetMakefile().GetState()->GetCacheEntryProperty(
name, propertyName);
}
StoreResult(infoType, status.GetMakefile(), variable, value);
return true;
}
bool HandleInstallMode(cmExecutionStatus& status, const std::string& name,
OutType infoType, const std::string& variable,
const std::string& propertyName)
{
if (name.empty()) {
status.SetError("not given name for INSTALL scope.");
return false;
}
// Get the installed file.
cmake* cm = status.GetMakefile().GetCMakeInstance();
if (cmInstalledFile* file =
cm->GetOrCreateInstalledFile(&status.GetMakefile(), name)) {
std::string value;
bool isSet = file->GetProperty(propertyName, value);
return StoreResult(infoType, status.GetMakefile(), variable,
isSet ? value.c_str() : nullptr);
}
status.SetError(
cmStrCat("given INSTALL name that could not be found or created: ", name));
return false;
}
}