/* 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #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 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 InvalidDeferCommands{ { // clang-format off "return"_s, } // clang-format on }; struct Defer { std::string Id; std::string IdVar; cmMakefile* Directory = nullptr; }; bool cmCMakeLanguageCommandCALL(std::vector const& args, std::string const& callCommand, size_t startArg, cm::optional 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 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 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 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 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 const& args, cmExecutionStatus& status) { cmMakefile& makefile = status.GetMakefile(); cmListFileContext context = makefile.GetBacktrace().Top(); std::vector 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 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> Methods; }; auto const ArgsParser = cmArgumentParser() .Bind("SET_DEPENDENCY_PROVIDER"_s, &SetProviderArgs::Command) .Bind("SUPPORTED_METHODS"_s, &SetProviderArgs::Methods); std::vector 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 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 const& args, cmExecutionStatus& status) { cmMakefile& makefile = status.GetMakefile(); std::vector 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 const& args, cmExecutionStatus& status) { cmMakefile& makefile = status.GetMakefile(); std::vector 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 Regex; bool ImportedOnly = false; bool NoImported = false; bool IgnoreCase = false; }; // 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 const& args, cmExecutionStatus& status) { cmMakefile& makefile = status.GetMakefile(); std::vector expandedArgs; makefile.ExpandArguments(args, expandedArgs); // Drop the leading "PRINT_TARGETS" subcommand keyword. std::vector body(expandedArgs.begin() + 1, expandedArgs.end()); auto const ArgsParser = cmArgumentParser() .Bind("REGEX"_s, &PrintTargetsArgs::Regex) .Bind("IMPORTED_ONLY"_s, &PrintTargetsArgs::ImportedOnly) .Bind("NO_IMPORTED"_s, &PrintTargetsArgs::NoImported) .Bind("IGNORE_CASE"_s, &PrintTargetsArgs::IgnoreCase); std::vector 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 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 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. std::string out = "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"; } out += cmStrCat(" ", 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> Named; cm::optional NameRegex; cm::optional 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 = ` 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 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, " = \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 nameRegex; cm::optional 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(""), "'"); 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 const& args, cmExecutionStatus& status) { cmMakefile& makefile = status.GetMakefile(); std::vector expandedArgs; makefile.ExpandArguments(args, expandedArgs); // Drop the leading "PRINT_VARIABLES" subcommand keyword. std::vector body(expandedArgs.begin() + 1, expandedArgs.end()); auto const ArgsParser = cmArgumentParser() .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 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 `$`-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 CollectDependentTargets( cmGeneratorTarget const* root, std::string const& config) { std::vector deps; std::set visited; visited.insert(root); std::vector queue; queue.push_back(root); while (!queue.empty()) { cmGeneratorTarget const* cur = queue.back(); queue.pop_back(); auto visit = [&](std::vector 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`: // " . = \"\"" // A null `value` writes "" 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 += " = "; } 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 const& nameRegexStr = cm::nullopt, cm::optional 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 :" framing. std::string EmptyMatchWarningMessage( std::string const& entityName, std::string const& entityType, cm::optional const& nameRegexStr, cm::optional 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 const& targets, HeaderSuffix suffix, cm::optional& nameRegex, cm::optional& valueRegex, cm::optional const& nameRegexStr, cm::optional 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 const& targets, std::vector 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 const& namedProperties, bool all, std::vector const& entityNames, EntityKind kind, cm::optional const& propertyNameRegexStr, cm::optional const& propertyValueRegexStr, cm::optional& propertyNameRegex, cm::optional& 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 targetNames, std::vector namedProperties, bool all, cm::optional propertyNameRegexStr, cm::optional propertyValueRegexStr, cm::optional propertyNameRegex, cm::optional 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 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 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 const& args, cmExecutionStatus& status) { struct PrintPropertiesArg : public ArgumentParser::ParseResult { bool All = false; ArgumentParser::NonEmpty> Named; cm::optional PropertyNameRegex; cm::optional 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() .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 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> Targets; ArgumentParser::MaybeEmpty> Sources; ArgumentParser::MaybeEmpty> Tests; ArgumentParser::MaybeEmpty> Directories; ArgumentParser::MaybeEmpty> CacheEntries; bool Deferred = false; bool FollowDependencies = false; }; auto const ArgsParserMode = cmArgumentParser() .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 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 modes; std::vector 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 propertyNameRegex; cm::optional 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 const& args, cmExecutionStatus& status) { std::vector 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 tmpArg; tmpArg.emplace_back(args[rawArg++]); status.GetMakefile().ExpandArguments(tmpArg, expArgs); } return true; }; auto finishArgs = [&]() { std::vector 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(retCode)); return true; } if (expArgs[expArg] == "SET_DEPENDENCY_PROVIDER"_s) { finishArgs(); return cmCMakeLanguageCommandSET_DEPENDENCY_PROVIDER(expArgs, status); } cm::optional 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 const printPropertyArgs(expArgs.begin() + expArg, expArgs.end()); return cmCMakeLanguageCommandPRINT_PROPERTIES(printPropertyArgs, status); } return FatalError(status, "called with unknown meta-operation"); }