Add apex versions in oat file headers.

Use the versions to know whether we need to recompile.

Test: oat_file_assistant_test
Bug: 182465342
Change-Id: Ic656ed4465d35d325c1823d531f7c4c5bc74598d
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 02afa7c..9de5ac5 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -1073,6 +1073,7 @@
     AssignTrueIfExists(args, M::CrashOnLinkageViolation, &crash_on_linkage_violation_);
     AssignTrueIfExists(args, M::ForceAllowOjInlines, &force_allow_oj_inlines_);
     AssignIfExists(args, M::PublicSdk, &public_sdk_);
+    AssignIfExists(args, M::ApexVersions, &apex_versions_argument_);
 
     AssignIfExists(args, M::Backend, &compiler_kind_);
     parser_options->requested_specific_compiler = args.Exists(M::Backend);
@@ -1601,6 +1602,11 @@
         key_value_store_->Put(
             OatHeader::kBootClassPathChecksumsKey,
             gc::space::ImageSpace::GetBootClassPathChecksums(image_spaces, bcp_dex_files));
+
+        std::string versions = apex_versions_argument_.empty()
+            ? runtime->GetApexVersions()
+            : apex_versions_argument_;
+        key_value_store_->Put(OatHeader::kApexVersionsKey, versions);
       }
 
       // Open dex files for class path.
@@ -2952,6 +2958,10 @@
   // The classpath that determines if a given symbol should be resolved at compile time or not.
   std::string public_sdk_;
 
+  // The apex versions of jars in the boot classpath. Set through command line
+  // argument.
+  std::string apex_versions_argument_;
+
   DISALLOW_IMPLICIT_CONSTRUCTORS(Dex2Oat);
 };
 
diff --git a/dex2oat/dex2oat_options.cc b/dex2oat/dex2oat_options.cc
index 26d0ff5..280db7a 100644
--- a/dex2oat/dex2oat_options.cc
+++ b/dex2oat/dex2oat_options.cc
@@ -416,7 +416,11 @@
           .IntoKey(M::CompileIndividually)
       .Define("--public-sdk=_")
           .WithType<std::string>()
-          .IntoKey(M::PublicSdk);;
+          .IntoKey(M::PublicSdk)
+      .Define("--apex-versions=_")
+          .WithType<std::string>()
+          .WithHelp("Versions of apexes in the boot classpath, separated by '/'")
+          .IntoKey(M::ApexVersions);
 
   AddCompilerOptionsArgumentParserOptions<Dex2oatArgumentMap>(*parser_builder);
 
diff --git a/dex2oat/dex2oat_options.def b/dex2oat/dex2oat_options.def
index 6bd3d05..71a769a 100644
--- a/dex2oat/dex2oat_options.def
+++ b/dex2oat/dex2oat_options.def
@@ -97,5 +97,6 @@
 DEX2OAT_OPTIONS_KEY (Unit,                           CompileIndividually)
 DEX2OAT_OPTIONS_KEY (std::string,                    PublicSdk)
 DEX2OAT_OPTIONS_KEY (Unit,                           ForceAllowOjInlines)
+DEX2OAT_OPTIONS_KEY (std::string,                    ApexVersions)
 
 #undef DEX2OAT_OPTIONS_KEY
diff --git a/runtime/Android.bp b/runtime/Android.bp
index e8ebc20..f57f59f 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -384,6 +384,10 @@
             ],
             static_libs: [
                 "libstatslog_art",
+                "libxml2",
+            ],
+            generated_sources: [
+                "apex-info-list",
             ],
         },
         android_arm: {
@@ -413,7 +417,9 @@
         },
     },
     cflags: ["-DBUILDING_LIBART=1"],
-    generated_sources: ["art_operator_srcs"],
+    generated_sources: [
+        "art_operator_srcs"
+    ],
     // asm_support_gen.h (used by asm_support.h) is generated with cpp-define-generator
     generated_headers: ["cpp-define-generator-asm-support"],
     // export our headers so the libart-gtest targets can use it as well.
diff --git a/runtime/oat.h b/runtime/oat.h
index 8205935..ab45b84 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -32,8 +32,8 @@
 class PACKED(4) OatHeader {
  public:
   static constexpr std::array<uint8_t, 4> kOatMagic { { 'o', 'a', 't', '\n' } };
-  // Last oat version changed reason: Record number of methods in OatClass.
-  static constexpr std::array<uint8_t, 4> kOatVersion { { '1', '9', '4', '\0' } };
+  // Last oat version changed reason: Apex versions in key/value store.
+  static constexpr std::array<uint8_t, 4> kOatVersion { { '1', '9', '5', '\0' } };
 
   static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
   static constexpr const char* kDebuggableKey = "debuggable";
@@ -42,6 +42,7 @@
   static constexpr const char* kClassPathKey = "classpath";
   static constexpr const char* kBootClassPathKey = "bootclasspath";
   static constexpr const char* kBootClassPathChecksumsKey = "bootclasspath-checksums";
+  static constexpr const char* kApexVersionsKey = "apex-versions";
   static constexpr const char* kConcurrentCopying = "concurrent-copying";
   static constexpr const char* kCompilationReasonKey = "compilation-reason";
   static constexpr const char* kRequiresImage = "requires-image";
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index 0bccc82..c74f19b 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -400,6 +400,19 @@
   return true;
 }
 
+static bool ValidateApexVersions(const OatFile& oat_file) {
+  const char* oat_apex_versions =
+      oat_file.GetOatHeader().GetStoreValueByKey(OatHeader::kApexVersionsKey);
+  if (oat_apex_versions == nullptr) {
+    return false;
+  }
+  // Some dex files get compiled with a subset of the boot classpath (for
+  // example currently system server is compiled with DEX2OAT_BOOTCLASSPATH).
+  // For such cases, the oat apex versions will be a prefix of the runtime apex
+  // versions.
+  return android::base::StartsWith(Runtime::Current()->GetApexVersions(), oat_apex_versions);
+}
+
 OatFileAssistant::OatStatus OatFileAssistant::GivenOatFileStatus(const OatFile& file) {
   // Verify the ART_USE_READ_BARRIER state.
   // TODO: Don't fully reject files due to read barrier state. If they contain
@@ -430,6 +443,10 @@
       VLOG(oat) << "Oat image checksum does not match image checksum.";
       return kOatBootImageOutOfDate;
     }
+    if (!ValidateApexVersions(file)) {
+      VLOG(oat) << "Apex versions do not match.";
+      return kOatBootImageOutOfDate;
+    }
   } else {
     VLOG(oat) << "Image checksum test skipped for compiler filter " << current_compiler_filter;
   }
diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc
index 5631e12..3760885 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -1654,6 +1654,54 @@
   EXPECT_FALSE(oat_file->IsExecutable());
 }
 
+TEST_F(OatFileAssistantTest, GetDexOptNeededWithApexVersions) {
+  std::string dex_location = GetScratchDir() + "/TestDex.jar";
+  std::string odex_location = GetOdexDir() + "/TestDex.odex";
+  Copy(GetDexSrc1(), dex_location);
+
+  // Test that using the current's runtime apex versions works.
+  {
+    std::string error_msg;
+    std::vector<std::string> args;
+    args.push_back("--dex-file=" + dex_location);
+    args.push_back("--oat-file=" + odex_location);
+    args.push_back("--apex-versions=" + Runtime::Current()->GetApexVersions());
+    ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
+
+    OatFileAssistant oat_file_assistant(
+        dex_location.c_str(), kRuntimeISA, default_context_.get(), false);
+    EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
+  }
+
+  // Test that a subset of apex versions works.
+  {
+    std::string error_msg;
+    std::vector<std::string> args;
+    args.push_back("--dex-file=" + dex_location);
+    args.push_back("--oat-file=" + odex_location);
+    args.push_back("--apex-versions=" + Runtime::Current()->GetApexVersions().substr(0, 1));
+    ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
+
+    OatFileAssistant oat_file_assistant(
+        dex_location.c_str(), kRuntimeISA, default_context_.get(), false);
+    EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
+  }
+
+  // Test that different apex versions require to recompile.
+  {
+    std::string error_msg;
+    std::vector<std::string> args;
+    args.push_back("--dex-file=" + dex_location);
+    args.push_back("--oat-file=" + odex_location);
+    args.push_back("--apex-versions=/1/2/3/4");
+    ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
+
+    OatFileAssistant oat_file_assistant(
+        dex_location.c_str(), kRuntimeISA, default_context_.get(), false);
+    EXPECT_EQ(OatFileAssistant::kDex2OatForBootImage, oat_file_assistant.OdexFileStatus());
+  }
+}
+
 // TODO: More Tests:
 //  * Test class linker falls back to unquickened dex for DexNoOat
 //  * Test class linker falls back to unquickened dex for MultiDexNoOat
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 10999dc..f111ff8 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -173,6 +173,9 @@
 
 #ifdef ART_TARGET_ANDROID
 #include <android/set_abort_message.h>
+#include "com_android_apex.h"
+namespace apex = com::android::apex;
+
 #endif
 
 // Static asserts to check the values of generated assembly-support macros.
@@ -1235,6 +1238,52 @@
   detailMessageField->SetObject</* kTransactionActive= */ false>(exception->Read(), message);
 }
 
+void Runtime::InitializeApexVersions() {
+  std::vector<std::string_view> bcp_apexes;
+  for (const std::string& jar : Runtime::Current()->GetBootClassPathLocations()) {
+    if (LocationIsOnApex(jar)) {
+      size_t start = jar.find('/', 1);
+      if (start == std::string::npos) {
+        continue;
+      }
+      size_t end = jar.find('/', start + 1);
+      if (end == std::string::npos) {
+        continue;
+      }
+      std::string apex = jar.substr(start + 1, end - start - 1);
+      bcp_apexes.push_back(apex);
+    }
+  }
+  std::string result;
+  static const char* kApexFileName = "/apex/apex-info-list.xml";
+  // When running on host or chroot, we just encode empty markers.
+  if (!kIsTargetBuild || !OS::FileExists(kApexFileName)) {
+    for (uint32_t i = 0; i < bcp_apexes.size(); ++i) {
+      result += '/';
+    }
+  } else {
+#ifdef ART_TARGET_ANDROID
+    auto info_list = apex::readApexInfoList(kApexFileName);
+    CHECK(info_list.has_value());
+    std::map<std::string_view, const apex::ApexInfo*> apex_infos;
+    for (const apex::ApexInfo& info : info_list->getApexInfo()) {
+      if (info.getIsActive()) {
+        apex_infos.emplace(info.getModuleName(), &info);
+      }
+    }
+    for (const std::string_view& str : bcp_apexes) {
+      auto info = apex_infos.find(str);
+      if (info == apex_infos.end() || info->second->getIsFactory()) {
+        result += '/';
+      } else {
+        android::base::StringAppendF(&result, "/%" PRIu64, info->second->getVersionCode());
+      }
+    }
+#endif
+  }
+  apex_versions_ = result;
+}
+
 bool Runtime::Init(RuntimeArgumentMap&& runtime_options_in) {
   // (b/30160149): protect subprocesses from modifications to LD_LIBRARY_PATH, etc.
   // Take a snapshot of the environment at the time the runtime was created, for use by Exec, etc.
@@ -1686,6 +1735,8 @@
     }
   }
 
+  InitializeApexVersions();
+
   CHECK(class_linker_ != nullptr);
 
   verifier::ClassVerifier::Init(class_linker_);
diff --git a/runtime/runtime.h b/runtime/runtime.h
index e8fde68..8513cd5 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -995,6 +995,10 @@
                                   const uint8_t* map_end,
                                   const std::string& file_name);
 
+  const std::string& GetApexVersions() const {
+    return apex_versions_;
+  }
+
  private:
   static void InitPlatformSignalHandlers();
 
@@ -1035,6 +1039,10 @@
   ThreadPool* AcquireThreadPool() REQUIRES(!Locks::runtime_thread_pool_lock_);
   void ReleaseThreadPool() REQUIRES(!Locks::runtime_thread_pool_lock_);
 
+  // Parses /apex/apex-info-list.xml to initialize a string containing versions
+  // of boot classpath jars and encoded into .oat files.
+  void InitializeApexVersions();
+
   // A pointer to the active runtime or null.
   static Runtime* instance_;
 
@@ -1367,6 +1375,14 @@
   metrics::ArtMetrics metrics_;
   std::unique_ptr<metrics::MetricsReporter> metrics_reporter_;
 
+  // Apex versions of boot classpath jars concatenated in a string. The format
+  // is of the type:
+  // '/apex1_version/apex2_version//'
+  //
+  // When the apex is the factory version, we don't encode it (for example in
+  // the third entry in the example above).
+  std::string apex_versions_;
+
   // Note: See comments on GetFaultMessage.
   friend std::string GetFaultMessageForAbortLogging();
   friend class Dex2oatImageTest;