| /* 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(std::move(lines), " ", 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(std::move(out), ' ', label); | |
| if (parsedArgs.Regex) { | |
| out = cmStrCat( | |
| std::move(out), " matching REGEX '", *parsedArgs.Regex, "' (", | |
| parsedArgs.IgnoreCase ? "case insensitive" : "case sensitive", ')'); | |
| } | |
| out = cmStrCat(std::move(out), ":\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(std::move(msg), 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(std::move(out), " ", name, " = \"", *v, "\"\n"); | |
| } else { | |
| out = cmStrCat(std::move(out), " ", 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(std::move(body), " ", name, " = \"", *regular, "\"\n"); | |
| anyMatched = true; | |
| } | |
| cmValue cached = state->GetInitializedCacheValue(name); | |
| if (cached && matches(name, *cached)) { | |
| auto const type = state->GetCacheEntryType(name); | |
| body = cmStrCat(std::move(body), " CACHE{", name, '}'); | |
| if (type != cmStateEnums::UNINITIALIZED) { | |
| body = cmStrCat(std::move(body), ':', | |
| cmState::CacheEntryTypeToString(type)); | |
| } | |
| body = cmStrCat(std::move(body), " = \"", *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(std::move(out), " name '", *parsed.NameRegex, '\''); | |
| } | |
| if (parsed.NameRegex && parsed.ValueRegex) { | |
| out += " and"; | |
| } | |
| if (parsed.ValueRegex) { | |
| out = cmStrCat(std::move(out), " value '", *parsed.ValueRegex, '\''); | |
| } | |
| out += parsed.IgnoreCase ? " (case insensitive)" : " (case sensitive)"; | |
| } | |
| out = cmStrCat(std::move(out), ":\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(std::move(msg), " name '", *parsed.NameRegex, '\''); | |
| } | |
| if (parsed.NameRegex && parsed.ValueRegex) { | |
| msg += " and"; | |
| } | |
| if (parsed.ValueRegex) { | |
| msg = cmStrCat(std::move(msg), " 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(std::move(out), " ", entityName, '.', propertyName); | |
| if (value) { | |
| out = cmStrCat(std::move(out), " = \"", *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(std::move(out), ' ', | |
| (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(std::move(out), " name '", *nameRegexStr, '\''); | |
| } | |
| if (nameRegexStr && valueRegexStr) { | |
| out += " and"; | |
| } | |
| if (valueRegexStr) { | |
| out = cmStrCat(std::move(out), " 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(std::move(msg), " name '", *nameRegexStr, '\''); | |
| } | |
| if (nameRegexStr && valueRegexStr) { | |
| msg += " and"; | |
| } | |
| if (valueRegexStr) { | |
| msg = cmStrCat(std::move(msg), " 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(std::move(out), "\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(std::move(out), "\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, | |
| "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, | |
| "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, | |
| "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, | |
| "ALL keyword in " | |
| "cmake_language(PRINT_PROPERTIES) call " | |
| "is only valid with the TARGETS scope."); | |
| } | |
| if (hasRegex) { | |
| return FatalError(status, | |
| "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, | |
| "DEFERRED keyword in " | |
| "cmake_language(PRINT_PROPERTIES) call " | |
| "is only valid with the TARGETS scope."); | |
| } | |
| if (parsedArgsMode.FollowDependencies) { | |
| return FatalError(status, | |
| "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, | |
| "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, | |
| "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, | |
| "__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"); | |
| } |