Adds ability to determine product version without starting a server

When `bazel --version` is run with no other args the client short-circuits
processing and returns the same info that `version --gnu_format` would return,
without the side-effect of starting the server daemon. This makes figuring out
the version of an arbitrary binary a much lighter-weight process than it
previously was.

The command itself is implemented by inspecting the `build-label.txt` file
which is derived from the server jar's build label and packed into the dist
archive.

Right now we rely on `--version` with no other args. In the future it may be
sensible to fold this into the option processor - I opted to skip this step
for now in interest of getting something functional out without needing to get
too deep into all of the flag parsing semantics. When/if we wind up folding
--version activation into the option processor it should be backwards
compatible with the functionality added in this change.

In the archive_processor code, I factored out the whole "run the zip processor"
step to simplify things. The only downside to this is the error messages might
be slightly different from before, but they shouldn't be terribly far off...

PiperOrigin-RevId: 249701614
diff --git a/src/main/cpp/archive_utils.cc b/src/main/cpp/archive_utils.cc
index 2e0cd40..64ee252 100644
--- a/src/main/cpp/archive_utils.cc
+++ b/src/main/cpp/archive_utils.cc
@@ -165,6 +165,50 @@
   blaze::embedded_binaries::Dumper *dumper_;
 };
 
+// A ZipExtractorProcessor that reads the contents of the build-label.txt file
+// from the archive.
+class GetBuildLabelFileProcessor
+    : public devtools_ijar::ZipExtractorProcessor {
+ public:
+  explicit GetBuildLabelFileProcessor(string *build_label)
+    : build_label_(build_label) {}
+
+  bool Accept(const char *filename, const devtools_ijar::u4 attr) override {
+    return strcmp(filename, "build-label.txt") == 0;
+  }
+
+  void Process(const char *filename,
+               const devtools_ijar::u4 attr,
+               const devtools_ijar::u1 *data,
+               const size_t size) override {
+    string contents(reinterpret_cast<const char *>(data), size);
+    blaze_util::StripWhitespace(&contents);
+    *build_label_ = contents;
+  }
+
+ private:
+  string *build_label_;
+};
+
+static void RunZipProcessorOrDie(
+    const string &archive_path,
+    const string &product_name,
+    devtools_ijar::ZipExtractorProcessor *processor) {
+  std::unique_ptr<devtools_ijar::ZipExtractor> extractor(
+      devtools_ijar::ZipExtractor::Create(archive_path.c_str(), processor));
+
+  if (extractor == NULL) {
+    BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
+        << "Failed to open " << product_name
+        << " as a zip file: " << blaze_util::GetLastErrorString();
+  }
+
+  if (extractor->ProcessAll() < 0) {
+    BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
+        << "Failed to extract install_base_key: " << extractor->GetError();
+  }
+}
+
 void DetermineArchiveContents(
     const string &archive_path,
     const string &product_name,
@@ -174,17 +218,8 @@
   GetInstallKeyFileProcessor install_key_processor(install_md5);
   CompoundZipProcessor processor({&note_all_files_processor,
                                   &install_key_processor});
-  std::unique_ptr<devtools_ijar::ZipExtractor> extractor(
-      devtools_ijar::ZipExtractor::Create(archive_path.c_str(), &processor));
-  if (extractor == NULL) {
-    BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
-        << "Failed to open " << product_name
-        << " as a zip file: " << blaze_util::GetLastErrorString();
-  }
-  if (extractor->ProcessAll() < 0) {
-    BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
-        << "Failed to extract install_base_key: " << extractor->GetError();
-  }
+
+  RunZipProcessorOrDie(archive_path, product_name, &processor);
 
   if (install_md5->empty()) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
@@ -219,18 +254,7 @@
   BAZEL_LOG(USER) << "Extracting " << product_name
                   << " installation...";
 
-  std::unique_ptr<devtools_ijar::ZipExtractor> extractor(
-      devtools_ijar::ZipExtractor::Create(archive_path.c_str(), &processor));
-  if (extractor == NULL) {
-    BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
-        << "Failed to open " << product_name
-        << " as a zip file: " << blaze_util::GetLastErrorString();
-  }
-  if (extractor->ProcessAll() < 0) {
-    BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
-        << "Failed to extract " << product_name
-        << " as a zip file: " << extractor->GetError();
-  }
+  RunZipProcessorOrDie(archive_path, product_name, &processor);
 
   if (!dumper->Finish(&error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
@@ -249,4 +273,18 @@
   }
 }
 
+void ExtractBuildLabel(const string &archive_path,
+                       const string &product_name,
+                       string *build_label) {
+  GetBuildLabelFileProcessor processor(build_label);
+  RunZipProcessorOrDie(archive_path, product_name, &processor);
+
+  // We expect the build label file to exist and be non-empty, if neither is the
+  // case then something unexpected is going on.
+  if (build_label->empty()) {
+    BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
+        << "Couldn't determine build label from archive";
+  }
+}
+
 }  // namespace blaze
diff --git a/src/main/cpp/archive_utils.h b/src/main/cpp/archive_utils.h
index 694900b..23aef1f 100644
--- a/src/main/cpp/archive_utils.h
+++ b/src/main/cpp/archive_utils.h
@@ -35,6 +35,11 @@
                          const std::string &expected_install_md5,
                          const std::string &output_dir);
 
+// Retrieves the build label (version string) from `archive_path` into
+// `build_label`.
+void ExtractBuildLabel(const std::string &archive_path,
+                       const std::string &product_name,
+                       std::string *build_label);
 }  // namespace blaze
 
 #endif  // BAZEL_SRC_MAIN_CPP_ARCHIVE_UTILS_H_
diff --git a/src/main/cpp/blaze.cc b/src/main/cpp/blaze.cc
index 3114fab..78b7002 100644
--- a/src/main/cpp/blaze.cc
+++ b/src/main/cpp/blaze.cc
@@ -1316,6 +1316,13 @@
   return custom_exit_code;
 }
 
+static void PrintVersionInfo(const string &self_path,
+                             const string &product_name) {
+  string build_label;
+  ExtractBuildLabel(self_path, product_name, &build_label);
+  printf("%s %s\n", product_name.c_str(), build_label.c_str());
+}
+
 int Main(int argc, const char *argv[], WorkspaceLayout *workspace_layout,
          OptionProcessor *option_processor, uint64_t start_time) {
   // Logging must be set first to assure no log statements are missed.
@@ -1323,6 +1330,13 @@
       new blaze_util::BazelLogHandler());
   blaze_util::SetLogHandler(std::move(default_handler));
 
+  const string self_path = GetSelfPath();
+
+  if (argc == 2 && strcmp(argv[1], "--version") == 0) {
+    PrintVersionInfo(self_path, option_processor->GetLowercaseProductName());
+    return blaze_exit_code::SUCCESS;
+  }
+
   globals = new GlobalVariables(option_processor);
   blaze::SetupStdStreams();
   if (argc == 1 && blaze::WarnIfStartedFromDesktop()) {
@@ -1367,7 +1381,6 @@
 
   blaze::CreateSecureOutputRoot(globals->options->output_user_root);
 
-  const string self_path = GetSelfPath();
   ComputeBaseDirectories(workspace_layout, self_path);
 
   blaze_server = static_cast<BlazeServer *>(
diff --git a/src/main/cpp/option_processor.cc b/src/main/cpp/option_processor.cc
index bef8f5d..a7428e7 100644
--- a/src/main/cpp/option_processor.cc
+++ b/src/main/cpp/option_processor.cc
@@ -67,10 +67,13 @@
       parse_options_called_(false),
       system_bazelrc_path_(system_bazelrc_path) {}
 
+std::string OptionProcessor::GetLowercaseProductName() const {
+  return startup_options_->GetLowercaseProductName();
+}
+
 std::unique_ptr<CommandLine> OptionProcessor::SplitCommandLine(
     vector<string> args, string* error) const {
-  const string lowercase_product_name =
-      startup_options_->GetLowercaseProductName();
+  const string lowercase_product_name = GetLowercaseProductName();
 
   if (args.empty()) {
     blaze_util::StringPrintf(error,
diff --git a/src/main/cpp/option_processor.h b/src/main/cpp/option_processor.h
index bfe2070..4692b0d 100644
--- a/src/main/cpp/option_processor.h
+++ b/src/main/cpp/option_processor.h
@@ -63,6 +63,9 @@
 
   virtual ~OptionProcessor() {}
 
+  // Returns the lower-case product name associated with this options processor.
+  std::string GetLowercaseProductName() const;
+
   // Splits the arguments of a command line invocation.
   //
   // For instance: