/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 should 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 fn) & { getter_ = std::move(fn); return *this; } Flag Flag::Getter(std::function fn) && { getter_ = std::move(fn); return *this; } Flag& Flag::Setter(std::function(const FlagMatch&)> setter) & { setter_ = std::move(setter); return *this; } Flag Flag::Setter(std::function(const FlagMatch&)> setter) && { setter_ = std::move(setter); return *this; } static bool LikelyFlag(const std::string& next_arg) { return android::base::StartsWith(next_arg, "-"); } Result 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::Process( const std::string& arg, const std::optional& next_arg) const { using android::base::StringReplace; auto normalized_arg = StringReplace(arg, "-", "_", true); if (!setter_ && aliases_.size() > 0) { return CF_ERRF("No setter for flag with alias {}", aliases_[0].name); } for (auto& alias : aliases_) { auto normalized_alias = StringReplace(alias.name, "-", "_", true); switch (alias.mode) { case FlagAliasMode::kFlagConsumesArbitrary: if (normalized_arg != normalized_alias) { 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 (normalized_arg != normalized_alias) { 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 (normalized_arg != normalized_alias) { continue; } CF_EXPECTF((*setter_)({arg, arg}), "Processing \"{}\" failed", arg); return FlagProcessResult::kFlagConsumed; case FlagAliasMode::kFlagPrefix: if (!android::base::StartsWith(normalized_arg, normalized_alias)) { 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 Flag::Parse(std::vector& arguments) const { for (int i = 0; i < arguments.size();) { std::string arg = arguments[i]; std::optional 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 Flag::Parse(std::vector&& arguments) const { CF_EXPECT(Parse(static_cast&>(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 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 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 << "\n"; out << " file.cc\n"; out << " " << XmlEscape(name) << "\n"; auto help = help_ ? XmlEscape(*help_) : std::string{""}; out << " " << help << "\n"; auto value = getter_ ? XmlEscape((*getter_)()) : std::string{""}; out << " " << value << "\n"; out << " " << value << "\n"; out << " " << type_str << "\n"; out << "\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 ArgsToVec(int argc, char** argv) { std::vector args; args.reserve(argc); for (int i = 0; i < argc; i++) { args.push_back(argv[i]); } return args; } struct Separated { std::vector args_before_mark; std::vector args_after_mark; }; static Separated SeparateByEndOfOptionMark(std::vector args) { std::vector args_before_mark; std::vector 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 ConsumeFlagsImpl(const std::vector& flags, std::vector& args) { for (const auto& flag : flags) { CF_EXPECT(flag.Parse(args)); } return {}; } static Result ConsumeFlagsImpl(const std::vector& flags, std::vector&& args) { for (const auto& flag : flags) { CF_EXPECT(flag.Parse(args)); } return {}; } Result ConsumeFlags(const std::vector& flags, std::vector& args, const bool recognize_end_of_option_mark) { if (!recognize_end_of_option_mark) { CF_EXPECT(ConsumeFlagsImpl(flags, args)); return {}; } auto separated = SeparateByEndOfOptionMark(std::move(args)); args.clear(); auto result = ConsumeFlagsImpl(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 ConsumeFlags(const std::vector& flags, std::vector&& args, const bool recognize_end_of_option_mark) { if (!recognize_end_of_option_mark) { CF_EXPECT(ConsumeFlagsImpl(flags, std::move(args))); return {}; } auto separated = SeparateByEndOfOptionMark(std::move(args)); CF_EXPECT(ConsumeFlagsImpl(flags, std::move(separated.args_before_mark))); return {}; } bool WriteGflagsCompatXml(const std::vector& 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 { value = CF_EXPECT(ToSeverity(match.value)); return {}; }) .Help("Used to set the verbosity level for logging."); } Flag HelpFlag(const std::vector& flags, std::string text) { auto setter = [&flags, text](FlagMatch) -> Result { 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 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& 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 { 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 << "" << std::endl << "" << std::endl; WriteGflagsCompatXml(flags, out); out << "" << 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 { 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 { 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 { value = match.value; return {}; }); } template std::optional 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(r) != r) { return {}; } return r; } template 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 { value = CF_EXPECTF(ParseInteger(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& value) { return GflagsCompatFlag(name) .Getter([&value]() { return android::base::Join(value, ','); }) .Setter([&value](const FlagMatch& match) -> Result { if (match.value.empty()) { value.clear(); return {}; } std::vector str_vals = android::base::Split(match.value, ","); value = std::move(str_vals); return {}; }); } Flag GflagsCompatFlag(const std::string& name, std::vector& 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 { if (match.value.empty()) { value.clear(); return {}; } std::vector str_vals = android::base::Split(match.value, ","); value.clear(); std::vector 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