Add a Compilation OS mode to odrefresh.
When the --compilation-os-mode is set, odrefresh should:
- Unconditionally compile everything
- Write `compilationOsMode="true"` to the cache info and omit
`lastUpdateMillis`
- On the next boot, `--check` returns `kCompilationRequired`, and
`--compile` updates the cache info to fill `lastUpdateMillis` but does
not actually re-compile anything
Bug: 211458160
Test: odsign_e2e_tests
Change-Id: Ie913846d2000f9b57aa0e5133f9d25eb24aa0052
diff --git a/odrefresh/CacheInfo.xsd b/odrefresh/CacheInfo.xsd
index 3aa94a5..ff83914 100644
--- a/odrefresh/CacheInfo.xsd
+++ b/odrefresh/CacheInfo.xsd
@@ -24,6 +24,8 @@
`/data/misc/apexdata/com.android.art/dalvik-cache` -->
<xs:element name="cacheInfo">
<xs:complexType>
+ <!-- True if the cache info is generated in the Compilation OS. -->
+ <xs:attribute name="compilationOsMode" type="xs:boolean" />
<xs:sequence>
<xs:element name="artModuleInfo" minOccurs="1" maxOccurs="1" type="t:moduleInfo" />
<xs:element name="moduleInfoList" minOccurs="1" maxOccurs="1" type="t:moduleInfoList" />
@@ -49,8 +51,9 @@
<xs:attribute name="versionCode" type="xs:long" use="required" />
<!-- 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 APEX from `/apex/apex-info-list.xml`. -->
- <xs:attribute name="lastUpdateMillis" type="xs:long" use="required" />
+ <!-- Module lastUpdateMillis for the active APEX from `/apex/apex-info-list.xml`. This field is
+ not set if the cache info is generated in Compilation OS. -->
+ <xs:attribute name="lastUpdateMillis" type="xs:long" />
</xs:complexType>
<!-- Components of a classpath. -->
diff --git a/odrefresh/odr_config.h b/odrefresh/odr_config.h
index af7c0a7..855b7a2 100644
--- a/odrefresh/odr_config.h
+++ b/odrefresh/odr_config.h
@@ -72,6 +72,7 @@
time_t max_execution_seconds_ = kMaximumExecutionSeconds;
time_t max_child_process_seconds_ = kMaxChildProcessSeconds;
std::string standalone_system_server_jars_;
+ bool compilation_os_mode_;
// Staging directory for artifacts. The directory must exist and will be automatically removed
// after compilation. If empty, use the default directory.
@@ -154,6 +155,7 @@
}
time_t GetMaxExecutionSeconds() const { return max_execution_seconds_; }
time_t GetMaxChildProcessSeconds() const { return max_child_process_seconds_; }
+ bool GetCompilationOsMode() const { return compilation_os_mode_; }
void SetApexInfoListFile(const std::string& file_path) { apex_info_list_file_ = file_path; }
void SetArtBinDir(const std::string& art_bin_dir) { art_bin_dir_ = art_bin_dir; }
@@ -204,6 +206,8 @@
standalone_system_server_jars_ = jars;
}
+ void SetCompilationOsMode(bool value) { compilation_os_mode_ = value; }
+
private:
// Returns a pair for the possible instruction sets for the configured instruction set
// architecture. The first item is the 32-bit architecture and the second item is the 64-bit
diff --git a/odrefresh/odrefresh.cc b/odrefresh/odrefresh.cc
index 9517e54..767f406 100644
--- a/odrefresh/odrefresh.cc
+++ b/odrefresh/odrefresh.cc
@@ -184,24 +184,26 @@
// Returns cache provenance information based on the current APEX version and filesystem
// information.
-art_apex::ModuleInfo GenerateModuleInfo(const apex::ApexInfo& apex_info) {
+art_apex::ModuleInfo GenerateModuleInfo(const apex::ApexInfo& apex_info,
+ bool include_last_update_millis) {
// The lastUpdateMillis is an addition to ApexInfoList.xsd to support samegrade installs.
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};
+ return art_apex::ModuleInfo{
+ apex_info.getModuleName(),
+ apex_info.getVersionCode(),
+ apex_info.getVersionName(),
+ include_last_update_millis ? std::make_optional(last_update_millis) : std::nullopt};
}
// Returns cache provenance information for all APEXes.
std::vector<art_apex::ModuleInfo> GenerateModuleInfoList(
- const std::vector<apex::ApexInfo>& apex_info_list) {
+ const std::vector<apex::ApexInfo>& apex_info_list, bool include_last_update_millis) {
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);
+ std::bind(GenerateModuleInfo, std::placeholders::_1, include_last_update_millis));
return module_info_list;
}
@@ -596,6 +598,9 @@
}
}
}
+ // The ART APEX is always relevant no matter it contains any compilable JAR or not, because it
+ // contains the runtime.
+ relevant_apexes.insert("com.android.art");
std::vector<apex::ApexInfo> filtered_info_list;
std::copy_if(info_list->getApexInfo().begin(),
@@ -610,64 +615,74 @@
return art_apex::read(cache_info_filename_.c_str());
}
-void OnDeviceRefresh::WriteCacheInfo() const {
+Result<void> OnDeviceRefresh::WriteCacheInfo() const {
if (OS::FileExists(cache_info_filename_.c_str())) {
if (unlink(cache_info_filename_.c_str()) != 0) {
- PLOG(ERROR) << "Failed to unlink() file " << QuotePath(cache_info_filename_);
+ return ErrnoErrorf("Failed to unlink() file {}", QuotePath(cache_info_filename_));
}
}
const std::string dir_name = android::base::Dirname(cache_info_filename_);
if (!EnsureDirectoryExists(dir_name)) {
- LOG(ERROR) << "Could not create directory: " << QuotePath(dir_name);
- return;
+ return Errorf("Could not create directory {}", QuotePath(dir_name));
}
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;
+ return Errorf("Could not update {}: no APEX info", QuotePath(cache_info_filename_));
}
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;
+ return Errorf("Could not update {}: no ART APEX info", QuotePath(cache_info_filename_));
}
- art_apex::ModuleInfo art_module_info = GenerateModuleInfo(art_apex_info.value());
+ // We don't write lastUpdateMillis to the cache info in Compilation OS because APEX timestamps
+ // inside the VM are different from those outside (b/211458160).
+ bool include_last_update_millis = !config_.GetCompilationOsMode();
+ art_apex::ModuleInfo art_module_info =
+ GenerateModuleInfo(art_apex_info.value(), include_last_update_millis);
std::vector<art_apex::ModuleInfo> module_info_list =
- GenerateModuleInfoList(apex_info_list.value());
+ GenerateModuleInfoList(apex_info_list.value(), include_last_update_millis);
std::optional<std::vector<art_apex::Component>> bcp_components =
GenerateBootClasspathComponents();
if (!bcp_components.has_value()) {
- LOG(ERROR) << "No boot classpath components.";
- return;
+ return Errorf("No boot classpath components.");
}
std::optional<std::vector<art_apex::Component>> bcp_compilable_components =
GenerateBootExtensionCompilableComponents();
if (!bcp_compilable_components.has_value()) {
- LOG(ERROR) << "No boot classpath extension compilable components.";
- return;
+ return Errorf("No boot classpath extension compilable components.");
}
std::optional<std::vector<art_apex::SystemServerComponent>> system_server_components =
GenerateSystemServerComponents();
if (!system_server_components.has_value()) {
- LOG(ERROR) << "No system_server extension components.";
- return;
+ return Errorf("No system_server extension components.");
}
std::ofstream out(cache_info_filename_.c_str());
- 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::SystemServerComponents(system_server_components.value())});
+ if (out.fail()) {
+ return Errorf("Cannot open {} for writing.", QuotePath(cache_info_filename_));
+ }
+
+ 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::SystemServerComponents(system_server_components.value())},
+ config_.GetCompilationOsMode() ? std::make_optional(true) : std::nullopt);
art_apex::write(out, info);
+ out.close();
+ if (out.fail()) {
+ return Errorf("Cannot write to {}", QuotePath(cache_info_filename_));
+ }
+
+ return {};
}
static void ReportNextBootAnimationProgress(uint32_t current_compilation,
@@ -816,16 +831,21 @@
return false;
}
- // Check lastUpdateMillis for samegrade installs. If `cached_art_info` is missing the
- // lastUpdateMillis field 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.
- const int64_t cached_art_last_update_millis =
- cached_art_info->hasLastUpdateMillis() ? cached_art_info->getLastUpdateMillis() : -1;
- if (cached_art_last_update_millis != art_apex_info.getLastUpdateMillis()) {
- LOG(INFO) << "ART APEX last update time mismatch (" << cached_art_last_update_millis
- << " != " << art_apex_info.getLastUpdateMillis() << ").";
- metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
- return false;
+ // We don't check lastUpdateMillis if the cache info is generated in Compilation OS because APEX
+ // timestamps inside the VM are different from those outside (b/211458160). The cache info will be
+ // updated in the `--compile` phase to fill this field.
+ if (!(cache_info->hasCompilationOsMode() && cache_info->getCompilationOsMode())) {
+ // Check lastUpdateMillis for samegrade installs. If `cached_art_info` is missing the
+ // lastUpdateMillis field 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.
+ const int64_t cached_art_last_update_millis =
+ cached_art_info->hasLastUpdateMillis() ? cached_art_info->getLastUpdateMillis() : -1;
+ if (cached_art_last_update_millis != art_apex_info.getLastUpdateMillis()) {
+ LOG(INFO) << "ART APEX last update time mismatch (" << cached_art_last_update_millis
+ << " != " << art_apex_info.getLastUpdateMillis() << ").";
+ metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
+ return false;
+ }
}
// Check boot class components.
@@ -960,13 +980,15 @@
return compile_all();
}
- if (!cached_module_info->hasLastUpdateMillis() ||
- cached_module_info->getLastUpdateMillis() != current_apex_info.getLastUpdateMillis()) {
- LOG(INFO) << "APEX (" << apex_name << ") last update time mismatch ("
- << cached_module_info->getLastUpdateMillis()
- << " != " << current_apex_info.getLastUpdateMillis() << ").";
- metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
- return compile_all();
+ if (!(cache_info->hasCompilationOsMode() && cache_info->getCompilationOsMode())) {
+ if (!cached_module_info->hasLastUpdateMillis() ||
+ cached_module_info->getLastUpdateMillis() != current_apex_info.getLastUpdateMillis()) {
+ LOG(INFO) << "APEX (" << apex_name << ") last update time mismatch ("
+ << cached_module_info->getLastUpdateMillis()
+ << " != " << current_apex_info.getLastUpdateMillis() << ").";
+ metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch);
+ return compile_all();
+ }
}
}
@@ -1148,6 +1170,13 @@
return RemoveArtifactsDirectory() ? ExitCode::kCompilationRequired : ExitCode::kCleanupFailed;
};
+ // Compilation OS cannot reuse the artifacts generated before because it cannot securely verify
+ // them (b/203630168). Therefore, we always compile everything in Compilation OS. This is fine
+ // because Compilation OS runs when the device is idling and it does not affact boot time.
+ if (config_.GetCompilationOsMode()) {
+ return cleanup_and_compile_all();
+ }
+
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.
@@ -1225,6 +1254,18 @@
return ExitCode::kCleanupFailed;
}
+ // If the cache info was genereted in Compilation OS, update it to fill the lastUpdateMillis
+ // field.
+ if (!compilation_required && cache_info->hasCompilationOsMode() &&
+ cache_info->getCompilationOsMode()) {
+ compilation_options->update_cache_info_only = true;
+ // Return `kCompilationRequired` though there is nothing to compile. This is needed so that
+ // odsign will invoke `odrefresh --compile` and therefore we can update the cache info in that
+ // phase. We cannot update the cache info here because `odrefresh --check` should not modify any
+ // file. Otherwise, it will break odsign's signature verification.
+ compilation_required = true;
+ }
+
return compilation_required ? ExitCode::kCompilationRequired : ExitCode::kOkay;
}
@@ -1508,6 +1549,18 @@
}
}
+ // Emit cache info before compiling. This can be used to throttle compilation attempts later.
+ Result<void> result = WriteCacheInfo();
+ if (!result.ok()) {
+ LOG(ERROR) << result.error();
+ return ExitCode::kCleanupFailed;
+ }
+
+ if (compilation_options.update_cache_info_only) {
+ metrics.SetStage(OdrMetrics::Stage::kComplete);
+ return ExitCode::kCompilationSuccess;
+ }
+
if (!config_.GetStagingDir().empty()) {
staging_dir = config_.GetStagingDir().c_str();
} else {
@@ -1518,9 +1571,6 @@
}
}
- // Emit cache info before compiling. This can be used to throttle compilation attempts later.
- WriteCacheInfo();
-
std::string error_msg;
uint32_t dex2oat_invocation_count = 0;
diff --git a/odrefresh/odrefresh.h b/odrefresh/odrefresh.h
index 0319094..bc6a68f 100644
--- a/odrefresh/odrefresh.h
+++ b/odrefresh/odrefresh.h
@@ -40,6 +40,9 @@
namespace odrefresh {
struct CompilationOptions {
+ // If true, update the cache info only and do not compile anything.
+ bool update_cache_info_only;
+
// If not empty, compile the bootclasspath extensions for ISAs in the list.
std::vector<InstructionSet> compile_boot_extensions_for_isas;
@@ -86,8 +89,8 @@
// Reads the ART APEX cache information (if any) found in the output artifact directory.
std::optional<com::android::art::CacheInfo> ReadCacheInfo() const;
- // Write ART APEX cache information to `kOnDeviceRefreshOdrefreshArtifactDirectory`.
- void WriteCacheInfo() const;
+ // Writes ART APEX cache information to `kOnDeviceRefreshOdrefreshArtifactDirectory`.
+ android::base::Result<void> WriteCacheInfo() const;
std::vector<com::android::art::Component> GenerateBootClasspathComponents() const;
diff --git a/odrefresh/odrefresh_main.cc b/odrefresh/odrefresh_main.cc
index e4ef25a..47ac9f0 100644
--- a/odrefresh/odrefresh_main.cc
+++ b/odrefresh/odrefresh_main.cc
@@ -135,6 +135,8 @@
ArgumentError("Failed to parse CID: %s", value.c_str());
}
config->SetCompilationOsAddress(cid);
+ } else if (ArgumentEquals(arg, "--compilation-os-mode")) {
+ config->SetCompilationOsMode(true);
} else if (ArgumentMatches(arg, "--dalvik-cache=", &value)) {
art::OverrideDalvikCacheSubDirectory(value);
config->SetArtifactDirectory(GetApexDataDalvikCacheDirectory(art::InstructionSet::kNone));
@@ -193,6 +195,8 @@
UsageError("--no-refresh Do not refresh existing artifacts.");
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("--compilation-os-mode Indicate that odrefresh is running in Compilation");
+ UsageError(" OS.");
UsageError("--dalvik-cache=<DIR> Write artifacts to .../<DIR> rather than");
UsageError(" .../dalvik-cache");
UsageError("--max-execution-seconds=<N> Maximum timeout of all compilation combined");
diff --git a/odrefresh/odrefresh_test.cc b/odrefresh/odrefresh_test.cc
index 4d70a66..7494084 100644
--- a/odrefresh/odrefresh_test.cc
+++ b/odrefresh/odrefresh_test.cc
@@ -28,6 +28,7 @@
#include "aidl/com/android/art/DexoptBcpExtArgs.h"
#include "aidl/com/android/art/DexoptSystemServerArgs.h"
#include "aidl/com/android/art/Isa.h"
+#include "android-base/file.h"
#include "android-base/parseint.h"
#include "android-base/scopeguard.h"
#include "android-base/stringprintf.h"
@@ -107,6 +108,25 @@
return ExplainMatchResult(matcher, path_str, result_listener);
}
+void WriteFakeApexInfoList(const std::string& filename) {
+ std::string content = R"xml(
+<?xml version="1.0" encoding="utf-8"?>
+<apex-info-list>
+ <apex-info
+ moduleName="com.android.art"
+ modulePath="/data/apex/active/com.android.art@319999900.apex"
+ preinstalledModulePath="/system/apex/com.android.art.capex"
+ versionCode="319999900"
+ versionName=""
+ isFactory="false"
+ isActive="true"
+ lastUpdateMillis="12345678">
+ </apex-info>
+</apex-info-list>
+)xml";
+ android::base::WriteStringToFile(content, filename);
+}
+
class OdRefreshTest : public CommonArtTest {
public:
OdRefreshTest() : config_("odrefresh") {}
@@ -163,7 +183,10 @@
ASSERT_TRUE(EnsureDirectoryExists(javalib_dir));
CreateEmptyFile(boot_art);
- config_.SetApexInfoListFile(Concatenate({temp_dir_path, "/apex-info-list.xml"}));
+ std::string apex_info_filename = Concatenate({temp_dir_path, "/apex-info-list.xml"});
+ WriteFakeApexInfoList(apex_info_filename);
+ config_.SetApexInfoListFile(apex_info_filename);
+
config_.SetArtBinDir(Concatenate({temp_dir_path, "/bin"}));
config_.SetBootClasspath(framework_jar_);
config_.SetDex2oatBootclasspath(framework_jar_);
@@ -172,6 +195,7 @@
config_.SetIsa(InstructionSet::kX86_64);
config_.SetZygoteKind(ZygoteKind::kZygote64_32);
config_.SetSystemServerCompilerFilter("speed"); // specify a default
+ config_.SetArtifactDirectory(dalvik_cache_dir_);
std::string staging_dir = dalvik_cache_dir_ + "/staging";
ASSERT_TRUE(EnsureDirectoryExists(staging_dir));
@@ -286,6 +310,7 @@
TEST_F(OdRefreshTest, MissingStandaloneSystemServerJars) {
config_.SetStandaloneSystemServerJars("");
auto [odrefresh, mock_odr_dexopt] = CreateOdRefresh();
+ EXPECT_CALL(*mock_odr_dexopt, DoDexoptSystemServer).WillRepeatedly(Return(0));
EXPECT_EQ(
odrefresh->Compile(*metrics_,
CompilationOptions{
diff --git a/odrefresh/schema/current.txt b/odrefresh/schema/current.txt
index a7aa52e..958f2a0 100644
--- a/odrefresh/schema/current.txt
+++ b/odrefresh/schema/current.txt
@@ -5,11 +5,13 @@
ctor public CacheInfo();
method public com.android.art.ModuleInfo getArtModuleInfo();
method public com.android.art.Classpath getBootClasspath();
+ method public boolean getCompilationOsMode();
method public com.android.art.Classpath getDex2oatBootClasspath();
method public com.android.art.ModuleInfoList getModuleInfoList();
method public com.android.art.SystemServerComponents getSystemServerComponents();
method public void setArtModuleInfo(com.android.art.ModuleInfo);
method public void setBootClasspath(com.android.art.Classpath);
+ method public void setCompilationOsMode(boolean);
method public void setDex2oatBootClasspath(com.android.art.Classpath);
method public void setModuleInfoList(com.android.art.ModuleInfoList);
method public void setSystemServerComponents(com.android.art.SystemServerComponents);
diff --git a/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java b/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
index 567c85d..fc8e57f 100644
--- a/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
+++ b/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
@@ -45,8 +45,9 @@
public class OdrefreshHostTest extends BaseHostJUnit4Test {
private static final String CACHE_INFO_FILE =
OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME + "/cache-info.xml";
+ private static final String ODREFRESH_BIN = "odrefresh";
private static final String ODREFRESH_COMMAND =
- "odrefresh --partial-compilation --no-refresh --compile";
+ ODREFRESH_BIN + " --partial-compilation --no-refresh --compile";
private static OdsignTestUtils sTestUtils;
@@ -160,6 +161,38 @@
assertThat(cacheInfo).doesNotContain("name=\"com.android.runtime\"");
}
+ @Test
+ public void verifyCompilationOsMode() throws Exception {
+ sTestUtils.removeCompilationLogToAvoidBackoff();
+ long timeMs = getCurrentTimeMs();
+ getDevice().executeShellV2Command(ODREFRESH_BIN + " --compilation-os-mode --compile");
+
+ // odrefresh should unconditionally compile everything in Compilation OS.
+ assertArtifactsModifiedAfter(sZygoteArtifacts, timeMs);
+ assertArtifactsModifiedAfter(sSystemServerArtifacts, timeMs);
+
+ String cacheInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
+ assertThat(cacheInfo).contains("compilationOsMode=\"true\"");
+ assertThat(cacheInfo).doesNotContain("lastUpdateMillis=");
+
+ // Compilation OS does not write the compilation log to the host.
+ sTestUtils.removeCompilationLogToAvoidBackoff();
+
+ // Simulate the odrefresh invocation on the next boot.
+ timeMs = getCurrentTimeMs();
+ getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+
+ // odrefresh should not re-compile anything regardless of the missing `lastUpdateMillis`
+ // field.
+ assertArtifactsNotModifiedAfter(sZygoteArtifacts, timeMs);
+ assertArtifactsNotModifiedAfter(sSystemServerArtifacts, timeMs);
+
+ // The cache info should be updated.
+ cacheInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
+ assertThat(cacheInfo).doesNotContain("compilationOsMode=\"true\"");
+ assertThat(cacheInfo).contains("lastUpdateMillis=");
+ }
+
/**
* Checks the input line by line and replaces all lines that match the regex with the given
* replacement.