Allow odrefresh to write elsewhere than dalvik-cache

For CompOS we want to be able to generate artifacts in different
subdirectories, e.g. /data/misc/apexdata/com.android.art/pending
rather than /data/misc/apexdata/com.android.art/dalvik-cache.

Add a command line flag to enable this.

Bug: 200020887
Test: atest art_standalone_libartbase_tests
Test: manual: odrefresh --dalvik-cache=foo --force-compile
Change-Id: Id10c9bcc8cb9f810c48727abc1858f5b16b360d2
diff --git a/libartbase/base/file_utils.cc b/libartbase/base/file_utils.cc
index 0780fe0..0cbc77b 100644
--- a/libartbase/base/file_utils.cc
+++ b/libartbase/base/file_utils.cc
@@ -370,11 +370,18 @@
   }
 }
 
+static /*constinit*/ std::string_view dalvik_cache_sub_dir = "dalvik-cache";
+
+void OverrideDalvikCacheSubDirectory(std::string sub_dir) {
+    static std::string overridden_dalvik_cache_sub_dir;
+    overridden_dalvik_cache_sub_dir = std::move(sub_dir);
+    dalvik_cache_sub_dir = overridden_dalvik_cache_sub_dir;
+}
+
 static std::string GetDalvikCacheDirectory(std::string_view root_directory,
                                            std::string_view sub_directory = {}) {
-  static constexpr std::string_view kDalvikCache = "dalvik-cache";
   std::stringstream oss;
-  oss << root_directory << '/' << kDalvikCache;
+  oss << root_directory << '/' << dalvik_cache_sub_dir;
   if (!sub_directory.empty()) {
     oss << '/' << sub_directory;
   }
diff --git a/libartbase/base/file_utils.h b/libartbase/base/file_utils.h
index 6fc1caa..33ea2e9 100644
--- a/libartbase/base/file_utils.h
+++ b/libartbase/base/file_utils.h
@@ -80,6 +80,10 @@
 // Returns the boot image path of the provided jar, on /system or /data.
 std::string GetBootImagePath(bool on_system, const std::string& jar_path);
 
+// Allows the name to be used for the dalvik cache directory (normally "dalvik-cache") to be
+// overridden with a new value.
+void OverrideDalvikCacheSubDirectory(std::string sub_dir);
+
 // Return true if we found the dalvik cache and stored it in the dalvik_cache argument.
 // `have_android_data` will be set to true if we have an ANDROID_DATA that exists,
 // `dalvik_cache_exists` will be true if there is a dalvik-cache directory that is present.
diff --git a/libartbase/base/file_utils_test.cc b/libartbase/base/file_utils_test.cc
index a0b3325..7678df4 100644
--- a/libartbase/base/file_utils_test.cc
+++ b/libartbase/base/file_utils_test.cc
@@ -29,6 +29,21 @@
 
 static constexpr const char kAndroidWifiApexDefaultPath[] = "/apex/com.android.wifi";
 
+namespace {
+class ScopedOverrideDalvikCacheSubDirectory {
+ public:
+  explicit ScopedOverrideDalvikCacheSubDirectory(const char* override) {
+    OverrideDalvikCacheSubDirectory(override);
+  }
+
+  ~ScopedOverrideDalvikCacheSubDirectory() {
+    OverrideDalvikCacheSubDirectory("dalvik-cache");
+  }
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ScopedOverrideDalvikCacheSubDirectory);
+};
+}  // namespace
+
 class FileUtilsTest : public CommonArtTest {};
 
 TEST_F(FileUtilsTest, GetDalvikCacheFilename) {
@@ -271,6 +286,33 @@
   CHECK_EQ(vdex_filename, ReplaceFileExtension(art_filename, "vdex"));
 }
 
+TEST_F(FileUtilsTest, OverrideDalvikCacheSubDirectory) {
+  ScopedUnsetEnvironmentVariable android_root("ANDROID_ROOT");
+  ScopedUnsetEnvironmentVariable i18n_root("ANDROID_I18N_ROOT");
+  ScopedUnsetEnvironmentVariable art_apex_data("ART_APEX_DATA");
+
+  ScopedOverrideDalvikCacheSubDirectory dalvik_cache("overridden-cache");
+
+  EXPECT_EQ(GetArtApexData() + "/overridden-cache/arm/boot-beep.oat",
+            GetApexDataOatFilename("/product/javalib/beep.jar", InstructionSet::kArm));
+
+  EXPECT_EQ(GetArtApexData() + "/overridden-cache/arm/data@some@code.odex",
+            GetApexDataOdexFilename("/data/some/code.dex", InstructionSet::kArm));
+
+  const std::string system_jar = "/system/framework/disk.jar";
+  const std::string boot_image = GetApexDataBootImage(system_jar.c_str());
+  EXPECT_EQ(GetArtApexData() + "/overridden-cache/boot-disk.art", boot_image);
+
+  EXPECT_EQ(
+      GetArtApexData() + "/overridden-cache/apex@com.android.wifi@lib@javalib@bar.jar@classes.art",
+      GetApexDataImage(std::string {kAndroidWifiApexDefaultPath} + "/lib/javalib/bar.jar"));
+
+  const std::string apex_jar = std::string {kAndroidWifiApexDefaultPath} + "/lib/javalib/bar.jar";
+  EXPECT_EQ(GetArtApexData() +
+                "/overridden-cache/x86_64/apex@com.android.wifi@lib@javalib@bar.jar@classes.art",
+            GetApexDataDalvikCacheFilename(apex_jar, InstructionSet::kX86_64, "art"));
+}
+
 TEST_F(FileUtilsTest, GetSystemOdexFilenameForApex) {
   ScopedUnsetEnvironmentVariable android_root("ANDROID_ROOT");
 
diff --git a/odrefresh/include/odrefresh/odrefresh.h b/odrefresh/include/odrefresh/odrefresh.h
index 1fe2382..f1c18ac 100644
--- a/odrefresh/include/odrefresh/odrefresh.h
+++ b/odrefresh/include/odrefresh/odrefresh.h
@@ -22,6 +22,8 @@
 namespace art {
 namespace odrefresh {
 
+// Default directory to which artifacts are written. (Overridable via the --dalvik-cache command
+// line argument.)
 static constexpr const char* kOdrefreshArtifactDirectory =
     "/data/misc/apexdata/com.android.art/dalvik-cache";
 
diff --git a/odrefresh/odr_config.h b/odrefresh/odr_config.h
index 253bd49..402955c 100644
--- a/odrefresh/odr_config.h
+++ b/odrefresh/odr_config.h
@@ -24,6 +24,7 @@
 #include "arch/instruction_set.h"
 #include "base/globals.h"
 #include "log/log.h"
+#include "odrefresh/odrefresh.h"
 
 namespace art {
 namespace odrefresh {
@@ -55,6 +56,7 @@
   ZygoteKind zygote_kind_;
   int compilation_os_address_ = 0;
   std::string boot_classpath_;
+  std::string artifact_dir_;
 
   // Staging directory for artifacts. The directory must exist and will be automatically removed
   // after compilation. If empty, use the default directory.
@@ -64,7 +66,8 @@
   explicit OdrConfig(const char* program_name)
     : dry_run_(false),
       isa_(InstructionSet::kNone),
-      program_name_(android::base::Basename(program_name)) {
+      program_name_(android::base::Basename(program_name)),
+      artifact_dir_(kOdrefreshArtifactDirectory) {
   }
 
   const std::string& GetApexInfoListFile() const { return apex_info_list_file_; }
@@ -96,6 +99,8 @@
 
   const std::string& GetDex2oatBootClasspath() const { return dex2oat_boot_classpath_; }
 
+  const std::string& GetArtifactDirectory() const { return artifact_dir_; }
+
   std::string GetDex2Oat() const {
     const char* prefix = UseDebugBinaries() ? "dex2oatd" : "dex2oat";
     const char* suffix = "";
@@ -136,6 +141,10 @@
     dex2oat_boot_classpath_ = classpath;
   }
 
+  void SetArtifactDirectory(const std::string& artifact_dir) {
+    artifact_dir_ = artifact_dir;
+  }
+
   void SetDryRun() { dry_run_ = true; }
   void SetIsa(const InstructionSet isa) { isa_ = isa; }
   void SetCompilationOsAddress(int address) { compilation_os_address_ = address; }
diff --git a/odrefresh/odrefresh.cc b/odrefresh/odrefresh.cc
index 4e4c800..be58702 100644
--- a/odrefresh/odrefresh.cc
+++ b/odrefresh/odrefresh.cc
@@ -472,7 +472,7 @@
 
 OnDeviceRefresh::OnDeviceRefresh(const OdrConfig& config)
     : OnDeviceRefresh(config,
-                      Concatenate({kOdrefreshArtifactDirectory, "/", kCacheInfoFile}),
+                      Concatenate({config.GetArtifactDirectory(), "/", kCacheInfoFile}),
                       std::make_unique<ExecUtils>(),
                       std::move(OdrDexopt::Create(config, std::make_unique<ExecUtils>()))) {}
 
@@ -700,11 +700,11 @@
 
 WARN_UNUSED bool OnDeviceRefresh::RemoveArtifactsDirectory() const {
   if (config_.GetDryRun()) {
-    LOG(INFO) << "Directory " << QuotePath(kOdrefreshArtifactDirectory)
+    LOG(INFO) << "Directory " << QuotePath(config_.GetArtifactDirectory())
               << " and contents would be removed (dry-run).";
     return true;
   }
-  return RemoveDirectory(kOdrefreshArtifactDirectory);
+  return RemoveDirectory(config_.GetArtifactDirectory());
 }
 
 WARN_UNUSED bool OnDeviceRefresh::BootExtensionArtifactsExist(
diff --git a/odrefresh/odrefresh_main.cc b/odrefresh/odrefresh_main.cc
index 2135224..7359df5 100644
--- a/odrefresh/odrefresh_main.cc
+++ b/odrefresh/odrefresh_main.cc
@@ -71,25 +71,6 @@
   exit(EX_USAGE);
 }
 
-NO_RETURN void UsageHelp(const char* argv0) {
-  std::string name(android::base::Basename(argv0));
-  UsageError("Usage: %s ACTION", name.c_str());
-  UsageError("On-device refresh tool for boot class path extensions and system server");
-  UsageError("following an update of the ART APEX.");
-  UsageError("");
-  UsageError("Valid ACTION choices are:");
-  UsageError("");
-  UsageError(
-      "--check          Check compilation artifacts are up-to-date based on metadata (fast).");
-  UsageError("--compile        Compile boot class path extensions and system_server jars");
-  UsageError("                 when necessary.");
-  UsageError("--force-compile  Unconditionally compile the boot class path extensions and");
-  UsageError("                 system_server jars.");
-  UsageError("--verify         Verify artifacts are up-to-date with dexoptanalyzer (slow).");
-  UsageError("--help           Display this help information.");
-  exit(EX_USAGE);
-}
-
 bool ParseZygoteKind(const char* input, ZygoteKind* zygote_kind) {
   std::string_view z(input);
   if (z == "zygote32") {
@@ -135,6 +116,10 @@
   return false;
 }
 
+void CommonOptionsHelp() {
+  UsageError("--dry-run");
+}
+
 int InitializeHostConfig(int argc, char** argv, OdrConfig* config) {
   __android_log_set_logger(__android_log_stderr_logger);
 
@@ -181,6 +166,17 @@
   return n;
 }
 
+void HostOptionsHelp() {
+  UsageError("--android-root");
+  UsageError("--android-art-root");
+  UsageError("--apex-info-list");
+  UsageError("--art-apex-data");
+  UsageError("--dex2oat-bootclasspath");
+  UsageError("--isa-root");
+  UsageError("--system-server-classpath");
+  UsageError("--zygote-arch");
+}
+
 int InitializeTargetConfig(int argc, char** argv, OdrConfig* config) {
   config->SetApexInfoListFile("/apex/apex-info-list.xml");
   config->SetArtBinDir(art::GetArtBinDir());
@@ -206,6 +202,10 @@
         ArgumentError("Failed to parse CID: %s", value.c_str());
       }
       config->SetCompilationOsAddress(cid);
+    } else if (ArgumentMatches(arg, "--dalvik-cache=", &value)) {
+      art::OverrideDalvikCacheSubDirectory(value);
+      config->SetArtifactDirectory(Concatenate(
+          {android::base::Dirname(art::odrefresh::kOdrefreshArtifactDirectory), "/", value}));
     } else if (!InitializeCommonConfig(arg, config)) {
       UsageError("Unrecognized argument: '%s'", arg);
     }
@@ -213,6 +213,13 @@
   return n;
 }
 
+void TargetOptionsHelp() {
+  UsageError("--use-compilation-os=<CID>  Run compilation in the VM with the given CID.");
+  UsageError("                            (0 = do not use VM, -1 = use composd's VM)");
+  UsageError(
+      "--dalvik-cache=<DIR>        Write artifacts to .../<DIR> rather than .../dalvik-cache");
+}
+
 int InitializeConfig(int argc, char** argv, OdrConfig* config) {
   if (art::kIsTargetBuild) {
     return InitializeTargetConfig(argc, argv, config);
@@ -221,6 +228,35 @@
   }
 }
 
+NO_RETURN void UsageHelp(const char* argv0) {
+  std::string name(android::base::Basename(argv0));
+  UsageError("Usage: %s [OPTION...] ACTION", name.c_str());
+  UsageError("On-device refresh tool for boot class path extensions and system server");
+  UsageError("following an update of the ART APEX.");
+  UsageError("");
+  UsageError("Valid ACTION choices are:");
+  UsageError("");
+  UsageError(
+      "--check          Check compilation artifacts are up-to-date based on metadata (fast).");
+  UsageError("--compile        Compile boot class path extensions and system_server jars");
+  UsageError("                 when necessary.");
+  UsageError("--force-compile  Unconditionally compile the boot class path extensions and");
+  UsageError("                 system_server jars.");
+  UsageError("--verify         Verify artifacts are up-to-date with dexoptanalyzer (slow).");
+  UsageError("--help           Display this help information.");
+  UsageError("");
+  UsageError("Available OPTIONs are:");
+  UsageError("");
+  CommonOptionsHelp();
+  if (art::kIsTargetBuild) {
+    TargetOptionsHelp();
+  } else {
+    HostOptionsHelp();
+  }
+
+  exit(EX_USAGE);
+}
+
 }  // namespace
 
 int main(int argc, char** argv) {
@@ -240,7 +276,7 @@
     UsageError("Expected 1 argument, but have %d.", argc);
   }
 
-  OdrMetrics metrics(art::odrefresh::kOdrefreshArtifactDirectory);
+  OdrMetrics metrics(config.GetArtifactDirectory());
   OnDeviceRefresh odr(config);
   for (int i = 0; i < argc; ++i) {
     std::string_view action(argv[i]);