diff options
Diffstat (limited to 'odrefresh/odrefresh.cc')
| -rw-r--r-- | odrefresh/odrefresh.cc | 1617 |
1 files changed, 934 insertions, 683 deletions
diff --git a/odrefresh/odrefresh.cc b/odrefresh/odrefresh.cc index f829bb8ee1..7bbc922a8c 100644 --- a/odrefresh/odrefresh.cc +++ b/odrefresh/odrefresh.cc @@ -53,19 +53,23 @@ #include <utility> #include <vector> +#include "android-base/chrono_utils.h" #include "android-base/file.h" #include "android-base/logging.h" #include "android-base/macros.h" +#include "android-base/parsebool.h" #include "android-base/parseint.h" #include "android-base/properties.h" #include "android-base/result.h" #include "android-base/scopeguard.h" #include "android-base/stringprintf.h" #include "android-base/strings.h" +#include "android-modules-utils/sdk_level.h" #include "android/log.h" #include "arch/instruction_set.h" #include "base/file_utils.h" #include "base/globals.h" +#include "base/logging.h" #include "base/macros.h" #include "base/os.h" #include "base/stl_util.h" @@ -76,6 +80,8 @@ #include "dex/art_dex_file_loader.h" #include "dexoptanalyzer.h" #include "exec_utils.h" +#include "fmt/format.h" +#include "gc/collector/mark_compact.h" #include "log/log.h" #include "odr_artifacts.h" #include "odr_common.h" @@ -86,31 +92,54 @@ #include "odrefresh/odrefresh.h" #include "palette/palette.h" #include "palette/palette_types.h" +#include "read_barrier_config.h" namespace art { namespace odrefresh { +namespace { + namespace apex = com::android::apex; namespace art_apex = com::android::art; -using android::base::Result; - -namespace { +using ::android::base::Basename; +using ::android::base::Dirname; +using ::android::base::GetProperty; +using ::android::base::Join; +using ::android::base::ParseBool; +using ::android::base::ParseBoolResult; +using ::android::base::ParseInt; +using ::android::base::Result; +using ::android::base::SetProperty; +using ::android::base::Split; +using ::android::base::StartsWith; +using ::android::base::StringPrintf; +using ::android::base::Timer; +using ::android::modules::sdklevel::IsAtLeastU; + +using ::fmt::literals::operator""_format; // NOLINT // Name of cache info file in the ART Apex artifact cache. constexpr const char* kCacheInfoFile = "cache-info.xml"; // Maximum execution time for odrefresh from start to end. -constexpr time_t kMaximumExecutionSeconds = 300; +constexpr time_t kMaximumExecutionSeconds = 480; // Maximum execution time for any child process spawned. -constexpr time_t kMaxChildProcessSeconds = 90; +constexpr time_t kMaxChildProcessSeconds = 120; constexpr mode_t kFileMode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; constexpr const char* kFirstBootImageBasename = "boot.art"; constexpr const char* kMinimalBootImageBasename = "boot_minimal.art"; +// The default compiler filter for primary boot image. +constexpr const char* kPrimaryCompilerFilter = "speed-profile"; + +// The compiler filter for boot image mainline extension. We don't have profiles for mainline BCP +// jars, so we always use "verify". +constexpr const char* kMainlineCompilerFilter = "verify"; + void EraseFiles(const std::vector<std::unique_ptr<File>>& files) { for (auto& file : files) { file->Erase(/*unlink=*/true); @@ -127,9 +156,9 @@ 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(); + std::string file_basename(Basename(file->GetPath())); + std::string output_file_path = "{}/{}"_format(output_directory_path, file_basename); + std::string input_file_path = file->GetPath(); output_files.emplace_back(OS::CreateEmptyFileWriteOnly(output_file_path.c_str())); if (output_files.back() == nullptr) { @@ -147,7 +176,7 @@ bool MoveOrEraseFiles(const std::vector<std::unique_ptr<File>>& files, return false; } - const size_t file_bytes = file->GetLength(); + 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); @@ -204,10 +233,12 @@ std::vector<art_apex::ModuleInfo> GenerateModuleInfoList( return module_info_list; } -// Returns a rewritten path based on ANDROID_ROOT if the path starts with "/system/". -std::string AndroidRootRewrite(const std::string& path) { +// Returns a rewritten path based on environment variables for interesting paths. +std::string RewriteParentDirectoryIfNeeded(const std::string& path) { if (StartsWith(path, "/system/")) { - return Concatenate({GetAndroidRoot(), path.substr(7)}); + return GetAndroidRoot() + path.substr(7); + } else if (StartsWith(path, "/system_ext/")) { + return GetSystemExtRoot() + path.substr(11); } else { return path; } @@ -279,9 +310,8 @@ std::vector<T> GenerateComponents( custom_generator) { std::vector<T> components; - ArtDexFileLoader loader; for (const std::string& path : jars) { - std::string actual_path = AndroidRootRewrite(path); + std::string actual_path = RewriteParentDirectoryIfNeeded(path); struct stat sb; if (stat(actual_path.c_str(), &sb) == -1) { PLOG(ERROR) << "Failed to stat component: " << QuotePath(actual_path); @@ -291,7 +321,8 @@ std::vector<T> GenerateComponents( std::vector<uint32_t> checksums; std::vector<std::string> dex_locations; std::string error_msg; - if (!loader.GetMultiDexChecksums(actual_path.c_str(), &checksums, &dex_locations, &error_msg)) { + if (!ArtDexFileLoader::GetMultiDexChecksums( + actual_path.c_str(), &checksums, &dex_locations, &error_msg)) { LOG(ERROR) << "Failed to get multi-dex checksums: " << error_msg; return {}; } @@ -301,7 +332,7 @@ std::vector<T> GenerateComponents( if (i != 0) { oss << ';'; } - oss << android::base::StringPrintf("%08x", checksums[i]); + oss << StringPrintf("%08x", checksums[i]); } const std::string checksum = oss.str(); @@ -370,31 +401,47 @@ void AddDex2OatCommonOptions(/*inout*/ std::vector<std::string>& args) { } bool IsCpuSetSpecValid(const std::string& cpu_set) { - for (auto& str : android::base::Split(cpu_set, ",")) { + for (const std::string& str : Split(cpu_set, ",")) { int id; - if (!android::base::ParseInt(str, &id, 0)) { + if (!ParseInt(str, &id, 0)) { return false; } } return true; } -bool AddDex2OatConcurrencyArguments(/*inout*/ std::vector<std::string>& args) { - std::string threads = android::base::GetProperty("dalvik.vm.boot-dex2oat-threads", ""); +Result<void> AddDex2OatConcurrencyArguments(/*inout*/ std::vector<std::string>& args, + bool is_compilation_os) { + std::string threads; + if (is_compilation_os) { + threads = GetProperty("dalvik.vm.background-dex2oat-threads", ""); + if (threads.empty()) { + threads = GetProperty("dalvik.vm.dex2oat-threads", ""); + } + } else { + threads = GetProperty("dalvik.vm.boot-dex2oat-threads", ""); + } if (!threads.empty()) { args.push_back("-j" + threads); } - std::string cpu_set = android::base::GetProperty("dalvik.vm.boot-dex2oat-cpu-set", ""); - if (cpu_set.empty()) { - return true; + std::string cpu_set; + if (is_compilation_os) { + cpu_set = GetProperty("dalvik.vm.background-dex2oat-cpu-set", ""); + if (cpu_set.empty()) { + cpu_set = GetProperty("dalvik.vm.dex2oat-cpu-set", ""); + } + } else { + cpu_set = GetProperty("dalvik.vm.boot-dex2oat-cpu-set", ""); } - if (!IsCpuSetSpecValid(cpu_set)) { - LOG(ERROR) << "Invalid CPU set spec: " << cpu_set; - return false; + if (!cpu_set.empty()) { + if (!IsCpuSetSpecValid(cpu_set)) { + return Errorf("Invalid CPU set spec '{}'", cpu_set); + } + args.push_back("--cpu-set=" + cpu_set); } - args.push_back("--cpu-set=" + cpu_set); - return true; + + return {}; } void AddDex2OatDebugInfo(/*inout*/ std::vector<std::string>& args) { @@ -404,10 +451,11 @@ void AddDex2OatDebugInfo(/*inout*/ std::vector<std::string>& args) { void AddDex2OatInstructionSet(/*inout*/ std::vector<std::string>& args, InstructionSet isa) { const char* isa_str = GetInstructionSetString(isa); - args.emplace_back(Concatenate({"--instruction-set=", isa_str})); + args.emplace_back(StringPrintf("--instruction-set=%s", isa_str)); } -void AddDex2OatProfileAndCompilerFilter( +// Returns true if any profile has been added. +bool AddDex2OatProfile( /*inout*/ std::vector<std::string>& args, /*inout*/ std::vector<std::unique_ptr<File>>& output_files, const std::vector<std::string>& profile_paths) { @@ -415,22 +463,17 @@ void AddDex2OatProfileAndCompilerFilter( for (auto& path : profile_paths) { std::unique_ptr<File> profile_file(OS::OpenFileForReading(path.c_str())); if (profile_file && profile_file->IsOpened()) { - args.emplace_back(android::base::StringPrintf("--profile-file-fd=%d", profile_file->Fd())); + args.emplace_back(StringPrintf("--profile-file-fd=%d", profile_file->Fd())); output_files.emplace_back(std::move(profile_file)); has_any_profile = true; } } - - if (has_any_profile) { - args.emplace_back("--compiler-filter=speed-profile"); - } else { - args.emplace_back("--compiler-filter=speed"); - } + return has_any_profile; } -bool AddBootClasspathFds(/*inout*/ std::vector<std::string>& args, - /*inout*/ std::vector<std::unique_ptr<File>>& output_files, - const std::vector<std::string>& bcp_jars) { +Result<void> AddBootClasspathFds(/*inout*/ std::vector<std::string>& args, + /*inout*/ std::vector<std::unique_ptr<File>>& output_files, + const std::vector<std::string>& bcp_jars) { std::vector<std::string> bcp_fds; for (const std::string& jar : bcp_jars) { // Special treatment for Compilation OS. JARs in staged APEX may not be visible to Android, and @@ -439,26 +482,38 @@ bool AddBootClasspathFds(/*inout*/ std::vector<std::string>& args, if (StartsWith(jar, "/apex/")) { bcp_fds.emplace_back("-1"); } else { - std::string actual_path = AndroidRootRewrite(jar); + std::string actual_path = RewriteParentDirectoryIfNeeded(jar); std::unique_ptr<File> jar_file(OS::OpenFileForReading(actual_path.c_str())); if (!jar_file || !jar_file->IsValid()) { - LOG(ERROR) << "Failed to open a BCP jar " << actual_path; - return false; + return Errorf("Failed to open a BCP jar '{}'", actual_path); } bcp_fds.push_back(std::to_string(jar_file->Fd())); output_files.push_back(std::move(jar_file)); } } args.emplace_back("--runtime-arg"); - args.emplace_back(Concatenate({"-Xbootclasspathfds:", android::base::Join(bcp_fds, ':')})); - return true; + args.emplace_back("-Xbootclasspathfds:" + Join(bcp_fds, ':')); + return {}; +} + +Result<void> AddCacheInfoFd(/*inout*/ std::vector<std::string>& args, + /*inout*/ std::vector<std::unique_ptr<File>>& readonly_files_raii, + const std::string& cache_info_filename) { + std::unique_ptr<File> cache_info_file(OS::OpenFileForReading(cache_info_filename.c_str())); + if (cache_info_file == nullptr) { + return ErrnoErrorf("Failed to open a cache info file '{}'", cache_info_file); + } + + args.emplace_back("--cache-info-fd=" + std::to_string(cache_info_file->Fd())); + readonly_files_raii.push_back(std::move(cache_info_file)); + return {}; } std::string GetBootImageComponentBasename(const std::string& jar_path, bool is_first_jar) { if (is_first_jar) { return kFirstBootImageBasename; } - const std::string jar_name = android::base::Basename(jar_path); + std::string jar_name = Basename(jar_path); return "boot-" + ReplaceFileExtension(jar_name, "art"); } @@ -466,17 +521,27 @@ void AddCompiledBootClasspathFdsIfAny( /*inout*/ std::vector<std::string>& args, /*inout*/ std::vector<std::unique_ptr<File>>& output_files, const std::vector<std::string>& bcp_jars, - const InstructionSet isa, - const std::string& artifact_dir) { + InstructionSet isa, + const std::vector<std::string>& boot_image_locations) { std::vector<std::string> bcp_image_fds; std::vector<std::string> bcp_oat_fds; std::vector<std::string> bcp_vdex_fds; std::vector<std::unique_ptr<File>> opened_files; bool added_any = false; + std::string artifact_dir; for (size_t i = 0; i < bcp_jars.size(); i++) { const std::string& jar = bcp_jars[i]; - std::string image_path = - artifact_dir + "/" + GetBootImageComponentBasename(jar, /*is_first_jar=*/i == 0); + std::string basename = GetBootImageComponentBasename(jar, /*is_first_jar=*/i == 0); + // If there is an entry in `boot_image_locations` for the current jar, update `artifact_dir` for + // the current jar and the subsequent jars. + for (const std::string& location : boot_image_locations) { + if (Basename(location) == basename) { + artifact_dir = Dirname(location); + break; + } + } + CHECK(!artifact_dir.empty()); + std::string image_path = artifact_dir + "/" + basename; image_path = GetSystemImageFilename(image_path.c_str(), isa); std::unique_ptr<File> image_file(OS::OpenFileForReading(image_path.c_str())); if (image_file && image_file->IsValid()) { @@ -512,19 +577,16 @@ void AddCompiledBootClasspathFdsIfAny( std::move(opened_files.begin(), opened_files.end(), std::back_inserter(output_files)); args.emplace_back("--runtime-arg"); - args.emplace_back( - Concatenate({"-Xbootclasspathimagefds:", android::base::Join(bcp_image_fds, ':')})); + args.emplace_back("-Xbootclasspathimagefds:" + Join(bcp_image_fds, ':')); args.emplace_back("--runtime-arg"); - args.emplace_back( - Concatenate({"-Xbootclasspathoatfds:", android::base::Join(bcp_oat_fds, ':')})); + args.emplace_back("-Xbootclasspathoatfds:" + Join(bcp_oat_fds, ':')); args.emplace_back("--runtime-arg"); - args.emplace_back( - Concatenate({"-Xbootclasspathvdexfds:", android::base::Join(bcp_vdex_fds, ':')})); + args.emplace_back("-Xbootclasspathvdexfds:" + Join(bcp_vdex_fds, ':')); } } std::string GetStagingLocation(const std::string& staging_dir, const std::string& path) { - return Concatenate({staging_dir, "/", android::base::Basename(path)}); + return staging_dir + "/" + Basename(path); } WARN_UNUSED bool CheckCompilationSpace() { @@ -553,13 +615,59 @@ WARN_UNUSED bool CheckCompilationSpace() { return true; } -std::string GetSystemBootImageDir() { return GetAndroidRoot() + "/framework"; } +bool HasVettedDeviceSystemServerProfiles() { + // While system_server profiles were bundled on the device prior to U+, they were not used by + // default or rigorously tested, so we cannot vouch for their efficacy. + static const bool kDeviceIsAtLeastU = IsAtLeastU(); + return kDeviceIsAtLeastU; +} } // namespace +CompilationOptions CompilationOptions::CompileAll(const OnDeviceRefresh& odr) { + CompilationOptions options; + for (InstructionSet isa : odr.Config().GetBootClasspathIsas()) { + options.boot_images_to_generate_for_isas.emplace_back( + isa, BootImages{.primary_boot_image = true, .boot_image_mainline_extension = true}); + } + options.system_server_jars_to_compile = odr.AllSystemServerJars(); + return options; +} + +int BootImages::Count() const { + int count = 0; + if (primary_boot_image) { + count++; + } + if (boot_image_mainline_extension) { + count++; + } + return count; +} + +OdrMetrics::BcpCompilationType BootImages::GetTypeForMetrics() const { + if (primary_boot_image && boot_image_mainline_extension) { + return OdrMetrics::BcpCompilationType::kPrimaryAndMainline; + } + if (boot_image_mainline_extension) { + return OdrMetrics::BcpCompilationType::kMainline; + } + LOG(FATAL) << "Unexpected BCP compilation type"; + UNREACHABLE(); +} + +int CompilationOptions::CompilationUnitCount() const { + int count = 0; + for (const auto& [isa, boot_images] : boot_images_to_generate_for_isas) { + count += boot_images.Count(); + } + count += system_server_jars_to_compile.size(); + return count; +} + OnDeviceRefresh::OnDeviceRefresh(const OdrConfig& config) : OnDeviceRefresh(config, - Concatenate({config.GetArtifactDirectory(), "/", kCacheInfoFile}), + config.GetArtifactDirectory() + "/" + kCacheInfoFile, std::make_unique<ExecUtils>()) {} OnDeviceRefresh::OnDeviceRefresh(const OdrConfig& config, @@ -569,19 +677,17 @@ OnDeviceRefresh::OnDeviceRefresh(const OdrConfig& config, cache_info_filename_{cache_info_filename}, start_time_{time(nullptr)}, exec_utils_{std::move(exec_utils)} { - for (const std::string& jar : android::base::Split(config_.GetDex2oatBootClasspath(), ":")) { - // 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. - boot_classpath_compilable_jars_.emplace_back(jar); - } + // 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. + dex2oat_boot_classpath_jars_ = Split(config_.GetDex2oatBootClasspath(), ":"); - all_systemserver_jars_ = android::base::Split(config_.GetSystemServerClasspath(), ":"); + all_systemserver_jars_ = Split(config_.GetSystemServerClasspath(), ":"); systemserver_classpath_jars_ = {all_systemserver_jars_.begin(), all_systemserver_jars_.end()}; - boot_classpath_jars_ = android::base::Split(config_.GetBootClasspath(), ":"); + boot_classpath_jars_ = Split(config_.GetBootClasspath(), ":"); std::string standalone_system_server_jars_str = config_.GetStandaloneSystemServerJars(); if (!standalone_system_server_jars_str.empty()) { std::vector<std::string> standalone_systemserver_jars = - android::base::Split(standalone_system_server_jars_str, ":"); + Split(standalone_system_server_jars_str, ":"); std::move(standalone_systemserver_jars.begin(), standalone_systemserver_jars.end(), std::back_inserter(all_systemserver_jars_)); @@ -610,8 +716,8 @@ std::optional<std::vector<apex::ApexInfo>> OnDeviceRefresh::GetApexInfoList() co std::unordered_set<std::string_view> relevant_apexes; relevant_apexes.reserve(info_list->getApexInfo().size()); for (const std::vector<std::string>* jar_list : - {&boot_classpath_compilable_jars_, &all_systemserver_jars_, &boot_classpath_jars_}) { - for (auto& jar : *jar_list) { + {&all_systemserver_jars_, &boot_classpath_jars_}) { + for (const std::string& jar : *jar_list) { std::string_view apex = ApexNameFromLocation(jar); if (!apex.empty()) { relevant_apexes.insert(apex); @@ -632,8 +738,16 @@ std::optional<std::vector<apex::ApexInfo>> OnDeviceRefresh::GetApexInfoList() co return filtered_info_list; } -std::optional<art_apex::CacheInfo> OnDeviceRefresh::ReadCacheInfo() const { - return art_apex::read(cache_info_filename_.c_str()); +Result<art_apex::CacheInfo> OnDeviceRefresh::ReadCacheInfo() const { + std::optional<art_apex::CacheInfo> cache_info = art_apex::read(cache_info_filename_.c_str()); + if (!cache_info.has_value()) { + if (errno != 0) { + return ErrnoErrorf("Failed to load {}", QuotePath(cache_info_filename_)); + } else { + return Errorf("Failed to parse {}", QuotePath(cache_info_filename_)); + } + } + return cache_info.value(); } Result<void> OnDeviceRefresh::WriteCacheInfo() const { @@ -643,7 +757,7 @@ Result<void> OnDeviceRefresh::WriteCacheInfo() const { } } - const std::string dir_name = android::base::Dirname(cache_info_filename_); + std::string dir_name = Dirname(cache_info_filename_); if (!EnsureDirectoryExists(dir_name)) { return Errorf("Could not create directory {}", QuotePath(dir_name)); } @@ -667,23 +781,11 @@ Result<void> OnDeviceRefresh::WriteCacheInfo() const { std::vector<art_apex::ModuleInfo> module_info_list = GenerateModuleInfoList(apex_info_list.value()); - std::optional<std::vector<art_apex::Component>> bcp_components = - GenerateBootClasspathComponents(); - if (!bcp_components.has_value()) { - return Errorf("No boot classpath components."); - } - - std::optional<std::vector<art_apex::Component>> bcp_compilable_components = - GenerateBootClasspathCompilableComponents(); - if (!bcp_compilable_components.has_value()) { - return Errorf("No boot classpath compilable components."); - } - - std::optional<std::vector<art_apex::SystemServerComponent>> system_server_components = + std::vector<art_apex::Component> bcp_components = GenerateBootClasspathComponents(); + std::vector<art_apex::Component> dex2oat_bcp_components = + GenerateDex2oatBootClasspathComponents(); + std::vector<art_apex::SystemServerComponent> system_server_components = GenerateSystemServerComponents(); - if (!system_server_components.has_value()) { - return Errorf("No system_server components."); - } std::ofstream out(cache_info_filename_.c_str()); if (out.fail()) { @@ -694,9 +796,9 @@ Result<void> OnDeviceRefresh::WriteCacheInfo() const { {art_apex::KeyValuePairList(system_properties)}, {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())}, + {art_apex::Classpath(bcp_components)}, + {art_apex::Classpath(dex2oat_bcp_components)}, + {art_apex::SystemServerComponents(system_server_components)}, config_.GetCompilationOsMode() ? std::make_optional(true) : std::nullopt)); art_apex::write(out, *info); @@ -713,16 +815,15 @@ static void ReportNextBootAnimationProgress(uint32_t current_compilation, // We arbitrarily show progress until 90%, expecting that our compilations take a large chunk of // boot time. uint32_t value = (90 * current_compilation) / number_of_compilations; - android::base::SetProperty("service.bootanim.progress", std::to_string(value)); + SetProperty("service.bootanim.progress", std::to_string(value)); } std::vector<art_apex::Component> OnDeviceRefresh::GenerateBootClasspathComponents() const { return GenerateComponents(boot_classpath_jars_); } -std::vector<art_apex::Component> OnDeviceRefresh::GenerateBootClasspathCompilableComponents() - const { - return GenerateComponents(boot_classpath_compilable_jars_); +std::vector<art_apex::Component> OnDeviceRefresh::GenerateDex2oatBootClasspathComponents() const { + return GenerateComponents(dex2oat_boot_classpath_jars_); } std::vector<art_apex::SystemServerComponent> OnDeviceRefresh::GenerateSystemServerComponents() @@ -735,7 +836,43 @@ std::vector<art_apex::SystemServerComponent> OnDeviceRefresh::GenerateSystemServ }); } -std::string OnDeviceRefresh::GetBootImage(bool on_system, bool minimal) const { +std::vector<std::string> OnDeviceRefresh::GetArtBcpJars() const { + std::string art_root = GetArtRoot() + "/"; + std::vector<std::string> art_bcp_jars; + for (const std::string& jar : dex2oat_boot_classpath_jars_) { + if (StartsWith(jar, art_root)) { + art_bcp_jars.push_back(jar); + } + } + CHECK(!art_bcp_jars.empty()); + return art_bcp_jars; +} + +std::vector<std::string> OnDeviceRefresh::GetFrameworkBcpJars() const { + std::string art_root = GetArtRoot() + "/"; + std::vector<std::string> framework_bcp_jars; + for (const std::string& jar : dex2oat_boot_classpath_jars_) { + if (!StartsWith(jar, art_root)) { + framework_bcp_jars.push_back(jar); + } + } + CHECK(!framework_bcp_jars.empty()); + return framework_bcp_jars; +} + +std::vector<std::string> OnDeviceRefresh::GetMainlineBcpJars() const { + // Elements in `dex2oat_boot_classpath_jars_` should be at the beginning of + // `boot_classpath_jars_`, followed by mainline BCP jars. + CHECK_LT(dex2oat_boot_classpath_jars_.size(), boot_classpath_jars_.size()); + CHECK(std::equal(dex2oat_boot_classpath_jars_.begin(), + dex2oat_boot_classpath_jars_.end(), + boot_classpath_jars_.begin(), + boot_classpath_jars_.begin() + dex2oat_boot_classpath_jars_.size())); + return {boot_classpath_jars_.begin() + dex2oat_boot_classpath_jars_.size(), + boot_classpath_jars_.end()}; +} + +std::string OnDeviceRefresh::GetPrimaryBootImage(bool on_system, bool minimal) const { DCHECK(!on_system || !minimal); const char* basename = minimal ? kMinimalBootImageBasename : kFirstBootImageBasename; if (on_system) { @@ -747,28 +884,74 @@ std::string OnDeviceRefresh::GetBootImage(bool on_system, bool minimal) const { } } -std::string OnDeviceRefresh::GetBootImagePath(bool on_system, - bool minimal, - const InstructionSet isa) const { +std::string OnDeviceRefresh::GetPrimaryBootImagePath(bool on_system, + bool minimal, + InstructionSet isa) const { // Typically "/data/misc/apexdata/com.android.art/dalvik-cache/<isa>/boot.art". - return GetSystemImageFilename(GetBootImage(on_system, minimal).c_str(), isa); + return GetSystemImageFilename(GetPrimaryBootImage(on_system, minimal).c_str(), isa); } -std::string OnDeviceRefresh::GetSystemBootImageExtension() const { - std::string art_root = GetArtRoot() + "/"; - // Find the first boot extension jar. - auto it = std::find_if_not( - boot_classpath_compilable_jars_.begin(), - boot_classpath_compilable_jars_.end(), - [&](const std::string& jar) { return android::base::StartsWith(jar, art_root); }); - CHECK(it != boot_classpath_compilable_jars_.end()); +std::string OnDeviceRefresh::GetSystemBootImageFrameworkExtension() const { + std::vector<std::string> framework_bcp_jars = GetFrameworkBcpJars(); + std::string basename = + GetBootImageComponentBasename(framework_bcp_jars[0], /*is_first_jar=*/false); // Typically "/system/framework/boot-framework.art". - return GetSystemBootImageDir() + "/" + GetBootImageComponentBasename(*it, /*is_first_jar=*/false); + return "{}/framework/{}"_format(GetAndroidRoot(), basename); } -std::string OnDeviceRefresh::GetSystemBootImageExtensionPath(const InstructionSet isa) const { +std::string OnDeviceRefresh::GetSystemBootImageFrameworkExtensionPath(InstructionSet isa) const { // Typically "/system/framework/<isa>/boot-framework.art". - return GetSystemImageFilename(GetSystemBootImageExtension().c_str(), isa); + return GetSystemImageFilename(GetSystemBootImageFrameworkExtension().c_str(), isa); +} + +std::string OnDeviceRefresh::GetBootImageMainlineExtension(bool on_system) const { + std::vector<std::string> mainline_bcp_jars = GetMainlineBcpJars(); + std::string basename = + GetBootImageComponentBasename(mainline_bcp_jars[0], /*is_first_jar=*/false); + if (on_system) { + // Typically "/system/framework/boot-framework-adservices.art". + return "{}/framework/{}"_format(GetAndroidRoot(), basename); + } else { + // Typically "/data/misc/apexdata/com.android.art/dalvik-cache/boot-framework-adservices.art". + return "{}/{}"_format(config_.GetArtifactDirectory(), basename); + } +} + +std::string OnDeviceRefresh::GetBootImageMainlineExtensionPath(bool on_system, + InstructionSet isa) const { + // Typically + // "/data/misc/apexdata/com.android.art/dalvik-cache/<isa>/boot-framework-adservices.art". + return GetSystemImageFilename(GetBootImageMainlineExtension(on_system).c_str(), isa); +} + +std::vector<std::string> OnDeviceRefresh::GetBestBootImages(InstructionSet isa, + bool include_mainline_extension) const { + std::vector<std::string> locations; + std::string unused_error_msg; + bool primary_on_data = false; + if (PrimaryBootImageExist( + /*on_system=*/false, /*minimal=*/false, isa, &unused_error_msg)) { + primary_on_data = true; + locations.push_back(GetPrimaryBootImage(/*on_system=*/false, /*minimal=*/false)); + } else { + locations.push_back(GetPrimaryBootImage(/*on_system=*/true, /*minimal=*/false)); + if (!IsAtLeastU()) { + // Prior to U, there was a framework extension. + locations.push_back(GetSystemBootImageFrameworkExtension()); + } + } + if (include_mainline_extension) { + if (BootImageMainlineExtensionExist(/*on_system=*/false, isa, &unused_error_msg)) { + locations.push_back(GetBootImageMainlineExtension(/*on_system=*/false)); + } else { + // If the primary boot image is on /data, it means we have regenerated all boot images, so the + // mainline extension must be on /data too. + CHECK(!primary_on_data) + << "Mainline extension not found while primary boot image is on /data"; + locations.push_back(GetBootImageMainlineExtension(/*on_system=*/true)); + } + } + return locations; } std::string OnDeviceRefresh::GetSystemServerImagePath(bool on_system, @@ -777,15 +960,15 @@ std::string OnDeviceRefresh::GetSystemServerImagePath(bool on_system, if (LocationIsOnApex(jar_path)) { return GetSystemOdexFilenameForApex(jar_path, config_.GetSystemServerIsa()); } - const std::string jar_name = android::base::Basename(jar_path); - const std::string image_name = ReplaceFileExtension(jar_name, "art"); + std::string jar_name = Basename(jar_path); + 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}); + return "{}/oat/{}/{}"_format(Dirname(jar_path), 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()); + const std::string image = GetApexDataImage(jar_path); return GetSystemImageFilename(image.c_str(), config_.GetSystemServerIsa()); } } @@ -799,21 +982,21 @@ WARN_UNUSED bool OnDeviceRefresh::RemoveArtifactsDirectory() const { return RemoveDirectory(config_.GetArtifactDirectory()); } -WARN_UNUSED bool OnDeviceRefresh::BootClasspathArtifactsExist( +WARN_UNUSED bool OnDeviceRefresh::PrimaryBootImageExist( bool on_system, bool minimal, - const InstructionSet isa, + InstructionSet isa, /*out*/ std::string* error_msg, /*out*/ std::vector<std::string>* checked_artifacts) const { - std::string path = GetBootImagePath(on_system, minimal, isa); + std::string path = GetPrimaryBootImagePath(on_system, minimal, isa); OdrArtifacts artifacts = OdrArtifacts::ForBootImage(path); if (!ArtifactsExist(artifacts, /*check_art_file=*/true, error_msg, checked_artifacts)) { return false; } - // There is a split between the primary boot image and the extension on /system, so they need to - // be checked separately. This does not apply to the boot image on /data. - if (on_system) { - std::string extension_path = GetSystemBootImageExtensionPath(isa); + // Prior to U, there was a split between the primary boot image and the extension on /system, so + // they need to be checked separately. This does not apply to the boot image on /data. + if (on_system && !IsAtLeastU()) { + std::string extension_path = GetSystemBootImageFrameworkExtensionPath(isa); OdrArtifacts extension_artifacts = OdrArtifacts::ForBootImage(extension_path); if (!ArtifactsExist( extension_artifacts, /*check_art_file=*/true, error_msg, checked_artifacts)) { @@ -823,7 +1006,17 @@ WARN_UNUSED bool OnDeviceRefresh::BootClasspathArtifactsExist( return true; } -WARN_UNUSED bool OnDeviceRefresh::SystemServerArtifactsExist( +WARN_UNUSED bool OnDeviceRefresh::BootImageMainlineExtensionExist( + bool on_system, + InstructionSet isa, + /*out*/ std::string* error_msg, + /*out*/ std::vector<std::string>* checked_artifacts) const { + std::string path = GetBootImageMainlineExtensionPath(on_system, isa); + OdrArtifacts artifacts = OdrArtifacts::ForBootImage(path); + return ArtifactsExist(artifacts, /*check_art_file=*/true, error_msg, checked_artifacts); +} + +bool OnDeviceRefresh::SystemServerArtifactsExist( bool on_system, /*out*/ std::string* error_msg, /*out*/ std::set<std::string>* jars_missing_artifacts, @@ -907,106 +1100,125 @@ WARN_UNUSED bool OnDeviceRefresh::CheckSystemPropertiesHaveNotChanged( return true; } -WARN_UNUSED bool OnDeviceRefresh::BootClasspathArtifactsOnSystemUsable( - const apex::ApexInfo& art_apex_info) const { - if (!art_apex_info.getIsFactory()) { - return false; - } - LOG(INFO) << "Factory ART APEX mounted."; - - if (!CheckSystemPropertiesAreDefault()) { +WARN_UNUSED bool OnDeviceRefresh::CheckBuildUserfaultFdGc() const { + auto it = config_.GetSystemProperties().find("ro.dalvik.vm.enable_uffd_gc"); + bool build_enable_uffd_gc = it != config_.GetSystemProperties().end() ? + ParseBool(it->second) == ParseBoolResult::kTrue : + false; + bool kernel_supports_uffd = KernelSupportsUffd(); + if (build_enable_uffd_gc && !kernel_supports_uffd) { + // Normally, this should not happen. If this happens, the system image was probably built with a + // wrong PRODUCT_ENABLE_UFFD_GC flag. + LOG(WARNING) << "Userfaultfd GC check failed (build-time: {}, runtime: {})."_format( + build_enable_uffd_gc, kernel_supports_uffd); return false; } - LOG(INFO) << "System properties are set to default values."; - return true; } -WARN_UNUSED bool OnDeviceRefresh::SystemServerArtifactsOnSystemUsable( +WARN_UNUSED PreconditionCheckResult OnDeviceRefresh::CheckPreconditionForSystem( const std::vector<apex::ApexInfo>& apex_info_list) const { - if (std::any_of(apex_info_list.begin(), - apex_info_list.end(), - [](const apex::ApexInfo& apex_info) { return !apex_info.getIsFactory(); })) { - return false; - } - LOG(INFO) << "Factory APEXes mounted."; - if (!CheckSystemPropertiesAreDefault()) { - return false; + return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch); } - LOG(INFO) << "System properties are set to default values."; - return true; -} + if (!CheckBuildUserfaultFdGc()) { + return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch); + } -WARN_UNUSED bool OnDeviceRefresh::CheckBootClasspathArtifactsAreUpToDate( - OdrMetrics& metrics, - const InstructionSet isa, - const apex::ApexInfo& art_apex_info, - const std::optional<art_apex::CacheInfo>& cache_info, - /*out*/ std::vector<std::string>* checked_artifacts) const { - if (BootClasspathArtifactsOnSystemUsable(art_apex_info)) { - // We can use the artifacts on /system. Check if they exist. - std::string error_msg; - if (BootClasspathArtifactsExist(/*on_system=*/true, /*minimal=*/false, isa, &error_msg)) { - return true; - } + std::optional<apex::ApexInfo> art_apex_info = GetArtApexInfo(apex_info_list); + 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."; + return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kUnknown); + } - LOG(INFO) << "Incomplete boot classpath artifacts on /system. " << error_msg; - LOG(INFO) << "Checking cache."; + if (!art_apex_info->getIsFactory()) { + LOG(INFO) << "Updated ART APEX mounted"; + return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch); } - if (!cache_info.has_value()) { - // If the cache info file does not exist, it usually means on-device compilation has not been - // done before because the device was using the factory version of modules, or artifacts were - // cleared because an updated version was uninstalled. Set the trigger to be - // `kApexVersionMismatch` so that compilation will always be performed. - PLOG(INFO) << "No prior cache-info file: " << QuotePath(cache_info_filename_); - metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch); - return false; + if (std::any_of(apex_info_list.begin(), + apex_info_list.end(), + [](const apex::ApexInfo& apex_info) { return !apex_info.getIsFactory(); })) { + LOG(INFO) << "Updated APEXes mounted"; + return PreconditionCheckResult::BootImageMainlineExtensionNotOk( + OdrMetrics::Trigger::kApexVersionMismatch); } - // Check whether the current cache ART module info differs from the current ART module info. - const art_apex::ModuleInfo* cached_art_info = cache_info->getFirstArtModuleInfo(); + return PreconditionCheckResult::AllOk(); +} - if (cached_art_info == nullptr) { - LOG(INFO) << "Missing ART APEX info from cache-info."; - metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch); +WARN_UNUSED static bool CheckModuleInfo(const art_apex::ModuleInfo& cached_info, + const apex::ApexInfo& current_info) { + if (cached_info.getVersionCode() != current_info.getVersionCode()) { + LOG(INFO) << "APEX ({}) version code mismatch (before: {}, now: {})"_format( + current_info.getModuleName(), cached_info.getVersionCode(), current_info.getVersionCode()); return false; } - 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); + if (cached_info.getVersionName() != current_info.getVersionName()) { + LOG(INFO) << "APEX ({}) version name mismatch (before: {}, now: {})"_format( + current_info.getModuleName(), cached_info.getVersionName(), current_info.getVersionName()); return false; } - 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); + // Check lastUpdateMillis for samegrade installs. If `cached_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_last_update_millis = + cached_info.hasLastUpdateMillis() ? cached_info.getLastUpdateMillis() : -1; + if (cached_last_update_millis != current_info.getLastUpdateMillis()) { + LOG(INFO) << "APEX ({}) last update time mismatch (before: {}, now: {})"_format( + current_info.getModuleName(), + cached_info.getLastUpdateMillis(), + current_info.getLastUpdateMillis()); 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; + return true; +} + +WARN_UNUSED PreconditionCheckResult OnDeviceRefresh::CheckPreconditionForData( + const std::vector<com::android::apex::ApexInfo>& apex_info_list) const { + Result<art_apex::CacheInfo> cache_info = ReadCacheInfo(); + if (!cache_info.ok()) { + if (cache_info.error().code() == ENOENT) { + // If the cache info file does not exist, it usually means it's the first boot, or the + // dalvik-cache directory is cleared by odsign due to corrupted files. Set the trigger to be + // `kApexVersionMismatch` to force generate the cache info file and compile if necessary. + LOG(INFO) << "No prior cache-info file: " << QuotePath(cache_info_filename_); + } else { + // 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. + LOG(ERROR) << cache_info.error().message(); + } + return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch); } if (!CheckSystemPropertiesHaveNotChanged(cache_info.value())) { // We don't have a trigger kind for system property changes. For now, we reuse // `kApexVersionMismatch` as it implies the expected behavior: re-compile regardless of the last // compilation attempt. - metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch); - return false; + return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch); + } + + // Check whether the current cache ART module info differs from the current ART module info. + const art_apex::ModuleInfo* cached_art_info = cache_info->getFirstArtModuleInfo(); + if (cached_art_info == nullptr) { + LOG(ERROR) << "Missing ART APEX info from cache-info."; + return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch); + } + + std::optional<apex::ApexInfo> current_art_info = GetArtApexInfo(apex_info_list); + if (!current_art_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."; + return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kUnknown); + } + + if (!CheckModuleInfo(*cached_art_info, *current_art_info)) { + return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch); } // Check boot class components. @@ -1017,102 +1229,33 @@ WARN_UNUSED bool OnDeviceRefresh::CheckBootClasspathArtifactsAreUpToDate( // // The boot class components may change unexpectedly, for example an OTA could update // framework.jar. - const std::vector<art_apex::Component> expected_bcp_compilable_components = - GenerateBootClasspathCompilableComponents(); - if (expected_bcp_compilable_components.size() != 0 && - (!cache_info->hasDex2oatBootClasspath() || - !cache_info->getFirstDex2oatBootClasspath()->hasComponent())) { + const std::vector<art_apex::Component> current_dex2oat_bcp_components = + GenerateDex2oatBootClasspathComponents(); + + const art_apex::Classpath* cached_dex2oat_bcp_components = + cache_info->getFirstDex2oatBootClasspath(); + if (cached_dex2oat_bcp_components == nullptr) { LOG(INFO) << "Missing Dex2oatBootClasspath components."; - metrics.SetTrigger(OdrMetrics::Trigger::kDexFilesChanged); - return false; + return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kApexVersionMismatch); } - const std::vector<art_apex::Component>& bcp_compilable_components = - cache_info->getFirstDex2oatBootClasspath()->getComponent(); - Result<void> result = - CheckComponents(expected_bcp_compilable_components, bcp_compilable_components); + Result<void> result = CheckComponents(current_dex2oat_bcp_components, + cached_dex2oat_bcp_components->getComponent()); if (!result.ok()) { LOG(INFO) << "Dex2OatClasspath components mismatch: " << result.error(); - metrics.SetTrigger(OdrMetrics::Trigger::kDexFilesChanged); - return false; - } - - // Cache info looks good, check all compilation artifacts exist. - std::string error_msg; - if (!BootClasspathArtifactsExist( - /*on_system=*/false, /*minimal=*/false, isa, &error_msg, checked_artifacts)) { - LOG(INFO) << "Incomplete boot classpath artifacts. " << error_msg; - metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts); - // Add the minimal boot image to `checked_artifacts` if exists. This is to prevent the minimal - // boot image from being deleted. It does not affect the return value because we should still - // attempt to generate a full boot image even if the minimal one exists. - if (BootClasspathArtifactsExist( - /*on_system=*/false, /*minimal=*/true, isa, &error_msg, checked_artifacts)) { - LOG(INFO) << "Found minimal boot classpath artifacts."; - } - return false; - } - - return true; -} - -bool OnDeviceRefresh::CheckSystemServerArtifactsAreUpToDate( - OdrMetrics& metrics, - const std::vector<apex::ApexInfo>& apex_info_list, - const std::optional<art_apex::CacheInfo>& cache_info, - /*out*/ std::set<std::string>* jars_to_compile, - /*out*/ std::vector<std::string>* checked_artifacts) const { - auto compile_all = [&, this]() { - *jars_to_compile = AllSystemServerJars(); - return false; - }; - - std::set<std::string> jars_missing_artifacts_on_system; - bool artifacts_on_system_up_to_date = false; - - if (SystemServerArtifactsOnSystemUsable(apex_info_list)) { - // We can use the artifacts on /system. Check if they exist. - std::string error_msg; - if (SystemServerArtifactsExist( - /*on_system=*/true, &error_msg, &jars_missing_artifacts_on_system)) { - return true; - } - - LOG(INFO) << "Incomplete system server artifacts on /system. " << error_msg; - LOG(INFO) << "Checking cache."; - artifacts_on_system_up_to_date = true; - } - - if (!cache_info.has_value()) { - // If the cache info file does not exist, it usually means on-device compilation has not been - // done before because the device was using the factory version of modules, or artifacts were - // cleared because an updated version was uninstalled. Set the trigger to be - // `kApexVersionMismatch` so that compilation will always be performed. - PLOG(INFO) << "No prior cache-info file: " << QuotePath(cache_info_filename_); - metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch); - if (artifacts_on_system_up_to_date) { - *jars_to_compile = jars_missing_artifacts_on_system; - return false; - } - return compile_all(); + return PreconditionCheckResult::NoneOk(OdrMetrics::Trigger::kDexFilesChanged); } // 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); - return compile_all(); + LOG(ERROR) << "Missing APEX info list from cache-info."; + return PreconditionCheckResult::BootImageMainlineExtensionNotOk( + OdrMetrics::Trigger::kApexVersionMismatch); } 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::kApexVersionMismatch); - return compile_all(); - } cached_module_info_map[module_info.getName()] = &module_info; } @@ -1125,44 +1268,33 @@ bool OnDeviceRefresh::CheckSystemServerArtifactsAreUpToDate( auto it = cached_module_info_map.find(apex_name); if (it == cached_module_info_map.end()) { LOG(INFO) << "Missing APEX info from cache-info (" << apex_name << ")."; - metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch); - return compile_all(); + return PreconditionCheckResult::BootImageMainlineExtensionNotOk( + OdrMetrics::Trigger::kApexVersionMismatch); } const art_apex::ModuleInfo* cached_module_info = it->second; - - if (cached_module_info->getVersionCode() != current_apex_info.getVersionCode()) { - LOG(INFO) << "APEX (" << apex_name << ") version code mismatch (" - << cached_module_info->getVersionCode() - << " != " << current_apex_info.getVersionCode() << ")."; - metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch); - return compile_all(); + if (!CheckModuleInfo(*cached_module_info, current_apex_info)) { + return PreconditionCheckResult::BootImageMainlineExtensionNotOk( + OdrMetrics::Trigger::kApexVersionMismatch); } + } - if (cached_module_info->getVersionName() != current_apex_info.getVersionName()) { - LOG(INFO) << "APEX (" << apex_name << ") version name mismatch (" - << cached_module_info->getVersionName() - << " != " << current_apex_info.getVersionName() << ")."; - metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch); - return compile_all(); - } + const std::vector<art_apex::Component> current_bcp_components = GenerateBootClasspathComponents(); - 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(); - } + const art_apex::Classpath* cached_bcp_components = cache_info->getFirstBootClasspath(); + if (cached_bcp_components == nullptr) { + LOG(INFO) << "Missing BootClasspath components."; + return PreconditionCheckResult::BootImageMainlineExtensionNotOk( + OdrMetrics::Trigger::kApexVersionMismatch); } - if (!CheckSystemPropertiesHaveNotChanged(cache_info.value())) { - // We don't have a trigger kind for system property changes. For now, we reuse - // `kApexVersionMismatch` as it implies the expected behavior: re-compile regardless of the last - // compilation attempt. - metrics.SetTrigger(OdrMetrics::Trigger::kApexVersionMismatch); - return false; + result = CheckComponents(current_bcp_components, cached_bcp_components->getComponent()); + if (!result.ok()) { + LOG(INFO) << "BootClasspath components mismatch: " << result.error(); + // 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 PreconditionCheckResult::BootImageMainlineExtensionNotOk( + OdrMetrics::Trigger::kDexFilesChanged); } // Check system server components. @@ -1174,78 +1306,166 @@ bool OnDeviceRefresh::CheckSystemServerArtifactsAreUpToDate( // // The system_server components may change unexpectedly, for example an OTA could update // services.jar. - const std::vector<art_apex::SystemServerComponent> expected_system_server_components = + const std::vector<art_apex::SystemServerComponent> current_system_server_components = GenerateSystemServerComponents(); - if (expected_system_server_components.size() != 0 && - (!cache_info->hasSystemServerComponents() || - !cache_info->getFirstSystemServerComponents()->hasComponent())) { + + const art_apex::SystemServerComponents* cached_system_server_components = + cache_info->getFirstSystemServerComponents(); + if (cached_system_server_components == nullptr) { LOG(INFO) << "Missing SystemServerComponents."; - metrics.SetTrigger(OdrMetrics::Trigger::kDexFilesChanged); - return compile_all(); + return PreconditionCheckResult::SystemServerNotOk(OdrMetrics::Trigger::kApexVersionMismatch); } - const std::vector<art_apex::SystemServerComponent>& system_server_components = - cache_info->getFirstSystemServerComponents()->getComponent(); - Result<void> result = - CheckSystemServerComponents(expected_system_server_components, system_server_components); + result = CheckSystemServerComponents(current_system_server_components, + cached_system_server_components->getComponent()); if (!result.ok()) { LOG(INFO) << "SystemServerComponents mismatch: " << result.error(); - metrics.SetTrigger(OdrMetrics::Trigger::kDexFilesChanged); - return compile_all(); + return PreconditionCheckResult::SystemServerNotOk(OdrMetrics::Trigger::kDexFilesChanged); } - const std::vector<art_apex::Component> expected_bcp_components = - GenerateBootClasspathComponents(); - if (expected_bcp_components.size() != 0 && - (!cache_info->hasBootClasspath() || !cache_info->getFirstBootClasspath()->hasComponent())) { - LOG(INFO) << "Missing BootClasspath components."; - metrics.SetTrigger(OdrMetrics::Trigger::kDexFilesChanged); - return false; + return PreconditionCheckResult::AllOk(); +} + +WARN_UNUSED BootImages OnDeviceRefresh::CheckBootClasspathArtifactsAreUpToDate( + OdrMetrics& metrics, + InstructionSet isa, + const PreconditionCheckResult& system_result, + const PreconditionCheckResult& data_result, + /*out*/ std::vector<std::string>* checked_artifacts) const { + const char* isa_str = GetInstructionSetString(isa); + + BootImages boot_images_on_system{.primary_boot_image = false, + .boot_image_mainline_extension = false}; + if (system_result.IsPrimaryBootImageOk()) { + // We can use the artifacts on /system. Check if they exist. + std::string error_msg; + if (PrimaryBootImageExist(/*on_system=*/true, /*minimal=*/false, isa, &error_msg)) { + boot_images_on_system.primary_boot_image = true; + } else { + LOG(INFO) << "Incomplete primary boot image or framework extension on /system: " << error_msg; + } } - const std::vector<art_apex::Component>& bcp_components = - cache_info->getFirstBootClasspath()->getComponent(); - result = CheckComponents(expected_bcp_components, bcp_components); - if (!result.ok()) { - LOG(INFO) << "BootClasspath components mismatch: " << result.error(); - 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 compile_all(); + if (boot_images_on_system.primary_boot_image && system_result.IsBootImageMainlineExtensionOk()) { + std::string error_msg; + if (BootImageMainlineExtensionExist(/*on_system=*/true, isa, &error_msg)) { + boot_images_on_system.boot_image_mainline_extension = true; + } else { + LOG(INFO) << "Incomplete boot image mainline extension on /system: " << error_msg; + } } - std::string error_msg; - std::set<std::string> jars_missing_artifacts_on_data; - if (!SystemServerArtifactsExist( - /*on_system=*/false, &error_msg, &jars_missing_artifacts_on_data, checked_artifacts)) { - if (artifacts_on_system_up_to_date) { - // Check if the remaining system_server artifacts are on /data. - std::set_intersection(jars_missing_artifacts_on_system.begin(), - jars_missing_artifacts_on_system.end(), - jars_missing_artifacts_on_data.begin(), - jars_missing_artifacts_on_data.end(), - std::inserter(*jars_to_compile, jars_to_compile->end())); - if (!jars_to_compile->empty()) { - LOG(INFO) << "Incomplete system_server artifacts on /data. " << error_msg; + if (boot_images_on_system.Count() == BootImages::kMaxCount) { + LOG(INFO) << "Boot images on /system OK ({})"_format(isa_str); + // Nothing to compile. + return BootImages{.primary_boot_image = false, .boot_image_mainline_extension = false}; + } + + LOG(INFO) << "Checking boot images /data ({})"_format(isa_str); + BootImages boot_images_on_data{.primary_boot_image = false, + .boot_image_mainline_extension = false}; + + if (data_result.IsPrimaryBootImageOk()) { + std::string error_msg; + if (PrimaryBootImageExist( + /*on_system=*/false, /*minimal=*/false, isa, &error_msg, checked_artifacts)) { + boot_images_on_data.primary_boot_image = true; + } else { + LOG(INFO) << "Incomplete primary boot image on /data: " << error_msg; + metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts); + // Add the minimal boot image to `checked_artifacts` if exists. This is to prevent the minimal + // boot image from being deleted. It does not affect the return value because we should still + // attempt to generate a full boot image even if the minimal one exists. + if (PrimaryBootImageExist( + /*on_system=*/false, /*minimal=*/true, isa, &error_msg, checked_artifacts)) { + LOG(INFO) << "Found minimal primary boot image ({})"_format(isa_str); + } + } + } else { + metrics.SetTrigger(data_result.GetTrigger()); + } + + if (boot_images_on_data.primary_boot_image) { + if (data_result.IsBootImageMainlineExtensionOk()) { + std::string error_msg; + if (BootImageMainlineExtensionExist( + /*on_system=*/false, isa, &error_msg, checked_artifacts)) { + boot_images_on_data.boot_image_mainline_extension = true; + } else { + LOG(INFO) << "Incomplete boot image mainline extension on /data: " << error_msg; metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts); - return false; } + } else { + metrics.SetTrigger(data_result.GetTrigger()); + } + } - LOG(INFO) << "Found the remaining system_server artifacts on /data."; - return true; + BootImages boot_images_to_generate{ + .primary_boot_image = + !boot_images_on_system.primary_boot_image && !boot_images_on_data.primary_boot_image, + .boot_image_mainline_extension = !boot_images_on_system.boot_image_mainline_extension && + !boot_images_on_data.boot_image_mainline_extension, + }; + + if (boot_images_to_generate.Count() == 0) { + LOG(INFO) << "Boot images on /data OK ({})"_format(isa_str); + } + + return boot_images_to_generate; +} + +std::set<std::string> OnDeviceRefresh::CheckSystemServerArtifactsAreUpToDate( + OdrMetrics& metrics, + const PreconditionCheckResult& system_result, + const PreconditionCheckResult& data_result, + /*out*/ std::vector<std::string>* checked_artifacts) const { + std::set<std::string> jars_to_compile; + std::set<std::string> jars_missing_artifacts_on_system; + if (system_result.IsSystemServerOk()) { + // We can use the artifacts on /system. Check if they exist. + std::string error_msg; + if (SystemServerArtifactsExist( + /*on_system=*/true, &error_msg, &jars_missing_artifacts_on_system)) { + LOG(INFO) << "system_server artifacts on /system OK"; + return {}; } - LOG(INFO) << "Incomplete system_server artifacts. " << error_msg; - metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts); - *jars_to_compile = jars_missing_artifacts_on_data; - return false; + LOG(INFO) << "Incomplete system server artifacts on /system: " << error_msg; + LOG(INFO) << "Checking system server artifacts /data"; + } else { + jars_missing_artifacts_on_system = AllSystemServerJars(); } - return true; + std::set<std::string> jars_missing_artifacts_on_data; + std::string error_msg; + if (data_result.IsSystemServerOk()) { + SystemServerArtifactsExist( + /*on_system=*/false, &error_msg, &jars_missing_artifacts_on_data, checked_artifacts); + } else { + jars_missing_artifacts_on_data = AllSystemServerJars(); + } + + std::set_intersection(jars_missing_artifacts_on_system.begin(), + jars_missing_artifacts_on_system.end(), + jars_missing_artifacts_on_data.begin(), + jars_missing_artifacts_on_data.end(), + std::inserter(jars_to_compile, jars_to_compile.end())); + if (!jars_to_compile.empty()) { + if (data_result.IsSystemServerOk()) { + LOG(INFO) << "Incomplete system_server artifacts on /data: " << error_msg; + metrics.SetTrigger(OdrMetrics::Trigger::kMissingArtifacts); + } else { + metrics.SetTrigger(data_result.GetTrigger()); + } + return jars_to_compile; + } + + LOG(INFO) << "system_server artifacts on /data OK"; + return {}; } Result<void> OnDeviceRefresh::CleanupArtifactDirectory( - const std::vector<std::string>& artifacts_to_keep) const { + OdrMetrics& metrics, const std::vector<std::string>& artifacts_to_keep) const { const std::string& artifact_dir = config_.GetArtifactDirectory(); std::unordered_set<std::string> artifact_set{artifacts_to_keep.begin(), artifacts_to_keep.end()}; @@ -1263,7 +1483,9 @@ Result<void> OnDeviceRefresh::CleanupArtifactDirectory( // undefined behavior; entries.push_back(entry); } - if (ec) { + if (ec && ec.value() != ENOENT) { + metrics.SetStatus(ec.value() == EPERM ? OdrMetrics::Status::kDalvikCachePermissionDenied : + OdrMetrics::Status::kIoError); return Errorf("Failed to iterate over entries in the artifact directory: {}", ec.message()); } @@ -1273,6 +1495,7 @@ Result<void> OnDeviceRefresh::CleanupArtifactDirectory( if (!ContainsElement(artifact_set, path)) { LOG(INFO) << "Removing " << path; if (unlink(path.c_str()) != 0) { + metrics.SetStatus(OdrMetrics::Status::kIoError); return ErrnoErrorf("Failed to remove file {}", QuotePath(path)); } } @@ -1280,6 +1503,7 @@ Result<void> OnDeviceRefresh::CleanupArtifactDirectory( // Neither a regular file nor a directory. Unexpected file type. LOG(INFO) << "Removing " << path; if (unlink(path.c_str()) != 0) { + metrics.SetStatus(OdrMetrics::Status::kIoError); return ErrnoErrorf("Failed to remove file {}", QuotePath(path)); } } @@ -1338,9 +1562,12 @@ OnDeviceRefresh::CheckArtifactsAreUpToDate(OdrMetrics& metrics, // Clean-up helper used to simplify clean-ups and handling failures there. auto cleanup_and_compile_all = [&, this]() { - compilation_options->compile_boot_classpath_for_isas = config_.GetBootClasspathIsas(); - compilation_options->system_server_jars_to_compile = AllSystemServerJars(); - return RemoveArtifactsDirectory() ? ExitCode::kCompilationRequired : ExitCode::kCleanupFailed; + *compilation_options = CompilationOptions::CompileAll(*this); + if (!RemoveArtifactsDirectory()) { + metrics.SetStatus(OdrMetrics::Status::kIoError); + return ExitCode::kCleanupFailed; + } + return ExitCode::kCompilationRequired; }; std::optional<std::vector<apex::ApexInfo>> apex_info_list = GetApexInfoList(); @@ -1368,23 +1595,18 @@ OnDeviceRefresh::CheckArtifactsAreUpToDate(OdrMetrics& metrics, // 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::kApexVersionMismatch); - return cleanup_and_compile_all(); - } - InstructionSet system_server_isa = config_.GetSystemServerIsa(); std::vector<std::string> checked_artifacts; - for (const InstructionSet isa : config_.GetBootClasspathIsas()) { - if (!CheckBootClasspathArtifactsAreUpToDate( - metrics, isa, art_apex_info.value(), cache_info, &checked_artifacts)) { - compilation_options->compile_boot_classpath_for_isas.push_back(isa); + PreconditionCheckResult system_result = CheckPreconditionForSystem(apex_info_list.value()); + PreconditionCheckResult data_result = CheckPreconditionForData(apex_info_list.value()); + + for (InstructionSet isa : config_.GetBootClasspathIsas()) { + BootImages boot_images_to_generate = CheckBootClasspathArtifactsAreUpToDate( + metrics, isa, system_result, data_result, &checked_artifacts); + if (boot_images_to_generate.Count() > 0) { + compilation_options->boot_images_to_generate_for_isas.emplace_back(isa, + boot_images_to_generate); // system_server artifacts are invalid without valid boot classpath artifacts. if (isa == system_server_isa) { compilation_options->system_server_jars_to_compile = AllSystemServerJars(); @@ -1393,15 +1615,17 @@ OnDeviceRefresh::CheckArtifactsAreUpToDate(OdrMetrics& metrics, } if (compilation_options->system_server_jars_to_compile.empty()) { - CheckSystemServerArtifactsAreUpToDate(metrics, - apex_info_list.value(), - cache_info, - &compilation_options->system_server_jars_to_compile, - &checked_artifacts); + compilation_options->system_server_jars_to_compile = CheckSystemServerArtifactsAreUpToDate( + metrics, system_result, data_result, &checked_artifacts); } - bool compilation_required = (!compilation_options->compile_boot_classpath_for_isas.empty() || - !compilation_options->system_server_jars_to_compile.empty()); + bool compilation_required = compilation_options->CompilationUnitCount() > 0; + + if (!compilation_required && !data_result.IsAllOk()) { + // Return kCompilationRequired to generate the cache info even if there's nothing to compile. + compilation_required = true; + metrics.SetTrigger(data_result.GetTrigger()); + } // If partial compilation is disabled, we should compile everything regardless of what's in // `compilation_options`. @@ -1409,12 +1633,10 @@ OnDeviceRefresh::CheckArtifactsAreUpToDate(OdrMetrics& metrics, return cleanup_and_compile_all(); } - // We should only keep the cache info if we have artifacts on /data. - if (!checked_artifacts.empty()) { - checked_artifacts.push_back(cache_info_filename_); - } + // Always keep the cache info. + checked_artifacts.push_back(cache_info_filename_); - Result<void> result = CleanupArtifactDirectory(checked_artifacts); + Result<void> result = CleanupArtifactDirectory(metrics, checked_artifacts); if (!result.ok()) { LOG(ERROR) << result.error(); return ExitCode::kCleanupFailed; @@ -1423,304 +1645,364 @@ OnDeviceRefresh::CheckArtifactsAreUpToDate(OdrMetrics& metrics, return compilation_required ? ExitCode::kCompilationRequired : ExitCode::kOkay; } -WARN_UNUSED bool OnDeviceRefresh::CompileBootClasspathArtifacts( - const InstructionSet isa, +WARN_UNUSED CompilationResult OnDeviceRefresh::RunDex2oat( const std::string& staging_dir, - OdrMetrics& metrics, - const std::function<void()>& on_dex2oat_success, - bool minimal, - std::string* error_msg) const { - ScopedOdrCompilationTimer compilation_timer(metrics); + const std::string& debug_message, + InstructionSet isa, + const std::vector<std::string>& dex_files, + const std::vector<std::string>& boot_classpath, + const std::vector<std::string>& input_boot_images, + const OdrArtifacts& artifacts, + const std::vector<std::string>& extra_args, + /*inout*/ std::vector<std::unique_ptr<File>>& readonly_files_raii) const { std::vector<std::string> args; args.push_back(config_.GetDex2Oat()); AddDex2OatCommonOptions(args); AddDex2OatDebugInfo(args); AddDex2OatInstructionSet(args, isa); - if (!AddDex2OatConcurrencyArguments(args)) { - return false; - } - - std::vector<std::unique_ptr<File>> readonly_files_raii; - const std::string art_boot_profile_file = GetArtRoot() + "/etc/boot-image.prof"; - const std::string framework_boot_profile_file = GetAndroidRoot() + "/etc/boot-image.prof"; - AddDex2OatProfileAndCompilerFilter(args, readonly_files_raii, - {art_boot_profile_file, framework_boot_profile_file}); - - // Compile as a single image for fewer files and slightly less memory overhead. - args.emplace_back("--single-image"); - - args.emplace_back(android::base::StringPrintf("--base=0x%08x", ART_BASE_ADDRESS)); - - const std::string dirty_image_objects_file(GetAndroidRoot() + "/etc/dirty-image-objects"); - if (OS::FileExists(dirty_image_objects_file.c_str())) { - std::unique_ptr<File> file(OS::OpenFileForReading(dirty_image_objects_file.c_str())); - args.emplace_back(android::base::StringPrintf("--dirty-image-objects-fd=%d", file->Fd())); - readonly_files_raii.push_back(std::move(file)); - } else { - LOG(WARNING) << "Missing dirty objects file : " << QuotePath(dirty_image_objects_file); - } - - const std::string preloaded_classes_file(GetAndroidRoot() + "/etc/preloaded-classes"); - if (OS::FileExists(preloaded_classes_file.c_str())) { - std::unique_ptr<File> file(OS::OpenFileForReading(preloaded_classes_file.c_str())); - args.emplace_back(android::base::StringPrintf("--preloaded-classes-fds=%d", file->Fd())); - readonly_files_raii.push_back(std::move(file)); - } else { - LOG(WARNING) << "Missing preloaded classes file : " << QuotePath(preloaded_classes_file); + Result<void> result = AddDex2OatConcurrencyArguments(args, config_.GetCompilationOsMode()); + if (!result.ok()) { + return CompilationResult::Error(OdrMetrics::Status::kUnknown, result.error().message()); } - // Add boot classpath jars to compile. - std::vector<std::string> jars_to_compile = boot_classpath_compilable_jars_; - if (minimal) { - auto end = - std::remove_if(jars_to_compile.begin(), jars_to_compile.end(), [](const std::string& jar) { - return !android::base::StartsWith(jar, GetArtRoot()); - }); - jars_to_compile.erase(end, jars_to_compile.end()); + // dex2oat reads some system properties from cache-info.xml generated by odrefresh. + result = AddCacheInfoFd(args, readonly_files_raii, cache_info_filename_); + if (!result.ok()) { + return CompilationResult::Error(OdrMetrics::Status::kUnknown, result.error().message()); } - for (const std::string& component : jars_to_compile) { - std::string actual_path = AndroidRootRewrite(component); - args.emplace_back("--dex-file=" + component); + for (const std::string& dex_file : dex_files) { + std::string actual_path = RewriteParentDirectoryIfNeeded(dex_file); + args.emplace_back("--dex-file=" + dex_file); std::unique_ptr<File> file(OS::OpenFileForReading(actual_path.c_str())); - args.emplace_back(android::base::StringPrintf("--dex-fd=%d", file->Fd())); + args.emplace_back(StringPrintf("--dex-fd=%d", file->Fd())); readonly_files_raii.push_back(std::move(file)); } args.emplace_back("--runtime-arg"); - args.emplace_back(Concatenate({"-Xbootclasspath:", android::base::Join(jars_to_compile, ":")})); - if (!AddBootClasspathFds(args, readonly_files_raii, jars_to_compile)) { - return false; + args.emplace_back("-Xbootclasspath:" + Join(boot_classpath, ":")); + result = AddBootClasspathFds(args, readonly_files_raii, boot_classpath); + if (!result.ok()) { + return CompilationResult::Error(OdrMetrics::Status::kIoError, result.error().message()); } - const std::string image_location = GetBootImagePath(/*on_system=*/false, minimal, isa); - const OdrArtifacts artifacts = OdrArtifacts::ForBootImage(image_location); + if (!input_boot_images.empty()) { + args.emplace_back("--boot-image=" + Join(input_boot_images, ':')); + AddCompiledBootClasspathFdsIfAny( + args, readonly_files_raii, boot_classpath, isa, input_boot_images); + } 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::pair<std::string, const char*> location_kind_pairs[] = { + std::make_pair(artifacts.ImagePath(), artifacts.ImageKind()), 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); + for (const auto& [location, kind] : location_kind_pairs) { + 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; - metrics.SetStatus(OdrMetrics::Status::kIoError); - EraseFiles(staging_files); - return false; - } - - if (fchmod(staging_file->Fd(), S_IRUSR | S_IWUSR) != 0) { - PLOG(ERROR) << "Could not set file mode on " << QuotePath(staging_location); - metrics.SetStatus(OdrMetrics::Status::kIoError); - EraseFiles(staging_files); - return false; - } - - args.emplace_back(android::base::StringPrintf("--%s-fd=%d", kind, staging_file->Fd())); + return CompilationResult::Error( + OdrMetrics::Status::kIoError, + "Failed to create {} file '{}'"_format(kind, staging_location)); + } + // Don't check the state of the staging file. It doesn't need to be flushed because it's removed + // after the compilation regardless of success or failure. + staging_file->MarkUnchecked(); + args.emplace_back(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); + std::string install_location = Dirname(artifacts.OatPath()); if (!EnsureDirectoryExists(install_location)) { - metrics.SetStatus(OdrMetrics::Status::kIoError); - return false; + return CompilationResult::Error( + OdrMetrics::Status::kIoError, + "Error encountered when preparing directory '{}'"_format(install_location)); } - const time_t timeout = GetSubprocessTimeout(); - const std::string cmd_line = android::base::Join(args, ' '); - LOG(INFO) << android::base::StringPrintf("Compiling boot classpath (%s%s): %s [timeout %lds]", - GetInstructionSetString(isa), - minimal ? ", minimal" : "", - cmd_line.c_str(), - timeout); + std::copy(extra_args.begin(), extra_args.end(), std::back_inserter(args)); + + Timer timer; + time_t timeout = GetSubprocessTimeout(); + std::string cmd_line = Join(args, ' '); + LOG(INFO) << "{}: {} [timeout {}s]"_format(debug_message, cmd_line, timeout); if (config_.GetDryRun()) { LOG(INFO) << "Compilation skipped (dry-run)."; - return true; + return CompilationResult::Ok(); } - bool timed_out = false; - int dex2oat_exit_code = exec_utils_->ExecAndReturnCode(args, timeout, &timed_out, error_msg); + std::string error_msg; + ExecResult dex2oat_result = exec_utils_->ExecAndReturnResult(args, timeout, &error_msg); - if (dex2oat_exit_code != 0) { - if (timed_out) { - metrics.SetStatus(OdrMetrics::Status::kTimeLimitExceeded); - } else { - metrics.SetStatus(OdrMetrics::Status::kDex2OatError); - } - EraseFiles(staging_files); - return false; + if (dex2oat_result.exit_code != 0) { + return CompilationResult::Dex2oatError( + dex2oat_result.exit_code < 0 ? + error_msg : + "dex2oat returned an unexpected code: {}"_format(dex2oat_result.exit_code), + timer.duration().count(), + dex2oat_result); } if (!MoveOrEraseFiles(staging_files, install_location)) { - metrics.SetStatus(OdrMetrics::Status::kInstallFailed); - return false; + return CompilationResult::Error(OdrMetrics::Status::kIoError, + "Failed to commit artifacts to '{}'"_format(install_location)); } - on_dex2oat_success(); - return true; + return CompilationResult::Dex2oatOk(timer.duration().count(), dex2oat_result); } -WARN_UNUSED bool OnDeviceRefresh::CompileSystemServerArtifacts( - const std::string& staging_dir, - OdrMetrics& metrics, - const std::set<std::string>& system_server_jars_to_compile, - const std::function<void()>& on_dex2oat_success, - std::string* error_msg) const { - ScopedOdrCompilationTimer compilation_timer(metrics); - std::vector<std::string> classloader_context; +WARN_UNUSED CompilationResult +OnDeviceRefresh::RunDex2oatForBootClasspath(const std::string& staging_dir, + const std::string& debug_name, + InstructionSet isa, + const std::vector<std::string>& dex_files, + const std::vector<std::string>& boot_classpath, + const std::vector<std::string>& input_boot_images, + const std::string& output_path) const { + std::vector<std::string> args; + std::vector<std::unique_ptr<File>> readonly_files_raii; - const std::string dex2oat = config_.GetDex2Oat(); - const InstructionSet isa = config_.GetSystemServerIsa(); - for (const std::string& jar : all_systemserver_jars_) { - auto scope_guard = android::base::make_scope_guard([&]() { - if (ContainsElement(systemserver_classpath_jars_, jar)) { - classloader_context.emplace_back(jar); - } - }); + // Compile as a single image for fewer files and slightly less memory overhead. + args.emplace_back("--single-image"); - if (!ContainsElement(system_server_jars_to_compile, jar)) { - continue; + if (input_boot_images.empty()) { + // Primary boot image. + std::string art_boot_profile_file = GetArtRoot() + "/etc/boot-image.prof"; + std::string framework_boot_profile_file = GetAndroidRoot() + "/etc/boot-image.prof"; + bool has_any_profile = AddDex2OatProfile( + args, readonly_files_raii, {art_boot_profile_file, framework_boot_profile_file}); + if (!has_any_profile) { + return CompilationResult::Error(OdrMetrics::Status::kIoError, "Missing boot image profile"); + } + const std::string& compiler_filter = config_.GetBootImageCompilerFilter(); + if (!compiler_filter.empty()) { + args.emplace_back("--compiler-filter=" + compiler_filter); + } else { + args.emplace_back(StringPrintf("--compiler-filter=%s", kPrimaryCompilerFilter)); } - std::vector<std::unique_ptr<File>> readonly_files_raii; - std::vector<std::string> args; - args.emplace_back(dex2oat); - args.emplace_back("--dex-file=" + jar); - - std::string actual_jar_path = AndroidRootRewrite(jar); - std::unique_ptr<File> dex_file(OS::OpenFileForReading(actual_jar_path.c_str())); - args.emplace_back(android::base::StringPrintf("--dex-fd=%d", dex_file->Fd())); - readonly_files_raii.push_back(std::move(dex_file)); + args.emplace_back(StringPrintf("--base=0x%08x", ART_BASE_ADDRESS)); - AddDex2OatCommonOptions(args); - AddDex2OatDebugInfo(args); - AddDex2OatInstructionSet(args, isa); - if (!AddDex2OatConcurrencyArguments(args)) { - return false; + std::string dirty_image_objects_file(GetAndroidRoot() + "/etc/dirty-image-objects"); + if (OS::FileExists(dirty_image_objects_file.c_str())) { + std::unique_ptr<File> file(OS::OpenFileForReading(dirty_image_objects_file.c_str())); + args.emplace_back(StringPrintf("--dirty-image-objects-fd=%d", file->Fd())); + readonly_files_raii.push_back(std::move(file)); + } else { + LOG(WARNING) << "Missing dirty objects file: '{}'"_format(dirty_image_objects_file); } - const std::string jar_name(android::base::Basename(jar)); - const std::string profile = Concatenate({GetAndroidRoot(), "/framework/", jar_name, ".prof"}); - std::string compiler_filter = config_.GetSystemServerCompilerFilter(); - if (compiler_filter == "speed-profile") { - AddDex2OatProfileAndCompilerFilter(args, readonly_files_raii, {profile}); + std::string preloaded_classes_file(GetAndroidRoot() + "/etc/preloaded-classes"); + if (OS::FileExists(preloaded_classes_file.c_str())) { + std::unique_ptr<File> file(OS::OpenFileForReading(preloaded_classes_file.c_str())); + args.emplace_back(StringPrintf("--preloaded-classes-fds=%d", file->Fd())); + readonly_files_raii.push_back(std::move(file)); } else { - args.emplace_back("--compiler-filter=" + compiler_filter); + LOG(WARNING) << "Missing preloaded classes file: '{}'"_format(preloaded_classes_file); } + } else { + // Mainline extension. + args.emplace_back(StringPrintf("--compiler-filter=%s", kMainlineCompilerFilter)); + } + + return RunDex2oat( + staging_dir, + "Compiling boot classpath ({}, {})"_format(GetInstructionSetString(isa), debug_name), + isa, + dex_files, + boot_classpath, + input_boot_images, + OdrArtifacts::ForBootImage(output_path), + args, + readonly_files_raii); +} - const std::string image_location = GetSystemServerImagePath(/*on_system=*/false, jar); - const std::string install_location = android::base::Dirname(image_location); - if (!EnsureDirectoryExists(install_location)) { - metrics.SetStatus(OdrMetrics::Status::kIoError); - return false; - } +WARN_UNUSED CompilationResult +OnDeviceRefresh::CompileBootClasspath(const std::string& staging_dir, + InstructionSet isa, + BootImages boot_images, + const std::function<void()>& on_dex2oat_success) const { + DCHECK_GT(boot_images.Count(), 0); + DCHECK_IMPLIES(boot_images.primary_boot_image, boot_images.boot_image_mainline_extension); - OdrArtifacts artifacts = OdrArtifacts::ForSystemServer(image_location); - CHECK_EQ(artifacts.OatPath(), GetApexDataOdexFilename(jar.c_str(), isa)); + CompilationResult result = CompilationResult::Ok(); - 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")}; + if (config_.GetMinimal()) { + result.Merge( + CompilationResult::Error(OdrMetrics::Status::kUnknown, "Minimal boot image requested")); + } - 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; - metrics.SetStatus(OdrMetrics::Status::kIoError); - 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)); + if (!CheckCompilationSpace()) { + result.Merge(CompilationResult::Error(OdrMetrics::Status::kNoSpace, "Insufficient space")); + } + + if (result.IsOk() && boot_images.primary_boot_image) { + CompilationResult primary_result = RunDex2oatForBootClasspath( + staging_dir, + "primary", + isa, + dex2oat_boot_classpath_jars_, + dex2oat_boot_classpath_jars_, + /*input_boot_images=*/{}, + GetPrimaryBootImagePath(/*on_system=*/false, /*minimal=*/false, isa)); + result.Merge(primary_result); + + if (primary_result.IsOk()) { + on_dex2oat_success(); + + // Remove the minimal boot image only if the full boot image is successfully generated. + std::string path = GetPrimaryBootImagePath(/*on_system=*/false, /*minimal=*/true, isa); + OdrArtifacts artifacts = OdrArtifacts::ForBootImage(path); + unlink(artifacts.ImagePath().c_str()); + unlink(artifacts.OatPath().c_str()); + unlink(artifacts.VdexPath().c_str()); } - args.emplace_back("--oat-location=" + artifacts.OatPath()); + } - args.emplace_back("--runtime-arg"); - args.emplace_back(Concatenate({"-Xbootclasspath:", config_.GetBootClasspath()})); + if (!result.IsOk() && boot_images.primary_boot_image) { + LOG(ERROR) << "Compilation of primary BCP failed: " << result.error_msg; - auto bcp_jars = android::base::Split(config_.GetBootClasspath(), ":"); - if (!AddBootClasspathFds(args, readonly_files_raii, bcp_jars)) { - return false; + // Fall back to generating a minimal boot image. + // The compilation of the full boot image will be retried on later reboots with a backoff + // time, and the minimal boot image will be removed once the compilation of the full boot + // image succeeds. + std::string ignored_error_msg; + if (PrimaryBootImageExist( + /*on_system=*/false, /*minimal=*/true, isa, &ignored_error_msg)) { + LOG(INFO) << "Minimal boot image already up-to-date"; + return result; } - std::string unused_error_msg; - // If the boot classpath artifacts are not on /data, then the boot classpath are not re-compiled - // and the artifacts must exist on /system. - bool boot_image_on_system = !BootClasspathArtifactsExist( - /*on_system=*/false, /*minimal=*/false, isa, &unused_error_msg); - AddCompiledBootClasspathFdsIfAny( - args, - readonly_files_raii, - bcp_jars, + std::vector<std::string> art_bcp_jars = GetArtBcpJars(); + CompilationResult minimal_result = RunDex2oatForBootClasspath( + staging_dir, + "minimal", isa, - boot_image_on_system ? GetSystemBootImageDir() : config_.GetArtifactDirectory()); - args.emplace_back( - Concatenate({"--boot-image=", - boot_image_on_system ? GetBootImage(/*on_system=*/true, /*minimal=*/false) + - ":" + GetSystemBootImageExtension() : - GetBootImage(/*on_system=*/false, /*minimal=*/false)})); - - const std::string context_path = android::base::Join(classloader_context, ':'); - if (art::ContainsElement(systemserver_classpath_jars_, jar)) { - args.emplace_back("--class-loader-context=PCL[" + context_path + "]"); - } else { - args.emplace_back("--class-loader-context=PCL[];PCL[" + context_path + "]"); - } - if (!classloader_context.empty()) { - std::vector<int> fds; - for (const std::string& path : classloader_context) { - std::string actual_path = AndroidRootRewrite(path); - std::unique_ptr<File> file(OS::OpenFileForReading(actual_path.c_str())); - if (!file->IsValid()) { - PLOG(ERROR) << "Failed to open classloader context " << actual_path; - metrics.SetStatus(OdrMetrics::Status::kIoError); - return false; - } - fds.emplace_back(file->Fd()); - readonly_files_raii.emplace_back(std::move(file)); - } - const std::string context_fds = android::base::Join(fds, ':'); - args.emplace_back(Concatenate({"--class-loader-context-fds=", context_fds})); + art_bcp_jars, + art_bcp_jars, + /*input_boot_images=*/{}, + GetPrimaryBootImagePath(/*on_system=*/false, /*minimal=*/true, isa)); + result.Merge(minimal_result); + + if (!minimal_result.IsOk()) { + LOG(ERROR) << "Compilation of minimal BCP failed: " << result.error_msg; } - 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; + return result; + } + + if (result.IsOk() && boot_images.boot_image_mainline_extension) { + CompilationResult mainline_result = + RunDex2oatForBootClasspath(staging_dir, + "mainline", + isa, + GetMainlineBcpJars(), + boot_classpath_jars_, + GetBestBootImages(isa, /*include_mainline_extension=*/false), + GetBootImageMainlineExtensionPath(/*on_system=*/false, isa)); + result.Merge(mainline_result); + + if (mainline_result.IsOk()) { + on_dex2oat_success(); } + } - bool timed_out = false; - int dex2oat_exit_code = exec_utils_->ExecAndReturnCode(args, timeout, &timed_out, error_msg); + if (!result.IsOk() && boot_images.boot_image_mainline_extension) { + LOG(ERROR) << "Compilation of mainline BCP failed: " << result.error_msg; + } - if (dex2oat_exit_code != 0) { - if (timed_out) { - metrics.SetStatus(OdrMetrics::Status::kTimeLimitExceeded); - } else { - metrics.SetStatus(OdrMetrics::Status::kDex2OatError); + return result; +} + +WARN_UNUSED CompilationResult OnDeviceRefresh::RunDex2oatForSystemServer( + const std::string& staging_dir, + const std::string& dex_file, + const std::vector<std::string>& classloader_context) const { + std::vector<std::string> args; + std::vector<std::unique_ptr<File>> readonly_files_raii; + InstructionSet isa = config_.GetSystemServerIsa(); + std::string output_path = GetSystemServerImagePath(/*on_system=*/false, dex_file); + + std::string actual_jar_path = RewriteParentDirectoryIfNeeded(dex_file); + std::string profile = actual_jar_path + ".prof"; + const std::string& compiler_filter = config_.GetSystemServerCompilerFilter(); + bool maybe_add_profile = !compiler_filter.empty() || HasVettedDeviceSystemServerProfiles(); + bool has_added_profile = + maybe_add_profile && AddDex2OatProfile(args, readonly_files_raii, {profile}); + if (!compiler_filter.empty()) { + args.emplace_back("--compiler-filter=" + compiler_filter); + } else if (has_added_profile) { + args.emplace_back("--compiler-filter=speed-profile"); + } else { + args.emplace_back("--compiler-filter=speed"); + } + + std::string context_path = Join(classloader_context, ':'); + if (art::ContainsElement(systemserver_classpath_jars_, dex_file)) { + args.emplace_back("--class-loader-context=PCL[" + context_path + "]"); + } else { + args.emplace_back("--class-loader-context=PCL[];PCL[" + context_path + "]"); + } + if (!classloader_context.empty()) { + std::vector<int> fds; + for (const std::string& path : classloader_context) { + std::string actual_path = RewriteParentDirectoryIfNeeded(path); + std::unique_ptr<File> file(OS::OpenFileForReading(actual_path.c_str())); + if (!file->IsValid()) { + return CompilationResult::Error( + OdrMetrics::Status::kIoError, + "Failed to open classloader context '{}': {}"_format(actual_path, strerror(errno))); } - EraseFiles(staging_files); - return false; + fds.emplace_back(file->Fd()); + readonly_files_raii.emplace_back(std::move(file)); } + args.emplace_back("--class-loader-context-fds=" + Join(fds, ':')); + } - if (!MoveOrEraseFiles(staging_files, install_location)) { - metrics.SetStatus(OdrMetrics::Status::kInstallFailed); - return false; + return RunDex2oat(staging_dir, + "Compiling {}"_format(Basename(dex_file)), + isa, + {dex_file}, + boot_classpath_jars_, + GetBestBootImages(isa, /*include_mainline_extension=*/true), + OdrArtifacts::ForSystemServer(output_path), + args, + readonly_files_raii); +} + +WARN_UNUSED CompilationResult +OnDeviceRefresh::CompileSystemServer(const std::string& staging_dir, + const std::set<std::string>& system_server_jars_to_compile, + const std::function<void()>& on_dex2oat_success) const { + DCHECK(!system_server_jars_to_compile.empty()); + + CompilationResult result = CompilationResult::Ok(); + std::vector<std::string> classloader_context; + + if (!CheckCompilationSpace()) { + LOG(ERROR) << "Compilation of system_server failed: Insufficient space"; + return CompilationResult::Error(OdrMetrics::Status::kNoSpace, "Insufficient space"); + } + + for (const std::string& jar : all_systemserver_jars_) { + if (ContainsElement(system_server_jars_to_compile, jar)) { + CompilationResult current_result = + RunDex2oatForSystemServer(staging_dir, jar, classloader_context); + result.Merge(current_result); + + if (current_result.IsOk()) { + on_dex2oat_success(); + } else { + LOG(ERROR) << "Compilation of {} failed: {}"_format(Basename(jar), result.error_msg); + } } - on_dex2oat_success(); + if (ContainsElement(systemserver_classpath_jars_, jar)) { + classloader_context.emplace_back(jar); + } } - return true; + return result; } WARN_UNUSED ExitCode OnDeviceRefresh::Compile(OdrMetrics& metrics, @@ -1728,10 +2010,18 @@ WARN_UNUSED ExitCode OnDeviceRefresh::Compile(OdrMetrics& metrics, const char* staging_dir = nullptr; metrics.SetStage(OdrMetrics::Stage::kPreparation); + if (!EnsureDirectoryExists(config_.GetArtifactDirectory())) { + LOG(ERROR) << "Failed to prepare artifact directory"; + metrics.SetStatus(errno == EPERM ? OdrMetrics::Status::kDalvikCachePermissionDenied : + OdrMetrics::Status::kIoError); + return ExitCode::kCleanupFailed; + } + if (config_.GetRefresh()) { Result<void> result = RefreshExistingArtifacts(); if (!result.ok()) { LOG(ERROR) << "Failed to refresh existing artifacts: " << result.error(); + metrics.SetStatus(OdrMetrics::Status::kIoError); return ExitCode::kCleanupFailed; } } @@ -1740,6 +2030,7 @@ WARN_UNUSED ExitCode OnDeviceRefresh::Compile(OdrMetrics& metrics, Result<void> result = WriteCacheInfo(); if (!result.ok()) { LOG(ERROR) << result.error(); + metrics.SetStatus(OdrMetrics::Status::kIoError); return ExitCode::kCleanupFailed; } @@ -1756,99 +2047,59 @@ WARN_UNUSED ExitCode OnDeviceRefresh::Compile(OdrMetrics& metrics, std::string error_msg; uint32_t dex2oat_invocation_count = 0; - uint32_t total_dex2oat_invocation_count = - compilation_options.compile_boot_classpath_for_isas.size() + - compilation_options.system_server_jars_to_compile.size(); + uint32_t total_dex2oat_invocation_count = compilation_options.CompilationUnitCount(); ReportNextBootAnimationProgress(dex2oat_invocation_count, total_dex2oat_invocation_count); auto advance_animation_progress = [&]() { ReportNextBootAnimationProgress(++dex2oat_invocation_count, total_dex2oat_invocation_count); }; - const auto& bcp_instruction_sets = config_.GetBootClasspathIsas(); + const std::vector<InstructionSet>& bcp_instruction_sets = config_.GetBootClasspathIsas(); DCHECK(!bcp_instruction_sets.empty() && bcp_instruction_sets.size() <= 2); - bool full_compilation_failed = false; - for (const InstructionSet isa : compilation_options.compile_boot_classpath_for_isas) { - auto stage = (isa == bcp_instruction_sets.front()) ? OdrMetrics::Stage::kPrimaryBootClasspath : - OdrMetrics::Stage::kSecondaryBootClasspath; - metrics.SetStage(stage); - if (!config_.GetMinimal()) { - if (CheckCompilationSpace()) { - if (CompileBootClasspathArtifacts(isa, - staging_dir, - metrics, - advance_animation_progress, - /*minimal=*/false, - &error_msg)) { - // Remove the minimal boot image only if the full boot image is successfully generated. - std::string path = GetBootImagePath(/*on_system=*/false, /*minimal=*/true, isa); - OdrArtifacts artifacts = OdrArtifacts::ForBootImage(path); - unlink(artifacts.ImagePath().c_str()); - unlink(artifacts.OatPath().c_str()); - unlink(artifacts.VdexPath().c_str()); - continue; - } - LOG(ERROR) << "Compilation of BCP failed: " << error_msg; - } else { - metrics.SetStatus(OdrMetrics::Status::kNoSpace); + InstructionSet system_server_isa = config_.GetSystemServerIsa(); + + bool system_server_isa_failed = false; + std::optional<std::pair<OdrMetrics::Stage, OdrMetrics::Status>> first_failure; + + for (const auto& [isa, boot_images_to_generate] : + compilation_options.boot_images_to_generate_for_isas) { + OdrMetrics::Stage stage = (isa == bcp_instruction_sets.front()) ? + OdrMetrics::Stage::kPrimaryBootClasspath : + OdrMetrics::Stage::kSecondaryBootClasspath; + CompilationResult bcp_result = + CompileBootClasspath(staging_dir, isa, boot_images_to_generate, advance_animation_progress); + metrics.SetDex2OatResult(stage, bcp_result.elapsed_time_ms, bcp_result.dex2oat_result); + metrics.SetBcpCompilationType(stage, boot_images_to_generate.GetTypeForMetrics()); + if (!bcp_result.IsOk()) { + if (isa == system_server_isa) { + system_server_isa_failed = true; } + first_failure = first_failure.value_or(std::make_pair(stage, bcp_result.status)); } + } - // Fall back to generating a minimal boot image. - // The compilation of the full boot image will be retried on later reboots with a backoff time, - // and the minimal boot image will be removed once the compilation of the full boot image - // succeeds. - full_compilation_failed = true; - std::string ignored_error_msg; - if (BootClasspathArtifactsExist( - /*on_system=*/false, /*minimal=*/true, isa, &ignored_error_msg)) { - continue; - } - if (CompileBootClasspathArtifacts(isa, - staging_dir, - metrics, - advance_animation_progress, - /*minimal=*/true, - &error_msg)) { - continue; + // Don't compile system server if the compilation of BCP failed. + if (!system_server_isa_failed && !compilation_options.system_server_jars_to_compile.empty()) { + OdrMetrics::Stage stage = OdrMetrics::Stage::kSystemServerClasspath; + CompilationResult ss_result = CompileSystemServer( + staging_dir, compilation_options.system_server_jars_to_compile, advance_animation_progress); + metrics.SetDex2OatResult(stage, ss_result.elapsed_time_ms, ss_result.dex2oat_result); + if (!ss_result.IsOk()) { + first_failure = first_failure.value_or(std::make_pair(stage, ss_result.status)); } - LOG(ERROR) << "Compilation of minimal BCP failed: " << error_msg; - if (!config_.GetDryRun() && !RemoveDirectory(staging_dir)) { - return ExitCode::kCleanupFailed; - } - return ExitCode::kCompilationFailed; } - if (full_compilation_failed) { + if (first_failure.has_value()) { + metrics.SetStage(first_failure->first); + metrics.SetStatus(first_failure->second); + if (!config_.GetDryRun() && !RemoveDirectory(staging_dir)) { return ExitCode::kCleanupFailed; } return ExitCode::kCompilationFailed; } - if (!compilation_options.system_server_jars_to_compile.empty()) { - metrics.SetStage(OdrMetrics::Stage::kSystemServerClasspath); - - if (!CheckCompilationSpace()) { - metrics.SetStatus(OdrMetrics::Status::kNoSpace); - // Return kCompilationFailed so odsign will keep and sign whatever we have been able to - // compile. - return ExitCode::kCompilationFailed; - } - - if (!CompileSystemServerArtifacts(staging_dir, - metrics, - compilation_options.system_server_jars_to_compile, - advance_animation_progress, - &error_msg)) { - LOG(ERROR) << "Compilation of system_server failed: " << error_msg; - if (!config_.GetDryRun() && !RemoveDirectory(staging_dir)) { - return ExitCode::kCleanupFailed; - } - return ExitCode::kCompilationFailed; - } - } - metrics.SetStage(OdrMetrics::Stage::kComplete); + metrics.SetStatus(OdrMetrics::Status::kOK); return ExitCode::kCompilationSuccess; } |