Create parent directories for resources

This makes singlejar's handling of resources consistent with JavaBuilder,
and allows the JavaBuilder resource handling to be replaced by a singlejar
action that adds resources to library jars separately from compilation
(see unknown commit).

PiperOrigin-RevId: 152082884
diff --git a/src/tools/singlejar/output_jar.cc b/src/tools/singlejar/output_jar.cc
index 26f445f..1b19de8 100644
--- a/src/tools/singlejar/output_jar.cc
+++ b/src/tools/singlejar/output_jar.cc
@@ -220,6 +220,17 @@
         }
       }
     }
+
+    // Add parent directory entries.
+    size_t pos = classpath_resource->filename().find('/');
+    while (pos != std::string::npos) {
+      std::string dir(classpath_resource->filename(), 0, pos + 1);
+      if (NewEntry(dir)) {
+        WriteDirEntry(dir, nullptr, 0);
+      }
+      pos = classpath_resource->filename().find('/', pos + 1);
+    }
+
     WriteEntry(classpath_resource->OutputEntry(do_compress));
   }
 
@@ -580,8 +591,7 @@
 }
 
 void OutputJar::WriteMetaInf() {
-  const char path[] = "META-INF/";
-  size_t n_path = strlen(path);
+  std::string path("META-INF/");
 
   // META_INF/ is always the first entry, and as such it should have an extra
   // field with the tag 0xCAFE and zero bytes of data. This is not the part of
@@ -590,7 +600,14 @@
   const uint8_t extra_fields[] = {0xFE, 0xCA, 0, 0};
   const uint16_t n_extra_fields =
       sizeof(extra_fields) / sizeof(extra_fields[0]);
-  size_t lh_size = sizeof(LH) + n_path + n_extra_fields;
+  WriteDirEntry(path, extra_fields, n_extra_fields);
+}
+
+// Writes a directory entry with the given name and extra fields.
+void OutputJar::WriteDirEntry(const std::string &name,
+                              const uint8_t *extra_fields,
+                              const uint16_t n_extra_fields) {
+  size_t lh_size = sizeof(LH) + name.size() + n_extra_fields;
   LH *lh = reinterpret_cast<LH *>(malloc(lh_size));
   lh->signature();
   lh->version(20);  // 2.0
@@ -599,9 +616,9 @@
   lh->crc32(0);
   lh->compressed_file_size32(0);
   lh->uncompressed_file_size32(0);
-  lh->file_name(path, n_path);
+  lh->file_name(name.c_str(), name.size());
   lh->extra_fields(extra_fields, n_extra_fields);
-  known_members_.emplace(path, EntryInfo{&null_combiner_});
+  known_members_.emplace(name, EntryInfo{&null_combiner_});
   WriteEntry(lh);
 }
 
diff --git a/src/tools/singlejar/output_jar.h b/src/tools/singlejar/output_jar.h
index f5ee200..53f6978 100644
--- a/src/tools/singlejar/output_jar.h
+++ b/src/tools/singlejar/output_jar.h
@@ -73,6 +73,9 @@
   void WriteEntry(void *local_header_and_payload);
   // Write META_INF/ entry (the first entry on output).
   void WriteMetaInf();
+  // Write a directory entry.
+  void WriteDirEntry(const std::string &name, const uint8_t *extra_fields,
+                     const uint16_t n_extra_fields);
   // Create output Central Directory Header for the given input entry and
   // append it to CEN (Central Directory) buffer.
   void AppendToDirectoryBuffer(const CDH *cdh, off_t local_header_offset,
diff --git a/src/tools/singlejar/output_jar_simple_test.cc b/src/tools/singlejar/output_jar_simple_test.cc
index ef71fba..036b883 100644
--- a/src/tools/singlejar/output_jar_simple_test.cc
+++ b/src/tools/singlejar/output_jar_simple_test.cc
@@ -320,6 +320,37 @@
   EXPECT_EQ("res2.line1\nres2.line2\n", res2);
 }
 
+TEST_F(OutputJarSimpleTest, ResourcesParentDirectories) {
+  string res1_path = CreateTextFile("res1", "res1.line1\nres1.line2\n");
+  string res2_path = CreateTextFile("res2", "res2.line1\nres2.line2\n");
+
+  string out_path = OutputFilePath("out.jar");
+  CreateOutput(out_path, {"--exclude_build_data", "--resources",
+                          res1_path + ":the/resources/res1",
+                          res2_path + ":the/resources2/res2"});
+
+  string res1 = GetEntryContents(out_path, "the/resources/res1");
+  EXPECT_EQ("res1.line1\nres1.line2\n", res1);
+
+  string res2 = GetEntryContents(out_path, "the/resources2/res2");
+  EXPECT_EQ("res2.line1\nres2.line2\n", res2);
+
+  // The output should contain entries for parent directories
+  std::vector<string> expected_entries(
+      {"META-INF/", "META-INF/MANIFEST.MF", "the/", "the/resources/",
+       "the/resources/res1", "the/resources2/", "the/resources2/res2"});
+  std::vector<string> jar_entries;
+  InputJar input_jar;
+  ASSERT_TRUE(input_jar.Open(out_path));
+  const LH *lh;
+  const CDH *cdh;
+  while ((cdh = input_jar.NextEntry(&lh))) {
+    jar_entries.push_back(cdh->file_name_string());
+  }
+  input_jar.Close();
+  EXPECT_EQ(expected_entries, jar_entries);
+}
+
 // --classpath_resources
 TEST_F(OutputJarSimpleTest, ClasspathResources) {
   string res1_path = OutputFilePath("cp_res");