Make fetch_cvd flags more concise.

There are now three flags to control the build:
 -default_build sets the baseline image, including vendor and host tools
 -system_build sets the system.img and product.img source
 -kernel_build sets the kernel/gki source

These three flags all accept builds in the format
 -branch_name/target_name
 -build_id/target_name
 -branch_name
 -build_id

Giving a branch name will use the latest build id from the branch.
If missing the target_name will default to aosp_cf_x86_phone-userdebug.

This also implements downloading alternate kernel builds.

Test: Run with a kernel from a different branch
Bug: 137304531
Change-Id: Ic3715d64b7ea51ca6ca1f83023f415a56faa07b1
diff --git a/host/commands/fetcher/build_api.cc b/host/commands/fetcher/build_api.cc
index 7823cab..a82a7c6 100644
--- a/host/commands/fetcher/build_api.cc
+++ b/host/commands/fetcher/build_api.cc
@@ -54,17 +54,15 @@
       + "&buildType=submitted&maxResults=1&successful=true&target=" + target;
   auto response = curl.DownloadToJson(url, Headers());
   if (response["builds"].size() != 1) {
-    LOG(ERROR) << "invalid number of builds\n";
+    LOG(WARNING) << "invalid number of builds\n";
     return "";
   }
   return response["builds"][0]["buildId"].asString();
 }
 
-std::vector<Artifact> BuildApi::Artifacts(const std::string& build_id,
-                                          const std::string& target,
-                                          const std::string& attempt_id) {
-  std::string url = BUILD_API + "/builds/" + build_id + "/" + target
-      + "/attempts/" + attempt_id + "/artifacts?maxResults=1000";
+std::vector<Artifact> BuildApi::Artifacts(const DeviceBuild& build) {
+  std::string url = BUILD_API + "/builds/" + build.id + "/" + build.target
+      + "/attempts/latest/artifacts?maxResults=1000";
   auto artifacts_json = curl.DownloadToJson(url, Headers());
   std::vector<Artifact> artifacts;
   for (const auto& artifact_json : artifacts_json["artifacts"]) {
@@ -73,12 +71,38 @@
   return artifacts;
 }
 
-bool BuildApi::ArtifactToFile(const std::string& build_id,
-                              const std::string& target,
-                              const std::string& attempt_id,
+bool BuildApi::ArtifactToFile(const DeviceBuild& build,
                               const std::string& artifact,
                               const std::string& path) {
-  std::string url = BUILD_API + "/builds/" + build_id + "/" + target
-      + "/attempts/" + attempt_id + "/artifacts/" + artifact + "?alt=media";
+  std::string url = BUILD_API + "/builds/" + build.id + "/" + build.target
+      + "/attempts/latest/artifacts/" + artifact + "?alt=media";
   return curl.DownloadToFile(url, path, Headers());
 }
+
+DeviceBuild ArgumentToBuild(BuildApi* build_api, const std::string& arg) {
+  size_t slash_pos = arg.find('/');
+  if (slash_pos != std::string::npos
+        && arg.find('/', slash_pos + 1) != std::string::npos) {
+    LOG(FATAL) << "Build argument cannot have more than one '/' slash. Was at "
+        << slash_pos << " and " << arg.find('/', slash_pos + 1);
+  }
+  std::string build_target = slash_pos == std::string::npos
+      ? "aosp_cf_x86_phone-userdebug" : arg.substr(slash_pos + 1);
+  std::string branch_or_id = slash_pos == std::string::npos
+      ? arg: arg.substr(0, slash_pos);
+  std::string branch_latest_build_id =
+      build_api->LatestBuildId(branch_or_id, build_target);
+  if (branch_latest_build_id != "") {
+    LOG(INFO) << "The latest good build on branch \"" << branch_or_id
+        << "\"with build target \"" << build_target
+        << "is \"" << branch_latest_build_id << "\"";
+    return DeviceBuild(branch_latest_build_id, build_target);
+  } else {
+    DeviceBuild proposed_build = DeviceBuild(branch_or_id, build_target);
+    if (build_api->Artifacts(proposed_build).size() == 0) {
+      LOG(FATAL) << '"' << branch_or_id << "\" with build target \""
+          << build_target << "\" is not a valid branch or build id.";
+    }
+    return proposed_build;
+  }
+}
diff --git a/host/commands/fetcher/build_api.h b/host/commands/fetcher/build_api.h
index be5596a..128c525 100644
--- a/host/commands/fetcher/build_api.h
+++ b/host/commands/fetcher/build_api.h
@@ -44,6 +44,16 @@
   unsigned int Crc32() const { return crc32; }
 };
 
+struct DeviceBuild {
+  DeviceBuild(const std::string& id, const std::string& target) {
+    this->id = id;
+    this->target = target;
+  }
+
+  std::string id;
+  std::string target;
+};
+
 class BuildApi {
   CurlWrapper curl;
   std::unique_ptr<CredentialSource> credential_source;
@@ -56,11 +66,10 @@
   std::string LatestBuildId(const std::string& branch,
                             const std::string& target);
 
-  std::vector<Artifact> Artifacts(const std::string& build_id,
-                                  const std::string& target,
-                                  const std::string& attempt_id);
+  std::vector<Artifact> Artifacts(const DeviceBuild&);
 
-  bool ArtifactToFile(const std::string& build_id, const std::string& target,
-                      const std::string& attempt_id,
-                      const std::string& artifact, const std::string& path);
+  bool ArtifactToFile(const DeviceBuild& build, const std::string& artifact,
+                      const std::string& path);
 };
+
+DeviceBuild ArgumentToBuild(BuildApi* api, const std::string& arg);
diff --git a/host/commands/fetcher/main.cc b/host/commands/fetcher/main.cc
index d5c1cac..4f3e35d 100644
--- a/host/commands/fetcher/main.cc
+++ b/host/commands/fetcher/main.cc
@@ -28,10 +28,11 @@
 #include "credential_source.h"
 #include "install_zip.h"
 
-// TODO(schuffelen): Mixed builds.
-DEFINE_string(build_id, "latest", "Build ID for all artifacts");
-DEFINE_string(branch, "aosp-master", "Branch when build_id=\"latest\"");
-DEFINE_string(target, "aosp_cf_x86_phone-userdebug", "Build target");
+DEFINE_string(default_build, "aosp-master/aosp_cf_x86_phone-userdebug",
+              "source for the cuttlefish build to use (vendor.img + host)");
+DEFINE_string(system_build, "", "source for system.img and product.img");
+DEFINE_string(kernel_build, "", "source for the kernel or gki target");
+
 DEFINE_string(credential_source, "", "Build API credential source");
 DEFINE_string(system_image_build_target, "", "Alternate target for the system "
                                              "image");
@@ -45,34 +46,33 @@
 
 const std::string& HOST_TOOLS = "cvd-host_package.tar.gz";
 
-std::string target_image_zip(std::string target, const std::string& build_id) {
+std::string target_image_zip(const DeviceBuild& build) {
+  std::string target = build.target;
   if (target.find("-userdebug") != std::string::npos) {
     target.replace(target.find("-userdebug"), sizeof("-userdebug"), "");
   }
   if (target.find("-eng") != std::string::npos) {
     target.replace(target.find("-eng"), sizeof("-eng"), "");
   }
-  return target + "-img-" + build_id + ".zip";
+  return target + "-img-" + build.id + ".zip";
 }
 
-bool download_images(BuildApi* build_api, const std::string& target,
-                     const std::string& build_id,
+bool download_images(BuildApi* build_api, const DeviceBuild& build,
                      const std::string& target_directory,
                      const std::vector<std::string>& images) {
-  std::string img_zip_name = target_image_zip(target, build_id);
-  auto artifacts = build_api->Artifacts(build_id, target, "latest");
+  std::string img_zip_name = target_image_zip(build);
+  auto artifacts = build_api->Artifacts(build);
   bool has_image_zip = false;
   for (const auto& artifact : artifacts) {
     has_image_zip |= artifact.Name() == img_zip_name;
   }
   if (!has_image_zip) {
-    LOG(ERROR) << "Target " << target << " at id " << build_id
+    LOG(ERROR) << "Target " << build.target << " at id " << build.id
         << " did not have " << img_zip_name;
     return false;
   }
   std::string local_path = target_directory + "/" + img_zip_name;
-  build_api->ArtifactToFile(build_id, target, "latest",
-                            img_zip_name, local_path);
+  build_api->ArtifactToFile(build, img_zip_name, local_path);
 
   auto could_extract = ExtractImages(local_path, target_directory, images);
   if (!could_extract) {
@@ -84,27 +84,25 @@
   }
   return true;
 }
-bool download_images(BuildApi* build_api, const std::string& target,
-                     const std::string& build_id,
+bool download_images(BuildApi* build_api, const DeviceBuild& build,
                      const std::string& target_directory) {
-  return download_images(build_api, target, build_id, target_directory, {});
+  return download_images(build_api, build, target_directory, {});
 }
 
-bool download_host_package(BuildApi* build_api, const std::string& target,
-                           const std::string& build_id,
+bool download_host_package(BuildApi* build_api, const DeviceBuild& build,
                            const std::string& target_directory) {
-  auto artifacts = build_api->Artifacts(build_id, target, "latest");
+  auto artifacts = build_api->Artifacts(build);
   bool has_host_package = false;
   for (const auto& artifact : artifacts) {
     has_host_package |= artifact.Name() == HOST_TOOLS;
   }
   if (!has_host_package) {
-    LOG(ERROR) << "Target " << target << " at id " << build_id
+    LOG(ERROR) << "Target " << build.target << " at id " << build.id
         << " did not have " << HOST_TOOLS;
     return false;
   }
   std::string local_path = target_directory + "/" + HOST_TOOLS;
-  build_api->ArtifactToFile(build_id, target, "latest", HOST_TOOLS, local_path);
+  build_api->ArtifactToFile(build, HOST_TOOLS, local_path);
   if (cvd::execute({"/bin/tar", "xvf", local_path, "-C", target_directory}) != 0) {
     LOG(FATAL) << "Could not extract " << local_path;
     return false;
@@ -124,10 +122,20 @@
   return true;
 }
 
+std::string USAGE_MESSAGE =
+    "<flags>\n"
+    "\n"
+    "\"*_build\" flags accept values in the following format:\n"
+    "\"branch/build_target\" - latest build of \"branch\" for \"build_target\"\n"
+    "\"build_id/build_target\" - build \"build_id\" for \"build_target\"\n"
+    "\"branch\" - latest build of \"branch\" for \"aosp_cf_x86_phone-userdebug\"\n"
+    "\"build_id\" - build \"build_id\" for \"aosp_cf_x86_phone-userdebug\"\n";
+
 } // namespace
 
 int main(int argc, char** argv) {
   ::android::base::InitLogging(argv, android::base::StderrLogger);
+  gflags::SetUsageMessage(USAGE_MESSAGE);
   gflags::ParseCommandLineFlags(&argc, &argv, true);
 
   std::string target_dir = cvd::AbsolutePath(FLAGS_directory);
@@ -144,31 +152,35 @@
       credential_source = FixedCredentialSource::make(FLAGS_credential_source);
     }
     BuildApi build_api(std::move(credential_source));
-    std::string build_id = FLAGS_build_id;
-    if (build_id == "latest") {
-      build_id = build_api.LatestBuildId(FLAGS_branch, FLAGS_target);
+
+    DeviceBuild default_build = ArgumentToBuild(&build_api, FLAGS_default_build);
+
+    if (!download_host_package(&build_api, default_build, target_dir)) {
+      LOG(FATAL) << "Could not download host package with target "
+          << default_build.target << " and build id " << default_build.id;
+    }
+    if (!download_images(&build_api, default_build, target_dir)) {
+      LOG(FATAL) << "Could not download images with target "
+          << default_build.target << " and build id " << default_build.id;
     }
 
-    if (!download_host_package(&build_api, FLAGS_target, build_id,
-                               target_dir)) {
-      LOG(FATAL) << "Could not download host package with target "
-          << FLAGS_target << " and build id " << build_id;
-    }
-    if (!download_images(&build_api, FLAGS_target, build_id, target_dir)) {
-      LOG(FATAL) << "Could not download images with target "
-          << FLAGS_target << " and build id " << build_id;
-    }
     desparse(target_dir + "/userdata.img");
-    if (FLAGS_system_image_build_id != "") {
-      std::string system_target = FLAGS_system_image_build_target == ""
-          ? FLAGS_target
-          : FLAGS_system_image_build_target;
-      if (!download_images(&build_api, system_target, FLAGS_system_image_build_id,
-                           target_dir, {"system.img"})) {
+
+    if (FLAGS_system_build != "") {
+      DeviceBuild system_build = ArgumentToBuild(&build_api, FLAGS_system_build);
+
+      if (!download_images(&build_api, system_build, target_dir,
+                           {"system.img"})) {
         LOG(FATAL) << "Could not download system image at target "
-            << FLAGS_target << " and build id " << FLAGS_system_image_build_id;
+            << system_build.target << " and build id " << system_build.id;
       }
     }
+
+    if (FLAGS_kernel_build != "") {
+      DeviceBuild kernel_build = ArgumentToBuild(&build_api, FLAGS_kernel_build);
+
+      build_api.ArtifactToFile(kernel_build, "bzImage", target_dir + "/kernel");
+    }
   }
   curl_global_cleanup();