diff --git a/base/cvd/cuttlefish/host/commands/cvd/cli/selector/BUILD.bazel b/base/cvd/cuttlefish/host/commands/cvd/cli/selector/BUILD.bazel index 4b5d978eb93..6a4ac303610 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/cli/selector/BUILD.bazel +++ b/base/cvd/cuttlefish/host/commands/cvd/cli/selector/BUILD.bazel @@ -70,7 +70,6 @@ cf_cc_library( "//cuttlefish/host/commands/cvd/cli:utils", "//cuttlefish/host/commands/cvd/instances", "//cuttlefish/host/commands/cvd/instances:instance_manager", - "//cuttlefish/host/libs/config:config_constants", "//cuttlefish/result", "//libbase", "@abseil-cpp//absl/strings", diff --git a/base/cvd/cuttlefish/host/commands/cvd/cli/selector/selector.cpp b/base/cvd/cuttlefish/host/commands/cvd/cli/selector/selector.cpp index 8edeab4c32b..36daaf90121 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/cli/selector/selector.cpp +++ b/base/cvd/cuttlefish/host/commands/cvd/cli/selector/selector.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -26,27 +27,23 @@ #include "absl/strings/ascii.h" #include "absl/strings/numbers.h" +#include "cuttlefish/host/commands/cvd/cli/command_request.h" #include "cuttlefish/host/commands/cvd/cli/interruptible_terminal.h" #include "cuttlefish/host/commands/cvd/cli/utils.h" +#include "cuttlefish/host/commands/cvd/instances/local_instance.h" #include "cuttlefish/host/commands/cvd/instances/local_instance_group.h" -#include "cuttlefish/host/libs/config/config_constants.h" namespace cuttlefish { namespace selector { namespace { -Result GetDefaultGroup( - const InstanceManager& instance_manager) { - const std::vector all_groups = - CF_EXPECT(instance_manager.FindGroups({})); - CF_EXPECTF(all_groups.size() == 1, - "There are {} instance groups, unable to pick one", - all_groups.size()); - return all_groups.front(); -} +enum class DisplayBehavior { + LabelGroup, + LabelInstance, +}; Result BuildFilterFromSelectors( - const SelectorOptions& selectors, const cvd_common::Envs& env) { + const SelectorOptions& selectors) { InstanceDatabase::Filter filter; filter.group_name = selectors.group_name; if (selectors.instance_names) { @@ -56,131 +53,144 @@ Result BuildFilterFromSelectors( filter.instance_names.insert(per_instance_name); } } - auto it = env.find(kCuttlefishInstanceEnvVarName); - if (it != env.end()) { - unsigned id; - std::string cuttlefish_instance = it->second; - CF_EXPECT(absl::SimpleAtoi(cuttlefish_instance, &id)); - } - return filter; } -std::string SelectionMenu(const std::vector& groups) { - // Multiple instance groups found, please choose one: - // [i] : group_name (created: TIME) - // instance0.device_name() (id: instance_id) - // instance1.device_name() (id: instance_id) - std::stringstream ss; - ss << "Multiple instance groups found, please choose one:" << std::endl; - int group_idx = 0; - for (const auto& group : groups) { - fmt::print(ss, " [{}] : {} (created: {})\n", group_idx, group.GroupName(), +std::string GroupDisplay(const std::vector& groups, + const DisplayBehavior behavior) { + std::stringstream result; + int group_index = 0; + for (const LocalInstanceGroup& group : groups) { + if (behavior == DisplayBehavior::LabelGroup) { + fmt::print(result, "[{}] - ", group_index); + } + fmt::print(result, "{} (created: {})\n", group.GroupName(), Format(group.StartTime())); - char instance_idx = 'a'; - for (const auto& instance : group.Instances()) { - fmt::print(ss, " <{}> {}-{} (id : {})\n", instance_idx++, - group.GroupName(), instance.name(), instance.id()); + + int instance_index = 0; + for (const LocalInstance& instance : group.Instances()) { + result << "\t"; + if (behavior == DisplayBehavior::LabelInstance) { + fmt::print(result, "[{}] - ", instance_index); + } + fmt::print(result, "{}-{} (id : {})\n", group.GroupName(), + instance.name(), instance.id()); + + instance_index++; } - group_idx++; + + group_index++; } - return ss.str(); + return result.str(); } -Result PromptUserForGroup( - const InstanceManager& instance_manager, const CommandRequest& request, - const cvd_common::Envs& envs, InstanceDatabase::Filter filter) { - // show the menu and let the user choose - std::vector groups = - CF_EXPECT(instance_manager.FindGroups({})); - std::string menu = SelectionMenu(groups); - - std::cout << menu << "\n"; - std::unique_ptr terminal_ = +Result PromptForSelection(const int max_selection) { + std::unique_ptr terminal = std::make_unique(); TerminalColors colors(isatty(2)); - while (true) { - std::string input_line = CF_EXPECT(terminal_->ReadLine()); - int selection = -1; - std::string chosen_group_name; - if (absl::SimpleAtoi(input_line, &selection)) { - const int n_groups = groups.size(); - if (n_groups <= selection || selection < 0) { - fmt::print(std::cerr, - "\n Selection {}{}{} is beyond the range {}[0, {}]{}\n\n", - colors.BoldRed(), selection, colors.Reset(), colors.Cyan(), - n_groups - 1, colors.Reset()); - continue; - } - chosen_group_name = groups[selection].GroupName(); - } else { - chosen_group_name = std::string(absl::StripAsciiWhitespace(input_line)); - } - filter.group_name = chosen_group_name; - Result instance_group_result = - instance_manager.FindGroup(filter); - if (instance_group_result.ok()) { - return instance_group_result; + int selection = -1; + while (selection < 0 || selection > max_selection) { + fmt::print(std::cout, "\nSelect {}[0,{}]{}: ", colors.Cyan(), max_selection, + colors.Reset()); + std::cout << std::flush; + std::string input_line = CF_EXPECT(terminal->ReadLine()); + if (!absl::SimpleAtoi(input_line, &selection)) { + selection = -1; + fmt::print(std::cerr, "Selection \"{}{}{}\" is not a valid.\n", + colors.BoldRed(), input_line, colors.Reset()); + continue; + } + if (selection > max_selection) { + fmt::print(std::cerr, + "Selection \"{}{}{}\" is beyond the allowed range.\n", + colors.BoldRed(), selection, colors.Reset()); + continue; } - fmt::print(std::cerr, - "\n Failed to find a group whose name is {}\"{}\"{}\n\n", - colors.BoldRed(), chosen_group_name, colors.Reset()); } + return selection; } -Result FindGroupOrDefault( - const InstanceDatabase::Filter& filter, +Result PromptUserForGroup( const InstanceManager& instance_manager) { - if (filter.Empty()) { - return CF_EXPECT(GetDefaultGroup(instance_manager)); - } - std::vector groups = - CF_EXPECT(instance_manager.FindGroups(filter)); - CF_EXPECT_EQ(groups.size(), 1u, "groups.size() = " << groups.size()); - return groups.front(); + const std::vector groups = + CF_EXPECT(instance_manager.FindGroups({})); + std::string menu = GroupDisplay(groups, DisplayBehavior::LabelGroup); + std::cout << menu; + + const int selection = CF_EXPECT(PromptForSelection(groups.size() - 1)); + auto group_filter = InstanceDatabase::Filter{ + .group_name = groups[selection].GroupName(), + }; + + return CF_EXPECT(instance_manager.FindGroup(group_filter)); } -Result> FindDefaultInstance( +Result> PromptUserForInstance( const InstanceManager& instance_manager) { - const LocalInstanceGroup group = CF_EXPECT(GetDefaultGroup(instance_manager)); - const std::vector instances = group.Instances(); - CF_EXPECT_EQ(instances.size(), 1u, - "Default instance is the single instance in the default group."); - return std::make_pair(instances.front(), group); + const LocalInstanceGroup group = + CF_EXPECT(PromptUserForGroup(instance_manager)); + const std::vector& instances = group.Instances(); + if (instances.size() == 1) { + fmt::print(std::cout, + "Single instance in group {}, defaulting to that choice.\n", + group.GroupName()); + return std::pair(instances.front(), group); + } + + const std::string menu = + GroupDisplay({group}, DisplayBehavior::LabelInstance); + std::cout << menu; + + const int selection = CF_EXPECT(PromptForSelection(instances.size() - 1)); + auto instance_filter = InstanceDatabase::Filter{ + .group_name = group.GroupName(), + .instance_names = {instances[selection].name()}, + }; + return CF_EXPECT(instance_manager.FindInstanceWithGroup(instance_filter)); } } // namespace Result SelectGroup(const InstanceManager& instance_manager, const CommandRequest& request) { - const bool has_groups = CF_EXPECT(instance_manager.HasInstanceGroups()); - CF_EXPECT(std::move(has_groups), "No instance groups available"); - const cvd_common::Envs& env = request.Env(); - const SelectorOptions& selector_options = request.Selectors(); - InstanceDatabase::Filter filter = - CF_EXPECT(BuildFilterFromSelectors(selector_options, request.Env())); - Result group_selection_result = - FindGroupOrDefault(filter, instance_manager); - if (group_selection_result.ok()) { - return CF_EXPECT(std::move(group_selection_result)); + const InstanceDatabase::Filter filter = + CF_EXPECT(BuildFilterFromSelectors(request.Selectors())); + std::vector groups; + if (filter.Empty()) { // try to default + groups = CF_EXPECT(instance_manager.FindGroups({})); + } else { + groups = CF_EXPECT(instance_manager.FindGroups(filter)); + } + CF_EXPECT(!groups.empty(), "No instance groups available"); + if (groups.size() == 1) { + return groups.front(); } CF_EXPECT(isatty(0), "Multiple groups found. Narrow the selection with selector " "arguments or run in an interactive terminal."); - return CF_EXPECT( - PromptUserForGroup(instance_manager, request, env, std::move(filter))); + return CF_EXPECT(PromptUserForGroup(instance_manager)); } Result> SelectInstance( const InstanceManager& instance_manager, const CommandRequest& request) { - InstanceDatabase::Filter filter = - CF_EXPECT(BuildFilterFromSelectors(request.Selectors(), request.Env())); - - return filter.Empty() - ? CF_EXPECT(FindDefaultInstance(instance_manager)) - : CF_EXPECT(instance_manager.FindInstanceWithGroup(filter)); + const InstanceDatabase::Filter filter = + CF_EXPECT(BuildFilterFromSelectors(request.Selectors())); + std::vector> instances; + if (filter.Empty()) { // try to default + instances = CF_EXPECT(instance_manager.FindInstances({})); + } else { + instances = CF_EXPECT(instance_manager.FindInstances(filter)); + } + CF_EXPECT(!instances.empty(), "No instances available"); + if (instances.size() == 1) { + return instances.front(); + } + CF_EXPECT(isatty(0), + "Multiple instances found. Narrow the selection with selector " + "arguments or run in an interactive terminal"); + return CF_EXPECT(PromptUserForInstance(instance_manager)); } } // namespace selector diff --git a/base/cvd/cuttlefish/host/commands/cvd/instances/instance_database.cpp b/base/cvd/cuttlefish/host/commands/cvd/instances/instance_database.cpp index 4573e27a94a..a47cbad4082 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/instances/instance_database.cpp +++ b/base/cvd/cuttlefish/host/commands/cvd/instances/instance_database.cpp @@ -254,6 +254,35 @@ InstanceDatabase::FindInstanceWithGroup(const Filter& filter) const { }); } +Result>> +InstanceDatabase::FindInstances(const Filter& filter) const { + CF_EXPECT_LE(filter.instance_names.size(), 1u, + "Can't find single instance when multiple names specified: " + << filter.instance_names.size()); + return viewer_.WithSharedLock< + std::vector>>( + [&filter](const auto& data) + -> Result>> { + std::vector> result; + for (const auto& group : data.instance_groups()) { + if (!GroupMatches(group, filter)) { + continue; + } + for (int i = 0; i < group.instances_size(); ++i) { + const auto& instance = group.instances(i); + if (!InstanceMatches(instance, filter)) { + continue; + } + LocalInstanceGroup local_group = + CF_EXPECT(LocalInstanceGroup::Create(group)); + result.push_back( + std::make_pair(local_group.Instances()[i], local_group)); + } + } + return result; + }); +} + Result> InstanceDatabase::InstanceGroups() const { return viewer_.WithSharedLock>( diff --git a/base/cvd/cuttlefish/host/commands/cvd/instances/instance_database.h b/base/cvd/cuttlefish/host/commands/cvd/instances/instance_database.h index 46144d9e0d8..d94d71a5078 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/instances/instance_database.h +++ b/base/cvd/cuttlefish/host/commands/cvd/instances/instance_database.h @@ -80,6 +80,8 @@ class InstanceDatabase { } Result> FindInstanceWithGroup( const Filter& filter) const; + Result>> + FindInstances(const Filter& filter) const; private: template diff --git a/base/cvd/cuttlefish/host/commands/cvd/instances/instance_manager.cpp b/base/cvd/cuttlefish/host/commands/cvd/instances/instance_manager.cpp index d920598bfd9..394fe58922d 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/instances/instance_manager.cpp +++ b/base/cvd/cuttlefish/host/commands/cvd/instances/instance_manager.cpp @@ -105,6 +105,11 @@ InstanceManager::FindInstanceWithGroup( return CF_EXPECT(instance_db_.FindInstanceWithGroup(filter)); } +Result>> +InstanceManager::FindInstances(const InstanceDatabase::Filter& filter) const { + return CF_EXPECT(instance_db_.FindInstances(filter)); +} + Result InstanceManager::HasInstanceGroups() const { return !CF_EXPECT(instance_db_.IsEmpty()); } diff --git a/base/cvd/cuttlefish/host/commands/cvd/instances/instance_manager.h b/base/cvd/cuttlefish/host/commands/cvd/instances/instance_manager.h index fadd57062b3..184bc7abb63 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/instances/instance_manager.h +++ b/base/cvd/cuttlefish/host/commands/cvd/instances/instance_manager.h @@ -77,6 +77,8 @@ class InstanceManager { Result> FindInstanceWithGroup( const InstanceDatabase::Filter& filter) const; + Result>> + FindInstances(const InstanceDatabase::Filter& filter) const; // Stops the device by asking it over the control socket. If launcher_timeout // has a value, it will wait for at most that time before returning an error.