Allow files to appear in the zip appended to the bazel C++ launcher which
are not to be extracted during "installation".  This is intended to support
a configuration where the bazel executable doubles as a jar on the classpath.

RELNOTES: None.
PiperOrigin-RevId: 278560382
diff --git a/src/main/cpp/archive_utils.cc b/src/main/cpp/archive_utils.cc
index 2beb881..78395eb 100644
--- a/src/main/cpp/archive_utils.cc
+++ b/src/main/cpp/archive_utils.cc
@@ -11,14 +11,16 @@
 // 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 "src/main/cpp/archive_utils.h"
-
+#include <functional>
+#include <memory>
+#include <string>
 #include <vector>
 
+#include "src/main/cpp/archive_utils.h"
 #include "src/main/cpp/blaze_util_platform.h"
 #include "src/main/cpp/util/errors.h"
-#include "src/main/cpp/util/file.h"
 #include "src/main/cpp/util/exit_code.h"
+#include "src/main/cpp/util/file.h"
 #include "src/main/cpp/util/logging.h"
 #include "src/main/cpp/util/path.h"
 #include "src/main/cpp/util/strings.h"
@@ -26,233 +28,108 @@
 
 namespace blaze {
 
-using std::vector;
 using std::string;
+using std::vector;
 
-// A devtools_ijar::ZipExtractorProcessor that has a pure version of Accept.
-class PureZipExtractorProcessor : public devtools_ijar::ZipExtractorProcessor {
- public:
-  virtual ~PureZipExtractorProcessor() {}
+struct PartialZipExtractor : public devtools_ijar::ZipExtractorProcessor {
+  using CallbackType =
+      std::function<void(const char *name, const char *data, size_t size)>;
 
-  // Like devtools_ijar::ZipExtractorProcessor::Accept, but is guaranteed to not
-  // have side-effects.
-  virtual bool AcceptPure(const char *filename,
-                          const devtools_ijar::u4 attr) const = 0;
-};
-
-// A devtools_ijar::ZipExtractorProcessor that processes the ZIP entries using
-// the given PureZipExtractorProcessors.
-class CompoundZipProcessor : public devtools_ijar::ZipExtractorProcessor {
- public:
-  explicit CompoundZipProcessor(
-      const vector<PureZipExtractorProcessor*>& processors)
-      : processors_(processors) {}
-
-  bool Accept(const char *filename, const devtools_ijar::u4 attr) override {
-    bool should_accept = false;
-    for (auto *processor : processors_) {
-      if (processor->Accept(filename, attr)) {
-        // ZipExtractorProcessor::Accept is allowed to be side-effectful, so
-        // we don't want to break out on the first true here.
-        should_accept = true;
-      }
-    }
-    return should_accept;
-  }
-
-  void Process(const char *filename, const devtools_ijar::u4 attr,
-               const devtools_ijar::u1 *data, const size_t size) override {
-    for (auto *processor : processors_) {
-      if (processor->AcceptPure(filename, attr)) {
-        processor->Process(filename, attr, data, size);
-      }
-    }
-  }
-
- private:
-  const vector<PureZipExtractorProcessor*> processors_;
-};
-
-// A PureZipExtractorProcessor to extract the InstallKeyFile
-class GetInstallKeyFileProcessor : public PureZipExtractorProcessor {
- public:
-  explicit GetInstallKeyFileProcessor(string *install_base_key)
-    : install_base_key_(install_base_key) {}
-
-  bool AcceptPure(const char *filename,
-                  const devtools_ijar::u4 attr) const override {
-    return strcmp(filename, "install_base_key") == 0;
-  }
-
-  bool Accept(const char *filename, const devtools_ijar::u4 attr) override {
-    return AcceptPure(filename, attr);
-  }
-
-  void Process(const char *filename,
-               const devtools_ijar::u4 attr,
-               const devtools_ijar::u1 *data,
-               const size_t size) override {
-    string str(reinterpret_cast<const char *>(data), size);
-    if (str.size() != 32) {
+  // Scan the zip file "archive_path" until a file named "stop_entry" is seen,
+  // then stop.
+  // If entry_names is not null, it receives a list of all file members
+  // preceding "stop_entry".
+  // If a callback is given, it is run with the name and contents of
+  // each such member.
+  // Returns the contents of the "stop_entry" member.
+  string UnzipUntil(const string &archive_path, const string &stop_entry,
+                    vector<string> *entry_names = nullptr,
+                    CallbackType &&callback = {}) {
+    std::unique_ptr<devtools_ijar::ZipExtractor> extractor(
+        devtools_ijar::ZipExtractor::Create(archive_path.c_str(), this));
+    if (!extractor) {
       BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
-          << "Failed to extract install_base_key: file size mismatch "
-             "(should be 32, is "
-          << str.size() << ")";
+          << "Failed to open '" << archive_path
+          << "' as a zip file: " << blaze_util::GetLastErrorString();
     }
-    *install_base_key_ = str;
+    stop_name_ = stop_entry;
+    seen_names_.clear();
+    callback_ = callback;
+    done_ = false;
+    while (!done_ && extractor->ProcessNext()) {
+      // Scan zip until EOF, an error, or Accept() has seen stop_entry.
+    }
+    if (const char *err = extractor->GetError()) {
+      BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
+          << "Error reading zip file '" << archive_path << "': " << err;
+    }
+    if (!done_) {
+      BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
+          << "Failed to find member '" << stop_entry << "' in zip file '"
+          << archive_path << "'";
+    }
+    if (entry_names) *entry_names = std::move(seen_names_);
+    return stop_value_;
   }
 
- private:
-  string *install_base_key_;
+  bool Accept(const char *filename, devtools_ijar::u4 attr) override {
+    string fn = filename;
+    if (stop_name_ == fn) {
+      done_ = true;
+      return true;  // save content in stop_value_
+    }
+    if (devtools_ijar::zipattr_is_dir(attr)) return false;
+    // Sometimes that fails to detect directories.  Check the name too.
+    if (fn.empty() || fn.back() == '/') return false;
+    seen_names_.push_back(std::move(fn));
+    return !!callback_;  // true if a callback was supplied
+  }
+
+  void Process(const char *filename, devtools_ijar::u4 attr,
+               const devtools_ijar::u1 *data, size_t size) override {
+    if (done_) {
+      stop_value_.assign(reinterpret_cast<const char *>(data), size);
+    } else {
+      callback_(filename, reinterpret_cast<const char *>(data), size);
+    }
+  }
+
+  string stop_name_;
+  string stop_value_;
+  vector<string> seen_names_;
+  CallbackType callback_;
+  bool done_ = false;
 };
 
-// A PureZipExtractorProcessor that adds the names of all the files ZIP up in
-// the Blaze binary to the given vector.
-class NoteAllFilesZipProcessor : public PureZipExtractorProcessor {
- public:
-  explicit NoteAllFilesZipProcessor(std::vector<std::string>* files)
-      : files_(files) {}
-
-  bool AcceptPure(const char *filename,
-                  const devtools_ijar::u4 attr) const override {
-    return false;
-  }
-
-  bool Accept(const char *filename,
-              const devtools_ijar::u4 attr) override {
-    files_->push_back(filename);
-    return false;
-  }
-
-  void Process(const char *filename,
-               const devtools_ijar::u4 attr,
-               const devtools_ijar::u1 *data,
-               const size_t size) override {
-    BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR)
-        << "NoteAllFilesZipProcessor::Process shouldn't be called";
-  }
-
- private:
-  std::vector<std::string>* files_;
-};
-
-// A PureZipExtractorProcessor to extract the files from the blaze zip.
-class ExtractBlazeZipProcessor : public PureZipExtractorProcessor {
- public:
-  explicit ExtractBlazeZipProcessor(const string &output_dir,
-                                    blaze::embedded_binaries::Dumper *dumper)
-      : output_dir_(output_dir), dumper_(dumper) {}
-
-  bool AcceptPure(const char *filename,
-                  const devtools_ijar::u4 attr) const override {
-    return !devtools_ijar::zipattr_is_dir(attr);
-  }
-
-  bool Accept(const char *filename, const devtools_ijar::u4 attr) override {
-    return AcceptPure(filename, attr);
-  }
-
-  void Process(const char *filename,
-               const devtools_ijar::u4 attr,
-               const devtools_ijar::u1 *data,
-               const size_t size) override {
-    dumper_->Dump(data, size, blaze_util::JoinPath(output_dir_, filename));
-  }
-
- private:
-  const string output_dir_;
-  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);
-    *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, vector<string> *files,
+                              string *install_md5) {
+  PartialZipExtractor pze;
+  *install_md5 = pze.UnzipUntil(archive_path, "install_base_key", files);
 }
 
-void DetermineArchiveContents(
-    const string &archive_path,
-    const string &product_name,
-    std::vector<std::string>* files,
-    string *install_md5) {
-  NoteAllFilesZipProcessor note_all_files_processor(files);
-  GetInstallKeyFileProcessor install_key_processor(install_md5);
-  CompoundZipProcessor processor({&note_all_files_processor,
-                                  &install_key_processor});
-
-  RunZipProcessorOrDie(archive_path, product_name, &processor);
-
-  if (install_md5->empty()) {
-    BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
-        << "Failed to find install_base_key's in zip file";
-  }
-}
-
-void ExtractArchiveOrDie(const string &archive_path,
-                         const string &product_name,
+void ExtractArchiveOrDie(const string &archive_path, const string &product_name,
                          const string &expected_install_md5,
                          const string &output_dir) {
-  std::string install_md5;
-  GetInstallKeyFileProcessor install_key_processor(&install_md5);
-
-  std::string error;
+  string error;
   std::unique_ptr<blaze::embedded_binaries::Dumper> dumper(
       blaze::embedded_binaries::Create(&error));
   if (dumper == nullptr) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR) << error;
   }
-  ExtractBlazeZipProcessor extract_blaze_processor(output_dir,
-                                                   dumper.get());
-
-  CompoundZipProcessor processor({&extract_blaze_processor,
-                                  &install_key_processor});
   if (!blaze_util::MakeDirectories(output_dir, 0777)) {
     BAZEL_DIE(blaze_exit_code::INTERNAL_ERROR)
         << "couldn't create '" << output_dir
         << "': " << blaze_util::GetLastErrorString();
   }
 
-  BAZEL_LOG(USER) << "Extracting " << product_name
-                  << " installation...";
+  BAZEL_LOG(USER) << "Extracting " << product_name << " installation...";
 
-  RunZipProcessorOrDie(archive_path, product_name, &processor);
+  PartialZipExtractor pze;
+  string install_md5 = pze.UnzipUntil(
+      archive_path, "install_base_key", nullptr,
+      [&](const char *name, const char *data, size_t size) {
+        dumper->Dump(data, size, blaze_util::JoinPath(output_dir, name));
+      });
 
   if (!dumper->Finish(&error)) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
@@ -271,22 +148,12 @@
   }
 }
 
-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";
-  }
+void ExtractBuildLabel(const string &archive_path, string *build_label) {
+  PartialZipExtractor pze;
+  *build_label = pze.UnzipUntil(archive_path, "build-label.txt");
 }
 
-std::string GetServerJarPath(
-    const std::vector<std::string> &archive_contents) {
+string GetServerJarPath(const vector<string> &archive_contents) {
   if (archive_contents.empty()) {
     BAZEL_DIE(blaze_exit_code::LOCAL_ENVIRONMENTAL_ERROR)
         << "Couldn't find server jar in archive";
diff --git a/src/main/cpp/archive_utils.h b/src/main/cpp/archive_utils.h
index f714c58..9410781 100644
--- a/src/main/cpp/archive_utils.h
+++ b/src/main/cpp/archive_utils.h
@@ -23,7 +23,6 @@
 // Determines the contents of the archive, storing the names of the contained
 // files into `files` and the install md5 key into `install_md5`.
 void DetermineArchiveContents(const std::string &archive_path,
-                              const std::string &product_name,
                               std::vector<std::string> *files,
                               std::string *install_md5);
 
@@ -38,7 +37,6 @@
 // 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);
 
 // Returns the server jar path from the archive contents.
diff --git a/src/main/cpp/blaze.cc b/src/main/cpp/blaze.cc
index 372aed6..6865e6e 100644
--- a/src/main/cpp/blaze.cc
+++ b/src/main/cpp/blaze.cc
@@ -72,7 +72,6 @@
 #include "src/main/cpp/util/strings.h"
 #include "src/main/cpp/workspace_layout.h"
 #include "src/main/protobuf/command_server.grpc.pb.h"
-#include "third_party/ijar/zip.h"
 
 using blaze_util::GetLastErrorString;
 
@@ -1463,7 +1462,7 @@
 
 void PrintVersionInfo(const string &self_path, const string &product_name) {
   string build_label;
-  ExtractBuildLabel(self_path, product_name, &build_label);
+  ExtractBuildLabel(self_path, &build_label);
   printf("%s %s\n", product_name.c_str(), build_label.c_str());
 }
 
@@ -1601,7 +1600,6 @@
   string install_md5;
   DetermineArchiveContents(
       self_path,
-      startup_options->product_name,
       &archive_contents,
       &install_md5);
 
diff --git a/src/package-bazel.sh b/src/package-bazel.sh
index f1c3acc..e20ef74 100755
--- a/src/package-bazel.sh
+++ b/src/package-bazel.sh
@@ -25,7 +25,7 @@
 DEPLOY_JAR=$3
 INSTALL_BASE_KEY=$4
 PLATFORMS_ARCHIVE=$5
-shift 4
+shift 5
 
 if [[ "$OUT" == *jdk_allmodules.zip ]]; then
   DEV_BUILD=1
@@ -66,28 +66,40 @@
   DEPLOY_JAR="$DEPLOY_UNCOMP"
 fi
 
-# The server jar needs to be the first binary we extract. This is how the Bazel
-# client knows what .jar to pass to the JVM.
-cp ${DEPLOY_JAR} ${PACKAGE_DIR}/A-server.jar
-cp ${INSTALL_BASE_KEY} ${PACKAGE_DIR}/install_base_key
-# The timestamp of embedded tools should already be zeroed out in the input zip
-touch -t 198001010000.00 ${PACKAGE_DIR}/*
-
 if [ -n "${EMBEDDED_TOOLS}" ]; then
   mkdir ${PACKAGE_DIR}/embedded_tools
   (cd ${PACKAGE_DIR}/embedded_tools && unzip -q "${WORKDIR}/${EMBEDDED_TOOLS}")
 fi
 
 # Unzip platforms.zip into platforms/, move files up from external/platforms
-# subdirectory, and cleanup after itself.
-( \
-  cd ${PACKAGE_DIR} && \
-    unzip -q -d platforms platforms.zip && \
-    rm platforms.zip && \
-    cd platforms && \
-    mv external/platforms/* . && \
-    rmdir -p external/platforms \
+# subdirectory, and create WORKSPACE if it doesn't exist.
+(
+  cd $PACKAGE_DIR
+  unzip -q -d platforms $WORKDIR/$PLATFORMS_ARCHIVE
+  cd platforms
+  mv external/platforms/* .
+  rmdir -p external/platforms
+  >> WORKSPACE
 )
-touch -t 198001010000.00 ${PACKAGE_DIR}/platforms/WORKSPACE
 
-(cd ${PACKAGE_DIR} && find . -type f | sort | zip -q9DX@ "${WORKDIR}/${OUT}")
+# Make a list of the files in the order we want them inside the final zip.
+(
+  cd $PACKAGE_DIR
+  # The server jar needs to be the first binary we extract.
+  # This is how the Bazel client knows which .jar to pass to the JVM.
+  echo A-server.jar
+  find . -type f | sort
+  # And install_base_key must be last.
+  echo install_base_key
+) > files.list
+
+# Move these after the 'find' above.
+cp $DEPLOY_JAR $PACKAGE_DIR/A-server.jar
+cp $INSTALL_BASE_KEY $PACKAGE_DIR/install_base_key
+
+# Zero timestamps.
+(cd $PACKAGE_DIR; xargs touch -t 198001010000.00) < files.list
+
+# Create output zip with compression.
+(cd $PACKAGE_DIR; zip -q9DX@ $WORKDIR/$OUT) < files.list
+