| /* 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; |
| }; |
| |
| // 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); |
| |
| 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. |
| 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<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"); |
| } |