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;