odrefresh: regenerate artifacts after ART APEX update
Replaces compile_bcp.sh to check and compile boot class path
extensions and system server jars.
Enable ART to load those artifacts when present in the ART APEX data
directory.
Bug: 160683548
Test: art_libartbase_tests
Test: adb root && adb odrefresh {--force-check,--force-compile}
Test: adb install com.android.art && adb reboot && adb root && \
adb shell odrefresh {--check,--compile} && adb reboot && \
adb shell cat /proc/<zygote>/maps | grep apexdata
Change-Id: I81bf520d38f9dc0109c91f192bc6e728099049fd
diff --git a/Android.mk b/Android.mk
index e51763c..2359bc8 100644
--- a/Android.mk
+++ b/Android.mk
@@ -349,6 +349,7 @@
$(call art_module_lib,libart-compiler) \
$(call art_module_lib,libopenjdkjvm) \
$(call art_module_lib,libopenjdkjvmti) \
+ $(call art_module_lib,odrefresh) \
$(call art_module_lib,profman) \
$(call art_module_lib,libadbconnection) \
$(call art_module_lib,libperfetto_hprof) \
diff --git a/build/Android.common_path.mk b/build/Android.common_path.mk
index 268c659..67e0557 100644
--- a/build/Android.common_path.mk
+++ b/build/Android.common_path.mk
@@ -88,6 +88,7 @@
dexoptanalyzer \
imgdiag \
oatdump \
+ odrefresh \
profman \
ART_CORE_EXECUTABLES := \
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index 7ae6596..2bc2e8b7 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -136,6 +136,7 @@
art_libdexfile_tests \
art_libprofile_tests \
art_oatdump_tests \
+ art_odrefresh_tests \
art_profman_tests \
art_runtime_compiler_tests \
art_runtime_tests \
diff --git a/build/apex/Android.bp b/build/apex/Android.bp
index 4c8502b..eaf0b5a 100644
--- a/build/apex/Android.bp
+++ b/build/apex/Android.bp
@@ -24,6 +24,7 @@
// only the "first" (likely 64-bit) version is required on host).
art_runtime_base_binaries_prefer32_on_device_first_on_host = [
"dexoptanalyzer",
+ "odrefresh",
"profman",
]
@@ -131,7 +132,6 @@
// Tools exclusively for the device APEX derived from art-tools in art/Android.mk.
art_tools_device_only_binaries = [
- "compile_bcp.sh",
// oatdump cannot link with host linux_bionic due to not using clang lld;
// TODO: Make it work with clang lld.
"oatdump",
@@ -333,6 +333,7 @@
"art_libdexfile_support_tests",
"art_libprofile_tests",
"art_oatdump_tests",
+ "art_odrefresh_tests",
"art_profman_tests",
"art_runtime_compiler_tests",
"art_runtime_tests",
diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py
index 95e193d..91af3db 100755
--- a/build/apex/art_apex_test.py
+++ b/build/apex/art_apex_test.py
@@ -619,9 +619,9 @@
# removed in Android R.
# Check binaries for ART.
- self._checker.check_executable("compile_bcp.sh")
- self._checker.check_executable('oatdump')
self._checker.check_multilib_executable('dex2oat')
+ self._checker.check_executable('oatdump')
+ self._checker.check_executable("odrefresh")
# Check internal libraries for ART.
self._checker.check_native_library('libperfetto_hprof')
@@ -646,6 +646,7 @@
self._checker.check_executable('hprof-conv')
self._checker.check_symlinked_first_executable('dex2oatd')
self._checker.check_symlinked_first_executable('dex2oat')
+ self._checker.check_executable("odrefresh")
# Check exported native libraries for Managed Core Library.
self._checker.check_native_library('libicu')
@@ -753,6 +754,7 @@
self._checker.check_art_test_executable('art_libdexfile_tests')
self._checker.check_art_test_executable('art_libprofile_tests')
self._checker.check_art_test_executable('art_oatdump_tests')
+ self._checker.check_art_test_executable('art_odrefresh_tests')
self._checker.check_art_test_executable('art_profman_tests')
self._checker.check_art_test_executable('art_runtime_compiler_tests')
self._checker.check_art_test_executable('art_runtime_tests')
diff --git a/libartbase/base/common_art_test.h b/libartbase/base/common_art_test.h
index f627fc9..0f5fb0c 100644
--- a/libartbase/base/common_art_test.h
+++ b/libartbase/base/common_art_test.h
@@ -159,6 +159,34 @@
std::vector<std::unique_ptr<FakeDex>> fake_dex_files;
};
+// Helper class that removes an environment variable whilst in scope.
+class ScopedUnsetEnvironmentVariable {
+ public:
+ explicit ScopedUnsetEnvironmentVariable(const char* variable)
+ : variable_{variable}, old_value_{GetOldValue(variable)} {
+ unsetenv(variable);
+ }
+
+ ~ScopedUnsetEnvironmentVariable() {
+ if (old_value_.has_value()) {
+ static constexpr int kReplace = 1; // tidy-issue: replace argument has libc dependent name.
+ setenv(variable_, old_value_.value().c_str(), kReplace);
+ } else {
+ unsetenv(variable_);
+ }
+ }
+
+ private:
+ static std::optional<std::string> GetOldValue(const char* variable) {
+ const char* value = getenv(variable);
+ return value != nullptr ? std::optional<std::string>{value} : std::nullopt;
+ }
+
+ const char* variable_;
+ std::optional<std::string> old_value_;
+ DISALLOW_COPY_AND_ASSIGN(ScopedUnsetEnvironmentVariable);
+};
+
class CommonArtTestImpl {
public:
CommonArtTestImpl() = default;
diff --git a/libartbase/base/file_utils.h b/libartbase/base/file_utils.h
index 67caaa6..bdabf5a 100644
--- a/libartbase/base/file_utils.h
+++ b/libartbase/base/file_utils.h
@@ -20,6 +20,7 @@
#include <stdlib.h>
#include <string>
+#include <string_view>
#include <android-base/logging.h>
diff --git a/libartbase/base/file_utils_test.cc b/libartbase/base/file_utils_test.cc
index b7cc625..4dce3dc 100644
--- a/libartbase/base/file_utils_test.cc
+++ b/libartbase/base/file_utils_test.cc
@@ -28,34 +28,6 @@
class FileUtilsTest : public CommonArtTest {};
-// Helper class that removes an environment variable whilst in scope.
-class ScopedUnsetEnvironmentVariable {
- public:
- explicit ScopedUnsetEnvironmentVariable(const char* variable)
- : variable_{variable}, old_value_{GetOldValue(variable)} {
- unsetenv(variable);
- }
-
- ~ScopedUnsetEnvironmentVariable() {
- if (old_value_.has_value()) {
- static constexpr int kReplace = 1; // tidy-issue: replace argument has libc dependent name.
- setenv(variable_, old_value_.value().c_str(), kReplace);
- } else {
- unsetenv(variable_);
- }
- }
-
- private:
- static std::optional<std::string> GetOldValue(const char* variable) {
- const char* value = getenv(variable);
- return value != nullptr ? std::optional<std::string>{value} : std::nullopt;
- }
-
- const char* variable_;
- std::optional<std::string> old_value_;
- DISALLOW_COPY_AND_ASSIGN(ScopedUnsetEnvironmentVariable);
-};
-
TEST_F(FileUtilsTest, GetDalvikCacheFilename) {
std::string name;
std::string error;
diff --git a/odrefresh/Android.bp b/odrefresh/Android.bp
new file mode 100644
index 0000000..4a96944
--- /dev/null
+++ b/odrefresh/Android.bp
@@ -0,0 +1,119 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_defaults {
+ name: "odrefresh-defaults",
+ host_supported: true,
+ defaults: ["art_defaults"],
+ srcs: [
+ "odrefresh.cc",
+ ],
+ local_include_dirs: ["include"],
+ header_libs: ["dexoptanalyzer_headers"],
+ generated_sources: ["apex-info-list"],
+ shared_libs: [
+ "libartpalette",
+ "libbase",
+ "liblog",
+ ],
+ static_libs: ["libxml2"],
+ target: {
+ android: {
+ // Use the 32-bit version of odrefresh on devices.
+ compile_multilib: "prefer32",
+ },
+ linux: {
+ enabled: true,
+ },
+ host: {
+ shared_libs: [
+ // Both these libraries for libxml2 on host for code derived from apex-info-list.
+ "libicui18n",
+ "libicuuc",
+ ],
+ },
+ },
+ tidy: true,
+ tidy_flags: [
+ "-format-style='file'",
+ "--header-filter='system/apex/'",
+ ],
+}
+
+cc_library_headers {
+ name: "odrefresh_headers",
+ export_include_dirs: ["include"],
+ host_supported: true,
+ stl: "none",
+ system_shared_libs: [],
+ min_sdk_version: "29", // As part of mainline modules(APEX), it should support at least 29(Q).
+ sdk_version: "minimum", // The minimum sdk version required by users of this module.
+ apex_available: [
+ "//apex_available:platform", // For odsign.
+ ],
+ visibility: ["//visibility:public"],
+}
+
+art_cc_binary {
+ name: "odrefresh",
+ defaults: ["odrefresh-defaults"],
+ required: [
+ "dexoptanalyzer",
+ "dex2oat",
+ ],
+ shared_libs: [
+ "libart",
+ "libartbase",
+ ],
+ apex_available: [
+ "com.android.art",
+ "com.android.art.debug",
+ ],
+}
+
+art_cc_binary {
+ name: "odrefreshd",
+ defaults: [
+ "art_debug_defaults",
+ "odrefresh-defaults",
+ ],
+ required: [
+ "dexoptanalyzerd",
+ "dex2oatd",
+ ],
+ shared_libs: [
+ "libartd",
+ "libartbased",
+ ],
+ apex_available: [
+ "com.android.art.debug",
+ ],
+}
+
+art_cc_test {
+ name: "art_odrefresh_tests",
+ defaults: [
+ "art_gtest_defaults",
+ ],
+ header_libs: ["odrefresh_headers"],
+ srcs: [
+ "odr_artifacts_test.cc",
+ "odrefresh_test.cc",
+ ],
+ shared_libs: [
+ "libbase",
+ ],
+}
diff --git a/odrefresh/README.md b/odrefresh/README.md
new file mode 100644
index 0000000..181ef72
--- /dev/null
+++ b/odrefresh/README.md
@@ -0,0 +1,7 @@
+# On-Device Refresh (odrefresh)
+
+This tool is part of the ART APEX and is used to refresh compilation artifacts following an
+ART APEX update. It provides two key features:
+
+* checking the freshness of compilation artifacts for boot class path extensions and system_server.
+* regenerating the compilation artifacts for boot class path extensions and system_server.
\ No newline at end of file
diff --git a/odrefresh/TODO.md b/odrefresh/TODO.md
new file mode 100644
index 0000000..95c4aa1
--- /dev/null
+++ b/odrefresh/TODO.md
@@ -0,0 +1,22 @@
+# TODO Work Items
+
+## TODO (STOPSHIP until done)
+
+1. Add a log file that tracks status of recent compilation.
+2. Implement back off on trying compilation when previous attempt(s) failed.
+3. Free space calculation and only attempting compilation if sufficient space.
+4. Metrics for tracking issues:
+ - Successful compilation of all artifacts.
+ - Time limit exceeded (indicates a pathological issue, e.g. dex2oat bug, device driver bug, etc).
+ - Insufficient space for compilation.
+ - Compilation failure (boot extensions)
+ - Compilation failure (system server)
+ - Unexpected error (a setup or clean-up action failed).
+5. Metrics recording for subprocess timeouts.
+6. Decide and implement testing.
+
+## DONE
+
+1. <strike>Fix dexoptanalyzer so it can analyze boot extensions.</strike>
+2. <strike>Parse apex-info-list.xml into an apex_info (to make version and location available).</strike>
+3. <strike>Timeouts for pathological failures.</strike>
diff --git a/odrefresh/include/odrefresh/odrefresh.h b/odrefresh/include/odrefresh/odrefresh.h
new file mode 100644
index 0000000..8d1166a
--- /dev/null
+++ b/odrefresh/include/odrefresh/odrefresh.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_ODREFRESH_INCLUDE_ODREFRESH_ODREFRESH_H_
+#define ART_ODREFRESH_INCLUDE_ODREFRESH_ODREFRESH_H_
+
+#include <sysexits.h>
+
+namespace art {
+namespace odrefresh {
+
+static constexpr const char* kOdrefreshArtifactDirectory =
+ "/data/misc/apexdata/com.android.art/dalvik-cache";
+
+//
+// Exit codes from the odrefresh process (in addition to standard exit codes in sysexits.h).
+//
+// NB if odrefresh crashes, then the caller should not sign any artifacts and should remove any
+// unsigned artifacts under `kOdrefreshArtifactDirectory`.
+//
+enum ExitCode {
+ // No compilation required, all artifacts look good or there is insufficient space to compile.
+ // For ART APEX in the system image, there may be no artifacts present under
+ // `kOdrefreshArtifactDirectory`.
+ kOkay = EX_OK,
+
+ // Compilation required. Re-run program with --compile on the command-line to generate
+ // new artifacts under `kOdrefreshArtifactDirectory`.
+ kCompilationRequired = 1,
+
+ // Compilation failed. Artifacts under `kOdrefreshArtifactDirectory` will be valid. This may
+ // happen, for example, if compilation of boot extensions succeeds, but the compilation of the
+ // system_server jars fails due to lack of storage space.
+ kCompilationFailed = 2,
+};
+
+static_assert(EX_OK == 0);
+static_assert(ExitCode::kOkay < EX__BASE);
+static_assert(ExitCode::kCompilationFailed < EX__BASE);
+
+} // namespace odrefresh
+} // namespace art
+
+#endif // ART_ODREFRESH_INCLUDE_ODREFRESH_ODREFRESH_H_
diff --git a/odrefresh/odr_artifacts.h b/odrefresh/odr_artifacts.h
new file mode 100644
index 0000000..66d76f0
--- /dev/null
+++ b/odrefresh/odr_artifacts.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_ODREFRESH_ODR_ARTIFACTS_H_
+#define ART_ODREFRESH_ODR_ARTIFACTS_H_
+
+#include <iosfwd>
+#include <string>
+
+#include <base/file_utils.h>
+
+namespace art {
+namespace odrefresh {
+
+// A grouping of odrefresh generated artifacts.
+class OdrArtifacts {
+ public:
+ static OdrArtifacts ForBootImageExtension(const std::string& image_path) {
+ return OdrArtifacts(image_path, "oat");
+ }
+
+ static OdrArtifacts ForSystemServer(const std::string& image_path) {
+ return OdrArtifacts(image_path, "odex");
+ }
+
+ const std::string& ImagePath() const { return image_path_; }
+ const std::string& OatPath() const { return oat_path_; }
+ const std::string& VdexPath() const { return vdex_path_; }
+
+ private:
+ OdrArtifacts(const std::string& image_path, const char* aot_extension)
+ : image_path_{image_path},
+ oat_path_{ReplaceFileExtension(image_path, aot_extension)},
+ vdex_path_{ReplaceFileExtension(image_path, "vdex")} {}
+
+ OdrArtifacts() = delete;
+ OdrArtifacts(const OdrArtifacts&) = delete;
+ OdrArtifacts& operator=(const OdrArtifacts&) = delete;
+
+ const std::string image_path_;
+ const std::string oat_path_;
+ const std::string vdex_path_;
+};
+
+} // namespace odrefresh
+} // namespace art
+
+#endif // ART_ODREFRESH_ODR_ARTIFACTS_H_
diff --git a/odrefresh/odr_artifacts_test.cc b/odrefresh/odr_artifacts_test.cc
new file mode 100644
index 0000000..97f1fd7
--- /dev/null
+++ b/odrefresh/odr_artifacts_test.cc
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "odr_artifacts.h"
+
+#include "arch/instruction_set.h"
+#include "base/common_art_test.h"
+#include "base/file_utils.h"
+#include "base/string_view_cpp20.h"
+#include "odrefresh/odrefresh.h"
+
+namespace art {
+namespace odrefresh {
+
+TEST(OdrArtifactsTest, ForBootImageExtension) {
+ ScopedUnsetEnvironmentVariable no_env("ART_APEX_DATA");
+
+ const std::string image_location = GetApexDataBootImage("/system/framework/framework.jar");
+ EXPECT_TRUE(StartsWith(image_location, GetArtApexData()));
+
+ const std::string image_filename =
+ GetSystemImageFilename(image_location.c_str(), InstructionSet::kArm64);
+
+ const auto artifacts = OdrArtifacts::ForBootImageExtension(image_filename);
+ CHECK_EQ(std::string(kOdrefreshArtifactDirectory) + "/arm64/boot-framework.art",
+ artifacts.ImagePath());
+ CHECK_EQ(std::string(kOdrefreshArtifactDirectory) + "/arm64/boot-framework.oat",
+ artifacts.OatPath());
+ CHECK_EQ(std::string(kOdrefreshArtifactDirectory) + "/arm64/boot-framework.vdex",
+ artifacts.VdexPath());
+}
+
+TEST(OdrArtifactsTest, ForSystemServer) {
+ ScopedUnsetEnvironmentVariable no_env("ART_APEX_DATA");
+
+ const std::string image_location = GetApexDataImage("/system/framework/services.jar");
+ EXPECT_TRUE(StartsWith(image_location, GetArtApexData()));
+
+ const std::string image_filename =
+ GetSystemImageFilename(image_location.c_str(), InstructionSet::kX86);
+ const auto artifacts = OdrArtifacts::ForSystemServer(image_filename);
+ CHECK_EQ(
+ std::string(kOdrefreshArtifactDirectory) + "/x86/system@framework@services.jar@classes.art",
+ artifacts.ImagePath());
+ CHECK_EQ(
+ std::string(kOdrefreshArtifactDirectory) + "/x86/system@framework@services.jar@classes.odex",
+ artifacts.OatPath());
+ CHECK_EQ(
+ std::string(kOdrefreshArtifactDirectory) + "/x86/system@framework@services.jar@classes.vdex",
+ artifacts.VdexPath());
+}
+
+} // namespace odrefresh
+} // namespace art
diff --git a/odrefresh/odr_config.h b/odrefresh/odr_config.h
new file mode 100644
index 0000000..6b0f212
--- /dev/null
+++ b/odrefresh/odr_config.h
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ART_ODREFRESH_ODR_CONFIG_H_
+#define ART_ODREFRESH_ODR_CONFIG_H_
+
+#include <string>
+#include <vector>
+
+#include "android-base/file.h"
+#include "arch/instruction_set.h"
+#include "base/globals.h"
+#include "log/log.h"
+
+namespace art {
+namespace odrefresh {
+
+// An enumeration of the possible zygote configurations on Android.
+enum class ZygoteKind : uint8_t {
+ // 32-bit primary zygote, no secondary zygote.
+ kZygote32 = 0,
+ // 32-bit primary zygote, 64-bit secondary zygote.
+ kZygote32_64 = 1,
+ // 64-bit primary zygote, 32-bit secondary zygote.
+ kZygote64_32 = 2,
+ // 64-bit praimry zygote, no secondary zygote.
+ kZygote64 = 3
+};
+
+// Configuration class for odrefresh. Exists to enable abstracting environment variables and
+// system properties into a configuration class for development and testing purposes.
+class OdrConfig final {
+ private:
+ std::string apex_info_list_file_;
+ std::string art_bin_dir_;
+ std::string dex2oat_;
+ std::string dex2oat_boot_classpath_;
+ bool dry_run_;
+ InstructionSet isa_;
+ std::string program_name_;
+ std::string system_server_classpath_;
+ std::string updatable_bcp_packages_file_;
+ ZygoteKind zygote_kind_;
+
+ public:
+ explicit OdrConfig(const char* program_name)
+ : dry_run_(false),
+ isa_(InstructionSet::kNone),
+ program_name_(android::base::Basename(program_name)) {
+ }
+
+ const std::string& GetApexInfoListFile() const { return apex_info_list_file_; }
+
+ std::vector<InstructionSet> GetBootExtensionIsas() const {
+ const auto [isa32, isa64] = GetPotentialInstructionSets();
+ switch (zygote_kind_) {
+ case ZygoteKind::kZygote32:
+ return {isa32};
+ case ZygoteKind::kZygote32_64:
+ case ZygoteKind::kZygote64_32:
+ return {isa32, isa64};
+ case ZygoteKind::kZygote64:
+ return {isa64};
+ }
+ }
+
+ InstructionSet GetSystemServerIsa() const {
+ const auto [isa32, isa64] = GetPotentialInstructionSets();
+ switch (zygote_kind_) {
+ case ZygoteKind::kZygote32:
+ case ZygoteKind::kZygote32_64:
+ return isa32;
+ case ZygoteKind::kZygote64_32:
+ case ZygoteKind::kZygote64:
+ return isa64;
+ }
+ }
+
+ const std::string& GetDex2oatBootClasspath() const { return dex2oat_boot_classpath_; }
+
+ std::string GetDex2Oat() const {
+ const char* prefix = UseDebugBinaries() ? "dex2oatd" : "dex2oat";
+ const char* suffix = "";
+ if (kIsTargetBuild) {
+ switch (zygote_kind_) {
+ case ZygoteKind::kZygote32:
+ suffix = "32";
+ break;
+ case ZygoteKind::kZygote32_64:
+ case ZygoteKind::kZygote64_32:
+ case ZygoteKind::kZygote64:
+ suffix = "64";
+ break;
+ }
+ }
+ return art_bin_dir_ + '/' + prefix + suffix;
+ }
+
+ std::string GetDexOptAnalyzer() const {
+ const char* dexoptanalyzer{UseDebugBinaries() ? "dexoptanalyzerd" : "dexoptanalyzer"};
+ return art_bin_dir_ + '/' + dexoptanalyzer;
+ }
+
+ bool GetDryRun() const { return dry_run_; }
+ const std::string& GetSystemServerClasspath() const { return system_server_classpath_; }
+ const std::string& GetUpdatableBcpPackagesFile() const { return updatable_bcp_packages_file_; }
+
+ 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; }
+
+ void SetDex2oatBootclasspath(const std::string& classpath) {
+ dex2oat_boot_classpath_ = classpath;
+ }
+
+ void SetDryRun() { dry_run_ = true; }
+ void SetIsa(const InstructionSet isa) { isa_ = isa; }
+
+ void SetSystemServerClasspath(const std::string& classpath) {
+ system_server_classpath_ = classpath;
+ }
+
+ void SetUpdatableBcpPackagesFile(const std::string& file) { updatable_bcp_packages_file_ = file; }
+ void SetZygoteKind(ZygoteKind zygote_kind) { zygote_kind_ = zygote_kind; }
+
+ 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
+ // architecture. The current `isa` is based on `kRuntimeISA` on target, odrefresh is compiled
+ // 32-bit by default so this method returns all options which are finessed based on the
+ // `ro.zygote` property.
+ std::pair<InstructionSet, InstructionSet> GetPotentialInstructionSets() const {
+ switch (isa_) {
+ case art::InstructionSet::kArm:
+ case art::InstructionSet::kArm64:
+ return std::make_pair(art::InstructionSet::kArm, art::InstructionSet::kArm64);
+ case art::InstructionSet::kX86:
+ case art::InstructionSet::kX86_64:
+ return std::make_pair(art::InstructionSet::kX86, art::InstructionSet::kX86_64);
+ case art::InstructionSet::kThumb2:
+ case art::InstructionSet::kNone:
+ LOG(FATAL) << "Invalid instruction set " << isa_;
+ return std::make_pair(art::InstructionSet::kNone, art::InstructionSet::kNone);
+ }
+ }
+
+ bool UseDebugBinaries() const { return program_name_ == "odrefreshd"; }
+
+ OdrConfig() = delete;
+ OdrConfig(const OdrConfig&) = delete;
+ OdrConfig& operator=(const OdrConfig&) = delete;
+};
+
+} // namespace odrefresh
+} // namespace art
+
+#endif // ART_ODREFRESH_ODR_CONFIG_H_
diff --git a/odrefresh/odrefresh.cc b/odrefresh/odrefresh.cc
new file mode 100644
index 0000000..c75a62e
--- /dev/null
+++ b/odrefresh/odrefresh.cc
@@ -0,0 +1,1061 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "odrefresh/odrefresh.h"
+
+#include <dirent.h>
+#include <fcntl.h>
+#include <ftw.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sysexits.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <cstdarg>
+#include <cstdlib>
+#include <initializer_list>
+#include <iosfwd>
+#include <iostream>
+#include <memory>
+#include <ostream>
+#include <queue>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "android-base/file.h"
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "android-base/properties.h"
+#include "android-base/stringprintf.h"
+#include "android-base/strings.h"
+#include "android/log.h"
+#include "arch/instruction_set.h"
+#include "base/bit_utils.h"
+#include "base/globals.h"
+#include "base/macros.h"
+#include "base/os.h"
+#include "base/string_view_cpp20.h"
+#include "base/unix_file/fd_file.h"
+#include "com_android_apex.h"
+#include "dexoptanalyzer.h"
+#include "exec_utils.h"
+#include "palette/palette.h"
+#include "palette/palette_types.h"
+
+#include "odr_artifacts.h"
+#include "odr_config.h"
+
+namespace art {
+namespace odrefresh {
+namespace {
+
+static void UsageErrorV(const char* fmt, va_list ap) {
+ std::string error;
+ android::base::StringAppendV(&error, fmt, ap);
+ if (isatty(fileno(stderr))) {
+ std::cerr << error << std::endl;
+ } else {
+ LOG(ERROR) << error;
+ }
+}
+
+static void UsageError(const char* fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ UsageErrorV(fmt, ap);
+ va_end(ap);
+}
+
+NO_RETURN static void ArgumentError(const char* fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ UsageErrorV(fmt, ap);
+ va_end(ap);
+ UsageError("Try '--help' for more information.");
+ exit(EX_USAGE);
+}
+
+NO_RETURN static void UsageHelp(const char* argv0) {
+ std::string name(android::base::Basename(argv0));
+ UsageError("Usage: %s ACTION", name.c_str());
+ UsageError("On-device refresh tool for boot class path extensions and system server");
+ UsageError("following an update of the ART APEX.");
+ UsageError("");
+ UsageError("Valid ACTION choices are:");
+ UsageError("");
+ UsageError("--check Check compilation artifacts are up to date.");
+ UsageError("--compile Compile boot class path extensions and system_server jars");
+ UsageError(" when necessary).");
+ UsageError("--force-compile Unconditionally compile the boot class path extensions and");
+ UsageError(" system_server jars.");
+ UsageError("--help Display this help information.");
+ exit(EX_USAGE);
+}
+
+static std::string Concatenate(std::initializer_list<std::string_view> args) {
+ std::stringstream ss;
+ for (auto arg : args) {
+ ss << arg;
+ }
+ return ss.str();
+}
+
+static std::string QuotePath(std::string_view path) {
+ return Concatenate({"'", path, "'"});
+}
+
+static void EraseFiles(const std::vector<std::unique_ptr<File>>& files) {
+ for (auto& file : files) {
+ file->Erase(/*unlink=*/true);
+ }
+}
+
+// Moves `files` to the directory `output_directory_path`.
+//
+// If any of the files cannot be moved, then all copies of the files are removed from both
+// the original location and the output location.
+//
+// Returns true if all files are moved, false otherwise.
+static bool MoveOrEraseFiles(const std::vector<std::unique_ptr<File>>& files,
+ std::string_view output_directory_path) {
+ std::vector<std::unique_ptr<File>> output_files;
+ for (auto& file : files) {
+ const std::string file_basename(android::base::Basename(file->GetPath()));
+ const std::string output_file_path = Concatenate({output_directory_path, "/", file_basename});
+ const std::string input_file_path = file->GetPath();
+
+ output_files.emplace_back(OS::CreateEmptyFileWriteOnly(output_file_path.c_str()));
+ if (output_files.back() == nullptr) {
+ PLOG(ERROR) << "Failed to open " << QuotePath(output_file_path);
+ output_files.pop_back();
+ EraseFiles(output_files);
+ EraseFiles(files);
+ return false;
+ }
+
+ const size_t file_bytes = file->GetLength();
+ if (!output_files.back()->Copy(file.get(), /*offset=*/0, file_bytes)) {
+ PLOG(ERROR) << "Failed to copy " << QuotePath(file->GetPath())
+ << " to " << QuotePath(output_file_path);
+ EraseFiles(output_files);
+ EraseFiles(files);
+ return false;
+ }
+
+ if (!file->Erase(/*unlink=*/true)) {
+ PLOG(ERROR) << "Failed to erase " << QuotePath(file->GetPath());
+ EraseFiles(output_files);
+ EraseFiles(files);
+ return false;
+ }
+
+ if (output_files.back()->FlushCloseOrErase() != 0) {
+ PLOG(ERROR) << "Failed to flush and close file " << QuotePath(output_file_path);
+ EraseFiles(output_files);
+ EraseFiles(files);
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace
+
+bool ParseZygoteKind(const char* input, ZygoteKind* zygote_kind) {
+ std::string_view z(input);
+ if (z == "zygote32") {
+ *zygote_kind = ZygoteKind::kZygote32;
+ return true;
+ } else if (z == "zygote32_64") {
+ *zygote_kind = ZygoteKind::kZygote32_64;
+ return true;
+ } else if (z == "zygote64_32") {
+ *zygote_kind = ZygoteKind::kZygote64_32;
+ return true;
+ } else if (z == "zygote64") {
+ *zygote_kind = ZygoteKind::kZygote64;
+ return true;
+ }
+ return false;
+}
+
+class OnDeviceRefresh final {
+ private:
+ // Maximum execution time for odrefresh from start to end.
+ static constexpr time_t kMaximumExecutionSeconds = 300;
+
+ // Maximum execution time for any child process spawned.
+ static constexpr time_t kMaxChildProcessSeconds = 90;
+
+ const OdrConfig& config_;
+
+ std::vector<std::string> boot_extension_compilable_jars_;
+
+ std::string systemserver_output_dir_;
+ std::vector<std::string> systemserver_compilable_jars_;
+
+ const time_t start_time_;
+
+ public:
+ explicit OnDeviceRefresh(const OdrConfig& config) : config_(config), start_time_(time(nullptr)) {
+ const std::string art_apex_data = GetArtApexData();
+
+ for (const std::string& jar : android::base::Split(config_.GetDex2oatBootClasspath(), ":")) {
+ // Boot class path extensions are those not in the ART APEX. Updatable APEXes should not
+ // have DEX files in the DEX2OATBOOTCLASSPATH. At the time of writing i18n is a non-updatable
+ // APEX and so does appear in the DEX2OATBOOTCLASSPATH.
+ if (!LocationIsOnArtModule(jar)) {
+ boot_extension_compilable_jars_.emplace_back(jar);
+ }
+ }
+
+ for (const std::string& jar : android::base::Split(config_.GetSystemServerClasspath(), ":")) {
+ // Only consider DEX files on the SYSTEMSERVERCLASSPATH for compilation that do not reside
+ // in APEX modules. Otherwise, we'll recompile on boot any time one of these APEXes updates.
+ if (!LocationIsOnApex(jar)) {
+ systemserver_compilable_jars_.emplace_back(jar);
+ }
+ }
+ }
+
+ time_t GetExecutionTimeUsed() const { return time(nullptr) - start_time_; }
+
+ time_t GetExecutionTimeRemaining() const {
+ return kMaximumExecutionSeconds - GetExecutionTimeUsed();
+ }
+
+ time_t GetSubprocessTimeout() const {
+ return std::max(GetExecutionTimeRemaining(), kMaxChildProcessSeconds);
+ }
+
+ // Read apex_info_list.xml from input stream and determine if the ART APEX
+ // listed is the factory installed version.
+ static bool IsFactoryApex(const std::string& apex_info_list_xml_path) {
+ auto info_list = com::android::apex::readApexInfoList(apex_info_list_xml_path.c_str());
+ if (!info_list.has_value()) {
+ LOG(FATAL) << "Failed to process " << QuotePath(apex_info_list_xml_path);
+ }
+
+ for (const com::android::apex::ApexInfo& info : info_list->getApexInfo()) {
+ if (info.getIsActive() && info.getModuleName() == "com.android.art") {
+ return info.getIsFactory();
+ }
+ }
+
+ LOG(FATAL) << "Failed to find active com.android.art in " << QuotePath(apex_info_list_xml_path);
+ return false;
+ }
+
+ static void AddDex2OatCommonOptions(/*inout*/ std::vector<std::string>& args) {
+ args.emplace_back("--android-root=out/empty");
+ args.emplace_back("--abort-on-hard-verifier-error");
+ args.emplace_back("--compilation-reason=boot");
+ args.emplace_back("--image-format=lz4hc");
+ args.emplace_back("--resolve-startup-const-strings=true");
+ }
+
+ static void AddDex2OatConcurrencyArguments(/*inout*/ std::vector<std::string>& args) {
+ static constexpr std::pair<const char*, const char*> kPropertyArgPairs[] = {
+ std::make_pair("dalvik.vm.boot-dex2oat-cpu-set", "--cpu-set="),
+ std::make_pair("dalvik.vm.boot-dex2oat-threads", "-j"),
+ };
+ for (auto property_arg_pair : kPropertyArgPairs) {
+ auto [property, arg] = property_arg_pair;
+ std::string value = android::base::GetProperty(property, {});
+ if (!value.empty()) {
+ args.push_back(arg + value);
+ }
+ }
+ }
+
+ static void AddDex2OatDebugInfo(/*inout*/ std::vector<std::string>& args) {
+ args.emplace_back("--generate-debug-info");
+ args.emplace_back("--generate-mini-debug-info");
+ args.emplace_back("--strip");
+ }
+
+ static void AddDex2OatInstructionSet(/*inout*/ std::vector<std::string> args,
+ InstructionSet isa) {
+ const char* isa_str = GetInstructionSetString(isa);
+ args.emplace_back(Concatenate({"--instruction-set=", isa_str}));
+ }
+
+ static void AddDex2OatProfileAndCompilerFilter(/*inout*/ std::vector<std::string>& args,
+ const std::string& profile_file) {
+ if (OS::FileExists(profile_file.c_str(), /*check_file_type=*/true)) {
+ args.emplace_back(Concatenate({"--profile-file=", profile_file}));
+ args.emplace_back("--compiler-filter=speed-profile");
+ } else {
+ args.emplace_back("--compiler-filter=speed");
+ }
+ }
+
+ bool CheckSystemServerArtifactsAreUpToDate(bool on_system) const {
+ std::vector<std::string> classloader_context;
+ for (const std::string& jar_path : systemserver_compilable_jars_) {
+ std::vector<std::string> args;
+ args.emplace_back(config_.GetDexOptAnalyzer());
+ args.emplace_back("--dex-file=" + jar_path);
+
+ const std::string image_location = GetSystemServerImagePath(on_system, jar_path);
+
+ // odrefresh produces app-image files, but these are not guaranteed for those pre-installed
+ // on /system.
+ if (!on_system && !OS::FileExists(image_location.c_str(), true)) {
+ LOG(INFO) << "Missing image file: " << QuotePath(image_location);
+ return false;
+ }
+
+ // Generate set of artifacts that are output by compilation.
+ OdrArtifacts artifacts = OdrArtifacts::ForSystemServer(image_location);
+ if (!on_system) {
+ CHECK_EQ(artifacts.OatPath(),
+ GetApexDataOdexFilename(jar_path, config_.GetSystemServerIsa()));
+ CHECK_EQ(artifacts.ImagePath(),
+ GetApexDataDalvikCacheFilename(jar_path, config_.GetSystemServerIsa(), "art"));
+ CHECK_EQ(artifacts.OatPath(),
+ GetApexDataDalvikCacheFilename(jar_path, config_.GetSystemServerIsa(), "odex"));
+ CHECK_EQ(artifacts.VdexPath(),
+ GetApexDataDalvikCacheFilename(jar_path, config_.GetSystemServerIsa(), "vdex"));
+ }
+
+ // Associate inputs and outputs with dexoptanalyzer arguments.
+ std::pair<const std::string, const char*> location_args[] = {
+ std::make_pair(artifacts.OatPath(), "--oat-fd="),
+ std::make_pair(artifacts.VdexPath(), "--vdex-fd="),
+ std::make_pair(jar_path, "--zip-fd=")
+ };
+
+ // Open file descriptors for dexoptanalyzer file inputs and add to the command-line.
+ std::vector<std::unique_ptr<File>> files;
+ for (const auto& location_arg : location_args) {
+ auto& [location, arg] = location_arg;
+ std::unique_ptr<File> file(OS::OpenFileForReading(location.c_str()));
+ if (file == nullptr) {
+ PLOG(ERROR) << "Failed to open \"" << location << "\"";
+ return false;
+ }
+ args.emplace_back(android::base::StringPrintf("%s%d", arg, file->Fd()));
+ files.emplace_back(file.release());
+ }
+
+ const std::string basename(android::base::Basename(jar_path));
+ const std::string root = GetAndroidRoot();
+ const std::string profile_file = Concatenate({root, "/framework/", basename, ".prof"});
+ if (OS::FileExists(profile_file.c_str())) {
+ args.emplace_back("--compiler-filter=speed-profile");
+ } else {
+ args.emplace_back("--compiler-filter=speed");
+ }
+
+ args.emplace_back(
+ Concatenate({"--image=", GetBootImage(), ":", GetBootImageExtensionImage(on_system)}));
+ args.emplace_back(
+ Concatenate({"--isa=", GetInstructionSetString(config_.GetSystemServerIsa())}));
+ args.emplace_back("--runtime-arg");
+ args.emplace_back(Concatenate({"-Xbootclasspath:", config_.GetDex2oatBootClasspath()}));
+ args.emplace_back(Concatenate(
+ {"--class-loader-context=PCL[", android::base::Join(classloader_context, ':'), "]"}));
+
+ classloader_context.emplace_back(jar_path);
+
+ LOG(INFO) << "Checking " << jar_path << ": " << android::base::Join(args, ' ');
+ std::string error_msg;
+ bool timed_out = false;
+ const time_t timeout = GetSubprocessTimeout();
+ const int dexoptanalyzer_result = ExecAndReturnCode(args, timeout, &timed_out, &error_msg);
+ if (dexoptanalyzer_result == -1) {
+ LOG(ERROR) << "Unexpected exit from dexoptanalyzer: " << error_msg;
+ if (timed_out) {
+ // TODO(oth): record metric for timeout.
+ }
+ return false;
+ }
+ LOG(INFO) << "dexoptanalyzer returned " << dexoptanalyzer_result;
+
+ bool unexpected_result = true;
+ switch (static_cast<dexoptanalyzer::ReturnCode>(dexoptanalyzer_result)) {
+ case art::dexoptanalyzer::ReturnCode::kNoDexOptNeeded:
+ unexpected_result = false;
+ break;
+
+ // Recompile needed
+ case art::dexoptanalyzer::ReturnCode::kDex2OatFromScratch:
+ case art::dexoptanalyzer::ReturnCode::kDex2OatForBootImageOat:
+ case art::dexoptanalyzer::ReturnCode::kDex2OatForFilterOat:
+ case art::dexoptanalyzer::ReturnCode::kDex2OatForBootImageOdex:
+ case art::dexoptanalyzer::ReturnCode::kDex2OatForFilterOdex:
+ return false;
+
+ // Unexpected issues (note no default-case here to catch missing enum values, but the
+ // return code from dexoptanalyzer may also be outside expected values, such as a
+ // process crash.
+ case art::dexoptanalyzer::ReturnCode::kFlattenClassLoaderContextSuccess:
+ case art::dexoptanalyzer::ReturnCode::kErrorInvalidArguments:
+ case art::dexoptanalyzer::ReturnCode::kErrorCannotCreateRuntime:
+ case art::dexoptanalyzer::ReturnCode::kErrorUnknownDexOptNeeded:
+ break;
+ }
+
+ if (unexpected_result) {
+ LOG(ERROR) << "Unexpected result from dexoptanalyzer: " << dexoptanalyzer_result;
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void RemoveSystemServerArtifactsFromData() const {
+ if (config_.GetDryRun()) {
+ LOG(INFO) << "Removal of system_server artifacts on /data skipped (dry-run).";
+ return;
+ }
+ for (const std::string& jar_path : systemserver_compilable_jars_) {
+ 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);
+ RemoveArtifacts(artifacts);
+ }
+ }
+
+ // Check the validity of system server artifacts on both /system and /data.
+ // This method has the side-effect of removing system server artifacts on /data, if there are
+ // valid artifacts on /system, or if the artifacts on /data are not valid.
+ // Returns true if valid artifacts are found.
+ bool CheckSystemServerArtifactsAreUpToDate() const {
+ bool system_ok = CheckSystemServerArtifactsAreUpToDate(/*on_system=*/true);
+ LOG(INFO) << "system_server artifacts on /system are " << (system_ok ? "ok" : "stale");
+ bool data_ok = CheckSystemServerArtifactsAreUpToDate(/*on_system=*/false);
+ LOG(INFO) << "system_server artifacts on /data are " << (data_ok ? "ok" : "stale");
+ if (system_ok || !data_ok) {
+ // Artifacts on /system are usable or the ones on /data are not usable. Either way, remove
+ // the artifacts /data as they serve no purpose.
+ RemoveSystemServerArtifactsFromData();
+ }
+ return system_ok || data_ok;
+ }
+
+ // Check the validity of boot class path extension artifacts.
+ //
+ // Returns true if artifacts exist and are valid according to dexoptanalyzer.
+ bool CheckBootExtensionArtifactsAreUpToDate(const InstructionSet isa, bool on_system) const {
+ const std::string dex_file = boot_extension_compilable_jars_.front();
+ const std::string image_location = GetBootImageExtensionImage(on_system);
+
+ std::vector<std::string> args;
+ args.emplace_back(config_.GetDexOptAnalyzer());
+ args.emplace_back("--validate-bcp");
+ args.emplace_back(Concatenate({"--image=", GetBootImage(), ":", image_location}));
+ args.emplace_back(Concatenate({"--isa=", GetInstructionSetString(isa)}));
+ args.emplace_back("--runtime-arg");
+ args.emplace_back(Concatenate({"-Xbootclasspath:", config_.GetDex2oatBootClasspath()}));
+
+ LOG(INFO) << "Checking " << dex_file << ": " << android::base::Join(args, ' ');
+
+ std::string error_msg;
+ bool timed_out = false;
+ const time_t timeout = GetSubprocessTimeout();
+ const int dexoptanalyzer_result = ExecAndReturnCode(args, timeout, &timed_out, &error_msg);
+ if (dexoptanalyzer_result == -1) {
+ LOG(ERROR) << "Unexpected exit from dexoptanalyzer: " << error_msg;
+ if (timed_out) {
+ // TODO(oth): record metric for timeout.
+ }
+ return false;
+ }
+ auto rc = static_cast<dexoptanalyzer::ReturnCode>(dexoptanalyzer_result);
+ if (rc == dexoptanalyzer::ReturnCode::kNoDexOptNeeded) {
+ return true;
+ }
+ return false;
+ }
+
+ // Remove boot extension artifacts from /data.
+ void RemoveBootExtensionArtifactsFromData(InstructionSet isa) const {
+ if (config_.GetDryRun()) {
+ LOG(INFO) << "Removal of bcp extension artifacts on /data skipped (dry-run).";
+ return;
+ }
+ const std::string apexdata_image_location = GetBootImageExtensionImagePath(isa);
+ LOG(INFO) << "Removing boot class path artifacts on /data for "
+ << QuotePath(apexdata_image_location);
+ RemoveArtifacts(OdrArtifacts::ForBootImageExtension(apexdata_image_location));
+ }
+
+ // Check whether boot extension artifacts for `isa` are valid on system partition or in apexdata.
+ // This method has the side-effect of removing boot classpath extension artifacts on /data,
+ // if there are valid artifacts on /system, or if the artifacts on /data are not valid.
+ // Returns true if valid boot externsion artifacts are valid.
+ bool CheckBootExtensionArtifactsAreUpToDate(InstructionSet isa) const {
+ bool system_ok = CheckBootExtensionArtifactsAreUpToDate(isa, /*on_system=*/true);
+ LOG(INFO) << "Boot extension artifacts on /system are " << (system_ok ? "ok" : "stale");
+ bool data_ok = CheckBootExtensionArtifactsAreUpToDate(isa, /*on_system=*/false);
+ LOG(INFO) << "Boot extension artifacts on /data are " << (data_ok ? "ok" : "stale");
+ if (system_ok || !data_ok) {
+ // Artifacts on /system are usable or the ones on /data are not usable. Either way, remove
+ // the artifacts /data as they serve no purpose.
+ RemoveBootExtensionArtifactsFromData(isa);
+ }
+ return system_ok || data_ok;
+ }
+
+ static bool GetFreeSpace(const char* path, uint64_t* bytes) {
+ struct statvfs sv;
+ if (statvfs(path, &sv) != 0) {
+ PLOG(ERROR) << "statvfs '" << path << "'";
+ return false;
+ }
+ *bytes = sv.f_bfree * sv.f_bsize;
+ return true;
+ }
+
+ static bool GetUsedSpace(const char* path, uint64_t* bytes) {
+ *bytes = 0;
+
+ std::queue<std::string> unvisited;
+ unvisited.push(path);
+ while (!unvisited.empty()) {
+ std::string current = unvisited.front();
+ std::unique_ptr<DIR, int (*)(DIR*)> dir(opendir(current.c_str()), closedir);
+ for (auto entity = readdir(dir.get()); entity != nullptr; entity = readdir(dir.get())) {
+ if (entity->d_name[0] == '.') {
+ continue;
+ }
+ std::string entity_name = Concatenate({current, "/", entity->d_name});
+ if (entity->d_type == DT_DIR) {
+ unvisited.push(entity_name.c_str());
+ } else if (entity->d_type == DT_REG) {
+ // RoundUp file size to number of blocks.
+ *bytes += RoundUp(OS::GetFileSizeBytes(entity_name.c_str()), 512);
+ } else {
+ LOG(FATAL) << "Unsupported directory entry type: " << static_cast<int>(entity->d_type);
+ }
+ }
+ unvisited.pop();
+ }
+ return true;
+ }
+
+ static void ReportSpace() {
+ uint64_t bytes;
+ std::string data_dir = GetArtApexData();
+ if (GetUsedSpace(data_dir.c_str(), &bytes)) {
+ LOG(INFO) << "Used space " << bytes << " bytes.";
+ }
+ if (GetFreeSpace(data_dir.c_str(), &bytes)) {
+ LOG(INFO) << "Available space " << bytes << " bytes.";
+ }
+ }
+
+ // Checks all artifacts are up-to-date.
+ //
+ // 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`).
+ ExitCode CheckArtifactsAreUpToDate() {
+ ExitCode exit_code = ExitCode::kOkay;
+ for (const InstructionSet isa : config_.GetBootExtensionIsas()) {
+ if (!CheckBootExtensionArtifactsAreUpToDate(isa)) {
+ exit_code = ExitCode::kCompilationRequired;
+ }
+ }
+ if (!CheckSystemServerArtifactsAreUpToDate()) {
+ exit_code = ExitCode::kCompilationRequired;
+ }
+ return exit_code;
+ }
+
+ // Callback for use with nftw(3) to assist with clearing files and sub-directories.
+ // This method removes files and directories below the top-level directory passed to nftw().
+ static int NftwUnlinkRemoveCallback(const char* fpath,
+ const struct stat* sb ATTRIBUTE_UNUSED,
+ int typeflag,
+ struct FTW* ftwbuf) {
+ switch (typeflag) {
+ case FTW_F:
+ case FTW_SL:
+ case FTW_SLN:
+ if (unlink(fpath)) {
+ PLOG(FATAL) << "Failed unlink(\"" << fpath << "\")";
+ }
+ return 0;
+
+ case FTW_DP:
+ if (ftwbuf->level == 0) {
+ return 0;
+ }
+ if (rmdir(fpath) != 0) {
+ PLOG(FATAL) << "Failed rmdir(\"" << fpath << "\")";
+ }
+ return 0;
+
+ case FTW_DNR:
+ LOG(FATAL) << "Inaccessible directory \"" << fpath << "\"";
+ return -1;
+
+ case FTW_NS:
+ LOG(FATAL) << "Failed stat() \"" << fpath << "\"";
+ return -1;
+
+ default:
+ LOG(FATAL) << "Unexpected typeflag " << typeflag << "for \"" << fpath << "\"";
+ return -1;
+ }
+ }
+
+ void RemoveArtifactsOrDie() const {
+ // Remove everything under ArtApexDataDir
+ std::string data_dir = GetArtApexData();
+
+ // Perform depth first traversal removing artifacts.
+ nftw(data_dir.c_str(), NftwUnlinkRemoveCallback, 1, FTW_DEPTH | FTW_MOUNT);
+ }
+
+ void RemoveArtifacts(const OdrArtifacts& artifacts) const {
+ for (const auto& location :
+ {artifacts.ImagePath(), artifacts.OatPath(), artifacts.VdexPath()}) {
+ if (OS::FileExists(location.c_str()) && TEMP_FAILURE_RETRY(unlink(location.c_str())) != 0) {
+ PLOG(ERROR) << "Failed to remove: " << QuotePath(location);
+ }
+ }
+ }
+
+ void RemoveStagingFilesOrDie(const char* staging_dir) const {
+ if (OS::DirectoryExists(staging_dir)) {
+ nftw(staging_dir, NftwUnlinkRemoveCallback, 1, FTW_DEPTH | FTW_MOUNT);
+ }
+ }
+
+ // Create all directory and all required parents.
+ static void EnsureDirectoryExists(const std::string& absolute_path) {
+ CHECK(absolute_path.size() > 0 && absolute_path[0] == '/');
+ std::string path;
+ for (const std::string& directory : android::base::Split(absolute_path, "/")) {
+ path.append("/").append(directory);
+ if (!OS::DirectoryExists(path.c_str())) {
+ if (mkdir(path.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) != 0) {
+ PLOG(FATAL) << "Could not create directory: " << path;
+ }
+ }
+ }
+ }
+
+ static std::string GetBootImage() {
+ // Typically "/apex/com.android.art/javalib/boot.art".
+ return GetArtRoot() + "/javalib/boot.art";
+ }
+
+ std::string GetBootImageExtensionImage(bool on_system) const {
+ CHECK(!boot_extension_compilable_jars_.empty());
+ const std::string leading_jar = boot_extension_compilable_jars_[0];
+ if (on_system) {
+ const std::string jar_name = android::base::Basename(leading_jar);
+ const std::string image_name = ReplaceFileExtension(jar_name, "art");
+ // Typically "/system/framework/boot-framework.art".
+ return Concatenate({GetAndroidRoot(), "/framework/boot-", image_name});
+ } else {
+ // Typically "/data/misc/apexdata/com.android.art/dalvik-cache/boot-framework.art".
+ return GetApexDataBootImage(leading_jar);
+ }
+ }
+
+ std::string GetBootImageExtensionImagePath(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);
+ }
+
+ std::string GetSystemServerImagePath(bool on_system, const std::string& jar_path) const {
+ if (on_system) {
+ 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());
+ // Typically "/system/framework/oat/<isa>/services.art".
+ return Concatenate({GetAndroidRoot(), "/framework/oat/", isa_str, "/", image_name});
+ } else {
+ // Typically
+ // "/data/misc/apexdata/.../dalvik-cache/<isa>/system@framework@services.jar@classes.art".
+ const std::string image = GetApexDataImage(jar_path.c_str());
+ return GetSystemImageFilename(image.c_str(), config_.GetSystemServerIsa());
+ }
+ }
+
+ std::string GetStagingLocation(const std::string& staging_dir, const std::string& path) const {
+ return Concatenate({staging_dir, "/", android::base::Basename(path)});
+ }
+
+ bool CompileBootExtensionArtifacts(const InstructionSet isa,
+ const std::string& staging_dir,
+ std::string* error_msg) const {
+ std::vector<std::string> args;
+ args.push_back(config_.GetDex2Oat());
+
+ AddDex2OatCommonOptions(args);
+ AddDex2OatConcurrencyArguments(args);
+ AddDex2OatDebugInfo(args);
+ AddDex2OatInstructionSet(args, isa);
+ const std::string boot_profile_file(GetAndroidRoot() + "/etc/boot-image.prof");
+ AddDex2OatProfileAndCompilerFilter(args, boot_profile_file);
+
+ // Compile as a single image for fewer files and slightly less memory overhead.
+ args.emplace_back("--single-image");
+
+ // Set boot-image and expectation of compiling boot classpath extensions.
+ args.emplace_back("--boot-image=" + GetBootImage());
+
+ const std::string dirty_image_objects_file(GetAndroidRoot() + "/etc/dirty-image-objects");
+ if (OS::FileExists(dirty_image_objects_file.c_str())) {
+ args.emplace_back(Concatenate({"--dirty-image-objects=", dirty_image_objects_file}));
+ } else {
+ LOG(WARNING) << "Missing dirty objects file : " << QuotePath(dirty_image_objects_file);
+ }
+
+ // Add boot extensions to compile.
+ for (const std::string& component : boot_extension_compilable_jars_) {
+ args.emplace_back("--dex-file=" + component);
+ }
+
+ args.emplace_back("--runtime-arg");
+ args.emplace_back(Concatenate({"-Xbootclasspath:", config_.GetDex2oatBootClasspath()}));
+
+ const std::string image_location = GetBootImageExtensionImagePath(isa);
+ const OdrArtifacts artifacts = OdrArtifacts::ForBootImageExtension(image_location);
+ CHECK_EQ(GetApexDataOatFilename(boot_extension_compilable_jars_.front().c_str(), isa),
+ artifacts.OatPath());
+
+ args.emplace_back("--oat-location=" + artifacts.OatPath());
+ const std::pair<const std::string, const char*> location_kind_pairs[] = {
+ std::make_pair(artifacts.ImagePath(), "image"),
+ std::make_pair(artifacts.OatPath(), "oat"),
+ std::make_pair(artifacts.VdexPath(), "output-vdex")
+ };
+
+ std::vector<std::unique_ptr<File>> staging_files;
+ for (const auto& location_kind_pair : location_kind_pairs) {
+ auto& [location, kind] = location_kind_pair;
+ const std::string staging_location = GetStagingLocation(staging_dir, location);
+ std::unique_ptr<File> staging_file(OS::CreateEmptyFile(staging_location.c_str()));
+ if (staging_file == nullptr) {
+ PLOG(ERROR) << "Failed to create " << kind << " file: " << staging_location;
+ EraseFiles(staging_files);
+ return false;
+ }
+ args.emplace_back(android::base::StringPrintf("--%s-fd=%d", kind, staging_file->Fd()));
+ staging_files.emplace_back(std::move(staging_file));
+ }
+
+ const std::string install_location = android::base::Dirname(image_location);
+ EnsureDirectoryExists(install_location);
+
+ const time_t timeout = GetSubprocessTimeout();
+ const std::string cmd_line = android::base::Join(args, ' ');
+ LOG(INFO) << "Compiling boot extensions (" << isa << "): " << cmd_line
+ << " [timeout " << timeout << "s]";
+ if (config_.GetDryRun()) {
+ LOG(INFO) << "Compilation skipped (dry-run).";
+ return true;
+ }
+
+ bool timed_out = false;
+ if (ExecAndReturnCode(args, timeout, &timed_out, error_msg) != 0) {
+ if (timed_out) {
+ // TODO(oth): record timeout event for compiling boot extension
+ }
+ EraseFiles(staging_files);
+ return false;
+ }
+
+ if (!MoveOrEraseFiles(staging_files, install_location)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ bool CompileSystemServerArtifacts(const std::string& staging_dir, std::string* error_msg) const {
+ std::vector<std::string> classloader_context;
+
+ const std::string dex2oat = config_.GetDex2Oat();
+ const InstructionSet isa = config_.GetSystemServerIsa();
+ for (const std::string& jar : systemserver_compilable_jars_) {
+ std::vector<std::string> args;
+ args.emplace_back(dex2oat);
+ args.emplace_back("--dex-file=" + jar);
+
+ AddDex2OatCommonOptions(args);
+ AddDex2OatConcurrencyArguments(args);
+ AddDex2OatDebugInfo(args);
+ AddDex2OatInstructionSet(args, isa);
+ const std::string jar_name(android::base::Basename(jar));
+ const std::string profile = Concatenate({GetAndroidRoot(), "/framework/", jar_name, ".prof"});
+ AddDex2OatProfileAndCompilerFilter(args, profile);
+
+ const std::string image_location = GetSystemServerImagePath(/*on_system=*/false, jar);
+ const std::string install_location = android::base::Dirname(image_location);
+ if (classloader_context.empty()) {
+ // All images are in the same directory, we only need to check on the first iteration.
+ EnsureDirectoryExists(install_location);
+ }
+
+ OdrArtifacts artifacts = OdrArtifacts::ForSystemServer(image_location);
+ CHECK_EQ(artifacts.OatPath(), GetApexDataOdexFilename(jar.c_str(), isa));
+
+ const std::pair<const std::string, const char*> location_kind_pairs[] = {
+ std::make_pair(artifacts.ImagePath(), "app-image"),
+ std::make_pair(artifacts.OatPath(), "oat"),
+ std::make_pair(artifacts.VdexPath(), "output-vdex")
+ };
+
+ std::vector<std::unique_ptr<File>> staging_files;
+ for (const auto& location_kind_pair : location_kind_pairs) {
+ auto& [location, kind] = location_kind_pair;
+ const std::string staging_location = GetStagingLocation(staging_dir, location);
+ std::unique_ptr<File> staging_file(OS::CreateEmptyFile(staging_location.c_str()));
+ if (staging_file == nullptr) {
+ PLOG(ERROR) << "Failed to create " << kind << " file: " << staging_location;
+ EraseFiles(staging_files);
+ return false;
+ }
+ args.emplace_back(android::base::StringPrintf("--%s-fd=%d", kind, staging_file->Fd()));
+ staging_files.emplace_back(std::move(staging_file));
+ }
+ args.emplace_back("--oat-location=" + artifacts.OatPath());
+
+ if (!config_.GetUpdatableBcpPackagesFile().empty()) {
+ args.emplace_back("--updatable-bcp-packages-file=" + config_.GetUpdatableBcpPackagesFile());
+ }
+
+ args.emplace_back("--runtime-arg");
+ args.emplace_back(Concatenate({"-Xbootclasspath:", config_.GetDex2oatBootClasspath()}));
+ const std::string context_path = android::base::Join(classloader_context, ':');
+ args.emplace_back(Concatenate({"--class-loader-context=PCL[", context_path, "]"}));
+ const std::string extension_image = GetBootImageExtensionImage(/*on_system=*/false);
+ args.emplace_back(Concatenate({"--boot-image=", GetBootImage(), ":", extension_image}));
+
+ const time_t timeout = GetSubprocessTimeout();
+ const std::string cmd_line = android::base::Join(args, ' ');
+ LOG(INFO) << "Compiling " << jar << ": " << cmd_line << " [timeout " << timeout << "s]";
+ if (config_.GetDryRun()) {
+ LOG(INFO) << "Compilation skipped (dry-run).";
+ return true;
+ }
+
+ bool timed_out = false;
+ if (!Exec(args, error_msg)) {
+ if (timed_out) {
+ // TODO(oth): record timeout event for compiling boot extension
+ }
+ EraseFiles(staging_files);
+ return false;
+ }
+
+ if (!MoveOrEraseFiles(staging_files, install_location)) {
+ return false;
+ }
+
+ classloader_context.emplace_back(jar);
+ }
+
+ return true;
+ }
+
+ ExitCode Compile(bool force_compile) const {
+ ReportSpace(); // TODO(oth): Factor available space into compilation logic.
+
+ // Clean-up existing files.
+ if (force_compile) {
+ RemoveArtifactsOrDie();
+ }
+
+ // Create staging area and assign label for generating compilation artifacts.
+ const char* staging_dir;
+ if (PaletteCreateOdrefreshStagingDirectory(&staging_dir) != PALETTE_STATUS_OK) {
+ return ExitCode::kCompilationFailed;
+ }
+
+ std::string error_msg;
+
+ for (const InstructionSet isa : config_.GetBootExtensionIsas()) {
+ if (force_compile || !CheckBootExtensionArtifactsAreUpToDate(isa)) {
+ if (!CompileBootExtensionArtifacts(isa, staging_dir, &error_msg)) {
+ LOG(ERROR) << "BCP compilation failed: " << error_msg;
+ RemoveStagingFilesOrDie(staging_dir);
+ return ExitCode::kCompilationFailed;
+ }
+ }
+ }
+
+ if (force_compile || !CheckSystemServerArtifactsAreUpToDate()) {
+ if (!CompileSystemServerArtifacts(staging_dir, &error_msg)) {
+ LOG(ERROR) << "system_server compilation failed: " << error_msg;
+ RemoveStagingFilesOrDie(staging_dir);
+ return ExitCode::kCompilationFailed;
+ }
+ }
+
+ return ExitCode::kOkay;
+ }
+
+ static bool ArgumentMatches(std::string_view argument,
+ std::string_view prefix,
+ std::string* value) {
+ if (StartsWith(argument, prefix)) {
+ *value = std::string(argument.substr(prefix.size()));
+ return true;
+ }
+ return false;
+ }
+
+ static bool ArgumentEquals(std::string_view argument, std::string_view expected) {
+ return argument == expected;
+ }
+
+ static bool InitializeCommonConfig(std::string_view argument, OdrConfig* config) {
+ static constexpr std::string_view kDryRunArgument{"--dry-run"};
+ if (ArgumentEquals(argument, kDryRunArgument)) {
+ config->SetDryRun();
+ return true;
+ }
+ return false;
+ }
+
+ static int InitializeHostConfig(int argc, const char** argv, OdrConfig* config) {
+ __android_log_set_logger(__android_log_stderr_logger);
+
+ std::string current_binary;
+ if (argv[0][0] == '/') {
+ current_binary = argv[0];
+ } else {
+ std::vector<char> buf(PATH_MAX);
+ if (getcwd(buf.data(), buf.size()) == nullptr) {
+ PLOG(FATAL) << "Failed getwd()";
+ }
+ current_binary = Concatenate({buf.data(), "/", argv[0]});
+ }
+ config->SetArtBinDir(android::base::Dirname(current_binary));
+
+ int n = 1;
+ for (; n < argc - 1; ++n) {
+ const char* arg = argv[n];
+ std::string value;
+ if (ArgumentMatches(arg, "--android-root=", &value)) {
+ setenv("ANDROID_ROOT", value.c_str(), 1);
+ } else if (ArgumentMatches(arg, "--android-art-root=", &value)) {
+ setenv("ANDROID_ART_ROOT", value.c_str(), 1);
+ } else if (ArgumentMatches(arg, "--apex-info-list=", &value)) {
+ config->SetApexInfoListFile(value);
+ } else if (ArgumentMatches(arg, "--art-apex-data=", &value)) {
+ setenv("ART_APEX_DATA", value.c_str(), 1);
+ } else if (ArgumentMatches(arg, "--dex2oat-bootclasspath=", &value)) {
+ config->SetDex2oatBootclasspath(value);
+ } else if (ArgumentMatches(arg, "--isa=", &value)) {
+ config->SetIsa(GetInstructionSetFromString(value.c_str()));
+ } else if (ArgumentMatches(arg, "--system-server-classpath=", &value)) {
+ config->SetSystemServerClasspath(arg);
+ } else if (ArgumentMatches(arg, "--updatable-bcp-packages-file=", &value)) {
+ config->SetUpdatableBcpPackagesFile(value);
+ } else if (ArgumentMatches(arg, "--zygote-arch=", &value)) {
+ ZygoteKind zygote_kind;
+ if (!ParseZygoteKind(value.c_str(), &zygote_kind)) {
+ ArgumentError("Unrecognized zygote kind: '%s'", value.c_str());
+ }
+ config->SetZygoteKind(zygote_kind);
+ } else if (!InitializeCommonConfig(arg, config)) {
+ UsageError("Unrecognized argument: '%s'", arg);
+ }
+ }
+ return n;
+ }
+
+ static int InitializeTargetConfig(int argc, const char** argv, OdrConfig* config) {
+ config->SetApexInfoListFile("/apex/apex-info-list.xml");
+ config->SetArtBinDir(GetArtBinDir());
+ config->SetDex2oatBootclasspath(getenv("DEX2OATBOOTCLASSPATH"));
+ config->SetSystemServerClasspath(getenv("SYSTEMSERVERCLASSPATH"));
+ config->SetIsa(kRuntimeISA);
+
+ const std::string zygote = android::base::GetProperty("ro.zygote", {});
+ ZygoteKind zygote_kind;
+ if (!ParseZygoteKind(zygote.c_str(), &zygote_kind)) {
+ LOG(FATAL) << "Unknown zygote: " << QuotePath(zygote);
+ }
+ config->SetZygoteKind(zygote_kind);
+
+ const std::string updatable_packages =
+ android::base::GetProperty("dalvik.vm.dex2oat-updatable-bcp-packages-file", {});
+ config->SetUpdatableBcpPackagesFile(updatable_packages);
+
+ int n = 1;
+ for (; n < argc - 1; ++n) {
+ if (!InitializeCommonConfig(argv[n], config)) {
+ UsageError("Unrecognized argument: '%s'", argv[n]);
+ }
+ }
+ return n;
+ }
+
+ static int InitializeConfig(int argc, const char** argv, OdrConfig* config) {
+ if (kIsTargetBuild) {
+ return InitializeTargetConfig(argc, argv, config);
+ } else {
+ return InitializeHostConfig(argc, argv, config);
+ }
+ }
+
+ static int main(int argc, const char** argv) {
+ OdrConfig config(argv[0]);
+
+ int n = InitializeConfig(argc, argv, &config);
+ argv += n;
+ argc -= n;
+
+ if (argc != 1) {
+ UsageError("Expected 1 argument, but have %d.", argc);
+ }
+
+ OnDeviceRefresh odr(config);
+ for (int i = 0; i < argc; ++i) {
+ std::string_view action(argv[i]);
+ if (action == "--check") {
+ return odr.CheckArtifactsAreUpToDate();
+ } else if (action == "--compile") {
+ return odr.Compile(/*force_compile=*/false);
+ } else if (action == "--force-compile") {
+ return odr.Compile(/*force_compile=*/true);
+ } else if (action == "--help") {
+ UsageHelp(argv[0]);
+ } else {
+ UsageError("Unknown argument: ", argv[i]);
+ }
+ }
+ return ExitCode::kOkay;
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(OnDeviceRefresh);
+};
+
+} // namespace odrefresh
+} // namespace art
+
+int main(int argc, const char** argv) {
+ return art::odrefresh::OnDeviceRefresh::main(argc, argv);
+}
diff --git a/odrefresh/odrefresh_test.cc b/odrefresh/odrefresh_test.cc
new file mode 100644
index 0000000..a09accb
--- /dev/null
+++ b/odrefresh/odrefresh_test.cc
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "odrefresh/odrefresh.h"
+
+#include "base/common_art_test.h"
+#include "base/file_utils.h"
+
+namespace art {
+namespace odrefresh {
+
+TEST(OdRefreshTest, OdrefreshArtifactDirectory) {
+ // odrefresh.h defines kOdrefreshArtifactDirectory for external callers of odrefresh. This is
+ // where compilation artifacts end up.
+ ScopedUnsetEnvironmentVariable no_env("ART_APEX_DATA");
+ EXPECT_EQ(kOdrefreshArtifactDirectory, GetArtApexData() + "/dalvik-cache");
+}
+
+} // namespace odrefresh
+} // namespace art
diff --git a/tools/Android.bp b/tools/Android.bp
index 9dba79a..a233bbe 100644
--- a/tools/Android.bp
+++ b/tools/Android.bp
@@ -71,12 +71,6 @@
},
}
-sh_binary {
- name: "compile_bcp.sh",
- host_supported: false,
- src: "compile_bcp.sh",
-}
-
python_binary_host {
name: "art-run-test-checker",
srcs: [
diff --git a/tools/compile_bcp.sh b/tools/compile_bcp.sh
deleted file mode 100755
index 2fcee13..0000000
--- a/tools/compile_bcp.sh
+++ /dev/null
@@ -1,178 +0,0 @@
-#!/system/bin/sh
-#
-# Copyright (C) 2020 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Script to recompile the non-APEX jars in the boot class path and
-# system server jars for on-device signing following an ART Module
-# update.
-#
-# For testing purposes, prepare Android device using:
-#
-# $ adb root
-# $ adb shell setenforce 0
-#
-# TODO: this is script is currently for proof-of-concept. It does not
-# respect many of the available dalvik.vm properties, e.g. affinity,
-# threads, etc nor dalvik.vm.dex2oat-resolve-startup-strings.
-#
-# TODO: some of the system server jars seem to be installed on device
-# for 32-bit and 64-bit, but services.jar artifacts are just one arch.
-# Not sure why both flavors are present. Does this script need to generate
-# both?
-#
-# TODO: Logging failure / error handling.
-
-# Output directory for generated files. Real location is TBD.
-output=$PWD/out
-
-function mkdir_clean() {
- # mkdir_clean <dir_path>
- local dir_path=$1
- rm -rf "${dir_path}"
- mkdir -p "${dir_path}"
-}
-
-# Determine candidate architectures for this device.
-case `getprop ro.product.cpu.abi` in
- arm*)
- arch32=arm
- arch64=arm64
- ;;
- x86*)
- arch32=x86
- arch64=x86_64
- ;;
- *)
- echo "Unknown abi"
- exit -1
-esac
-
-# Determine architectures to use for Zygote. system_server runs under the primary
-# architecture. Prefer dex2oat64 if device supports 64-bits.
-case $(getprop ro.zygote) in
- zygote32)
- # The primary architecture is 32-bits.
- archs="$arch32"
- systemserver_arch="$arch32"
- dex2oat=/apex/com.android.art/bin/dex2oat32
- ;;
- zygote32_64)
- # The primary architecture is 32-bits and the secondary is 64-bits.
- archs="$arch32 $arch64"
- systemserver_arch="$arch32"
- dex2oat=/apex/com.android.art/bin/dex2oat64
- ;;
- zygote64_32)
- # The primary architecture is 64-bits and the secondary is 32-bits.
- archs="$arch32 $arch64"
- systemserver_arch="$arch64"
- dex2oat=/apex/com.android.art/bin/dex2oat64
- ;;
- zygote64)
- # Primary architecture is 64-bits.
- archs="$arch64"
- systemserver_arch="$arch64"
- dex2oat=/apex/com.android.art/bin/dex2oat64
- ;;
- *)
- echo "Unknown ro.zygote value"
- exit -1
- ;;
-esac
-
-# Determine which boot class path jars to compile.
-device_bcp_list=""
-device_bcp_dex_files=""
-for jar in ${DEX2OATBOOTCLASSPATH//:/ }; do
- if [[ ${jar} = *com.android.art* ]]; then
- continue
- fi
- device_bcp_list="${device_bcp_list}${device_bcp_list:+:}${jar}"
- device_bcp_dex_files="$device_bcp_dex_files --dex-file=${jar}"
-done
-
-# Compile the boot class path elements that are present on device.
-for arch in ${archs}; do
- arch_output="${output}/$arch"
- mkdir_clean "${arch_output}"
-
- invocation_dir="${output}/$arch"
- mkdir_clean "${invocation_dir}"
-
- echo "Compiling ${device_bcp_list} ($arch)"
- ${dex2oat} --avoid-storing-invocation \
- --compiler-filter=speed-profile \
- --profile-file=/system/etc/boot-image.prof \
- --dirty-image-objects=/system/etc/dirty-image-objects \
- --runtime-arg -Xbootclasspath:${DEX2OATBOOTCLASSPATH} \
- --boot-image=/apex/com.android.art/javalib/boot.art \
- ${device_bcp_dex_files} \
- --generate-debug-info \
- --image-format=lz4hc \
- --strip \
- --oat-file=${arch_output}/boot.oat \
- --image=${arch_output}/boot.art \
- --android-root=out/empty \
- --abort-on-hard-verifier-error \
- --instruction-set=$arch \
- --generate-mini-debug-info
-done
-
-# Compile system_server and related jars.
-classloader_context=""
-for jar in ${SYSTEMSERVERCLASSPATH//:/ }; do
- # Skip class path components in APEXes
- if [[ ${jar} = "/apex"* ]]; then
- continue
- fi
-
- # Add profile if it exists. Only services.jar has a profile in AOSP.
- stem=$(basename $jar .jar)
- profile_file=/system/framework/${stem}.jar.prof
- if [ -f "${profile_file}" ] ; then
- profile_arg=--profile-file=${profile_file}
- filter=speed-profile
- else
- profile_arg=""
- filter=speed
- fi
-
- # Add updatable boot class path packages file if there is a property for it.
- updatable_bcp_file=$(getprop dalvik.vm.dex2oat-updatable-bcp-packages-file)
- if [ "${updatable_bcp_file}" -a -f "${updatable_bcp_file}" ] ; then
- updatable_bcp_file_arg="--updatable-bcp-packages-file=${updatable_bcp_file}"
- fi
-
- echo "Compiling ${jar} (${systemserver_arch} ${filter} PCL[${classloader_context}])"
- $dex2oat --avoid-storing-invocation \
- --runtime-arg -Xbootclasspath:${DEX2OATBOOTCLASSPATH} \
- --class-loader-context=PCL[${classloader_context}] \
- --boot-image=/apex/com.android.art/javalib/boot.art:${output}/boot-framework.art \
- --dex-file=${jar} \
- --oat-file=${arch_output}/${stem}.odex \
- --app-image-file=${arch_output}/${stem}.art \
- --android-root=out/empty \
- --instruction-set=${systemserver_arch} \
- --abort-on-hard-verifier-error \
- --compiler-filter=$filter \
- --generate-mini-debug-info \
- --compilation-reason=prebuilt \
- --image-format=lz4 \
- --resolve-startup-const-strings=true \
- ${profile_arg} \
- ${updatable_bcp_file_arg}
-
- classloader_context=${classloader_context}${classloader_context:+:}${jar}
-done