Packaging CDS archive file (.jsa) in deploy JAR.
This is the first step of a series of planned changes to support producing a CDS archive at build-time automatically and embedding it into the output deploy JAR as part of the 'java_binary' rule action, when it is opt-in.
This CL augments the singlejar tool to prepend the CDS archive data before the JAR content but after the launcher binary, if '--cds_archive <archive_path>' is specified. The start of the CDS archive within the deploy JAR is aligned at page boundary, which is required by file-backed mapping. The start offset is recorded as an attribute, 'Jsa-Offset' in the deploy JAR META-INF/MANIFEST.MF. The recorded 'Jsa-Offset' is used by the JVM to locate and map the archived data at runtime.
RELNOTES: Add --cds_archive option for embedding CDS archive into deploy JAR.
PiperOrigin-RevId: 295991052
diff --git a/src/tools/singlejar/options.cc b/src/tools/singlejar/options.cc
index 95189c9..f6673c7 100644
--- a/src/tools/singlejar/options.cc
+++ b/src/tools/singlejar/options.cc
@@ -36,6 +36,7 @@
if (tokens->MatchAndSet("--output", &output_jar) ||
tokens->MatchAndSet("--main_class", &main_class) ||
tokens->MatchAndSet("--java_launcher", &java_launcher) ||
+ tokens->MatchAndSet("--cds_archive", &cds_archive) ||
tokens->MatchAndSet("--deploy_manifest_lines", &manifest_lines) ||
tokens->MatchAndSet("--sources", &input_jars) ||
tokens->MatchAndSet("--resources", &resources) ||
diff --git a/src/tools/singlejar/options.h b/src/tools/singlejar/options.h
index 1125dd6..0c3c960 100644
--- a/src/tools/singlejar/options.h
+++ b/src/tools/singlejar/options.h
@@ -43,6 +43,7 @@
std::string output_jar;
std::string main_class;
std::string java_launcher;
+ std::string cds_archive;
std::vector<std::string> manifest_lines;
std::vector<std::pair<std::string, std::string> > input_jars;
std::vector<std::string> resources;
diff --git a/src/tools/singlejar/options_test.cc b/src/tools/singlejar/options_test.cc
index 90a9b19..e9e591b 100644
--- a/src/tools/singlejar/options_test.cc
+++ b/src/tools/singlejar/options_test.cc
@@ -65,7 +65,8 @@
"--build_info_file", "build_file1",
"--extra_build_info", "extra_build_line1",
"--build_info_file", "build_file2",
- "--extra_build_info", "extra_build_line2"};
+ "--extra_build_info", "extra_build_line2",
+ "--cds_archive", "classes.jsa"};
Options options;
options.ParseCommandLine(arraysize(args), args);
@@ -78,6 +79,7 @@
ASSERT_EQ(2UL, options.build_info_lines.size());
EXPECT_EQ("extra_build_line1", options.build_info_lines[0]);
EXPECT_EQ("extra_build_line2", options.build_info_lines[1]);
+ EXPECT_EQ("classes.jsa", options.cds_archive);
}
TEST(OptionsTest, MultiOptargs) {
diff --git a/src/tools/singlejar/output_jar.cc b/src/tools/singlejar/output_jar.cc
index 87ce0d8..7c38c74 100644
--- a/src/tools/singlejar/output_jar.cc
+++ b/src/tools/singlejar/output_jar.cc
@@ -122,28 +122,7 @@
// Copy launcher if it is set.
if (!options_->java_launcher.empty()) {
- const char *const launcher_path = options_->java_launcher.c_str();
- int in_fd = open(launcher_path, O_RDONLY);
- struct stat statbuf;
- if (file_ == nullptr || fstat(in_fd, &statbuf)) {
- diag_err(1, "%s", launcher_path);
- }
- // TODO(asmundak): Consider going back to sendfile() or reflink
- // (BTRFS_IOC_CLONE/XFS_IOC_CLONE) here. The launcher preamble can
- // be very large for targets with many native deps.
- ssize_t byte_count = AppendFile(in_fd, 0, statbuf.st_size);
- if (byte_count < 0) {
- diag_err(1, "%s:%d: Cannot copy %s to %s", __FILE__, __LINE__,
- launcher_path, options_->output_jar.c_str());
- } else if (byte_count != statbuf.st_size) {
- diag_err(1, "%s:%d: Copied only %zu bytes out of %" PRIu64 " from %s",
- __FILE__, __LINE__, byte_count, statbuf.st_size, launcher_path);
- }
- close(in_fd);
- if (options_->verbose) {
- fprintf(stderr, "Prepended %s (%" PRIu64 " bytes)\n", launcher_path,
- statbuf.st_size);
- }
+ AppendFile(options_, options_->java_launcher.c_str());
}
if (!options_->main_class.empty()) {
@@ -153,6 +132,11 @@
manifest_.Append("\r\n");
}
+ // Copy CDS archive file (.jsa) if it is set.
+ if (!options_->cds_archive.empty()) {
+ AppendCDSArchive(options->cds_archive);
+ }
+
for (auto &manifest_line : options_->manifest_lines) {
if (!manifest_line.empty()) {
manifest_.Append(manifest_line);
@@ -959,7 +943,7 @@
}
}
-ssize_t OutputJar::AppendFile(int in_fd, off64_t offset, size_t count) {
+ssize_t OutputJar::CopyAppendData(int in_fd, off64_t offset, size_t count) {
if (count == 0) {
return 0;
}
@@ -1005,6 +989,66 @@
return total_written;
}
+void OutputJar::AppendFile(Options *options, const char *const file_path) {
+ int in_fd = open(file_path, O_RDONLY);
+ struct stat statbuf;
+ if (fstat(in_fd, &statbuf)) {
+ diag_err(1, "%s", file_path);
+ }
+ // TODO(asmundak): Consider going back to sendfile() or reflink
+ // (BTRFS_IOC_CLONE/XFS_IOC_CLONE) here. The launcher preamble can
+ // be very large for targets with many native deps.
+ ssize_t byte_count = CopyAppendData(in_fd, 0, statbuf.st_size);
+ if (byte_count < 0) {
+ diag_err(1, "%s:%d: Cannot copy %s to %s", __FILE__, __LINE__,
+ file_path, options->output_jar.c_str());
+ } else if (byte_count != statbuf.st_size) {
+ diag_err(1, "%s:%d: Copied only %zu bytes out of %" PRIu64 " from %s",
+ __FILE__, __LINE__, byte_count, statbuf.st_size, file_path);
+ }
+ close(in_fd);
+ if (options->verbose) {
+ fprintf(stderr, "Prepended %s (%" PRIu64 " bytes)\n", file_path,
+ statbuf.st_size);
+ }
+}
+
+void OutputJar::AppendCDSArchive(const std::string &cds_archive) {
+ // Align the shared archive start offset at page alignment, which is
+ // required by mmap.
+ off64_t cur_offset = Position();
+ size_t pagesize;
+#ifdef _WIN32
+ SYSTEM_INFO si;
+ GetSystemInfo(&si);
+ pagesize = si.dwPageSize;
+#else
+ pagesize = sysconf(_SC_PAGESIZE);
+#endif
+ off64_t aligned_offset = (cur_offset + (pagesize - 1)) & ~(pagesize - 1);
+ size_t gap = aligned_offset - cur_offset;
+ if (gap > 0) {
+ char zero = 0;
+ size_t written = fwrite(&zero, 1, gap, file_);
+ outpos_ += written;
+ }
+
+ // Copy archived data
+ AppendFile(options_, cds_archive.c_str());
+
+ // Write the file offset of the shared archive section as a manifest
+ // attribute.
+ char cds_manifest_attr[50];
+ snprintf(cds_manifest_attr, sizeof(cds_manifest_attr),
+ "Jsa-Offset: %ld", aligned_offset);
+ manifest_.Append(cds_manifest_attr);
+ manifest_.Append("\r\n");
+
+ // Add to build_properties
+ build_properties_.AddProperty("cds.archive",
+ cds_archive.c_str());
+}
+
void OutputJar::ExtraCombiner(const std::string &entry_name,
Combiner *combiner) {
extra_combiners_.emplace_back(combiner);
diff --git a/src/tools/singlejar/output_jar.h b/src/tools/singlejar/output_jar.h
index 18ecb10..322aca3 100644
--- a/src/tools/singlejar/output_jar.h
+++ b/src/tools/singlejar/output_jar.h
@@ -96,8 +96,12 @@
// Set classpath resource with given resource name and path.
void ClasspathResource(const std::string& resource_name,
const std::string& resource_path);
+ // Append CDS archive file.
+ void AppendCDSArchive(const std::string &cds_archive);
+ // Append data from the file specified by file_path.
+ void AppendFile(Options *options, const char *const file_path);
// Copy 'count' bytes starting at 'offset' from the given file.
- ssize_t AppendFile(int in_fd, off64_t offset, size_t count);
+ ssize_t CopyAppendData(int in_fd, off64_t offset, size_t count);
// Write bytes to the output file, return true on success.
bool WriteBytes(const void *buffer, size_t count);
diff --git a/src/tools/singlejar/output_jar_simple_test.cc b/src/tools/singlejar/output_jar_simple_test.cc
index 349439a..7b427c8 100644
--- a/src/tools/singlejar/output_jar_simple_test.cc
+++ b/src/tools/singlejar/output_jar_simple_test.cc
@@ -277,6 +277,36 @@
input_jar.Close();
}
+// --cds_archive option
+TEST_F(OutputJarSimpleTest, CDSArchive) {
+ string out_path = OutputFilePath("out.jar");
+ string launcher_path = CreateTextFile("launcher", "Dummy");
+ string cds_archive_path = CreateTextFile("classes.jsa", "Dummy");
+ CreateOutput(out_path, {"--java_launcher", launcher_path,
+ "--cds_archive", cds_archive_path});
+
+ // check META-INF/MANIFEST.MF attribute
+ string manifest = GetEntryContents(out_path, "META-INF/MANIFEST.MF");
+ size_t pagesize;
+#ifndef _WIN32
+ pagesize = sysconf(_SC_PAGESIZE);
+#else
+ SYSTEM_INFO si;
+ GetSystemInfo(&si);
+ pagesize = si.dwPageSize;
+#endif
+ char attr[128];
+ snprintf(attr, sizeof(attr), "Jsa-Offset: %ld", pagesize);
+ EXPECT_PRED2(HasSubstr, manifest, attr);
+
+ // check build-data.properties entry
+ string build_properties = GetEntryContents(out_path, "build-data.properties");
+ char prop[4096];
+ snprintf(prop, sizeof(prop), "\ncds.archive=%s\n",
+ cds_archive_path.c_str());
+ EXPECT_PRED2(HasSubstr, build_properties, prop);
+}
+
// --main_class option.
TEST_F(OutputJarSimpleTest, MainClass) {
string out_path = OutputFilePath("out.jar");