| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file Copyright.txt or https://cmake.org/licensing for details. */ | |
| #include "cmParseArgumentsCommand.h" | |
| #include <map> | |
| #include <set> | |
| #include <sstream> | |
| #include <utility> | |
| #include <cm/string_view> | |
| #include "cmArgumentParser.h" | |
| #include "cmExecutionStatus.h" | |
| #include "cmMakefile.h" | |
| #include "cmMessageType.h" | |
| #include "cmProperty.h" | |
| #include "cmRange.h" | |
| #include "cmStringAlgorithms.h" | |
| #include "cmSystemTools.h" | |
| static std::string EscapeArg(const std::string& arg) | |
| { | |
| // replace ";" with "\;" so output argument lists will split correctly | |
| std::string escapedArg; | |
| for (char i : arg) { | |
| if (i == ';') { | |
| escapedArg += '\\'; | |
| } | |
| escapedArg += i; | |
| } | |
| return escapedArg; | |
| } | |
| static std::string JoinList(std::vector<std::string> const& arg, bool escape) | |
| { | |
| return escape ? cmJoin(cmMakeRange(arg).transform(EscapeArg), ";") | |
| : cmJoin(cmMakeRange(arg), ";"); | |
| } | |
| namespace { | |
| using options_map = std::map<std::string, bool>; | |
| using single_map = std::map<std::string, std::string>; | |
| using multi_map = std::map<std::string, std::vector<std::string>>; | |
| using options_set = std::set<std::string>; | |
| struct UserArgumentParser : public cmArgumentParser<void> | |
| { | |
| template <typename T, typename H> | |
| void Bind(std::vector<std::string> const& names, | |
| std::map<std::string, T>& ref, H duplicateKey) | |
| { | |
| for (std::string const& key : names) { | |
| auto const it = ref.emplace(key, T{}).first; | |
| bool const inserted = this->cmArgumentParser<void>::Bind( | |
| cm::string_view(it->first), it->second); | |
| if (!inserted) { | |
| duplicateKey(key); | |
| } | |
| } | |
| } | |
| }; | |
| } // namespace | |
| static void PassParsedArguments( | |
| const std::string& prefix, cmMakefile& makefile, const options_map& options, | |
| const single_map& singleValArgs, const multi_map& multiValArgs, | |
| const std::vector<std::string>& unparsed, | |
| const options_set& keywordsMissingValues, bool parseFromArgV) | |
| { | |
| for (auto const& iter : options) { | |
| makefile.AddDefinition(prefix + iter.first, | |
| iter.second ? "TRUE" : "FALSE"); | |
| } | |
| for (auto const& iter : singleValArgs) { | |
| if (!iter.second.empty()) { | |
| makefile.AddDefinition(prefix + iter.first, iter.second); | |
| } else { | |
| makefile.RemoveDefinition(prefix + iter.first); | |
| } | |
| } | |
| for (auto const& iter : multiValArgs) { | |
| if (!iter.second.empty()) { | |
| makefile.AddDefinition(prefix + iter.first, | |
| JoinList(iter.second, parseFromArgV)); | |
| } else { | |
| makefile.RemoveDefinition(prefix + iter.first); | |
| } | |
| } | |
| if (!unparsed.empty()) { | |
| makefile.AddDefinition(prefix + "UNPARSED_ARGUMENTS", | |
| JoinList(unparsed, parseFromArgV)); | |
| } else { | |
| makefile.RemoveDefinition(prefix + "UNPARSED_ARGUMENTS"); | |
| } | |
| if (!keywordsMissingValues.empty()) { | |
| makefile.AddDefinition(prefix + "KEYWORDS_MISSING_VALUES", | |
| cmJoin(cmMakeRange(keywordsMissingValues), ";")); | |
| } else { | |
| makefile.RemoveDefinition(prefix + "KEYWORDS_MISSING_VALUES"); | |
| } | |
| } | |
| bool cmParseArgumentsCommand(std::vector<std::string> const& args, | |
| cmExecutionStatus& status) | |
| { | |
| // cmake_parse_arguments(prefix options single multi <ARGN>) | |
| // 1 2 3 4 | |
| // or | |
| // cmake_parse_arguments(PARSE_ARGV N prefix options single multi) | |
| if (args.size() < 4) { | |
| status.SetError("must be called with at least 4 arguments."); | |
| return false; | |
| } | |
| auto argIter = args.begin(); | |
| auto argEnd = args.end(); | |
| bool parseFromArgV = false; | |
| unsigned long argvStart = 0; | |
| if (*argIter == "PARSE_ARGV") { | |
| if (args.size() != 6) { | |
| status.GetMakefile().IssueMessage( | |
| MessageType::FATAL_ERROR, | |
| "PARSE_ARGV must be called with exactly 6 arguments."); | |
| cmSystemTools::SetFatalErrorOccured(); | |
| return true; | |
| } | |
| parseFromArgV = true; | |
| argIter++; // move past PARSE_ARGV | |
| if (!cmStrToULong(*argIter, &argvStart)) { | |
| status.GetMakefile().IssueMessage(MessageType::FATAL_ERROR, | |
| "PARSE_ARGV index '" + *argIter + | |
| "' is not an unsigned integer"); | |
| cmSystemTools::SetFatalErrorOccured(); | |
| return true; | |
| } | |
| argIter++; // move past N | |
| } | |
| // the first argument is the prefix | |
| const std::string prefix = (*argIter++) + "_"; | |
| UserArgumentParser parser; | |
| // define the result maps holding key/value pairs for | |
| // options, single values and multi values | |
| options_map options; | |
| single_map singleValArgs; | |
| multi_map multiValArgs; | |
| // anything else is put into a vector of unparsed strings | |
| std::vector<std::string> unparsed; | |
| auto const duplicateKey = [&status](std::string const& key) { | |
| status.GetMakefile().IssueMessage( | |
| MessageType::WARNING, "keyword defined more than once: " + key); | |
| }; | |
| // the second argument is a (cmake) list of options without argument | |
| std::vector<std::string> list = cmExpandedList(*argIter++); | |
| parser.Bind(list, options, duplicateKey); | |
| // the third argument is a (cmake) list of single argument options | |
| list.clear(); | |
| cmExpandList(*argIter++, list); | |
| parser.Bind(list, singleValArgs, duplicateKey); | |
| // the fourth argument is a (cmake) list of multi argument options | |
| list.clear(); | |
| cmExpandList(*argIter++, list); | |
| parser.Bind(list, multiValArgs, duplicateKey); | |
| list.clear(); | |
| if (!parseFromArgV) { | |
| // Flatten ;-lists in the arguments into a single list as was done | |
| // by the original function(CMAKE_PARSE_ARGUMENTS). | |
| for (; argIter != argEnd; ++argIter) { | |
| cmExpandList(*argIter, list); | |
| } | |
| } else { | |
| // in the PARSE_ARGV move read the arguments from ARGC and ARGV# | |
| std::string argc = status.GetMakefile().GetSafeDefinition("ARGC"); | |
| unsigned long count; | |
| if (!cmStrToULong(argc, &count)) { | |
| status.GetMakefile().IssueMessage(MessageType::FATAL_ERROR, | |
| "PARSE_ARGV called with ARGC='" + | |
| argc + | |
| "' that is not an unsigned integer"); | |
| cmSystemTools::SetFatalErrorOccured(); | |
| return true; | |
| } | |
| for (unsigned long i = argvStart; i < count; ++i) { | |
| std::ostringstream argName; | |
| argName << "ARGV" << i; | |
| cmProp arg = status.GetMakefile().GetDefinition(argName.str()); | |
| if (!arg) { | |
| status.GetMakefile().IssueMessage(MessageType::FATAL_ERROR, | |
| "PARSE_ARGV called with " + | |
| argName.str() + " not set"); | |
| cmSystemTools::SetFatalErrorOccured(); | |
| return true; | |
| } | |
| list.emplace_back(*arg); | |
| } | |
| } | |
| std::vector<std::string> keywordsMissingValues; | |
| parser.Parse(list, &unparsed, &keywordsMissingValues); | |
| PassParsedArguments( | |
| prefix, status.GetMakefile(), options, singleValArgs, multiValArgs, | |
| unparsed, | |
| options_set(keywordsMissingValues.begin(), keywordsMissingValues.end()), | |
| parseFromArgV); | |
| return true; | |
| } |