Update on-device AOT compilation to cover all cases.
After the change, odrefresh can properly determine whether to do
compilation when:
1. There is a mainline update (either a normal one or a samegrade one) that updates the ART module.
2. There is a mainline update (either a normal one or a samegrade one) that updates a module other than ART.
3. There is an OTA that updates a boot classpath jar.
4. There is an OTA that updates a system server jar.
5. There is no change since the last run.
Test: manual - 1. Install a mainline module without changing its
contents.
2. Reboot the device.
3. See if system_server components are recompiled.
Test: manual - 1. Do nothing and reboot the device.
2. See if nothing is recompiled.
Test: atest odsign_e2e_tests
Test: atest art_odrefresh_tests
Bug: 189467174
Change-Id: I43dea5380fcd221d5d4e34753d64d46be4dbc27e
diff --git a/odrefresh/CacheInfo.xsd b/odrefresh/CacheInfo.xsd
index 485c6b6..44f733f 100644
--- a/odrefresh/CacheInfo.xsd
+++ b/odrefresh/CacheInfo.xsd
@@ -25,40 +25,36 @@
<xs:element name="cacheInfo">
<xs:complexType>
<xs:sequence>
- <xs:element name="artModuleInfo" minOccurs="1" maxOccurs="1" type="t:artModuleInfo" />
- <xs:element name="bootClasspath" type="t:bootClasspath" />
- <xs:element name="dex2oatBootClasspath" type="t:dex2oatBootClasspath" />
- <xs:element name="systemServerClasspath" type="t:systemServerClasspath" />
+ <xs:element name="artModuleInfo" minOccurs="1" maxOccurs="1" type="t:moduleInfo" />
+ <xs:element name="moduleInfoList" minOccurs="1" maxOccurs="1" type="t:moduleInfoList" />
+ <xs:element name="bootClasspath" minOccurs="1" maxOccurs="1" type="t:classpath" />
+ <xs:element name="dex2oatBootClasspath" minOccurs="1" maxOccurs="1" type="t:classpath" />
+ <xs:element name="systemServerClasspath" minOccurs="1" maxOccurs="1" type="t:classpath" />
</xs:sequence>
</xs:complexType>
</xs:element>
+ <!-- List of modules. -->
+ <xs:complexType name="moduleInfoList">
+ <xs:sequence>
+ <xs:element name="moduleInfo" type="t:moduleInfo" />
+ </xs:sequence>
+ </xs:complexType>
+
<!-- Data type representing the provenance of the AOT artifacts in the cache. -->
- <xs:complexType name="artModuleInfo">
- <!-- Module versionCode for the active ART APEX from `/apex/apex-info-list.xml`. -->
+ <xs:complexType name="moduleInfo">
+ <!-- Module name for the active APEX from `/apex/apex-info-list.xml`. -->
+ <xs:attribute name="name" type="xs:string" use="required" />
+ <!-- Module versionCode for the active APEX from `/apex/apex-info-list.xml`. -->
<xs:attribute name="versionCode" type="xs:long" use="required" />
- <!-- Module versionName for the active ART APEX from `/apex/apex-info-list.xml`. -->
+ <!-- Module versionName for the active APEX from `/apex/apex-info-list.xml`. -->
<xs:attribute name="versionName" type="xs:string" use="required" />
- <!-- Module lastUpdateMillis for the active ART APEX from `/apex/apex-info-list.xml`. -->
+ <!-- Module lastUpdateMillis for the active APEX from `/apex/apex-info-list.xml`. -->
<xs:attribute name="lastUpdateMillis" type="xs:long" use="required" />
</xs:complexType>
- <!-- Components of the `BOOTCLASSPATH`. -->
- <xs:complexType name="bootClasspath">
- <xs:sequence>
- <xs:element name="component" type="t:component" />
- </xs:sequence>
- </xs:complexType>
-
- <!-- Components of the `DEX2OATBOOTCLASSPATH`. -->
- <xs:complexType name="dex2oatBootClasspath">
- <xs:sequence>
- <xs:element name="component" type="t:component" />
- </xs:sequence>
- </xs:complexType>
-
- <!-- Components of the `SYSTEMSERVERCLASSPATH`. -->
- <xs:complexType name="systemServerClasspath">
+ <!-- Components of a classpath. -->
+ <xs:complexType name="classpath">
<xs:sequence>
<xs:element name="component" type="t:component" />
</xs:sequence>
diff --git a/odrefresh/odrefresh.cc b/odrefresh/odrefresh.cc
index bf76010..5bcb904 100644
--- a/odrefresh/odrefresh.cc
+++ b/odrefresh/odrefresh.cc
@@ -35,6 +35,7 @@
#include <initializer_list>
#include <iosfwd>
#include <iostream>
+#include <iterator>
#include <memory>
#include <optional>
#include <ostream>
@@ -42,6 +43,7 @@
#include <string>
#include <string_view>
#include <type_traits>
+#include <unordered_map>
#include <utility>
#include <vector>
@@ -65,14 +67,13 @@
#include "dexoptanalyzer.h"
#include "exec_utils.h"
#include "log/log.h"
-#include "palette/palette.h"
-#include "palette/palette_types.h"
-
#include "odr_artifacts.h"
#include "odr_compilation_log.h"
#include "odr_config.h"
#include "odr_fs_utils.h"
#include "odr_metrics.h"
+#include "palette/palette.h"
+#include "palette/palette_types.h"
namespace art {
namespace odrefresh {
@@ -285,19 +286,29 @@
return std::max(GetExecutionTimeRemaining(), kMaxChildProcessSeconds);
}
- // Gets the `ApexInfo` associated with the currently active ART APEX.
- std::optional<apex::ApexInfo> GetArtApexInfo() const {
- auto info_list = apex::readApexInfoList(config_.GetApexInfoListFile().c_str());
+ // Gets the `ApexInfo` for active APEXes.
+ std::optional<std::vector<apex::ApexInfo>> GetApexInfoList() const {
+ std::optional<apex::ApexInfoList> info_list =
+ apex::readApexInfoList(config_.GetApexInfoListFile().c_str());
if (!info_list.has_value()) {
- return {};
+ return std::nullopt;
}
- for (const apex::ApexInfo& info : info_list->getApexInfo()) {
- if (info.getIsActive() && info.getModuleName() == "com.android.art") {
- return info;
- }
- }
- return {};
+ std::vector<apex::ApexInfo> filtered_info_list;
+ std::copy_if(info_list->getApexInfo().begin(),
+ info_list->getApexInfo().end(),
+ std::back_inserter(filtered_info_list),
+ [](const apex::ApexInfo& info) { return info.getIsActive(); });
+ return filtered_info_list;
+ }
+
+ // Gets the `ApexInfo` associated with the currently active ART APEX.
+ static std::optional<apex::ApexInfo> GetArtApexInfo(
+ const std::vector<apex::ApexInfo>& info_list) {
+ auto it = std::find_if(info_list.begin(), info_list.end(), [](const apex::ApexInfo& info) {
+ return info.getModuleName() == "com.android.art";
+ });
+ return it != info_list.end() ? std::make_optional(*it) : std::nullopt;
}
// Reads the ART APEX cache information (if any) found in `kOdrefreshArtifactDirectory`.
@@ -319,15 +330,21 @@
return;
}
- std::optional<art_apex::ArtModuleInfo> art_module_info = GenerateArtModuleInfo();
- if (!art_module_info.has_value()) {
- LOG(ERROR) << "Unable to generate cache provenance";
+ std::optional<std::vector<apex::ApexInfo>> apex_info_list = GetApexInfoList();
+ if (!apex_info_list.has_value()) {
+ LOG(ERROR) << "Could not update " << QuotePath(cache_info_filename_) << " : no APEX info";
return;
}
- // There can be only one CacheProvence in the XML file, but `xsdc` does not have
- // minOccurs/maxOccurs in the xsd schema.
- const std::vector<art_apex::ArtModuleInfo> art_module_infos { art_module_info.value() };
+ std::optional<apex::ApexInfo> art_apex_info = GetArtApexInfo(apex_info_list.value());
+ if (!art_apex_info.has_value()) {
+ LOG(ERROR) << "Could not update " << QuotePath(cache_info_filename_) << " : no ART APEX info";
+ return;
+ }
+
+ art_apex::ModuleInfo art_module_info = GenerateModuleInfo(art_apex_info.value());
+ std::vector<art_apex::ModuleInfo> module_info_list =
+ GenerateModuleInfoList(apex_info_list.value());
std::optional<std::vector<art_apex::Component>> bcp_components =
GenerateBootClasspathComponents();
@@ -351,26 +368,36 @@
}
std::ofstream out(cache_info_filename_.c_str());
- art_apex::CacheInfo info{art_module_infos,
- {{art_apex::BootClasspath{bcp_components.value()}}},
- {{art_apex::Dex2oatBootClasspath{bcp_compilable_components.value()}}},
- {{art_apex::SystemServerClasspath{system_server_components.value()}}}};
+ art_apex::CacheInfo info({art_module_info},
+ {art_apex::ModuleInfoList(module_info_list)},
+ {art_apex::Classpath(bcp_components.value())},
+ {art_apex::Classpath(bcp_compilable_components.value())},
+ {art_apex::Classpath(system_server_components.value())});
art_apex::write(out, info);
}
- // Returns cache provenance information based on the current ART APEX version and filesystem
+ // Returns cache provenance information based on the current APEX version and filesystem
// information.
- std::optional<art_apex::ArtModuleInfo> GenerateArtModuleInfo() const {
- auto info = GetArtApexInfo();
- if (!info.has_value()) {
- LOG(ERROR) << "Could not update " << QuotePath(cache_info_filename_) << " : no ART Apex info";
- return {};
- }
+ static art_apex::ModuleInfo GenerateModuleInfo(const apex::ApexInfo& apex_info) {
// The lastUpdateMillis is an addition to ApexInfoList.xsd to support samegrade installs.
- int64_t last_update_millis = info->hasLastUpdateMillis() ? info->getLastUpdateMillis() : 0;
- return art_apex::ArtModuleInfo{
- info->getVersionCode(), info->getVersionName(), last_update_millis};
+ int64_t last_update_millis =
+ apex_info.hasLastUpdateMillis() ? apex_info.getLastUpdateMillis() : 0;
+ return art_apex::ModuleInfo {apex_info.getModuleName(),
+ apex_info.getVersionCode(),
+ apex_info.getVersionName(),
+ last_update_millis};
+ }
+
+ // Returns cache provenance information for all APEXes.
+ static std::vector<art_apex::ModuleInfo> GenerateModuleInfoList(
+ const std::vector<apex::ApexInfo>& apex_info_list) {
+ std::vector<art_apex::ModuleInfo> module_info_list;
+ std::transform(apex_info_list.begin(),
+ apex_info_list.end(),
+ std::back_inserter(module_info_list),
+ GenerateModuleInfo);
+ return module_info_list;
}
bool CheckComponents(const std::vector<art_apex::Component>& expected_components,
@@ -392,11 +419,11 @@
return false;
}
if (expected.getSize() != actual.getSize()) {
- *error_msg = android::base::StringPrintf("Component %zu size differs (%" PRIu64
- " != %" PRIu64 ")",
- i,
- expected.getSize(),
- actual.getSize());
+ *error_msg =
+ android::base::StringPrintf("Component %zu size differs (%" PRIu64 " != %" PRIu64 ")",
+ i,
+ expected.getSize(),
+ actual.getSize());
return false;
}
if (expected.getChecksums() != actual.getChecksums()) {
@@ -459,9 +486,13 @@
}
// Checks whether a group of artifacts exists. Returns true if all are present, false otherwise.
- static bool ArtifactsExist(const OdrArtifacts& artifacts, /*out*/ std::string* error_msg) {
- const auto paths = {
- artifacts.ImagePath().c_str(), artifacts.OatPath().c_str(), artifacts.VdexPath().c_str()};
+ static bool ArtifactsExist(const OdrArtifacts& artifacts,
+ bool check_art_file,
+ /*out*/ std::string* error_msg) {
+ std::vector<const char*> paths{artifacts.OatPath().c_str(), artifacts.VdexPath().c_str()};
+ if (check_art_file) {
+ paths.push_back(artifacts.ImagePath().c_str());
+ }
for (const char* path : paths) {
if (!OS::FileExists(path)) {
if (errno == EACCES) {
@@ -474,112 +505,106 @@
return true;
}
- // Checks whether all boot extension artifacts are present on /data. Returns true if all are
- // present, false otherwise.
- WARN_UNUSED bool BootExtensionArtifactsExistOnData(const InstructionSet isa,
- /*out*/ std::string* error_msg) const {
- const std::string apexdata_image_location = GetBootImageExtensionImagePath(isa);
+ // Checks whether all boot extension artifacts are present. Returns true if all are present, false
+ // otherwise.
+ WARN_UNUSED bool BootExtensionArtifactsExist(bool on_system,
+ const InstructionSet isa,
+ /*out*/ std::string* error_msg) const {
+ const std::string apexdata_image_location = GetBootImageExtensionImagePath(on_system, isa);
const OdrArtifacts artifacts = OdrArtifacts::ForBootImageExtension(apexdata_image_location);
- return ArtifactsExist(artifacts, error_msg);
+ return ArtifactsExist(artifacts, /*check_art_file=*/true, error_msg);
}
- // Checks whether all system_server artifacts are present on /data. The artifacts are checked in
- // their order of compilation. Returns true if all are present, false otherwise.
- WARN_UNUSED bool SystemServerArtifactsExistOnData(/*out*/ std::string* error_msg) const {
+ // Checks whether all system_server artifacts are present. The artifacts are checked in their
+ // order of compilation. Returns true if all are present, false otherwise.
+ WARN_UNUSED bool SystemServerArtifactsExist(bool on_system,
+ /*out*/ std::string* error_msg) const {
for (const std::string& jar_path : systemserver_compilable_jars_) {
- const std::string image_location = GetSystemServerImagePath(/*on_system=*/false, jar_path);
+ // Temporarily skip checking APEX jar artifacts on system to prevent compilation on the first
+ // boot. Currently, APEX jar artifacts can never be found on system because we don't preopt
+ // them.
+ // TODO(b/194150908): Preopt APEX jars for system server and put the artifacts on /system.
+ if (on_system && StartsWith(jar_path, "/apex")) {
+ continue;
+ }
+ const std::string image_location = GetSystemServerImagePath(on_system, jar_path);
const OdrArtifacts artifacts = OdrArtifacts::ForSystemServer(image_location);
- if (!ArtifactsExist(artifacts, error_msg)) {
+ // .art files are optional and are not generated for all jars by the build system.
+ const bool check_art_file = !on_system;
+ if (!ArtifactsExist(artifacts, check_art_file, error_msg)) {
return false;
}
}
return true;
}
- WARN_UNUSED ExitCode CheckArtifactsAreUpToDate(OdrMetrics& metrics) {
- metrics.SetStage(OdrMetrics::Stage::kCheck);
+ WARN_UNUSED bool CheckBootExtensionArtifactsAreUpToDate(
+ OdrMetrics& metrics,
+ const InstructionSet isa,
+ const apex::ApexInfo& art_apex_info,
+ const std::optional<art_apex::CacheInfo>& cache_info,
+ /*out*/ bool* cleanup_required) {
+ std::string error_msg;
- // Clean-up helper used to simplify clean-ups and handling failures there.
- auto cleanup_return = [this](ExitCode exit_code) {
- return RemoveArtifactsDirectory() ? exit_code : ExitCode::kCleanupFailed;
- };
+ if (art_apex_info.getIsFactory()) {
+ LOG(INFO) << "Factory ART APEX mounted.";
- const auto apex_info = GetArtApexInfo();
- if (!apex_info.has_value()) {
- // This should never happen, further up-to-date checks are not possible if it does.
- LOG(ERROR) << "Could not get ART APEX info.";
- metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
- return cleanup_return(ExitCode::kCompilationRequired);
+ // ART is not updated, so we can use the artifacts on /system. Check if they exist.
+ if (BootExtensionArtifactsExist(/*on_system=*/true, isa, &error_msg)) {
+ // We don't need the artifacts on /data since we can use those on /system.
+ *cleanup_required = true;
+ return true;
+ }
+
+ LOG(INFO) << "Incomplete boot extension artifacts on /system. " << error_msg;
+ LOG(INFO) << "Checking cache.";
}
- // Generate current module info for the current ART APEX.
- const auto current_info = GenerateArtModuleInfo();
- if (!current_info.has_value()) {
- // This should never happen, further up-to-date checks are not possible if it does.
- LOG(ERROR) << "Failed to generate cache provenance.";
- metrics.SetTrigger(OdrMetrics::Trigger::kUnknown);
- return cleanup_return(ExitCode::kCompilationRequired);
- }
-
- // Record ART APEX version for metrics reporting.
- metrics.SetArtApexVersion(current_info->getVersionCode());
-
- // Record ART APEX last update milliseconds (used in compilation log).
- metrics.SetArtApexLastUpdateMillis(current_info->getLastUpdateMillis());
-
- if (apex_info->getIsFactory()) {
- // Remove any artifacts on /data as they are not necessary and no compilation is necessary.
- LOG(INFO) << "Factory APEX mounted.";
- return cleanup_return(ExitCode::kOkay);
- }
-
- if (!OS::FileExists(cache_info_filename_.c_str())) {
- // If the cache info file does not exist, assume compilation is required because the
- // file is missing and because the current ART APEX is not factory installed.
- PLOG(ERROR) << "No prior cache-info file: " << QuotePath(cache_info_filename_);
- metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
- return cleanup_return(ExitCode::kCompilationRequired);
- }
-
- // Get and parse the ART APEX cache info file.
- std::optional<art_apex::CacheInfo> cache_info = ReadCacheInfo();
if (!cache_info.has_value()) {
- // This should never happen, further up-to-date checks are not possible if it does.
- PLOG(ERROR) << "Failed to read cache-info file: " << QuotePath(cache_info_filename_);
- metrics.SetTrigger(OdrMetrics::Trigger::kUnknown);
- return cleanup_return(ExitCode::kCompilationRequired);
+ // If the cache info file does not exist, it means on-device compilation has not been done
+ // before.
+ PLOG(INFO) << "No prior cache-info file: " << QuotePath(cache_info_filename_);
+ metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts);
+ *cleanup_required = true;
+ return false;
}
// Check whether the current cache ART module info differs from the current ART module info.
- // Always check APEX version.
- const auto cached_info = cache_info->getFirstArtModuleInfo();
+ const art_apex::ModuleInfo* cached_art_info = cache_info->getFirstArtModuleInfo();
- if (cached_info->getVersionCode() != current_info->getVersionCode()) {
- LOG(INFO) << "ART APEX version code mismatch ("
- << cached_info->getVersionCode()
- << " != " << current_info->getVersionCode() << ").";
+ if (cached_art_info == nullptr) {
+ LOG(INFO) << "Missing ART APEX info from cache-info.";
metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
- return cleanup_return(ExitCode::kCompilationRequired);
+ *cleanup_required = true;
+ return false;
}
- if (cached_info->getVersionName() != current_info->getVersionName()) {
- LOG(INFO) << "ART APEX version name mismatch ("
- << cached_info->getVersionName()
- << " != " << current_info->getVersionName() << ").";
+ if (cached_art_info->getVersionCode() != art_apex_info.getVersionCode()) {
+ LOG(INFO) << "ART APEX version code mismatch (" << cached_art_info->getVersionCode()
+ << " != " << art_apex_info.getVersionCode() << ").";
metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
- return cleanup_return(ExitCode::kCompilationRequired);
+ *cleanup_required = true;
+ return false;
}
- // Check lastUpdateMillis for samegrade installs. If `cached_info` is missing lastUpdateMillis
- // then it is not current with the schema used by this binary so treat it as a samegrade
- // update. Otherwise check whether the lastUpdateMillis changed.
- if (!cached_info->hasLastUpdateMillis() ||
- cached_info->getLastUpdateMillis() != current_info->getLastUpdateMillis()) {
- LOG(INFO) << "ART APEX last update time mismatch ("
- << cached_info->getLastUpdateMillis()
- << " != " << current_info->getLastUpdateMillis() << ").";
+ if (cached_art_info->getVersionName() != art_apex_info.getVersionName()) {
+ LOG(INFO) << "ART APEX version name mismatch (" << cached_art_info->getVersionName()
+ << " != " << art_apex_info.getVersionName() << ").";
metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
- return cleanup_return(ExitCode::kCompilationRequired);
+ *cleanup_required = true;
+ return false;
+ }
+
+ // Check lastUpdateMillis for samegrade installs. If `cached_art_info` is missing
+ // lastUpdateMillis then it is not current with the schema used by this binary so treat it as a
+ // samegrade update. Otherwise check whether the lastUpdateMillis changed.
+ if (!cached_art_info->hasLastUpdateMillis() ||
+ cached_art_info->getLastUpdateMillis() != art_apex_info.getLastUpdateMillis()) {
+ LOG(INFO) << "ART APEX last update time mismatch (" << cached_art_info->getLastUpdateMillis()
+ << " != " << art_apex_info.getLastUpdateMillis() << ").";
+ metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
+ *cleanup_required = true;
+ return false;
}
// Check boot class components.
@@ -597,17 +622,124 @@
!cache_info->getFirstDex2oatBootClasspath()->hasComponent())) {
LOG(INFO) << "Missing Dex2oatBootClasspath components.";
metrics.SetTrigger(OdrMetrics::Trigger::kDexFilesChanged);
- return cleanup_return(ExitCode::kCompilationRequired);
+ *cleanup_required = true;
+ return false;
}
- std::string error_msg;
const std::vector<art_apex::Component>& bcp_compilable_components =
cache_info->getFirstDex2oatBootClasspath()->getComponent();
if (!CheckComponents(
expected_bcp_compilable_components, bcp_compilable_components, &error_msg)) {
LOG(INFO) << "Dex2OatClasspath components mismatch: " << error_msg;
metrics.SetTrigger(OdrMetrics::Trigger::kDexFilesChanged);
- return cleanup_return(ExitCode::kCompilationRequired);
+ *cleanup_required = true;
+ return false;
+ }
+
+ // Cache info looks good, check all compilation artifacts exist.
+ if (!BootExtensionArtifactsExist(/*on_system=*/false, isa, &error_msg)) {
+ LOG(INFO) << "Incomplete boot extension artifacts. " << error_msg;
+ metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts);
+ *cleanup_required = true;
+ return false;
+ }
+
+ *cleanup_required = false;
+ return true;
+ }
+
+ WARN_UNUSED bool CheckSystemServerArtifactsAreUpToDate(
+ OdrMetrics& metrics,
+ const std::vector<apex::ApexInfo>& apex_info_list,
+ const std::optional<art_apex::CacheInfo>& cache_info,
+ /*out*/ bool* cleanup_required) {
+ std::string error_msg;
+
+ if (std::all_of(apex_info_list.begin(),
+ apex_info_list.end(),
+ [](const apex::ApexInfo& apex_info) { return apex_info.getIsFactory(); })) {
+ LOG(INFO) << "Factory APEXes mounted.";
+
+ // APEXes are not updated, so we can use the artifacts on /system. Check if they exist.
+ if (SystemServerArtifactsExist(/*on_system=*/true, &error_msg)) {
+ // We don't need the artifacts on /data since we can use those on /system.
+ *cleanup_required = true;
+ return true;
+ }
+
+ LOG(INFO) << "Incomplete system server artifacts on /system. " << error_msg;
+ LOG(INFO) << "Checking cache.";
+ }
+
+ if (!cache_info.has_value()) {
+ // If the cache info file does not exist, it means on-device compilation has not been done
+ // before.
+ PLOG(INFO) << "No prior cache-info file: " << QuotePath(cache_info_filename_);
+ metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts);
+ *cleanup_required = true;
+ return false;
+ }
+
+ // Check whether the current cached module info differs from the current module info.
+ const art_apex::ModuleInfoList* cached_module_info_list = cache_info->getFirstModuleInfoList();
+
+ if (cached_module_info_list == nullptr) {
+ LOG(INFO) << "Missing APEX info list from cache-info.";
+ metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
+ *cleanup_required = true;
+ return false;
+ }
+
+ std::unordered_map<std::string, const art_apex::ModuleInfo*> cached_module_info_map;
+ for (const art_apex::ModuleInfo& module_info : cached_module_info_list->getModuleInfo()) {
+ if (!module_info.hasName()) {
+ LOG(INFO) << "Unexpected module info from cache-info. Missing module name.";
+ metrics.SetTrigger(OdrMetrics::Trigger::kUnknown);
+ *cleanup_required = true;
+ return false;
+ }
+ cached_module_info_map[module_info.getName()] = &module_info;
+ }
+
+ for (const apex::ApexInfo& current_apex_info : apex_info_list) {
+ auto it = cached_module_info_map.find(current_apex_info.getModuleName());
+ if (it == cached_module_info_map.end()) {
+ LOG(INFO) << "Missing APEX info from cache-info (" << current_apex_info.getModuleName()
+ << ").";
+ metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
+ *cleanup_required = true;
+ return false;
+ }
+
+ const art_apex::ModuleInfo* cached_module_info = it->second;
+
+ if (cached_module_info->getVersionCode() != current_apex_info.getVersionCode()) {
+ LOG(INFO) << "APEX (" << current_apex_info.getModuleName() << ") version code mismatch ("
+ << cached_module_info->getVersionCode()
+ << " != " << current_apex_info.getVersionCode() << ").";
+ metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
+ *cleanup_required = true;
+ return false;
+ }
+
+ if (cached_module_info->getVersionName() != current_apex_info.getVersionName()) {
+ LOG(INFO) << "APEX (" << current_apex_info.getModuleName() << ") version name mismatch ("
+ << cached_module_info->getVersionName()
+ << " != " << current_apex_info.getVersionName() << ").";
+ metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
+ *cleanup_required = true;
+ return false;
+ }
+
+ if (!cached_module_info->hasLastUpdateMillis() ||
+ cached_module_info->getLastUpdateMillis() != current_apex_info.getLastUpdateMillis()) {
+ LOG(INFO) << "APEX (" << current_apex_info.getModuleName()
+ << ") last update time mismatch (" << cached_module_info->getLastUpdateMillis()
+ << " != " << current_apex_info.getLastUpdateMillis() << ").";
+ metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
+ *cleanup_required = true;
+ return false;
+ }
}
// Check system server components.
@@ -619,10 +751,6 @@
//
// The system_server components may change unexpectedly, for example an OTA could update
// services.jar.
- auto cleanup_system_server_return = [this](ExitCode exit_code) {
- return RemoveSystemServerArtifactsFromData() ? exit_code : ExitCode::kCleanupFailed;
- };
-
const std::vector<art_apex::Component> expected_system_server_components =
GenerateSystemServerComponents();
if (expected_system_server_components.size() != 0 &&
@@ -630,7 +758,8 @@
!cache_info->getFirstSystemServerClasspath()->hasComponent())) {
LOG(INFO) << "Missing SystemServerClasspath components.";
metrics.SetTrigger(OdrMetrics::Trigger::kDexFilesChanged);
- return cleanup_system_server_return(ExitCode::kCompilationRequired);
+ *cleanup_required = true;
+ return false;
}
const std::vector<art_apex::Component>& system_server_components =
@@ -638,7 +767,8 @@
if (!CheckComponents(expected_system_server_components, system_server_components, &error_msg)) {
LOG(INFO) << "SystemServerClasspath components mismatch: " << error_msg;
metrics.SetTrigger(OdrMetrics::Trigger::kDexFilesChanged);
- return cleanup_system_server_return(ExitCode::kCompilationRequired);
+ *cleanup_required = true;
+ return false;
}
const std::vector<art_apex::Component> expected_bcp_components =
@@ -647,7 +777,8 @@
(!cache_info->hasBootClasspath() || !cache_info->getFirstBootClasspath()->hasComponent())) {
LOG(INFO) << "Missing BootClasspath components.";
metrics.SetTrigger(OdrMetrics::Trigger::kDexFilesChanged);
- return cleanup_system_server_return(ExitCode::kCompilationRequired);
+ *cleanup_required = true;
+ return false;
}
const std::vector<art_apex::Component>& bcp_components =
@@ -657,33 +788,110 @@
metrics.SetTrigger(OdrMetrics::Trigger::kDexFilesChanged);
// Boot classpath components can be dependencies of system_server components, so system_server
// components need to be recompiled if boot classpath components are changed.
- return cleanup_system_server_return(ExitCode::kCompilationRequired);
+ *cleanup_required = true;
+ return false;
}
- // Cache info looks good, check all compilation artifacts exist.
- auto cleanup_boot_extensions_return = [this](ExitCode exit_code, InstructionSet isa) {
- return RemoveBootExtensionArtifactsFromData(isa) ? exit_code : ExitCode::kCleanupFailed;
- };
-
- for (const InstructionSet isa : config_.GetBootExtensionIsas()) {
- if (!BootExtensionArtifactsExistOnData(isa, &error_msg)) {
- LOG(INFO) << "Incomplete boot extension artifacts. " << error_msg;
- metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts);
- return cleanup_boot_extensions_return(ExitCode::kCompilationRequired, isa);
- }
- }
-
- if (!SystemServerArtifactsExistOnData(&error_msg)) {
+ if (!SystemServerArtifactsExist(/*on_system=*/false, &error_msg)) {
LOG(INFO) << "Incomplete system_server artifacts. " << error_msg;
// No clean-up is required here: we have boot extension artifacts. The method
// `SystemServerArtifactsExistOnData()` checks in compilation order so it is possible some of
// the artifacts are here. We likely ran out of space compiling the system_server artifacts.
// Any artifacts present are usable.
metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts);
- return ExitCode::kCompilationRequired;
+ *cleanup_required = false;
+ return false;
}
- return ExitCode::kOkay;
+ *cleanup_required = false;
+ return true;
+ }
+
+ // Returns the exit code, a list of ISAs that boot extensions should be compiled for, and a
+ // boolean indicating whether the system server should be compiled.
+ WARN_UNUSED ExitCode
+ CheckArtifactsAreUpToDate(OdrMetrics& metrics,
+ /*out*/ std::vector<InstructionSet>* compile_boot_extensions,
+ /*out*/ bool* compile_system_server) {
+ metrics.SetStage(OdrMetrics::Stage::kCheck);
+
+ // Clean-up helper used to simplify clean-ups and handling failures there.
+ auto cleanup_and_compile_all = [&, this]() {
+ *compile_boot_extensions = config_.GetBootExtensionIsas();
+ *compile_system_server = true;
+ return RemoveArtifactsDirectory() ? ExitCode::kCompilationRequired : ExitCode::kCleanupFailed;
+ };
+
+ std::optional<std::vector<apex::ApexInfo>> apex_info_list = GetApexInfoList();
+ if (!apex_info_list.has_value()) {
+ // This should never happen, further up-to-date checks are not possible if it does.
+ LOG(ERROR) << "Could not get APEX info.";
+ metrics.SetTrigger(OdrMetrics::Trigger::kUnknown);
+ return cleanup_and_compile_all();
+ }
+
+ std::optional<apex::ApexInfo> art_apex_info = GetArtApexInfo(apex_info_list.value());
+ if (!art_apex_info.has_value()) {
+ // This should never happen, further up-to-date checks are not possible if it does.
+ LOG(ERROR) << "Could not get ART APEX info.";
+ metrics.SetTrigger(OdrMetrics::Trigger::kUnknown);
+ return cleanup_and_compile_all();
+ }
+
+ // Record ART APEX version for metrics reporting.
+ metrics.SetArtApexVersion(art_apex_info->getVersionCode());
+
+ // Record ART APEX last update milliseconds (used in compilation log).
+ metrics.SetArtApexLastUpdateMillis(art_apex_info->getLastUpdateMillis());
+
+ std::optional<art_apex::CacheInfo> cache_info = ReadCacheInfo();
+ if (!cache_info.has_value() && OS::FileExists(cache_info_filename_.c_str())) {
+ // This should not happen unless odrefresh is updated to a new version that is not
+ // compatible with an old cache-info file. Further up-to-date checks are not possible if it
+ // does.
+ PLOG(ERROR) << "Failed to parse cache-info file: " << QuotePath(cache_info_filename_);
+ metrics.SetTrigger(OdrMetrics::Trigger::kUnknown);
+ return cleanup_and_compile_all();
+ }
+
+ InstructionSet system_server_isa = config_.GetSystemServerIsa();
+ bool cleanup_required;
+
+ for (const InstructionSet isa : config_.GetBootExtensionIsas()) {
+ cleanup_required = false;
+ if (!CheckBootExtensionArtifactsAreUpToDate(
+ metrics, isa, art_apex_info.value(), cache_info, &cleanup_required)) {
+ compile_boot_extensions->push_back(isa);
+ // system_server artifacts are invalid without valid boot extension artifacts.
+ if (isa == system_server_isa) {
+ *compile_system_server = true;
+ if (!RemoveSystemServerArtifactsFromData()) {
+ return ExitCode::kCleanupFailed;
+ }
+ }
+ }
+ if (cleanup_required) {
+ if (!RemoveBootExtensionArtifactsFromData(isa)) {
+ return ExitCode::kCleanupFailed;
+ }
+ }
+ }
+
+ cleanup_required = false;
+ if (!*compile_system_server &&
+ !CheckSystemServerArtifactsAreUpToDate(
+ metrics, apex_info_list.value(), cache_info, &cleanup_required)) {
+ *compile_system_server = true;
+ }
+ if (cleanup_required) {
+ if (!RemoveSystemServerArtifactsFromData()) {
+ return ExitCode::kCleanupFailed;
+ }
+ }
+
+ return (!compile_boot_extensions->empty() || *compile_system_server)
+ ? ExitCode::kCompilationRequired
+ : ExitCode::kOkay;
}
static void AddDex2OatCommonOptions(/*inout*/ std::vector<std::string>* args) {
@@ -934,8 +1142,7 @@
bool success = true;
for (const std::string& jar_path : systemserver_compilable_jars_) {
- const std::string image_location =
- GetSystemServerImagePath(/*on_system=*/false, jar_path);
+ const std::string image_location = GetSystemServerImagePath(/*on_system=*/false, jar_path);
const OdrArtifacts artifacts = OdrArtifacts::ForSystemServer(image_location);
LOG(INFO) << "Removing system_server artifacts on /data for " << QuotePath(jar_path);
success &= RemoveArtifacts(artifacts);
@@ -998,17 +1205,11 @@
return true;
}
- bool success = true;
- if (isa == config_.GetSystemServerIsa()) {
- // system_server artifacts are invalid without boot extension artifacts.
- success &= RemoveSystemServerArtifactsFromData();
- }
-
- const std::string apexdata_image_location = GetBootImageExtensionImagePath(isa);
+ const std::string apexdata_image_location =
+ GetBootImageExtensionImagePath(/*on_system=*/false, isa);
LOG(INFO) << "Removing boot class path artifacts on /data for "
<< QuotePath(apexdata_image_location);
- success &= RemoveArtifacts(OdrArtifacts::ForBootImageExtension(apexdata_image_location));
- return success;
+ return RemoveArtifacts(OdrArtifacts::ForBootImageExtension(apexdata_image_location));
}
// Verify whether boot extension artifacts for `isa` are valid on system partition or in apexdata.
@@ -1029,13 +1230,13 @@
//
// Returns ExitCode::kOkay if artifacts are up-to-date, ExitCode::kCompilationRequired otherwise.
//
- // NB This is the main function used by the --check command-line option. When invoked with
- // --compile, we only recompile the out-of-date artifacts, not all (see `Odrefresh::Compile`).
+ // NB This is the main function used by the --verify command-line option.
WARN_UNUSED ExitCode VerifyArtifactsAreUpToDate() {
ExitCode exit_code = ExitCode::kOkay;
for (const InstructionSet isa : config_.GetBootExtensionIsas()) {
if (!VerifyBootExtensionArtifactsAreUpToDate(isa)) {
- if (!RemoveBootExtensionArtifactsFromData(isa)) {
+ // system_server artifacts are invalid without valid boot extension artifacts.
+ if (!RemoveSystemServerArtifactsFromData() || !RemoveBootExtensionArtifactsFromData(isa)) {
return ExitCode::kCleanupFailed;
}
exit_code = ExitCode::kCompilationRequired;
@@ -1095,13 +1296,14 @@
}
}
- std::string GetBootImageExtensionImagePath(const InstructionSet isa) const {
+ std::string GetBootImageExtensionImagePath(bool on_system, const InstructionSet isa) const {
// Typically "/data/misc/apexdata/com.android.art/dalvik-cache/<isa>/boot-framework.art".
- return GetSystemImageFilename(GetBootImageExtensionImage(/*on_system=*/false).c_str(), isa);
+ return GetSystemImageFilename(GetBootImageExtensionImage(on_system).c_str(), isa);
}
std::string GetSystemServerImagePath(bool on_system, const std::string& jar_path) const {
if (on_system) {
+ // TODO(b/194150908): Define a path for "preopted" APEX artifacts.
const std::string jar_name = android::base::Basename(jar_path);
const std::string image_name = ReplaceFileExtension(jar_name, "art");
const char* isa_str = GetInstructionSetString(config_.GetSystemServerIsa());
@@ -1181,7 +1383,7 @@
return false;
}
- const std::string image_location = GetBootImageExtensionImagePath(isa);
+ const std::string image_location = GetBootImageExtensionImagePath(/*on_system=*/false, isa);
const OdrArtifacts artifacts = OdrArtifacts::ForBootImageExtension(image_location);
CHECK_EQ(GetApexDataOatFilename(boot_extension_compilable_jars_.front().c_str(), isa),
artifacts.OatPath());
@@ -1342,7 +1544,8 @@
return false;
}
std::unique_ptr<File> file(OS::OpenFileForReading(bcp_packages.c_str()));
- args.emplace_back(android::base::StringPrintf("--updatable-bcp-packages-fd=%d", file->Fd()));
+ args.emplace_back(
+ android::base::StringPrintf("--updatable-bcp-packages-fd=%d", file->Fd()));
readonly_files_raii.push_back(std::move(file));
}
@@ -1453,14 +1656,11 @@
return true;
}
- WARN_UNUSED ExitCode Compile(OdrMetrics& metrics, bool force_compile) const {
+ WARN_UNUSED ExitCode Compile(OdrMetrics& metrics,
+ const std::vector<InstructionSet>& compile_boot_extensions,
+ bool compile_system_server) const {
const char* staging_dir = nullptr;
metrics.SetStage(OdrMetrics::Stage::kPreparation);
- // Clean-up existing files.
- if (force_compile && !RemoveArtifactsDirectory()) {
- metrics.SetStatus(OdrMetrics::Status::kIoError);
- return ExitCode::kCleanupFailed;
- }
// Create staging area and assign label for generating compilation artifacts.
if (PaletteCreateOdrefreshStagingDirectory(&staging_dir) != PALETTE_STATUS_OK) {
@@ -1478,36 +1678,28 @@
const auto& bcp_instruction_sets = config_.GetBootExtensionIsas();
DCHECK(!bcp_instruction_sets.empty() && bcp_instruction_sets.size() <= 2);
- for (const InstructionSet isa : bcp_instruction_sets) {
+ for (const InstructionSet isa : compile_boot_extensions) {
auto stage = (isa == bcp_instruction_sets.front()) ?
OdrMetrics::Stage::kPrimaryBootClasspath :
OdrMetrics::Stage::kSecondaryBootClasspath;
metrics.SetStage(stage);
- if (force_compile || !BootExtensionArtifactsExistOnData(isa, &error_msg)) {
- // Remove artifacts we are about to generate. Ordinarily these are removed in the checking
- // step, but this is not always run (e.g. during manual testing).
- if (!RemoveBootExtensionArtifactsFromData(isa)) {
- return ExitCode::kCleanupFailed;
- }
+ if (!CheckCompilationSpace()) {
+ metrics.SetStatus(OdrMetrics::Status::kNoSpace);
+ // Return kOkay so odsign will keep and sign whatever we have been able to compile.
+ return ExitCode::kOkay;
+ }
- if (!CheckCompilationSpace()) {
- metrics.SetStatus(OdrMetrics::Status::kNoSpace);
- // Return kOkay so odsign will keep and sign whatever we have been able to compile.
- return ExitCode::kOkay;
+ if (!CompileBootExtensionArtifacts(
+ isa, staging_dir, metrics, &dex2oat_invocation_count, &error_msg)) {
+ LOG(ERROR) << "Compilation of BCP failed: " << error_msg;
+ if (!config_.GetDryRun() && !RemoveDirectory(staging_dir)) {
+ return ExitCode::kCleanupFailed;
}
-
- if (!CompileBootExtensionArtifacts(
- isa, staging_dir, metrics, &dex2oat_invocation_count, &error_msg)) {
- LOG(ERROR) << "Compilation of BCP failed: " << error_msg;
- if (!config_.GetDryRun() && !RemoveDirectory(staging_dir)) {
- return ExitCode::kCleanupFailed;
- }
- return ExitCode::kCompilationFailed;
- }
+ return ExitCode::kCompilationFailed;
}
}
- if (force_compile || !SystemServerArtifactsExistOnData(&error_msg)) {
+ if (compile_system_server) {
metrics.SetStage(OdrMetrics::Stage::kSystemServerClasspath);
if (!CheckCompilationSpace()) {
@@ -1654,11 +1846,15 @@
OnDeviceRefresh odr(config);
for (int i = 0; i < argc; ++i) {
std::string_view action(argv[i]);
+ std::vector<InstructionSet> compile_boot_extensions;
+ bool compile_system_server;
if (action == "--check") {
// Fast determination of whether artifacts are up to date.
- return odr.CheckArtifactsAreUpToDate(metrics);
+ return odr.CheckArtifactsAreUpToDate(
+ metrics, &compile_boot_extensions, &compile_system_server);
} else if (action == "--compile") {
- const ExitCode exit_code = odr.CheckArtifactsAreUpToDate(metrics);
+ const ExitCode exit_code = odr.CheckArtifactsAreUpToDate(
+ metrics, &compile_boot_extensions, &compile_system_server);
if (exit_code != ExitCode::kCompilationRequired) {
return exit_code;
}
@@ -1668,14 +1864,22 @@
metrics.GetTrigger())) {
return ExitCode::kOkay;
}
- ExitCode compile_result = odr.Compile(metrics, /*force_compile=*/false);
+ ExitCode compile_result =
+ odr.Compile(metrics, compile_boot_extensions, compile_system_server);
compilation_log.Log(metrics.GetArtApexVersion(),
metrics.GetArtApexLastUpdateMillis(),
metrics.GetTrigger(),
compile_result);
return compile_result;
} else if (action == "--force-compile") {
- return odr.Compile(metrics, /*force_compile=*/true);
+ // Clean-up existing files.
+ if (!odr.RemoveArtifactsDirectory()) {
+ metrics.SetStatus(OdrMetrics::Status::kIoError);
+ return ExitCode::kCleanupFailed;
+ }
+ return odr.Compile(metrics,
+ /*compile_boot_extensions=*/config.GetBootExtensionIsas(),
+ /*compile_system_server=*/true);
} else if (action == "--verify") {
// Slow determination of whether artifacts are up to date. These are too slow for checking
// during boot (b/181689036).
diff --git a/odrefresh/schema/current.txt b/odrefresh/schema/current.txt
index 9a38400..4757c87 100644
--- a/odrefresh/schema/current.txt
+++ b/odrefresh/schema/current.txt
@@ -1,32 +1,24 @@
// Signature format: 2.0
package com.android.art {
- public class ArtModuleInfo {
- ctor public ArtModuleInfo();
- method public long getLastUpdateMillis();
- method public long getVersionCode();
- method public String getVersionName();
- method public void setLastUpdateMillis(long);
- method public void setVersionCode(long);
- method public void setVersionName(String);
- }
-
- public class BootClasspath {
- ctor public BootClasspath();
- method public com.android.art.Component getComponent();
- method public void setComponent(com.android.art.Component);
- }
-
public class CacheInfo {
ctor public CacheInfo();
- method public com.android.art.ArtModuleInfo getArtModuleInfo();
- method public com.android.art.BootClasspath getBootClasspath();
- method public com.android.art.Dex2oatBootClasspath getDex2oatBootClasspath();
- method public com.android.art.SystemServerClasspath getSystemServerClasspath();
- method public void setArtModuleInfo(com.android.art.ArtModuleInfo);
- method public void setBootClasspath(com.android.art.BootClasspath);
- method public void setDex2oatBootClasspath(com.android.art.Dex2oatBootClasspath);
- method public void setSystemServerClasspath(com.android.art.SystemServerClasspath);
+ method public com.android.art.ModuleInfo getArtModuleInfo();
+ method public com.android.art.Classpath getBootClasspath();
+ method public com.android.art.Classpath getDex2oatBootClasspath();
+ method public com.android.art.ModuleInfoList getModuleInfoList();
+ method public com.android.art.Classpath getSystemServerClasspath();
+ method public void setArtModuleInfo(com.android.art.ModuleInfo);
+ method public void setBootClasspath(com.android.art.Classpath);
+ method public void setDex2oatBootClasspath(com.android.art.Classpath);
+ method public void setModuleInfoList(com.android.art.ModuleInfoList);
+ method public void setSystemServerClasspath(com.android.art.Classpath);
+ }
+
+ public class Classpath {
+ ctor public Classpath();
+ method public com.android.art.Component getComponent();
+ method public void setComponent(com.android.art.Component);
}
public class Component {
@@ -39,16 +31,22 @@
method public void setSize(java.math.BigInteger);
}
- public class Dex2oatBootClasspath {
- ctor public Dex2oatBootClasspath();
- method public com.android.art.Component getComponent();
- method public void setComponent(com.android.art.Component);
+ public class ModuleInfo {
+ ctor public ModuleInfo();
+ method public long getLastUpdateMillis();
+ method public String getName();
+ method public long getVersionCode();
+ method public String getVersionName();
+ method public void setLastUpdateMillis(long);
+ method public void setName(String);
+ method public void setVersionCode(long);
+ method public void setVersionName(String);
}
- public class SystemServerClasspath {
- ctor public SystemServerClasspath();
- method public com.android.art.Component getComponent();
- method public void setComponent(com.android.art.Component);
+ public class ModuleInfoList {
+ ctor public ModuleInfoList();
+ method public com.android.art.ModuleInfo getModuleInfo();
+ method public void setModuleInfo(com.android.art.ModuleInfo);
}
public class XmlParser {
diff --git a/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java b/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java
index b1fb4a2..d5ea545 100644
--- a/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java
+++ b/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java
@@ -72,6 +72,10 @@
private final String[] ZYGOTE_NAMES = new String[] {"zygote", "zygote64"};
+ private Set<String> mZygoteArtifacts;
+ private Set<String> mSystemServerArtifacts;
+ private long mBootTimeMs;
+
@Before
public void setUp() throws Exception {
assumeTrue("Updating APEX is not supported", mInstallUtils.isApexUpdateSupported());
@@ -79,6 +83,13 @@
mInstallUtils.installApexes(APEX_FILENAME);
removeCompilationLogToAvoidBackoff();
reboot();
+
+ mZygoteArtifacts = new HashSet<>();
+ for (String zygoteName : ZYGOTE_NAMES) {
+ mZygoteArtifacts.addAll(getZygoteLoadedArtifacts(zygoteName).orElse(new HashSet<>()));
+ }
+ mSystemServerArtifacts = getSystemServerLoadedArtifacts();
+ mBootTimeMs = getDevice().getDeviceDate();
}
@After
@@ -125,18 +136,18 @@
* process does not exist.
*/
private Optional<Set<String>> getZygoteLoadedArtifacts(String zygoteName) throws Exception {
- final CommandResult pgrepResult = getDevice().executeShellV2Command("pgrep " + zygoteName);
+ final CommandResult pgrepResult = getDevice().executeShellV2Command("pidof " + zygoteName);
if (pgrepResult.getExitCode() != 0) {
return Optional.empty();
}
final String zygotePid = pgrepResult.getStdout();
- final String bootExtensionName = "boot-framework";
- return Optional.of(getMappedArtifacts(zygotePid, bootExtensionName));
+ final String grepPattern = ART_APEX_DALVIK_CACHE_DIRNAME + ".*boot-framework";
+ return Optional.of(getMappedArtifacts(zygotePid, grepPattern));
}
private Set<String> getSystemServerLoadedArtifacts() throws Exception {
- String systemServerPid = getDevice().executeShellCommand("pgrep system_server");
+ String systemServerPid = getDevice().executeShellCommand("pidof system_server");
assertTrue(systemServerPid != null);
// system_server artifacts are in the APEX data dalvik cache and names all contain
@@ -243,116 +254,146 @@
verifyGeneratedArtifactsLoaded();
}
- @Test
- public void verifyGeneratedArtifactsLoadedForSamegradeUpdate() throws Exception {
- // Install the same APEX effecting a samegrade update. The setUp method has installed it
- // before us.
- mInstallUtils.installApexes(APEX_FILENAME);
- reboot();
-
- final boolean adbEnabled = getDevice().enableAdbRoot();
- assertTrue("ADB root failed and required to get odrefresh compilation log", adbEnabled);
-
- // Check that odrefresh logged a compilation attempt due to samegrade ART APEX install.
- String[] logLines = getDevice().pullFileContents(ODREFRESH_COMPILATION_LOG).split("\n");
- assertTrue(
- "Expected 3 lines in " + ODREFRESH_COMPILATION_LOG + ", found " + logLines.length,
- logLines.length == 3);
-
- // Check that the compilation log entries are reasonable, ie times move forward.
- // The first line of the log is the log format version number.
- String[] firstUpdateEntry = logLines[1].split(" ");
- String[] secondUpdateEntry = logLines[2].split(" ");
- final int LOG_ENTRY_FIELDS = 5;
- assertTrue(
- "Unexpected number of fields: " + firstUpdateEntry.length + " != " +
- LOG_ENTRY_FIELDS,
- firstUpdateEntry.length == LOG_ENTRY_FIELDS);
- assertTrue(firstUpdateEntry.length == secondUpdateEntry.length);
-
- final int LAST_UPDATE_MILLIS_INDEX = 1;
- final int COMPILATION_TIME_INDEX = 3;
- for (int i = 0; i < firstUpdateEntry.length; ++i) {
- final long firstField = Long.parseLong(firstUpdateEntry[i]);
- final long secondField = Long.parseLong(secondUpdateEntry[i]);
- if (i == LAST_UPDATE_MILLIS_INDEX) {
- // The second APEX lastUpdateMillis should be after the first, but a clock
- // adjustment might reverse the order so we can't assert this (b/194365586).
- assertTrue(
- "Last update times are expected to differ, but they are equal " +
- firstField + " == " + secondField,
- firstField != secondField);
- } else if (i == COMPILATION_TIME_INDEX) {
- // The second compilation time should be after the first compilation time, but
- // a clock adjustment might reverse the order so we can't assert this
- // (b/194365586).
- assertTrue(
- "Compilation times are expected to differ, but they are equal " +
- firstField + " == " + secondField,
- firstField != secondField);
- } else {
- // The remaining fields should be the same, ie trigger for compilation.
- assertTrue(
- "Compilation entries differ for position " + i + ": " +
- firstField + " != " + secondField,
- firstField == secondField);
- }
- }
-
- verifyGeneratedArtifactsLoaded();
- }
-
/**
- * A workaround to simulate that an APEX has been upgraded. We could install a real APEX, but
- * that would introduce an extra dependency to this test, which we want to avoid.
+ * Checks the input line by line and replaces all lines that match the regex with the given
+ * replacement.
*/
- void mutateCacheInfo() throws Exception {
- String cacheInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
+ String replaceLine(String input, String regex, String replacement) {
StringBuffer output = new StringBuffer();
- // com.android.wifi is a module in $BOOTCLASSPATH, but not in $DEX2OATBOOTCLASSPATH.
- Pattern p = Pattern.compile("(.*/apex/com\\.android\\.wifi.*checksums=\\\").*?(\\\".*)");
- for (String line : cacheInfo.split("\n")) {
+ Pattern p = Pattern.compile(regex);
+ for (String line : input.split("\n")) {
Matcher m = p.matcher(line);
if (m.matches()) {
- m.appendReplacement(output, "$1aaaaaaaa$2");
+ m.appendReplacement(output, replacement);
output.append("\n");
} else {
output.append(line + "\n");
}
}
- getDevice().pushString(output.toString(), CACHE_INFO_FILE);
+ return output.toString();
}
- long getModifiedTimeSec(String filename) throws Exception {
+ /**
+ * Simulates that there is an OTA that updates a boot classpath jar.
+ */
+ void simulateBootClasspathOta() throws Exception {
+ String cacheInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
+ // Replace the cached checksum of /system/framework/framework.jar with "aaaaaaaa".
+ cacheInfo = replaceLine(
+ cacheInfo,
+ "(.*/system/framework/framework\\.jar.*checksums=\").*?(\".*)",
+ "$1aaaaaaaa$2");
+ getDevice().pushString(cacheInfo, CACHE_INFO_FILE);
+ }
+
+ /**
+ * Simulates that there is an OTA that updates a system server jar.
+ */
+ void simulateSystemServerOta() throws Exception {
+ String cacheInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
+ // Replace the cached checksum of /system/framework/services.jar with "aaaaaaaa".
+ cacheInfo = replaceLine(
+ cacheInfo,
+ "(.*/system/framework/services\\.jar.*checksums=\").*?(\".*)",
+ "$1aaaaaaaa$2");
+ getDevice().pushString(cacheInfo, CACHE_INFO_FILE);
+ }
+
+ /**
+ * Simulates that an ART APEX has been upgraded.
+ */
+ void simulateArtApexUpgrade() throws Exception {
+ String apexInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
+ // Replace the lastUpdateMillis of com.android.art with "1".
+ apexInfo = replaceLine(
+ apexInfo,
+ "(.*com\\.android\\.art.*lastUpdateMillis=\").*?(\".*)",
+ "$11$2");
+ getDevice().pushString(apexInfo, CACHE_INFO_FILE);
+ }
+
+ /**
+ * Simulates that an APEX has been upgraded. We could install a real APEX, but that would
+ * introduce an extra dependency to this test, which we want to avoid.
+ */
+ void simulateApexUpgrade() throws Exception {
+ String apexInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
+ // Replace the lastUpdateMillis of com.android.wifi with "1".
+ apexInfo = replaceLine(
+ apexInfo,
+ "(.*com\\.android\\.wifi.*lastUpdateMillis=\").*?(\".*)",
+ "$11$2");
+ getDevice().pushString(apexInfo, CACHE_INFO_FILE);
+ }
+
+ long getModifiedTimeMs(String filename) throws Exception {
String timeStr = getDevice()
- .executeShellCommand(String.format("stat -c '%%Y' '%s'", filename))
+ .executeShellCommand(String.format("stat -c '%%.3Y' '%s'", filename))
.trim();
- return Long.parseLong(timeStr);
+ return (long)(Double.parseDouble(timeStr) * 1000);
+ }
+
+ void assertArtifactsModifiedAfter(Set<String> artifacts, long timeMs) throws Exception {
+ for (String artifact : artifacts) {
+ assertTrue("Artifact " + artifact + " is not re-compiled",
+ getModifiedTimeMs(artifact) > timeMs);
+ }
+ }
+
+ void assertArtifactsNotModifiedAfter(Set<String> artifacts, long timeMs) throws Exception {
+ for (String artifact : artifacts) {
+ assertTrue("Artifact " + artifact + " is unexpectedly re-compiled",
+ getModifiedTimeMs(artifact) < timeMs);
+ }
}
@Test
- public void verifyApexUpgradeTriggersCompilation() throws Exception {
- Set<String> zygoteArtifacts = new HashSet<>();
- for (String zygoteName : ZYGOTE_NAMES) {
- zygoteArtifacts.addAll(getZygoteLoadedArtifacts(zygoteName).orElse(new HashSet<>()));
- }
- Set<String> systemServerArtifacts = getSystemServerLoadedArtifacts();
- long timeSec = Math.floorDiv(getDevice().getDeviceDate(), 1000);
-
- mutateCacheInfo();
+ public void verifyArtSamegradeUpdateTriggersCompilation() throws Exception {
+ simulateArtApexUpgrade();
removeCompilationLogToAvoidBackoff();
- CommandResult result =
- getDevice().executeShellV2Command("odrefresh --compile");
+ getDevice().executeShellV2Command("odrefresh --compile");
- for (String artifact : zygoteArtifacts) {
- assertTrue("Boot classpath artifact " + artifact + " is re-compiled",
- getModifiedTimeSec(artifact) < timeSec);
- }
+ assertArtifactsModifiedAfter(mZygoteArtifacts, mBootTimeMs);
+ assertArtifactsModifiedAfter(mSystemServerArtifacts, mBootTimeMs);
+ }
- for (String artifact : systemServerArtifacts) {
- assertTrue("System server artifact " + artifact + " is not re-compiled",
- getModifiedTimeSec(artifact) >= timeSec);
- }
+ @Test
+ public void verifyOtherApexSamegradeUpdateTriggersCompilation() throws Exception {
+ simulateApexUpgrade();
+ removeCompilationLogToAvoidBackoff();
+ getDevice().executeShellV2Command("odrefresh --compile");
+
+ assertArtifactsNotModifiedAfter(mZygoteArtifacts, mBootTimeMs);
+ assertArtifactsModifiedAfter(mSystemServerArtifacts, mBootTimeMs);
+ }
+
+ @Test
+ public void verifyBootClasspathOtaTriggersCompilation() throws Exception {
+ simulateBootClasspathOta();
+ removeCompilationLogToAvoidBackoff();
+ getDevice().executeShellV2Command("odrefresh --compile");
+
+ assertArtifactsModifiedAfter(mZygoteArtifacts, mBootTimeMs);
+ assertArtifactsModifiedAfter(mSystemServerArtifacts, mBootTimeMs);
+ }
+
+ @Test
+ public void verifySystemServerOtaTriggersCompilation() throws Exception {
+ simulateSystemServerOta();
+ removeCompilationLogToAvoidBackoff();
+ getDevice().executeShellV2Command("odrefresh --compile");
+
+ assertArtifactsNotModifiedAfter(mZygoteArtifacts, mBootTimeMs);
+ assertArtifactsModifiedAfter(mSystemServerArtifacts, mBootTimeMs);
+ }
+
+ @Test
+ public void verifyNoCompilationWhenCacheIsGood() throws Exception {
+ removeCompilationLogToAvoidBackoff();
+ getDevice().executeShellV2Command("odrefresh --compile");
+
+ assertArtifactsNotModifiedAfter(mZygoteArtifacts, mBootTimeMs);
+ assertArtifactsNotModifiedAfter(mSystemServerArtifacts, mBootTimeMs);
}
private boolean haveCompilationLog() throws Exception {