blob: 63f96c964cba8574280143814de3af8b8be45f63 [file]
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmCMakeLanguageCommand.h"
#include <algorithm>
#include <array>
#include <cstddef>
#include <initializer_list>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <cm/optional>
#include <cm/string_view>
#include <cmext/string_view>
#include "cmsys/RegularExpression.hxx"
#include "cmArgumentParser.h"
#include "cmArgumentParserTypes.h"
#include "cmDependencyProvider.h"
#include "cmExecutionStatus.h"
#include "cmExperimental.h"
#include "cmGeneratorTarget.h"
#include "cmGetPropertyCommand.h"
#include "cmGlobalGenerator.h"
#include "cmLinkItem.h"
#include "cmListFileCache.h"
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
#include "cmMessageType.h" // IWYU pragma: keep
#include "cmPropertyMap.h"
#include "cmRange.h"
#include "cmState.h"
#include "cmStateSnapshot.h"
#include "cmStateTypes.h"
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmTargetPropertyComputer.h"
#include "cmTargetPropertyHelper.h"
#include "cmValue.h"
#include "cmake.h"
namespace cm {
enum class TargetType;
}
namespace {
bool FatalError(cmExecutionStatus& status, std::string const& error)
{
status.SetError(error);
cmSystemTools::SetFatalErrorOccurred();
return false;
}
std::array<cm::static_string_view, 14> InvalidCommands{
{ // clang-format off
"function"_s, "endfunction"_s,
"macro"_s, "endmacro"_s,
"if"_s, "elseif"_s, "else"_s, "endif"_s,
"while"_s, "endwhile"_s,
"foreach"_s, "endforeach"_s,
"block"_s, "endblock"_s
} // clang-format on
};
std::array<cm::static_string_view, 1> InvalidDeferCommands{
{
// clang-format off
"return"_s,
} // clang-format on
};
struct Defer
{
std::string Id;
std::string IdVar;
cmMakefile* Directory = nullptr;
};
bool cmCMakeLanguageCommandCALL(std::vector<cmListFileArgument> const& args,
std::string const& callCommand,
size_t startArg, cm::optional<Defer> defer,
cmExecutionStatus& status)
{
// ensure specified command is valid
// start/end flow control commands are not allowed
auto cmd = cmSystemTools::LowerCase(callCommand);
if (std::find(InvalidCommands.cbegin(), InvalidCommands.cend(), cmd) !=
InvalidCommands.cend()) {
return FatalError(status,
cmStrCat("invalid command specified: "_s, callCommand));
}
if (defer &&
std::find(InvalidDeferCommands.cbegin(), InvalidDeferCommands.cend(),
cmd) != InvalidDeferCommands.cend()) {
return FatalError(status,
cmStrCat("invalid command specified: "_s, callCommand));
}
cmMakefile& makefile = status.GetMakefile();
cmListFileContext context = makefile.GetBacktrace().Top();
std::vector<cmListFileArgument> funcArgs;
funcArgs.reserve(args.size() - startArg);
// The rest of the arguments are passed to the function call above
for (size_t i = startArg; i < args.size(); ++i) {
funcArgs.emplace_back(args[i].Value, args[i].Delim, context.Line);
}
cmListFileFunction func{ callCommand, context.Line, context.Line,
std::move(funcArgs) };
if (defer) {
if (defer->Id.empty()) {
defer->Id = makefile.NewDeferId();
}
if (!defer->IdVar.empty()) {
makefile.AddDefinition(defer->IdVar, defer->Id);
}
cmMakefile* deferMakefile =
defer->Directory ? defer->Directory : &makefile;
if (!deferMakefile->DeferCall(defer->Id, context.FilePath, func)) {
return FatalError(
status,
cmStrCat("DEFER CALL may not be scheduled in directory:\n "_s,
deferMakefile->GetCurrentBinaryDirectory(),
"\nat this time."_s));
}
return true;
}
return makefile.ExecuteCommand(func, status);
}
bool cmCMakeLanguageCommandDEFER(Defer const& defer,
std::vector<std::string> const& args,
size_t arg, cmExecutionStatus& status)
{
cmMakefile* deferMakefile =
defer.Directory ? defer.Directory : &status.GetMakefile();
if (args[arg] == "CANCEL_CALL"_s) {
++arg; // Consume CANCEL_CALL.
auto ids = cmMakeRange(args).advance(arg);
for (std::string const& id : ids) {
if (id[0] >= 'A' && id[0] <= 'Z') {
return FatalError(
status, cmStrCat("DEFER CANCEL_CALL unknown argument:\n "_s, id));
}
if (!deferMakefile->DeferCancelCall(id)) {
return FatalError(
status,
cmStrCat("DEFER CANCEL_CALL may not update directory:\n "_s,
deferMakefile->GetCurrentBinaryDirectory(),
"\nat this time."_s));
}
}
return true;
}
if (args[arg] == "GET_CALL_IDS"_s) {
++arg; // Consume GET_CALL_IDS.
if (arg == args.size()) {
return FatalError(status, "DEFER GET_CALL_IDS missing output variable");
}
std::string const& var = args[arg++];
if (arg != args.size()) {
return FatalError(status, "DEFER GET_CALL_IDS given too many arguments");
}
cm::optional<std::string> ids = deferMakefile->DeferGetCallIds();
if (!ids) {
return FatalError(
status,
cmStrCat("DEFER GET_CALL_IDS may not access directory:\n "_s,
deferMakefile->GetCurrentBinaryDirectory(),
"\nat this time."_s));
}
status.GetMakefile().AddDefinition(var, *ids);
return true;
}
if (args[arg] == "GET_CALL"_s) {
++arg; // Consume GET_CALL.
if (arg == args.size()) {
return FatalError(status, "DEFER GET_CALL missing id");
}
std::string const& id = args[arg++];
if (arg == args.size()) {
return FatalError(status, "DEFER GET_CALL missing output variable");
}
std::string const& var = args[arg++];
if (arg != args.size()) {
return FatalError(status, "DEFER GET_CALL given too many arguments");
}
if (id.empty()) {
return FatalError(status, "DEFER GET_CALL id may not be empty");
}
if (id[0] >= 'A' && id[0] <= 'Z') {
return FatalError(status,
cmStrCat("DEFER GET_CALL unknown argument:\n "_s, id));
}
cm::optional<std::string> call = deferMakefile->DeferGetCall(id);
if (!call) {
return FatalError(
status,
cmStrCat("DEFER GET_CALL may not access directory:\n "_s,
deferMakefile->GetCurrentBinaryDirectory(),
"\nat this time."_s));
}
status.GetMakefile().AddDefinition(var, *call);
return true;
}
return FatalError(status,
cmStrCat("DEFER operation unknown: "_s, args[arg]));
}
bool cmCMakeLanguageCommandEVAL(std::vector<cmListFileArgument> const& args,
cmExecutionStatus& status)
{
cmMakefile& makefile = status.GetMakefile();
cmListFileContext context = makefile.GetBacktrace().Top();
std::vector<std::string> expandedArgs;
makefile.ExpandArguments(args, expandedArgs);
if (expandedArgs.size() < 2) {
return FatalError(status, "called with incorrect number of arguments");
}
if (expandedArgs[1] != "CODE") {
auto code_iter =
std::find(expandedArgs.begin() + 2, expandedArgs.end(), "CODE");
if (code_iter == expandedArgs.end()) {
return FatalError(status, "called without CODE argument");
}
return FatalError(
status,
"called with unsupported arguments between EVAL and CODE arguments");
}
std::string const code =
cmJoin(cmMakeRange(expandedArgs.begin() + 2, expandedArgs.end()), " ");
return makefile.ReadListFileAsString(
code, cmStrCat(context.FilePath, ':', context.Line, ":EVAL"));
}
bool cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(
std::vector<std::string> const& args, cmExecutionStatus& status)
{
cmState* state = status.GetMakefile().GetState();
if (!state->InTopLevelIncludes()) {
return FatalError(
status,
"Dependency providers can only be set as part of the first call to "
"project(). More specifically, cmake_language(SET_DEPENDENCY_PROVIDER) "
"can only be called while the first project() command processes files "
"listed in CMAKE_PROJECT_TOP_LEVEL_INCLUDES.");
}
struct SetProviderArgs
{
std::string Command;
ArgumentParser::NonEmpty<std::vector<std::string>> Methods;
};
auto const ArgsParser =
cmArgumentParser<SetProviderArgs>()
.Bind("SET_DEPENDENCY_PROVIDER"_s, &SetProviderArgs::Command)
.Bind("SUPPORTED_METHODS"_s, &SetProviderArgs::Methods);
std::vector<std::string> unparsed;
auto parsedArgs = ArgsParser.Parse(args, &unparsed);
if (!unparsed.empty()) {
return FatalError(
status, cmStrCat("Unrecognized keyword: \"", unparsed.front(), '"'));
}
// We store the command that FetchContent_MakeAvailable() can call in a
// global (but considered internal) property. If the provider doesn't
// support this method, we set this property to an empty string instead.
// This simplifies the logic in FetchContent_MakeAvailable() and doesn't
// require us to define a new internal command or sub-command.
std::string fcmasProperty = "__FETCHCONTENT_MAKEAVAILABLE_SERIAL_PROVIDER";
if (parsedArgs.Command.empty()) {
if (!parsedArgs.Methods.empty()) {
return FatalError(status,
"Must specify a non-empty command name when provider "
"methods are given");
}
state->ClearDependencyProvider();
state->SetGlobalProperty(fcmasProperty, "");
return true;
}
cmState::Command command = state->GetCommand(parsedArgs.Command);
if (!command) {
return FatalError(status,
cmStrCat("Command \"", parsedArgs.Command,
"\" is not a defined command"));
}
if (parsedArgs.Methods.empty()) {
return FatalError(status, "Must specify at least one provider method");
}
bool supportsFetchContentMakeAvailableSerial = false;
std::vector<cmDependencyProvider::Method> methods;
for (auto const& method : parsedArgs.Methods) {
if (method == "FIND_PACKAGE") {
methods.emplace_back(cmDependencyProvider::Method::FindPackage);
} else if (method == "FETCHCONTENT_MAKEAVAILABLE_SERIAL") {
supportsFetchContentMakeAvailableSerial = true;
methods.emplace_back(
cmDependencyProvider::Method::FetchContentMakeAvailableSerial);
} else {
return FatalError(
status,
cmStrCat("Unknown dependency provider method \"", method, '"'));
}
}
state->SetDependencyProvider({ parsedArgs.Command, methods });
state->SetGlobalProperty(
fcmasProperty,
supportsFetchContentMakeAvailableSerial ? parsedArgs.Command : "");
return true;
}
bool cmCMakeLanguageCommandGET_MESSAGE_LOG_LEVEL(
std::vector<cmListFileArgument> const& args, cmExecutionStatus& status)
{
cmMakefile& makefile = status.GetMakefile();
std::vector<std::string> expandedArgs;
makefile.ExpandArguments(args, expandedArgs);
if (args.size() < 2 || expandedArgs.size() > 2) {
return FatalError(
status,
"sub-command GET_MESSAGE_LOG_LEVEL expects exactly one argument");
}
Message::LogLevel logLevel = makefile.GetCurrentLogLevel();
std::string outputValue = cmake::LogLevelToString(logLevel);
std::string const& outputVariable = expandedArgs[1];
makefile.AddDefinition(outputVariable, outputValue);
return true;
}
bool cmCMakeLanguageCommandGET_EXPERIMENTAL_FEATURE_ENABLED(
std::vector<cmListFileArgument> const& args, cmExecutionStatus& status)
{
cmMakefile& makefile = status.GetMakefile();
std::vector<std::string> expandedArgs;
makefile.ExpandArguments(args, expandedArgs);
if (expandedArgs.size() != 3) {
return FatalError(status,
"sub-command GET_EXPERIMENTAL_FEATURE_ENABLED expects "
"exactly two arguments");
}
auto const& featureName = expandedArgs[1];
auto const& variableName = expandedArgs[2];
if (auto feature = cmExperimental::FeatureByName(featureName)) {
if (cmExperimental::HasSupportEnabled(makefile, *feature)) {
makefile.AddDefinition(variableName, "TRUE");
} else {
makefile.AddDefinition(variableName, "FALSE");
}
} else {
return FatalError(status,
cmStrCat("Experimental feature name \"", featureName,
"\" does not exist."));
}
return true;
}
struct PrintTargetsArgs : public ArgumentParser::ParseResult
{
cm::optional<std::string> Regex;
bool ImportedOnly = false;
bool NoImported = false;
bool IgnoreCase = false;
cm::optional<std::string> MessagePrefix;
};
// Lists every target that currently exists, optionally filtered by a
// name REGEX and by imported state. "Currently exists" means anything
// CMake has defined up to this call: targets in the current directory,
// its ancestors, and any already-processed subdirectories. Walking the
// global generator's makefiles captures exactly that set; a name-keyed
// map sorts the output and dedupes imported targets, which are inherited
// into child makefiles and would otherwise be seen many times.
bool cmCMakeLanguageCommandPRINT_TARGETS(
std::vector<cmListFileArgument> const& args, cmExecutionStatus& status)
{
cmMakefile& makefile = status.GetMakefile();
std::vector<std::string> expandedArgs;
makefile.ExpandArguments(args, expandedArgs);
// Drop the leading "PRINT_TARGETS" subcommand keyword.
std::vector<std::string> body(expandedArgs.begin() + 1, expandedArgs.end());
auto const ArgsParser =
cmArgumentParser<PrintTargetsArgs>()
.Bind("REGEX"_s, &PrintTargetsArgs::Regex)
.Bind("IMPORTED_ONLY"_s, &PrintTargetsArgs::ImportedOnly)
.Bind("NO_IMPORTED"_s, &PrintTargetsArgs::NoImported)
.Bind("IGNORE_CASE"_s, &PrintTargetsArgs::IgnoreCase)
.Bind("__MESSAGE_PREFIX"_s, &PrintTargetsArgs::MessagePrefix);
std::vector<std::string> unparsed;
auto parsedArgs = ArgsParser.Parse(body, &unparsed);
if (!unparsed.empty()) {
return FatalError(
status,
cmStrCat(
"Unknown argument(s) given to cmake_language(PRINT_TARGETS) call: \"",
cmJoin(unparsed, "\" \""), "\"."));
}
if (parsedArgs.MaybeReportError(makefile)) {
cmSystemTools::SetFatalErrorOccurred();
return true;
}
if (parsedArgs.ImportedOnly && parsedArgs.NoImported) {
return FatalError(status,
"IMPORTED_ONLY and NO_IMPORTED keywords are mutually "
"exclusive in cmake_language(PRINT_TARGETS) call.");
}
if (parsedArgs.IgnoreCase && !parsedArgs.Regex) {
return FatalError(status,
"IGNORE_CASE keyword in cmake_language(PRINT_TARGETS) "
"call is only valid with REGEX.");
}
// Compile the optional REGEX up front so a bad pattern fails fast. With
// IGNORE_CASE the pattern and the candidate names are both lower-cased.
cm::optional<cmsys::RegularExpression> regex;
if (parsedArgs.Regex) {
cmsys::RegularExpression re;
std::string const pat = parsedArgs.IgnoreCase
? cmSystemTools::LowerCase(*parsedArgs.Regex)
: *parsedArgs.Regex;
if (!re.compile(pat)) {
return FatalError(status,
cmStrCat("REGEX regular expression \"",
*parsedArgs.Regex, "\" cannot compile."));
}
regex = std::move(re);
}
bool const includeNormal = !parsedArgs.ImportedOnly;
bool const includeImported = !parsedArgs.NoImported;
struct TargetInfo
{
cm::TargetType Type;
bool Imported;
};
std::map<std::string, TargetInfo> targets;
for (auto const& mf : makefile.GetGlobalGenerator()->GetMakefiles()) {
if (includeNormal) {
for (auto const& ti : mf->GetTargets()) {
cmTarget const& t = ti.second;
targets.insert({ t.GetName(), { t.GetType(), false } });
}
}
if (includeImported) {
for (cmTarget const* t : mf->GetImportedTargets()) {
targets.insert({ t->GetName(), { t->GetType(), true } });
}
}
}
// Build the body first so the header is suppressed when a REGEX filters
// everything out (matches cmake_language(PRINT_VARIABLES) behavior).
std::string lines;
bool anyMatched = false;
for (auto const& t : targets) {
if (regex) {
std::string const subj =
parsedArgs.IgnoreCase ? cmSystemTools::LowerCase(t.first) : t.first;
if (!regex->find(subj)) {
continue;
}
}
lines +=
cmStrCat(" ", t.first, " (", cmState::GetTargetTypeName(t.second.Type),
t.second.Imported ? ", IMPORTED" : "", ")\n");
anyMatched = true;
}
if (anyMatched) {
// The message opens with a banner line. The internal __MESSAGE_PREFIX
// keyword overrides it (used by wrappers to reproduce legacy output); it
// is not part of the public interface.
std::string const messagePrefix =
parsedArgs.MessagePrefix.value_or("Printing targets...\n");
// The header reflects the imported-filter mode and any REGEX in effect.
char const* label = "All targets";
if (parsedArgs.ImportedOnly) {
label = "Imported targets";
} else if (parsedArgs.NoImported) {
label = "Non-imported targets";
}
std::string out = cmStrCat(messagePrefix, " ", label);
if (parsedArgs.Regex) {
out += cmStrCat(
" matching REGEX '", *parsedArgs.Regex, "' (",
parsedArgs.IgnoreCase ? "case insensitive" : "case sensitive", ")");
}
out += cmStrCat(":\n", lines);
makefile.DisplayStatus(out, -1);
}
if (!anyMatched && parsedArgs.Regex) {
makefile.IssueMessage(
MessageType::WARNING,
cmStrCat("No targets matching REGEX '", *parsedArgs.Regex, "' (",
parsedArgs.IgnoreCase ? "case insensitive" : "case sensitive",
") in cmake_language(PRINT_TARGETS ...)."));
}
return true;
}
struct PrintVariablesArgs : public ArgumentParser::ParseResult
{
bool All = false;
ArgumentParser::NonEmpty<std::vector<std::string>> Named;
cm::optional<std::string> NameRegex;
cm::optional<std::string> ValueRegex;
bool IgnoreCase = false;
// Internal: set by the deprecated cmake_print_variables() module wrapper to
// request the historical flush single-line output (no banner, undefined
// names printed as name=""). Not part of the public interface.
bool CmakePrintVariables = false;
};
// Banner opening a cmake_language(PRINT_VARIABLES) status message. Suppressed
// in the deprecated cmake_print_variables() (legacy) path.
cm::static_string_view const PrintVariablesBanner =
"Printing variables...\n"_s;
// NAMED mode. By default (matching cmake_language(PRINT_PROPERTIES)) this
// emits a " Named variables:" header followed by one `name = "value"` entry
// per line; a name that is not defined prints as `name = <NOTFOUND>` in the
// order given. When `legacy` is set (the deprecated cmake_print_variables()
// wrapper) it instead emits the historical flush single line
// "name=\"value\" ; ..." with no header and undefined names as name="".
// Values go through GetDefinition so users see the same
// in-scope/cache-fallback value they'd get from ${var}.
void PrintVariablesNamed(cmMakefile& makefile,
std::vector<std::string> const& names, bool legacy)
{
if (legacy) {
std::string msg;
bool first = true;
for (std::string const& name : names) {
if (!first) {
msg += " ; ";
}
first = false;
cmValue v = makefile.GetDefinition(name);
msg += cmStrCat(name, "=\"", v ? *v : std::string(), "\"");
}
makefile.DisplayStatus(msg, -1);
return;
}
std::string out = cmStrCat(PrintVariablesBanner, " Named variables:\n");
for (std::string const& name : names) {
cmValue v = makefile.GetDefinition(name);
if (v) {
out += cmStrCat(" ", name, " = \"", *v, "\"\n");
} else {
out += cmStrCat(" ", name, " = <NOTFOUND>\n");
}
}
makefile.DisplayStatus(out, -1);
}
// ALL mode: enumerate every regular variable in scope and
// every cache entry. A name with both a regular variable and a cache
// entry is printed on two distinct lines so the user sees the full
// picture, including the value the cache holds even when a regular
// variable shadows it. Values are read via the snapshot and cache APIs
// directly to avoid firing variable-watch callbacks (which would
// otherwise trip CMP0218 when CMAKE_WARN_DEPRECATED or
// CMAKE_ERROR_DEPRECATED are in scope).
bool PrintVariablesAll(cmMakefile& makefile, PrintVariablesArgs const& parsed,
cmExecutionStatus& status)
{
cm::optional<cmsys::RegularExpression> nameRegex;
cm::optional<cmsys::RegularExpression> valueRegex;
if (parsed.NameRegex) {
cmsys::RegularExpression re;
std::string const pat = parsed.IgnoreCase
? cmSystemTools::LowerCase(*parsed.NameRegex)
: *parsed.NameRegex;
if (!re.compile(pat)) {
return FatalError(status,
cmStrCat("NAME_REGEX regular expression \"",
*parsed.NameRegex, "\" cannot compile."));
}
nameRegex = std::move(re);
}
if (parsed.ValueRegex) {
cmsys::RegularExpression re;
std::string const pat = parsed.IgnoreCase
? cmSystemTools::LowerCase(*parsed.ValueRegex)
: *parsed.ValueRegex;
if (!re.compile(pat)) {
return FatalError(status,
cmStrCat("VALUE_REGEX regular expression \"",
*parsed.ValueRegex, "\" cannot compile."));
}
valueRegex = std::move(re);
}
auto matches = [&](std::string const& name, std::string const& value) {
if (nameRegex) {
std::string const subj =
parsed.IgnoreCase ? cmSystemTools::LowerCase(name) : name;
if (!nameRegex->find(subj)) {
return false;
}
}
if (valueRegex) {
std::string const subj =
parsed.IgnoreCase ? cmSystemTools::LowerCase(value) : value;
if (!valueRegex->find(subj)) {
return false;
}
}
return true;
};
cmStateSnapshot const snapshot = makefile.GetStateSnapshot();
cmState* state = makefile.GetState();
// GetDefinitions() already unions ClosureKeys() with the cache keys and
// sorts the result; just dedupe so a name that lives in both lists isn't
// visited twice.
auto names = makefile.GetDefinitions();
names.erase(std::unique(names.begin(), names.end()), names.end());
// Build the body first so nothing is printed when a regex filters
// everything out; the warning below covers that case.
std::string body;
bool anyMatched = false;
for (std::string const& name : names) {
cmValue regular = snapshot.GetDefinition(name);
if (regular && matches(name, *regular)) {
body += cmStrCat(" ", name, " = \"", *regular, "\"\n");
anyMatched = true;
}
cmValue cached = state->GetInitializedCacheValue(name);
if (cached && matches(name, *cached)) {
auto const type = state->GetCacheEntryType(name);
body += cmStrCat(" CACHE{", name, "}");
if (type != cmStateEnums::UNINITIALIZED) {
body += cmStrCat(":", cmState::CacheEntryTypeToString(type));
}
body += cmStrCat(" = \"", *cached, "\"\n");
anyMatched = true;
}
}
if (anyMatched) {
cmValue listFile = snapshot.GetDefinition("CMAKE_CURRENT_LIST_FILE");
std::string out =
cmStrCat(PrintVariablesBanner, " Variables in scope at '",
listFile ? *listFile : std::string("<unknown>"), "'");
if (parsed.NameRegex || parsed.ValueRegex) {
out += " matching";
if (parsed.NameRegex) {
out += cmStrCat(" name '", *parsed.NameRegex, "'");
}
if (parsed.NameRegex && parsed.ValueRegex) {
out += " and";
}
if (parsed.ValueRegex) {
out += cmStrCat(" value '", *parsed.ValueRegex, "'");
}
out += parsed.IgnoreCase ? " (case insensitive)" : " (case sensitive)";
}
out += cmStrCat(":\n", body);
makefile.DisplayStatus(out, -1);
}
if (!anyMatched && (parsed.NameRegex || parsed.ValueRegex)) {
std::string msg = "No variables in scope matching";
if (parsed.NameRegex) {
msg += cmStrCat(" name '", *parsed.NameRegex, "'");
}
if (parsed.NameRegex && parsed.ValueRegex) {
msg += " and";
}
if (parsed.ValueRegex) {
msg += cmStrCat(" value '", *parsed.ValueRegex, "'");
}
msg += parsed.IgnoreCase ? " (case insensitive)" : " (case sensitive)";
msg += " in cmake_language(PRINT_VARIABLES ...).";
makefile.IssueMessage(MessageType::WARNING, msg);
}
return true;
}
bool cmCMakeLanguageCommandPRINT_VARIABLES(
std::vector<cmListFileArgument> const& args, cmExecutionStatus& status)
{
cmMakefile& makefile = status.GetMakefile();
std::vector<std::string> expandedArgs;
makefile.ExpandArguments(args, expandedArgs);
// Drop the leading "PRINT_VARIABLES" subcommand keyword.
std::vector<std::string> body(expandedArgs.begin() + 1, expandedArgs.end());
auto const ArgsParser =
cmArgumentParser<PrintVariablesArgs>()
.Bind("ALL"_s, &PrintVariablesArgs::All)
.Bind("NAMED"_s, &PrintVariablesArgs::Named)
.Bind("NAME_REGEX"_s, &PrintVariablesArgs::NameRegex)
.Bind("VALUE_REGEX"_s, &PrintVariablesArgs::ValueRegex)
.Bind("IGNORE_CASE"_s, &PrintVariablesArgs::IgnoreCase)
.Bind("__CMAKE_PRINT_VARIABLES"_s,
&PrintVariablesArgs::CmakePrintVariables);
std::vector<std::string> unparsed;
auto parsedArgs = ArgsParser.Parse(body, &unparsed);
// No bareword form: every token must belong to ALL, NAMED, or a filter.
if (!unparsed.empty()) {
return FatalError(
status,
cmStrCat("Unknown argument(s) given to cmake_language(PRINT_VARIABLES)"
": \"",
cmJoin(unparsed, "\" \""), "\"."));
}
if (parsedArgs.MaybeReportError(makefile)) {
cmSystemTools::SetFatalErrorOccurred();
return true;
}
bool const hasNamed = !parsedArgs.Named.empty();
bool const hasFilters =
parsedArgs.NameRegex || parsedArgs.ValueRegex || parsedArgs.IgnoreCase;
// ALL and NAMED are mutually exclusive modes.
if (parsedArgs.All && hasNamed) {
return FatalError(status,
"ALL and NAMED keywords in "
"cmake_language(PRINT_VARIABLES) call "
"are mutually exclusive.");
}
// The filter keywords narrow an enumeration, so they only make sense with
// ALL (explicit or implicit), never with NAMED.
if (hasNamed && hasFilters) {
return FatalError(status,
"NAME_REGEX, VALUE_REGEX, and IGNORE_CASE in "
"cmake_language(PRINT_VARIABLES) call "
"are only valid with ALL, not NAMED.");
}
// __CMAKE_PRINT_VARIABLES selects the legacy single-line NAMED output; it is
// meaningless when enumerating variables (ALL, explicit or implicit).
if (!hasNamed && parsedArgs.CmakePrintVariables) {
return FatalError(status,
"__CMAKE_PRINT_VARIABLES in "
"cmake_language(PRINT_VARIABLES) call is only valid "
"with NAMED.");
}
// Default to ALL when neither mode keyword is given.
bool const all = parsedArgs.All || !hasNamed;
if (all) {
return PrintVariablesAll(makefile, parsedArgs, status);
}
PrintVariablesNamed(makefile, parsedArgs.Named,
parsedArgs.CmakePrintVariables);
return true;
}
// Walks every target reachable from `root` through any linkage - PUBLIC,
// INTERFACE, PRIVATE, and `$<LINK_ONLY:>`-wrapped deps. At each level we
// union the link implementation and link interface, each queried with both
// UseTo::Compile and UseTo::Link, so deps that only appear under LINK_ONLY
// (which evaluates to empty under UseTo::Compile) still come through.
std::vector<cmGeneratorTarget const*> CollectDependentTargets(
cmGeneratorTarget const* root, std::string const& config)
{
std::vector<cmGeneratorTarget const*> deps;
std::set<cmGeneratorTarget const*> visited;
visited.insert(root);
std::vector<cmGeneratorTarget const*> queue;
queue.push_back(root);
while (!queue.empty()) {
cmGeneratorTarget const* cur = queue.back();
queue.pop_back();
auto visit = [&](std::vector<cmLinkItem> const& libs) {
for (cmLinkItem const& item : libs) {
cmGeneratorTarget const* dep = item.Target;
if (!dep || !visited.insert(dep).second) {
continue;
}
deps.push_back(dep);
queue.push_back(dep);
}
};
for (auto useTo : { cmGeneratorTarget::UseTo::Compile,
cmGeneratorTarget::UseTo::Link }) {
if (cmLinkImplementationLibraries const* impl =
cur->GetLinkImplementationLibraries(config, useTo)) {
visit(impl->Libraries);
}
if (cmLinkInterfaceLibraries const* iface =
cur->GetLinkInterfaceLibraries(config, root, useTo)) {
visit(iface->Libraries);
}
}
}
return deps;
}
// Append one property line to `out`:
// " <entityName>.<propertyName> = \"<value>\""
// A null `value` writes "<NOTFOUND>" instead of "= \"...\"".
void WritePropertyLine(std::string& out, std::string const& entityName,
std::string const& propertyName, cmValue value)
{
out += cmStrCat(" ", entityName, ".", propertyName);
if (value) {
out += cmStrCat(" = \"", *value, "\"");
} else {
out += " = <NOTFOUND>";
}
out += "\n";
}
enum class BlockKind
{
Explicit,
All,
};
enum class HeaderSuffix
{
None,
Reachable,
};
void EmitBlockHeader(
std::string& out, std::string const& entityName,
std::string const& entityType, BlockKind kind, HeaderSuffix suffix,
cm::optional<std::string> const& nameRegexStr = cm::nullopt,
cm::optional<std::string> const& valueRegexStr = cm::nullopt)
{
out +=
cmStrCat(" ", (kind == BlockKind::All ? "All properties" : "Properties"),
" for ", entityType, " ", entityName);
if (suffix == HeaderSuffix::Reachable) {
out += " (and all reachable)";
}
if (kind == BlockKind::All && (nameRegexStr || valueRegexStr)) {
out += " matching";
if (nameRegexStr) {
out += cmStrCat(" name '", *nameRegexStr, "'");
}
if (nameRegexStr && valueRegexStr) {
out += " and";
}
if (valueRegexStr) {
out += cmStrCat(" value '", *valueRegexStr, "'");
}
}
out += ":\n";
}
// Build the "no properties matched" warning text used when the regex filter
// wipes everything out for a target's ALL block. Returned without any
// "Warning:" prefix - callers feed it to IssueMessage, which adds the
// standard "CMake Warning at <file>:<line>" framing.
std::string EmptyMatchWarningMessage(
std::string const& entityName, std::string const& entityType,
cm::optional<std::string> const& nameRegexStr,
cm::optional<std::string> const& valueRegexStr)
{
std::string msg =
cmStrCat("No properties for ", entityType, " ", entityName, " matching");
if (nameRegexStr) {
msg += cmStrCat(" name '", *nameRegexStr, "'");
}
if (nameRegexStr && valueRegexStr) {
msg += " and";
}
if (valueRegexStr) {
msg += cmStrCat(" value '", *valueRegexStr, "'");
}
msg += " in cmake_language(PRINT_PROPERTIES ...).";
return msg;
}
// Entity kinds supported by cmake_language(PRINT_PROPERTIES).
enum class EntityKind
{
Target,
Source,
Test,
Directory,
Cache
};
// Display name used in PRINT_PROPERTIES output ("TARGET", "SOURCE", etc.).
char const* EntityTypeName(EntityKind kind)
{
switch (kind) {
case EntityKind::Target:
return "TARGET";
case EntityKind::Source:
return "SOURCE";
case EntityKind::Test:
return "TEST";
case EntityKind::Directory:
return "DIRECTORY";
case EntityKind::Cache:
return "CACHE";
}
return "";
}
// Dispatch a property lookup to the appropriate per-entity helper.
bool GetPropertyHelper(cmExecutionStatus& status, EntityKind kind,
std::string const& name,
std::string const& propertyName, cmValue& out)
{
switch (kind) {
case EntityKind::Target:
return GetPropertyCommand::LookupTargetProperty(status, name,
propertyName, out);
case EntityKind::Source:
return GetPropertyCommand::LookupSourceProperty(status, name,
propertyName, out);
case EntityKind::Test:
return GetPropertyCommand::LookupTestProperty(status, name, propertyName,
out);
case EntityKind::Directory:
return GetPropertyCommand::LookupDirectoryProperty(status, name,
propertyName, out);
case EntityKind::Cache:
return GetPropertyCommand::LookupCacheProperty(status, name,
propertyName, out);
}
return false;
}
// Emit one ALL-mode block: prints all properties matching the specified
// regexes for a head target (`targets[0]`) and optionally any reachable
// dependencies (`targets[1]` and on).
bool EmitAllPropertiesBlock(std::string& out, std::string const& headerName,
std::vector<cmTarget*> const& targets,
HeaderSuffix suffix,
cm::optional<cmsys::RegularExpression>& nameRegex,
cm::optional<cmsys::RegularExpression>& valueRegex,
cm::optional<std::string> const& nameRegexStr,
cm::optional<std::string> const& valueRegexStr)
{
std::string groupBuf;
bool groupEmitted = false;
for (cmTarget* t : targets) {
std::string const& rowName =
(t == targets.front()) ? headerName : t->GetName();
for (auto const& kv : t->GetExtendedProperties().GetList()) {
if (nameRegex && !nameRegex->find(kv.first)) {
continue;
}
if (valueRegex && !valueRegex->find(kv.second)) {
continue;
}
WritePropertyLine(groupBuf, rowName, kv.first, cmValue(kv.second));
groupEmitted = true;
}
}
if (!groupEmitted) {
return false;
}
EmitBlockHeader(out, headerName, "TARGET", BlockKind::All, suffix,
nameRegexStr, valueRegexStr);
out += groupBuf;
return true;
}
// Outcome of emitting a NAMED-mode block.
enum class NamedBlockResult
{
Failed, // a property lookup hard-failed
Emitted, // at least one property line was written
Empty, // every requested property was skipped; header suppressed
};
// Emit one block of user-specified properties for a head target (`targets[0]`)
// and optionally any reachable dependencies (`targets[1]` and on).
NamedBlockResult EmitNamedPropertiesBlock(
std::string& out, std::string const& headerName,
std::vector<cmTarget*> const& targets,
std::vector<std::string> const& namedProperties, HeaderSuffix suffix,
cmMakefile& mf)
{
bool headerWritten = false;
for (cmTarget* t : targets) {
std::string const& rowName =
(t == targets.front()) ? headerName : t->GetName();
for (std::string const& propName : namedProperties) {
// A computed location property may not be read from a non-imported
// target; skip it with a warning rather than triggering the getter's
// fatal error.
if (!t->IsImported() &&
cmTargetPropertyComputer::IsComputedLocationProperty(t->GetType(),
propName)) {
mf.IssueMessage(
MessageType::WARNING,
cmStrCat("The ", propName,
" property may not be read from non-imported target \"",
rowName, "\"; skipping."));
continue;
}
cmValue v;
if (cmGetTargetProperty(rowName, t, propName, mf, v) !=
cmGetTargetPropertyResult::Success) {
return NamedBlockResult::Failed;
}
if (!headerWritten) {
EmitBlockHeader(out, headerName, "TARGET", BlockKind::Explicit,
suffix);
headerWritten = true;
}
WritePropertyLine(out, rowName, propName, v);
}
}
return headerWritten ? NamedBlockResult::Emitted : NamedBlockResult::Empty;
}
bool PrintPropertiesConfigureTime(
cmExecutionStatus& status, std::vector<std::string> const& namedProperties,
bool all, std::vector<std::string> const& entityNames, EntityKind kind,
cm::optional<std::string> const& propertyNameRegexStr,
cm::optional<std::string> const& propertyValueRegexStr,
cm::optional<cmsys::RegularExpression>& propertyNameRegex,
cm::optional<cmsys::RegularExpression>& propertyValueRegex,
std::string const& messagePrefix)
{
cmMakefile& makefile = status.GetMakefile();
bool const hasRegex = propertyNameRegexStr || propertyValueRegexStr;
std::string out = messagePrefix;
bool anyEmitted = false;
for (auto const& entityName : entityNames) {
if (kind == EntityKind::Target) {
cmTarget* target = makefile.FindTargetToUse(entityName);
if (!target) {
out += cmStrCat("\n No such TARGET \"", entityName, "\" !\n\n");
anyEmitted = true;
continue;
}
if (all) {
bool const emitted = EmitAllPropertiesBlock(
out, entityName, { target }, HeaderSuffix::None, propertyNameRegex,
propertyValueRegex, propertyNameRegexStr, propertyValueRegexStr);
if (emitted) {
anyEmitted = true;
} else if (hasRegex) {
makefile.IssueMessage(MessageType::WARNING,
EmptyMatchWarningMessage(
entityName, "TARGET", propertyNameRegexStr,
propertyValueRegexStr));
}
} else {
NamedBlockResult const result = EmitNamedPropertiesBlock(
out, entityName, { target }, namedProperties, HeaderSuffix::None,
makefile);
if (result == NamedBlockResult::Failed) {
status.SetError(cmStrCat(
"failed to retrieve properties for TARGET \"", entityName, "\""));
return false;
}
if (result == NamedBlockResult::Emitted) {
anyEmitted = true;
}
}
} else {
EmitBlockHeader(out, entityName, EntityTypeName(kind),
BlockKind::Explicit, HeaderSuffix::None);
for (auto const& propertyName : namedProperties) {
cmValue v;
if (!GetPropertyHelper(status, kind, entityName, propertyName, v)) {
return false;
}
WritePropertyLine(out, entityName, propertyName, v);
}
anyEmitted = true;
}
}
if (anyEmitted) {
makefile.DisplayStatus(out, -1);
}
return true;
}
bool PrintTargetPropertiesDeferred(
std::vector<std::string> targetNames,
std::vector<std::string> namedProperties, bool all,
cm::optional<std::string> propertyNameRegexStr,
cm::optional<std::string> propertyValueRegexStr,
cm::optional<cmsys::RegularExpression> propertyNameRegex,
cm::optional<cmsys::RegularExpression> propertyValueRegex,
bool followDependencies, std::string messagePrefix,
cmExecutionStatus& status)
{
cmListFileBacktrace const bt = status.GetMakefile().GetBacktrace();
status.GetMakefile().AddGeneratorAction(
[targetNames, namedProperties, all, propertyNameRegexStr,
propertyValueRegexStr, propertyNameRegex, propertyValueRegex,
followDependencies, messagePrefix, bt](
cmLocalGenerator& lg, cmListFileBacktrace const& /*lambdaBt*/) mutable {
std::vector<std::string> const configs =
lg.GetMakefile()->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
std::string const config =
configs.empty() ? std::string() : configs.front();
bool const hasRegex = propertyNameRegexStr || propertyValueRegexStr;
cmake* cmakeInst = lg.GetMakefile()->GetCMakeInstance();
std::string out = messagePrefix;
bool anyEmitted = false;
for (std::string const& name : targetNames) {
cmGeneratorTarget* root = lg.FindGeneratorTargetToUse(name);
if (!root) {
out += cmStrCat("\n No such TARGET \"", name, "\" !\n\n");
anyEmitted = true;
continue;
}
std::vector<cmTarget*> targets;
targets.push_back(root->Target);
HeaderSuffix const suffix =
followDependencies ? HeaderSuffix::Reachable : HeaderSuffix::None;
if (followDependencies) {
for (cmGeneratorTarget const* dep :
CollectDependentTargets(root, config)) {
targets.push_back(dep->Target);
}
}
if (all) {
bool const emitted = EmitAllPropertiesBlock(
out, name, targets, suffix, propertyNameRegex, propertyValueRegex,
propertyNameRegexStr, propertyValueRegexStr);
if (emitted) {
anyEmitted = true;
} else if (hasRegex) {
cmakeInst->IssueMessage(
MessageType::WARNING,
EmptyMatchWarningMessage(name, "TARGET", propertyNameRegexStr,
propertyValueRegexStr),
bt);
}
} else {
NamedBlockResult const result = EmitNamedPropertiesBlock(
out, name, targets, namedProperties, suffix, *lg.GetMakefile());
if (result == NamedBlockResult::Failed) {
cmakeInst->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat("failed to retrieve properties for TARGET \"", name,
"\""),
bt);
return;
}
if (result == NamedBlockResult::Emitted) {
anyEmitted = true;
}
}
}
if (anyEmitted) {
lg.GetMakefile()->DisplayStatus(out, -1);
}
},
cmMakefile::GeneratorActionWhen::AfterGeneratorTargets);
return true;
}
bool cmCMakeLanguageCommandPRINT_PROPERTIES(
std::vector<std::string> const& args, cmExecutionStatus& status)
{
struct PrintPropertiesArg : public ArgumentParser::ParseResult
{
bool All = false;
ArgumentParser::NonEmpty<std::vector<std::string>> Named;
cm::optional<std::string> PropertyNameRegex;
cm::optional<std::string> PropertyValueRegex;
// Internal: set by the deprecated cmake_print_properties() module wrapper
// to select the historical leading-blank-line output instead of the
// banner. Not part of the public interface.
bool CmakePrintProperties = false;
};
auto ArgsParser =
cmArgumentParser<PrintPropertiesArg>()
.Bind("ALL"_s, &PrintPropertiesArg::All)
.Bind("NAMED"_s, &PrintPropertiesArg::Named)
.Bind("PROPERTY_NAME_REGEX"_s, &PrintPropertiesArg::PropertyNameRegex)
.Bind("PROPERTY_VALUE_REGEX"_s, &PrintPropertiesArg::PropertyValueRegex)
.Bind("__CMAKE_PRINT_PROPERTIES"_s,
&PrintPropertiesArg::CmakePrintProperties);
std::vector<std::string> unparsed;
auto parsedArgs = ArgsParser.Parse(args, &unparsed);
if (unparsed.empty()) {
return FatalError(status,
cmStrCat("mode keyword missing in ",
"cmake_language(PRINT_PROPERTIES) call, ",
"there must be exactly one of TARGETS SOURCES "
"TESTS DIRECTORIES CACHE_ENTRIES"));
}
if (parsedArgs.MaybeReportError(status.GetMakefile())) {
cmSystemTools::SetFatalErrorOccurred();
return true;
}
// Second parse args to get the mode
struct PrintPropertiesModesArgs : public ArgumentParser::ParseResult
{
ArgumentParser::MaybeEmpty<std::vector<std::string>> Targets;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Sources;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Tests;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Directories;
ArgumentParser::MaybeEmpty<std::vector<std::string>> CacheEntries;
bool Deferred = false;
bool FollowDependencies = false;
};
auto const ArgsParserMode =
cmArgumentParser<PrintPropertiesModesArgs>()
.Bind("TARGETS"_s, &PrintPropertiesModesArgs::Targets)
.Bind("SOURCES"_s, &PrintPropertiesModesArgs::Sources)
.Bind("TESTS"_s, &PrintPropertiesModesArgs::Tests)
.Bind("DIRECTORIES"_s, &PrintPropertiesModesArgs::Directories)
.Bind("CACHE_ENTRIES"_s, &PrintPropertiesModesArgs::CacheEntries)
.Bind("DEFERRED"_s, &PrintPropertiesModesArgs::Deferred)
.Bind("FOLLOW_DEPENDENCIES"_s,
&PrintPropertiesModesArgs::FollowDependencies);
std::vector<std::string> modeArgs = unparsed;
unparsed.clear();
auto parsedArgsMode = ArgsParserMode.Parse(modeArgs, &unparsed);
if (!unparsed.empty()) {
return FatalError(
status, cmStrCat("Unknown keywords: \"", cmJoin(unparsed, " "), "\""));
}
if (parsedArgsMode.MaybeReportError(status.GetMakefile())) {
cmSystemTools::SetFatalErrorOccurred();
return true;
}
std::vector<std::string> modes;
std::vector<std::string> items;
EntityKind kind = EntityKind::Target;
if (!parsedArgsMode.Targets.empty()) {
modes.push_back("TARGETS");
items = parsedArgsMode.Targets;
kind = EntityKind::Target;
}
if (!parsedArgsMode.Sources.empty()) {
modes.push_back("SOURCES");
items = parsedArgsMode.Sources;
kind = EntityKind::Source;
}
if (!parsedArgsMode.Tests.empty()) {
modes.push_back("TESTS");
items = parsedArgsMode.Tests;
kind = EntityKind::Test;
}
if (!parsedArgsMode.Directories.empty()) {
modes.push_back("DIRECTORIES");
items = parsedArgsMode.Directories;
kind = EntityKind::Directory;
}
if (!parsedArgsMode.CacheEntries.empty()) {
modes.push_back("CACHE_ENTRIES");
items = parsedArgsMode.CacheEntries;
kind = EntityKind::Cache;
}
if (modes.empty()) {
return FatalError(status,
cmStrCat("mode keyword missing in ",
"cmake_language(PRINT_PROPERTIES) call, ",
"there must be exactly one of TARGETS SOURCES "
"TESTS DIRECTORIES CACHE_ENTRIES"));
}
if (modes.size() > 1) {
return FatalError(status,
cmStrCat("multiple mode keywords used in ",
"cmake_language(PRINT_PROPERTIES) call, ",
"there must be exactly one of TARGETS SOURCES "
"TESTS DIRECTORIES CACHE_ENTRIES."));
}
std::string const mode = modes[0];
bool const isTargets = (mode == "TARGETS");
bool const hasNamed = !parsedArgs.Named.empty();
bool const hasRegex =
parsedArgs.PropertyNameRegex || parsedArgs.PropertyValueRegex;
if (!isTargets) {
if (parsedArgs.All) {
return FatalError(status,
cmStrCat("ALL keyword in ",
"cmake_language(PRINT_PROPERTIES) call ",
"is only valid with the TARGETS scope."));
}
if (hasRegex) {
return FatalError(
status,
cmStrCat("PROPERTY_NAME_REGEX and PROPERTY_VALUE_REGEX in ",
"cmake_language(PRINT_PROPERTIES) call ",
"are only valid with the TARGETS scope and ALL."));
}
if (parsedArgsMode.Deferred) {
return FatalError(status,
cmStrCat("DEFERRED keyword in ",
"cmake_language(PRINT_PROPERTIES) call ",
"is only valid with the TARGETS scope."));
}
if (parsedArgsMode.FollowDependencies) {
return FatalError(status,
cmStrCat("FOLLOW_DEPENDENCIES keyword in ",
"cmake_language(PRINT_PROPERTIES) call ",
"is only valid with the TARGETS scope."));
}
if (!hasNamed) {
return FatalError(status,
cmStrCat("NAMED keyword missing in ",
"cmake_language(PRINT_PROPERTIES) call ",
"with ", mode, " scope."));
}
} else {
// ALL and NAMED are mutually exclusive on TARGETS.
if (parsedArgs.All && hasNamed) {
return FatalError(status,
cmStrCat("ALL and NAMED keywords in ",
"cmake_language(PRINT_PROPERTIES) call ",
"are mutually exclusive."));
}
// Regex filters require ALL - explicit or implicit. Combining regex
// with NAMED is an error.
if (hasNamed && hasRegex) {
return FatalError(
status,
cmStrCat("PROPERTY_NAME_REGEX and PROPERTY_VALUE_REGEX in ",
"cmake_language(PRINT_PROPERTIES) call ",
"are only valid with ALL, not NAMED."));
}
}
// FOLLOW_DEPENDENCIES implies DEFERRED.
if (parsedArgsMode.FollowDependencies) {
parsedArgsMode.Deferred = true;
}
// Default to ALL when neither ALL nor NAMED is given (TARGETS only - the
// non-TARGETS path already rejected this combination above).
bool const all = parsedArgs.All || !hasNamed;
// __CMAKE_PRINT_PROPERTIES marks the deprecated cmake_print_properties()
// wrapper, which is always NAMED; reject it in ALL enumeration mode.
if (all && parsedArgs.CmakePrintProperties) {
return FatalError(status,
cmStrCat("__CMAKE_PRINT_PROPERTIES in ",
"cmake_language(PRINT_PROPERTIES) call ",
"is only valid with NAMED."));
}
// Compile regexes once up front so syntax errors are reported here rather
// than from inside helpers (in particular, from the deferred lambda at
// generate time).
cm::optional<cmsys::RegularExpression> propertyNameRegex;
cm::optional<cmsys::RegularExpression> propertyValueRegex;
if (parsedArgs.PropertyNameRegex) {
cmsys::RegularExpression re;
if (!re.compile(*parsedArgs.PropertyNameRegex)) {
return FatalError(status,
cmStrCat("PROPERTY_NAME_REGEX regular expression \"",
*parsedArgs.PropertyNameRegex,
"\" cannot compile."));
}
propertyNameRegex = std::move(re);
}
if (parsedArgs.PropertyValueRegex) {
cmsys::RegularExpression re;
if (!re.compile(*parsedArgs.PropertyValueRegex)) {
return FatalError(status,
cmStrCat("PROPERTY_VALUE_REGEX regular expression \"",
*parsedArgs.PropertyValueRegex,
"\" cannot compile."));
}
propertyValueRegex = std::move(re);
}
// The message opens with a leading banner line. The internal
// __CMAKE_PRINT_PROPERTIES marker (not part of the public interface) selects
// the legacy format instead; cmake_print_properties() sets it to reproduce
// its historical leading blank line.
std::string const messagePrefix = parsedArgs.CmakePrintProperties
? std::string("\n")
: std::string("Printing properties...\n");
if (parsedArgsMode.Deferred) {
return PrintTargetPropertiesDeferred(
items, parsedArgs.Named, all, parsedArgs.PropertyNameRegex,
parsedArgs.PropertyValueRegex, std::move(propertyNameRegex),
std::move(propertyValueRegex), parsedArgsMode.FollowDependencies,
messagePrefix, status);
}
return PrintPropertiesConfigureTime(
status, parsedArgs.Named, all, items, kind, parsedArgs.PropertyNameRegex,
parsedArgs.PropertyValueRegex, propertyNameRegex, propertyValueRegex,
messagePrefix);
}
}
bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args,
cmExecutionStatus& status)
{
std::vector<std::string> expArgs;
size_t rawArg = 0;
size_t expArg = 0;
// Helper to consume and expand one raw argument at a time.
auto moreArgs = [&]() -> bool {
while (expArg >= expArgs.size()) {
if (rawArg >= args.size()) {
return false;
}
std::vector<cmListFileArgument> tmpArg;
tmpArg.emplace_back(args[rawArg++]);
status.GetMakefile().ExpandArguments(tmpArg, expArgs);
}
return true;
};
auto finishArgs = [&]() {
std::vector<cmListFileArgument> tmpArgs(args.begin() + rawArg, args.end());
status.GetMakefile().ExpandArguments(tmpArgs, expArgs);
rawArg = args.size();
};
if (!moreArgs()) {
return FatalError(status, "called with incorrect number of arguments");
}
if (expArgs[expArg] == "EXIT"_s) {
++expArg; // consume "EXIT".
if (!moreArgs()) {
return FatalError(status, "EXIT requires one argument");
}
if (!status.GetMakefile().GetCMakeInstance()->RoleSupportsExitCode()) {
return FatalError(status, "EXIT can be used only in SCRIPT mode");
}
long retCode = 0;
if (!cmStrToLong(expArgs[expArg], &retCode)) {
return FatalError(status,
cmStrCat("EXIT requires one integral argument, got \"",
expArgs[expArg], '\"'));
}
status.SetExitCode(static_cast<int>(retCode));
return true;
}
if (expArgs[expArg] == "SET_DEPENDENCY_PROVIDER"_s) {
finishArgs();
return cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(expArgs, status);
}
cm::optional<Defer> maybeDefer;
if (expArgs[expArg] == "DEFER"_s) {
++expArg; // Consume "DEFER".
if (!moreArgs()) {
return FatalError(status, "DEFER requires at least one argument");
}
Defer defer;
// Process optional arguments.
while (moreArgs()) {
if (expArgs[expArg] == "CALL"_s) {
break;
}
if (expArgs[expArg] == "CANCEL_CALL"_s ||
expArgs[expArg] == "GET_CALL_IDS"_s ||
expArgs[expArg] == "GET_CALL"_s) {
if (!defer.Id.empty() || !defer.IdVar.empty()) {
return FatalError(status,
cmStrCat("DEFER "_s, expArgs[expArg],
" does not accept ID or ID_VAR."_s));
}
finishArgs();
return cmCMakeLanguageCommandDEFER(defer, expArgs, expArg, status);
}
if (expArgs[expArg] == "DIRECTORY"_s) {
++expArg; // Consume "DIRECTORY".
if (defer.Directory) {
return FatalError(status,
"DEFER given multiple DIRECTORY arguments");
}
if (!moreArgs()) {
return FatalError(status, "DEFER DIRECTORY missing value");
}
std::string dir = expArgs[expArg++];
if (dir.empty()) {
return FatalError(status, "DEFER DIRECTORY may not be empty");
}
dir = cmSystemTools::CollapseFullPath(
dir, status.GetMakefile().GetCurrentSourceDirectory());
defer.Directory =
status.GetMakefile().GetGlobalGenerator()->FindMakefile(dir);
if (!defer.Directory) {
return FatalError(status,
cmStrCat("DEFER DIRECTORY:\n "_s, dir,
"\nis not known. "
"It may not have been processed yet."_s));
}
} else if (expArgs[expArg] == "ID"_s) {
++expArg; // Consume "ID".
if (!defer.Id.empty()) {
return FatalError(status, "DEFER given multiple ID arguments");
}
if (!moreArgs()) {
return FatalError(status, "DEFER ID missing value");
}
defer.Id = expArgs[expArg++];
if (defer.Id.empty()) {
return FatalError(status, "DEFER ID may not be empty");
}
if (defer.Id[0] >= 'A' && defer.Id[0] <= 'Z') {
return FatalError(status, "DEFER ID may not start in A-Z.");
}
} else if (expArgs[expArg] == "ID_VAR"_s) {
++expArg; // Consume "ID_VAR".
if (!defer.IdVar.empty()) {
return FatalError(status, "DEFER given multiple ID_VAR arguments");
}
if (!moreArgs()) {
return FatalError(status, "DEFER ID_VAR missing variable name");
}
defer.IdVar = expArgs[expArg++];
if (defer.IdVar.empty()) {
return FatalError(status, "DEFER ID_VAR may not be empty");
}
} else {
return FatalError(
status, cmStrCat("DEFER unknown option:\n "_s, expArgs[expArg]));
}
}
if (!(moreArgs() && expArgs[expArg] == "CALL"_s)) {
return FatalError(status, "DEFER must be followed by a CALL argument");
}
maybeDefer = std::move(defer);
}
if (expArgs[expArg] == "CALL") {
++expArg; // Consume "CALL".
// CALL requires a command name.
if (!moreArgs()) {
return FatalError(status, "CALL missing command name");
}
std::string const& callCommand = expArgs[expArg++];
// CALL accepts no further expanded arguments.
if (expArg != expArgs.size()) {
return FatalError(status, "CALL command's arguments must be literal");
}
// Run the CALL.
return cmCMakeLanguageCommandCALL(args, callCommand, rawArg,
std::move(maybeDefer), status);
}
if (expArgs[expArg] == "EVAL") {
return cmCMakeLanguageCommandEVAL(args, status);
}
if (expArgs[expArg] == "GET_MESSAGE_LOG_LEVEL") {
return cmCMakeLanguageCommandGET_MESSAGE_LOG_LEVEL(args, status);
}
if (expArgs[expArg] == "GET_EXPERIMENTAL_FEATURE_ENABLED") {
return cmCMakeLanguageCommandGET_EXPERIMENTAL_FEATURE_ENABLED(args,
status);
}
if (expArgs[expArg] == "PRINT_TARGETS") {
return cmCMakeLanguageCommandPRINT_TARGETS(args, status);
}
if (expArgs[expArg] == "PRINT_VARIABLES") {
return cmCMakeLanguageCommandPRINT_VARIABLES(args, status);
}
if (expArgs[expArg] == "TRACE") {
++expArg; // Consume "TRACE".
if (!moreArgs()) {
return FatalError(status, "TRACE missing a boolean value");
}
bool const value = cmValue::IsOn(expArgs[expArg++]);
bool expand = false;
if (value && moreArgs()) {
expand = (expArgs[expArg] == "EXPAND");
if (!expand) {
return FatalError(
status,
cmStrCat("TRACE ON given an invalid argument ", expArgs[expArg]));
}
++expArg;
}
if (moreArgs()) {
return FatalError(
status,
cmStrCat("TRACE O", value ? "N" : "FF", " given too many arguments"));
}
cmMakefile& makefile = status.GetMakefile();
if (value) {
makefile.GetCMakeInstance()->PushTraceCmd(expand);
return true;
}
return makefile.GetCMakeInstance()->PopTraceCmd() ||
FatalError(status, "TRACE OFF request without a corresponding TRACE ON");
}
if (expArgs[expArg] == "PRINT_PROPERTIES") {
++expArg;
finishArgs();
std::vector<std::string> const printPropertyArgs(expArgs.begin() + expArg,
expArgs.end());
return cmCMakeLanguageCommandPRINT_PROPERTIES(printPropertyArgs, status);
}
return FatalError(status, "called with unknown meta-operation");
}