| /* |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "common/libs/utils/flag_parser.h" |
| |
| #include <algorithm> |
| #include <cerrno> |
| #include <cstdlib> |
| #include <cstring> |
| #include <functional> |
| #include <iostream> |
| #include <iterator> |
| #include <sstream> |
| #include <string> |
| #include <string_view> |
| #include <type_traits> |
| #include <unordered_map> |
| #include <unordered_set> |
| #include <utility> |
| #include <vector> |
| |
| #include <android-base/logging.h> |
| #include <android-base/parsebool.h> |
| #include <android-base/scopeguard.h> |
| #include <android-base/strings.h> |
| #include <fmt/format.h> |
| |
| #include "common/libs/utils/result.h" |
| #include "common/libs/utils/tee_logging.h" |
| |
| namespace cuttlefish { |
| |
| std::ostream& operator<<(std::ostream& out, const FlagAlias& alias) { |
| switch (alias.mode) { |
| case FlagAliasMode::kFlagExact: |
| return out << alias.name; |
| case FlagAliasMode::kFlagPrefix: |
| return out << alias.name << "*"; |
| case FlagAliasMode::kFlagConsumesFollowing: |
| return out << alias.name << " *"; |
| default: |
| LOG(FATAL) << "Unexpected flag alias mode " << (int)alias.mode; |
| } |
| return out; |
| } |
| |
| Flag& Flag::UnvalidatedAlias(const FlagAlias& alias) & { |
| aliases_.push_back(alias); |
| return *this; |
| } |
| Flag Flag::UnvalidatedAlias(const FlagAlias& alias) && { |
| aliases_.push_back(alias); |
| return *this; |
| } |
| |
| void Flag::ValidateAlias(const FlagAlias& alias) { |
| using android::base::EndsWith; |
| using android::base::StartsWith; |
| |
| CHECK(StartsWith(alias.name, "-")) << "Flags should start with \"-\""; |
| if (alias.mode == FlagAliasMode::kFlagPrefix) { |
| CHECK(EndsWith(alias.name, "=")) << "Prefix flags shold end with \"=\""; |
| } |
| |
| CHECK(!HasAlias(alias)) << "Duplicate flag alias: " << alias.name; |
| if (alias.mode == FlagAliasMode::kFlagConsumesFollowing) { |
| CHECK(!HasAlias({FlagAliasMode::kFlagExact, alias.name})) |
| << "Overlapping flag aliases for " << alias.name; |
| CHECK(!HasAlias({FlagAliasMode::kFlagConsumesArbitrary, alias.name})) |
| << "Overlapping flag aliases for " << alias.name; |
| } else if (alias.mode == FlagAliasMode::kFlagExact) { |
| CHECK(!HasAlias({FlagAliasMode::kFlagConsumesFollowing, alias.name})) |
| << "Overlapping flag aliases for " << alias.name; |
| CHECK(!HasAlias({FlagAliasMode::kFlagConsumesArbitrary, alias.name})) |
| << "Overlapping flag aliases for " << alias.name; |
| } else if (alias.mode == FlagAliasMode::kFlagConsumesArbitrary) { |
| CHECK(!HasAlias({FlagAliasMode::kFlagExact, alias.name})) |
| << "Overlapping flag aliases for " << alias.name; |
| CHECK(!HasAlias({FlagAliasMode::kFlagConsumesFollowing, alias.name})) |
| << "Overlapping flag aliases for " << alias.name; |
| } |
| } |
| |
| Flag& Flag::Alias(const FlagAlias& alias) & { |
| ValidateAlias(alias); |
| aliases_.push_back(alias); |
| return *this; |
| } |
| Flag Flag::Alias(const FlagAlias& alias) && { |
| ValidateAlias(alias); |
| aliases_.push_back(alias); |
| return *this; |
| } |
| |
| Flag& Flag::Help(const std::string& help) & { |
| help_ = help; |
| return *this; |
| } |
| Flag Flag::Help(const std::string& help) && { |
| help_ = help; |
| return *this; |
| } |
| |
| Flag& Flag::Getter(std::function<std::string()> fn) & { |
| getter_ = std::move(fn); |
| return *this; |
| } |
| Flag Flag::Getter(std::function<std::string()> fn) && { |
| getter_ = std::move(fn); |
| return *this; |
| } |
| |
| Flag& Flag::Setter(std::function<Result<void>(const FlagMatch&)> setter) & { |
| setter_ = std::move(setter); |
| return *this; |
| } |
| Flag Flag::Setter(std::function<Result<void>(const FlagMatch&)> setter) && { |
| setter_ = std::move(setter); |
| return *this; |
| } |
| |
| static bool LikelyFlag(const std::string& next_arg) { |
| return android::base::StartsWith(next_arg, "-"); |
| } |
| |
| Result<bool> ParseBool(const std::string& value, const std::string& name) { |
| auto result = android::base::ParseBool(value); |
| CF_EXPECT(result != android::base::ParseBoolResult::kError, |
| "Failed to parse value \"" << value << "\" for " << name); |
| if (result == android::base::ParseBoolResult::kTrue) { |
| return true; |
| } |
| return false; |
| } |
| |
| Result<Flag::FlagProcessResult> Flag::Process( |
| const std::string& arg, const std::optional<std::string>& next_arg) const { |
| if (!setter_ && aliases_.size() > 0) { |
| return CF_ERRF("No setter for flag with alias {}", aliases_[0].name); |
| } |
| for (auto& alias : aliases_) { |
| switch (alias.mode) { |
| case FlagAliasMode::kFlagConsumesArbitrary: |
| if (arg != alias.name) { |
| continue; |
| } |
| if (!next_arg || LikelyFlag(*next_arg)) { |
| CF_EXPECTF((*setter_)({arg, ""}), "Processing \"{}\" failed", arg); |
| return FlagProcessResult::kFlagConsumed; |
| } |
| CF_EXPECTF((*setter_)({arg, *next_arg}), |
| "Processing \"{}\" \"{}\" failed", arg, *next_arg); |
| return FlagProcessResult::kFlagConsumedOnlyFollowing; |
| case FlagAliasMode::kFlagConsumesFollowing: |
| if (arg != alias.name) { |
| continue; |
| } |
| CF_EXPECTF(next_arg.has_value(), "Expected an argument after \"{}\"", |
| arg); |
| CF_EXPECTF((*setter_)({arg, *next_arg}), |
| "Processing \"{}\" \"{}\" failed", arg, *next_arg); |
| return FlagProcessResult::kFlagConsumedWithFollowing; |
| case FlagAliasMode::kFlagExact: |
| if (arg != alias.name) { |
| continue; |
| } |
| CF_EXPECTF((*setter_)({arg, arg}), "Processing \"{}\" failed", arg); |
| return FlagProcessResult::kFlagConsumed; |
| case FlagAliasMode::kFlagPrefix: |
| if (!android::base::StartsWith(arg, alias.name)) { |
| continue; |
| } |
| CF_EXPECTF((*setter_)({alias.name, arg.substr(alias.name.size())}), |
| "Processing \"{}\" failed", arg); |
| return FlagProcessResult::kFlagConsumed; |
| default: |
| return CF_ERRF("Unknown flag alias mode: {}", (int)alias.mode); |
| } |
| } |
| return FlagProcessResult::kFlagSkip; |
| } |
| |
| Result<void> Flag::Parse(std::vector<std::string>& arguments) const { |
| for (int i = 0; i < arguments.size();) { |
| std::string arg = arguments[i]; |
| std::optional<std::string> next_arg; |
| if (i < arguments.size() - 1) { |
| next_arg = arguments[i + 1]; |
| } |
| switch (CF_EXPECT(Process(arg, next_arg))) { |
| case FlagProcessResult::kFlagConsumed: |
| arguments.erase(arguments.begin() + i); |
| break; |
| case FlagProcessResult::kFlagConsumedWithFollowing: |
| arguments.erase(arguments.begin() + i, arguments.begin() + i + 2); |
| break; |
| case FlagProcessResult::kFlagConsumedOnlyFollowing: |
| arguments.erase(arguments.begin() + i + 1, arguments.begin() + i + 2); |
| break; |
| case FlagProcessResult::kFlagSkip: |
| i++; |
| break; |
| } |
| } |
| return {}; |
| } |
| Result<void> Flag::Parse(std::vector<std::string>&& arguments) const { |
| CF_EXPECT(Parse(static_cast<std::vector<std::string>&>(arguments))); |
| return {}; |
| } |
| |
| bool Flag::HasAlias(const FlagAlias& test) const { |
| for (const auto& alias : aliases_) { |
| if (alias.mode == test.mode && alias.name == test.name) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| static std::string XmlEscape(const std::string& s) { |
| using android::base::StringReplace; |
| return StringReplace(StringReplace(s, "<", "<", true), ">", ">", true); |
| } |
| |
| bool Flag::WriteGflagsCompatXml(std::ostream& out) const { |
| std::unordered_set<std::string> name_guesses; |
| for (const auto& alias : aliases_) { |
| std::string_view name = alias.name; |
| if (!android::base::ConsumePrefix(&name, "-")) { |
| continue; |
| } |
| android::base::ConsumePrefix(&name, "-"); |
| if (alias.mode == FlagAliasMode::kFlagExact) { |
| android::base::ConsumePrefix(&name, "no"); |
| name_guesses.insert(std::string{name}); |
| } else if (alias.mode == FlagAliasMode::kFlagConsumesFollowing) { |
| name_guesses.insert(std::string{name}); |
| } else if (alias.mode == FlagAliasMode::kFlagPrefix) { |
| if (!android::base::ConsumeSuffix(&name, "=")) { |
| continue; |
| } |
| name_guesses.insert(std::string{name}); |
| } |
| } |
| bool found_alias = false; |
| for (const auto& name : name_guesses) { |
| bool has_bool_aliases = |
| HasAlias({FlagAliasMode::kFlagPrefix, "-" + name + "="}) && |
| HasAlias({FlagAliasMode::kFlagPrefix, "--" + name + "="}) && |
| HasAlias({FlagAliasMode::kFlagExact, "-" + name}) && |
| HasAlias({FlagAliasMode::kFlagExact, "--" + name}) && |
| HasAlias({FlagAliasMode::kFlagExact, "-no" + name}) && |
| HasAlias({FlagAliasMode::kFlagExact, "--no" + name}); |
| bool has_other_aliases = |
| HasAlias({FlagAliasMode::kFlagPrefix, "-" + name + "="}) && |
| HasAlias({FlagAliasMode::kFlagPrefix, "--" + name + "="}) && |
| HasAlias({FlagAliasMode::kFlagConsumesFollowing, "-" + name}) && |
| HasAlias({FlagAliasMode::kFlagConsumesFollowing, "--" + name}); |
| bool has_help_aliases = HasAlias({FlagAliasMode::kFlagExact, "-help"}) && |
| HasAlias({FlagAliasMode::kFlagExact, "--help"}); |
| std::vector<bool> has_aliases = {has_bool_aliases, has_other_aliases, |
| has_help_aliases}; |
| const auto true_count = |
| std::count(has_aliases.cbegin(), has_aliases.cend(), true); |
| if (true_count > 1) { |
| LOG(ERROR) << "Expected exactly one of has_bool_aliases, " |
| << "has_other_aliases, and has_help_aliases, got " |
| << true_count << " for \"" << name << "\"."; |
| return false; |
| } |
| if (true_count == 0) { |
| continue; |
| } |
| found_alias = true; |
| std::string type_str = |
| (has_bool_aliases || has_help_aliases) ? "bool" : "string"; |
| // Lifted from external/gflags/src/gflags_reporting.cc:DescribeOneFlagInXML |
| out << "<flag>\n"; |
| out << " <file>file.cc</file>\n"; |
| out << " <name>" << XmlEscape(name) << "</name>\n"; |
| auto help = help_ ? XmlEscape(*help_) : std::string{""}; |
| out << " <meaning>" << help << "</meaning>\n"; |
| auto value = getter_ ? XmlEscape((*getter_)()) : std::string{""}; |
| out << " <default>" << value << "</default>\n"; |
| out << " <current>" << value << "</current>\n"; |
| out << " <type>" << type_str << "</type>\n"; |
| out << "</flag>\n"; |
| } |
| return found_alias; |
| } |
| |
| std::ostream& operator<<(std::ostream& out, const Flag& flag) { |
| out << "["; |
| for (auto it = flag.aliases_.begin(); it != flag.aliases_.end(); it++) { |
| if (it != flag.aliases_.begin()) { |
| out << ", "; |
| } |
| out << *it; |
| } |
| out << "]\n"; |
| if (flag.help_) { |
| out << "(" << *flag.help_ << ")\n"; |
| } |
| if (flag.getter_) { |
| out << "(Current value: \"" << (*flag.getter_)() << "\")\n"; |
| } |
| return out; |
| } |
| |
| std::vector<std::string> ArgsToVec(int argc, char** argv) { |
| std::vector<std::string> args; |
| args.reserve(argc); |
| for (int i = 0; i < argc; i++) { |
| args.push_back(argv[i]); |
| } |
| return args; |
| } |
| |
| struct Separated { |
| std::vector<std::string> args_before_mark; |
| std::vector<std::string> args_after_mark; |
| }; |
| static Separated SeparateByEndOfOptionMark(std::vector<std::string> args) { |
| std::vector<std::string> args_before_mark; |
| std::vector<std::string> args_after_mark; |
| |
| auto itr = std::find(args.begin(), args.end(), "--"); |
| bool has_mark = (itr != args.end()); |
| if (!has_mark) { |
| args_before_mark = std::move(args); |
| } else { |
| args_before_mark.insert(args_before_mark.end(), args.begin(), itr); |
| args_after_mark.insert(args_after_mark.end(), itr + 1, args.end()); |
| } |
| |
| return Separated{ |
| .args_before_mark = std::move(args_before_mark), |
| .args_after_mark = std::move(args_after_mark), |
| }; |
| } |
| |
| static Result<void> ParseFlagsImpl(const std::vector<Flag>& flags, |
| std::vector<std::string>& args) { |
| for (const auto& flag : flags) { |
| CF_EXPECT(flag.Parse(args)); |
| } |
| return {}; |
| } |
| |
| static Result<void> ParseFlagsImpl(const std::vector<Flag>& flags, |
| std::vector<std::string>&& args) { |
| for (const auto& flag : flags) { |
| CF_EXPECT(flag.Parse(args)); |
| } |
| return {}; |
| } |
| |
| Result<void> ParseFlags(const std::vector<Flag>& flags, |
| std::vector<std::string>& args, |
| const bool recognize_end_of_option_mark) { |
| if (!recognize_end_of_option_mark) { |
| CF_EXPECT(ParseFlagsImpl(flags, args)); |
| return {}; |
| } |
| auto separated = SeparateByEndOfOptionMark(std::move(args)); |
| args.clear(); |
| auto result = ParseFlagsImpl(flags, separated.args_before_mark); |
| args = std::move(separated.args_before_mark); |
| args.insert(args.end(), |
| std::make_move_iterator(separated.args_after_mark.begin()), |
| std::make_move_iterator(separated.args_after_mark.end())); |
| CF_EXPECT(std::move(result)); |
| return {}; |
| } |
| |
| Result<void> ParseFlags(const std::vector<Flag>& flags, |
| std::vector<std::string>&& args, |
| const bool recognize_end_of_option_mark) { |
| if (!recognize_end_of_option_mark) { |
| CF_EXPECT(ParseFlagsImpl(flags, std::move(args))); |
| return {}; |
| } |
| auto separated = SeparateByEndOfOptionMark(std::move(args)); |
| CF_EXPECT(ParseFlagsImpl(flags, std::move(separated.args_before_mark))); |
| return {}; |
| } |
| |
| bool WriteGflagsCompatXml(const std::vector<Flag>& flags, std::ostream& out) { |
| for (const auto& flag : flags) { |
| if (!flag.WriteGflagsCompatXml(out)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| Flag VerbosityFlag(android::base::LogSeverity& value) { |
| return GflagsCompatFlag("verbosity") |
| .Getter([&value]() { return FromSeverity(value); }) |
| .Setter([&value](const FlagMatch& match) -> Result<void> { |
| value = CF_EXPECT(ToSeverity(match.value)); |
| return {}; |
| }) |
| .Help("Used to set the verbosity level for logging."); |
| } |
| |
| Flag HelpFlag(const std::vector<Flag>& flags, std::string text) { |
| auto setter = [&flags, text](FlagMatch) -> Result<void> { |
| if (text.size() > 0) { |
| LOG(INFO) << text; |
| } |
| for (const auto& flag : flags) { |
| LOG(INFO) << flag; |
| } |
| return CF_ERR("user requested early exit"); |
| }; |
| return Flag() |
| .Alias({FlagAliasMode::kFlagExact, "-help"}) |
| .Alias({FlagAliasMode::kFlagExact, "--help"}) |
| .Setter(setter); |
| } |
| |
| static Result<void> GflagsCompatBoolFlagSetter(const std::string& name, |
| bool& value, |
| const FlagMatch& match) { |
| const auto& key = match.key; |
| if (key == "-" + name || key == "--" + name) { |
| value = true; |
| return {}; |
| } else if (key == "-no" + name || key == "--no" + name) { |
| value = false; |
| return {}; |
| } else if (key == "-" + name + "=" || key == "--" + name + "=") { |
| if (match.value == "true") { |
| value = true; |
| return {}; |
| } else if (match.value == "false") { |
| value = false; |
| return {}; |
| } else { |
| return CF_ERRF("Unexpected boolean value \"{}\" for \{}\"", match.value, |
| name); |
| } |
| } |
| return CF_ERRF("Unexpected key \"{}\" for \"{}\"", match.key, name); |
| } |
| |
| static Flag GflagsCompatBoolFlagBase(const std::string& name) { |
| return Flag() |
| .Alias({FlagAliasMode::kFlagPrefix, "-" + name + "="}) |
| .Alias({FlagAliasMode::kFlagPrefix, "--" + name + "="}) |
| .Alias({FlagAliasMode::kFlagExact, "-" + name}) |
| .Alias({FlagAliasMode::kFlagExact, "--" + name}) |
| .Alias({FlagAliasMode::kFlagExact, "-no" + name}) |
| .Alias({FlagAliasMode::kFlagExact, "--no" + name}); |
| } |
| |
| Flag HelpXmlFlag(const std::vector<Flag>& flags, std::ostream& out, bool& value, |
| std::string text) { |
| const std::string name = "helpxml"; |
| auto setter = [name, &out, &value, text, |
| &flags](const FlagMatch& match) -> Result<void> { |
| bool print_xml = false; |
| CF_EXPECT(GflagsCompatBoolFlagSetter(name, print_xml, match)); |
| if (!print_xml) { |
| return {}; |
| } |
| if (!text.empty()) { |
| out << text << std::endl; |
| } |
| value = print_xml; |
| out << "<?xml version=\"1.0\"?>" << std::endl << "<AllFlags>" << std::endl; |
| WriteGflagsCompatXml(flags, out); |
| out << "</AllFlags>" << std::flush; |
| return CF_ERR("Requested early exit"); |
| }; |
| return GflagsCompatBoolFlagBase(name).Setter(setter); |
| } |
| |
| Flag InvalidFlagGuard() { |
| return Flag() |
| .UnvalidatedAlias({FlagAliasMode::kFlagPrefix, "-"}) |
| .Help( |
| "This executable only supports the flags in `-help`. Positional " |
| "arguments may be supported.") |
| .Setter([](const FlagMatch& match) -> Result<void> { |
| return CF_ERRF("Unknown flag \"{}\"", match.value); |
| }); |
| } |
| |
| Flag UnexpectedArgumentGuard() { |
| return Flag() |
| .UnvalidatedAlias({FlagAliasMode::kFlagPrefix, ""}) |
| .Help( |
| "This executable only supports the flags in `-help`. Positional " |
| "arguments are not supported.") |
| .Setter([](const FlagMatch& match) -> Result<void> { |
| return CF_ERRF("Unexpected argument \"{}\"", match.value); |
| }); |
| } |
| |
| Flag GflagsCompatFlag(const std::string& name) { |
| return Flag() |
| .Alias({FlagAliasMode::kFlagPrefix, "-" + name + "="}) |
| .Alias({FlagAliasMode::kFlagPrefix, "--" + name + "="}) |
| .Alias({FlagAliasMode::kFlagConsumesFollowing, "-" + name}) |
| .Alias({FlagAliasMode::kFlagConsumesFollowing, "--" + name}); |
| }; |
| |
| Flag GflagsCompatFlag(const std::string& name, std::string& value) { |
| return GflagsCompatFlag(name) |
| .Getter([&value]() { return value; }) |
| .Setter([&value](const FlagMatch& match) -> Result<void> { |
| value = match.value; |
| return {}; |
| }); |
| } |
| |
| template <typename T> |
| std::optional<T> ParseInteger(const std::string& value) { |
| if (value.size() == 0) { |
| return {}; |
| } |
| const char* base = value.c_str(); |
| char* end = nullptr; |
| errno = 0; |
| auto r = strtoll(base, &end, /* auto-detect */ 0); |
| if (errno != 0 || end != base + value.size()) { |
| return {}; |
| } |
| if (static_cast<T>(r) != r) { |
| return {}; |
| } |
| return r; |
| } |
| |
| template <typename T> |
| static Flag GflagsCompatNumericFlagGeneric(const std::string& name, T& value) { |
| return GflagsCompatFlag(name) |
| .Getter([&value]() { return std::to_string(value); }) |
| .Setter([&value](const FlagMatch& match) -> Result<void> { |
| value = CF_EXPECTF(ParseInteger<T>(match.value), |
| "Failed to parse \"{}\" as an integer", match.value); |
| return {}; |
| }); |
| } |
| |
| Flag GflagsCompatFlag(const std::string& name, int32_t& value) { |
| return GflagsCompatNumericFlagGeneric(name, value); |
| } |
| |
| Flag GflagsCompatFlag(const std::string& name, bool& value) { |
| return GflagsCompatBoolFlagBase(name) |
| .Getter([&value]() { return fmt::format("{}", value); }) |
| .Setter([name, &value](const FlagMatch& match) { |
| return GflagsCompatBoolFlagSetter(name, value, match); |
| }); |
| }; |
| |
| Flag GflagsCompatFlag(const std::string& name, |
| std::vector<std::string>& value) { |
| return GflagsCompatFlag(name) |
| .Getter([&value]() { return android::base::Join(value, ','); }) |
| .Setter([&name, &value](const FlagMatch& match) -> Result<void> { |
| CF_EXPECTF(!match.value.empty(), "No values given for flag \"{}\"", |
| name); |
| std::vector<std::string> str_vals = |
| android::base::Split(match.value, ","); |
| value = std::move(str_vals); |
| return {}; |
| }); |
| } |
| |
| Flag GflagsCompatFlag(const std::string& name, std::vector<bool>& value, |
| const bool def_val) { |
| return GflagsCompatFlag(name) |
| .Getter([&value]() { return fmt::format("{}", fmt::join(value, ",")); }) |
| .Setter([&name, &value, def_val](const FlagMatch& match) -> Result<void> { |
| CF_EXPECTF(!match.value.empty(), "No values given for flag \"{}\"", |
| name); |
| std::vector<std::string> str_vals = |
| android::base::Split(match.value, ","); |
| value.clear(); |
| std::vector<bool> output_vals; |
| output_vals.reserve(str_vals.size()); |
| for (const auto& str_val : str_vals) { |
| if (str_val.empty()) { |
| output_vals.push_back(def_val); |
| } else { |
| output_vals.push_back(CF_EXPECT(ParseBool(str_val, name))); |
| } |
| } |
| value = output_vals; |
| return {}; |
| }); |
| } |
| |
| } // namespace cuttlefish |