diff options
| author | 2025-02-04 18:03:31 +0000 | |
|---|---|---|
| committer | 2025-02-14 05:55:36 -0800 | |
| commit | 1802832343c0374cde99493bf3bf882a74ec944b (patch) | |
| tree | e15bfeddf53d877c26457759df0a042f8b427e1c | |
| parent | bde490d3c291724ff0512d4b6c1904f9d2bb5b5d (diff) | |
Refactor OatFileAssistant - Step 3.
After this change, error messages in OatFileAssistant are all propagated
along the way to `GetBestInfo` and logged there.
This change reduces the amount of artd log by 82%.
Before: https://paste.googleplex.com/4527623093616640
After: https://paste.googleplex.com/4842357558870016
As we are adding two more paths to check, for SDM files, reducing the
amount of artd log becomes more critical than before. Otherwise, the log
spamming problem will get even worse.
Bug: 377474232
Bug: 345762752
Test: adb shell pm art dump
Change-Id: I34cdd7228fb063466c86511610687f831ae465ad
| -rw-r--r-- | oatdump/oatdump.cc | 3 | ||||
| -rw-r--r-- | runtime/dexopt_test.cc | 2 | ||||
| -rw-r--r-- | runtime/oat/oat_file_assistant.cc | 268 | ||||
| -rw-r--r-- | runtime/oat/oat_file_assistant.h | 26 | ||||
| -rw-r--r-- | test/677-fsi/expected-stderr.txt | 4 | ||||
| -rw-r--r-- | test/677-fsi/run.py | 2 |
6 files changed, 164 insertions, 141 deletions
diff --git a/oatdump/oatdump.cc b/oatdump/oatdump.cc index 13aef63b95..c567a5e730 100644 --- a/oatdump/oatdump.cc +++ b/oatdump/oatdump.cc @@ -3553,8 +3553,7 @@ struct OatdumpMain : public CmdlineMain<OatdumpArgs> { /*only_load_trusted_executable=*/false, ofa_context.get()); - if (!oat_file_assistant.ValidateBootClassPathChecksums(*oat_file)) { - *error_msg = "BCP checksum check failed"; + if (!oat_file_assistant.ValidateBootClassPathChecksums(*oat_file, error_msg)) { return false; } diff --git a/runtime/dexopt_test.cc b/runtime/dexopt_test.cc index edb28823c7..40e10142d7 100644 --- a/runtime/dexopt_test.cc +++ b/runtime/dexopt_test.cc @@ -169,7 +169,7 @@ void DexoptTest::GenerateOatForTest(const std::string& dex_location, context.get(), /*load_executable=*/false); - bool match = oat_file_assistant.ValidateBootClassPathChecksums(*odex_file); + bool match = oat_file_assistant.ValidateBootClassPathChecksums(*odex_file, &error_msg); ASSERT_EQ(!with_alternate_image, match) << error_msg; } } diff --git a/runtime/oat/oat_file_assistant.cc b/runtime/oat/oat_file_assistant.cc index 98e46644c8..e6f9cb15a3 100644 --- a/runtime/oat/oat_file_assistant.cc +++ b/runtime/oat/oat_file_assistant.cc @@ -21,6 +21,8 @@ #include <memory> #include <optional> #include <sstream> +#include <string_view> +#include <utility> #include <vector> #include "android-base/file.h" @@ -49,6 +51,7 @@ #include "gc/space/image_space.h" #include "image.h" #include "oat.h" +#include "oat/oat_file.h" #include "oat_file_assistant_context.h" #include "runtime.h" #include "scoped_thread_state_change-inl.h" @@ -420,55 +423,51 @@ bool OatFileAssistant::DexChecksumUpToDate(const OatFile& file, std::string* err return true; } -OatFileAssistant::OatStatus OatFileAssistant::GivenOatFileStatus(const OatFile& file) { +OatFileAssistant::OatStatus OatFileAssistant::GivenOatFileStatus(const OatFile& file, + /*out*/ std::string* error_msg) { // Verify the ART_USE_READ_BARRIER state. // TODO: Don't fully reject files due to read barrier state. If they contain // compiled code and are otherwise okay, we should return something like // kOatRelocationOutOfDate. If they don't contain compiled code, the read // barrier state doesn't matter. if (file.GetOatHeader().IsConcurrentCopying() != gUseReadBarrier) { + *error_msg = "Read barrier state mismatch"; return kOatCannotOpen; } // Verify the dex checksum. - std::string error_msg; - if (!DexChecksumUpToDate(file, &error_msg)) { - LOG(ERROR) << error_msg; + if (!DexChecksumUpToDate(file, error_msg)) { + LOG(ERROR) << *error_msg; return kOatDexOutOfDate; } CompilerFilter::Filter current_compiler_filter = file.GetCompilerFilter(); // Verify the image checksum - if (file.IsBackedByVdexOnly()) { - VLOG(oat) << "Image checksum test skipped for vdex file " << file.GetLocation(); - } else if (CompilerFilter::DependsOnImageChecksum(current_compiler_filter)) { - if (!ValidateBootClassPathChecksums(file)) { - VLOG(oat) << "Oat image checksum does not match image checksum."; + if (!file.IsBackedByVdexOnly() && + CompilerFilter::DependsOnImageChecksum(current_compiler_filter)) { + if (!ValidateBootClassPathChecksums(file, error_msg)) { return kOatBootImageOutOfDate; } if (!gc::space::ImageSpace::ValidateApexVersions( file.GetOatHeader(), GetOatFileAssistantContext()->GetApexVersions(), file.GetLocation(), - &error_msg)) { - VLOG(oat) << error_msg; + error_msg)) { return kOatBootImageOutOfDate; } - } else { - VLOG(oat) << "Image checksum test skipped for compiler filter " << current_compiler_filter; } // The constraint is only enforced if the zip has uncompressed dex code. if (only_load_trusted_executable_ && !LocationIsTrusted(file.GetLocation(), !GetRuntimeOptions().deny_art_apex_data_files) && file.ContainsDexCode() && ZipFileOnlyContainsUncompressedDex()) { - LOG(ERROR) << "Not loading " << dex_location_ - << ": oat file has dex code, but APK has uncompressed dex code"; + *error_msg = "Oat file has dex code, but APK has uncompressed dex code"; + LOG(ERROR) << "Not loading " << dex_location_ << ": " << *error_msg; return kOatDexOutOfDate; } - if (!ClassLoaderContextIsOkay(file)) { + if (!ClassLoaderContextIsOkay(file, error_msg)) { return kOatContextOutOfDate; } @@ -762,29 +761,23 @@ bool OatFileAssistant::ValidateBootClassPathChecksums(OatFileAssistantContext* o return true; } -bool OatFileAssistant::ValidateBootClassPathChecksums(const OatFile& oat_file) { +bool OatFileAssistant::ValidateBootClassPathChecksums(const OatFile& oat_file, + /*out*/ std::string* error_msg) { // Get the checksums and the BCP from the oat file. const char* oat_boot_class_path_checksums = oat_file.GetOatHeader().GetStoreValueByKey(OatHeader::kBootClassPathChecksumsKey); const char* oat_boot_class_path = oat_file.GetOatHeader().GetStoreValueByKey(OatHeader::kBootClassPathKey); if (oat_boot_class_path_checksums == nullptr || oat_boot_class_path == nullptr) { + *error_msg = "Missing boot image information from oat file"; return false; } - std::string error_msg; - bool result = ValidateBootClassPathChecksums(GetOatFileAssistantContext(), - isa_, - oat_boot_class_path_checksums, - oat_boot_class_path, - &error_msg); - if (!result) { - VLOG(oat) << "Failed to verify checksums of oat file " << oat_file.GetLocation() - << " error: " << error_msg; - return false; - } - - return true; + return ValidateBootClassPathChecksums(GetOatFileAssistantContext(), + isa_, + oat_boot_class_path_checksums, + oat_boot_class_path, + error_msg); } bool OatFileAssistant::IsPrimaryBootImageUsable() { @@ -794,12 +787,31 @@ bool OatFileAssistant::IsPrimaryBootImageUsable() { OatFileAssistant::OatFileInfo& OatFileAssistant::GetBestInfo() { ScopedTrace trace("GetBestInfo"); + auto log_status = [&](std::string_view location, OatFileInfo* info) { + if (!VLOG_IS_ON(oat)) { + return; + } + std::string error_msg; + OatStatus status = info->Status(&error_msg); + std::string message = ART_FORMAT( + "GetBestInfo: {} ({}) is {}", location, info->DisplayFilename(), fmt::streamed(status)); + const OatFile* file = info->GetFile(); + if (file != nullptr) { + message += ART_FORMAT(" with filter '{}' executable '{}'", + fmt::streamed(file->GetCompilerFilter()), + file->IsExecutable()); + } + if (!info->IsUseable()) { + message += ": " + error_msg; + } + VLOG(oat) << message; + }; + // If the oat location is useable, take it. This must be an app on a readonly filesystem // (typically, a system app or an incremental app). This must be prioritized over the odex // location, because the odex location probably has the dexpreopt artifacts. if (oat_.FileExists()) { - VLOG(oat) << ART_FORMAT("GetBestInfo checking odex in dalvik-cache ({})", - oat_.DisplayFilename()); + log_status("odex in dalvik-cache", &oat_); if (oat_.IsUseable()) { return oat_; } @@ -807,8 +819,7 @@ OatFileAssistant::OatFileInfo& OatFileAssistant::GetBestInfo() { // The odex location, which is the most common. if (odex_.FileExists()) { - VLOG(oat) << ART_FORMAT("GetBestInfo checking odex next to the dex file ({})", - odex_.DisplayFilename()); + log_status("odex next to the dex file", &odex_); if (odex_.IsUseable()) { return odex_; } @@ -816,8 +827,7 @@ OatFileAssistant::OatFileInfo& OatFileAssistant::GetBestInfo() { // No odex/oat available, look for a useable vdex file. if (vdex_for_oat_.FileExists()) { - VLOG(oat) << ART_FORMAT("GetBestInfo checking vdex in dalvik-cache ({})", - vdex_for_oat_.DisplayFilename()); + log_status("vdex in dalvik-cache", &vdex_for_oat_); if (vdex_for_oat_.IsUseable()) { return vdex_for_oat_; } @@ -832,14 +842,14 @@ OatFileAssistant::OatFileInfo& OatFileAssistant::GetBestInfo() { // A .dm file may be available, look for it. if (dm_.FileExists()) { - VLOG(oat) << ART_FORMAT("GetBestInfo checking dm ({})", dm_.DisplayFilename()); + log_status("dm", &dm_); if (dm_.IsUseable()) { return dm_; } } // No usable artifact. Pick the odex if it exists, or the oat if not. - VLOG(oat) << "GetBestInfo no usable artifacts"; + VLOG(oat) << ART_FORMAT("GetBestInfo: {} has no usable artifacts", dex_location_); return (odex_.Status() == kOatCannotOpen) ? oat_ : odex_; } @@ -886,20 +896,22 @@ bool OatFileAssistant::OatFileInfo::IsUseable() { } } -OatFileAssistant::OatStatus OatFileAssistant::OatFileInfo::Status() { +OatFileAssistant::OatStatus OatFileAssistant::OatFileInfo::Status(/*out*/ std::string* error_msg) { ScopedTrace trace("Status"); - if (!status_attempted_) { - status_attempted_ = true; - const OatFile* file = GetFile(); + if (!status_.has_value()) { + std::string temp_error_msg; + const OatFile* file = GetFile(&temp_error_msg); if (file == nullptr) { - status_ = kOatCannotOpen; + status_ = std::make_pair(kOatCannotOpen, std::move(temp_error_msg)); } else { - status_ = oat_file_assistant_->GivenOatFileStatus(*file); - VLOG(oat) << file->GetLocation() << " is " << status_ << " with filter " - << file->GetCompilerFilter(); + status_ = std::make_pair(oat_file_assistant_->GivenOatFileStatus(*file, &temp_error_msg), + std::move(temp_error_msg)); } } - return status_; + if (error_msg != nullptr) { + *error_msg = status_->second; + } + return status_->first; } OatFileAssistant::DexOptNeeded OatFileAssistant::OatFileInfo::GetDexOptNeeded( @@ -939,107 +951,110 @@ bool OatFileAssistant::OatFileInfo::FileExists() const { return use_fd_ || (!filename_.empty() && OS::FileExists(filename_.c_str())); } -const OatFile* OatFileAssistant::OatFileInfo::GetFile() { +const OatFile* OatFileAssistant::OatFileInfo::GetFile(/*out*/ std::string* error_msg) { CHECK(!file_released_) << "GetFile called after oat file released."; - if (load_attempted_) { - return file_.get(); - } - load_attempted_ = true; if (!filename_provided_) { return nullptr; } - if (LocationIsOnArtApexData(filename_) && - oat_file_assistant_->GetRuntimeOptions().deny_art_apex_data_files) { - LOG(WARNING) << "OatFileAssistant rejected file " << filename_ - << ": ART apexdata is untrusted."; - return nullptr; + if (!file_.has_value()) { + if (LocationIsOnArtApexData(filename_) && + oat_file_assistant_->GetRuntimeOptions().deny_art_apex_data_files) { + file_ = std::make_pair(nullptr, "ART apexdata is untrusted"); + LOG(WARNING) << "OatFileAssistant rejected file " << filename_ << ": " << file_->second; + } else { + std::string temp_error_msg; + file_ = std::make_pair(LoadFile(&temp_error_msg), std::move(temp_error_msg)); + } } - std::string error_msg; - bool executable = oat_file_assistant_->load_executable_; + if (error_msg != nullptr) { + *error_msg = file_->second; + } + return file_->first.get(); +} + +std::unique_ptr<OatFile> OatFileAssistant::OatFileInfo::LoadFile(std::string* error_msg) const { if (filename_.ends_with(kVdexExtension)) { - executable = false; // Check to see if there is a vdex file we can make use of. std::unique_ptr<VdexFile> vdex; if (use_fd_) { - if (vdex_fd_ >= 0) { - struct stat s; - int rc = TEMP_FAILURE_RETRY(fstat(vdex_fd_, &s)); - if (rc == -1) { - error_msg = StringPrintf("Failed getting length of the vdex file %s.", strerror(errno)); - } else { - vdex = VdexFile::Open(vdex_fd_, - s.st_size, - filename_, - /*low_4gb=*/false, - &error_msg); - } + if (vdex_fd_ < 0) { + *error_msg = "vdex_fd not provided"; + return nullptr; + } + struct stat s; + if (fstat(vdex_fd_, &s) < 0) { + *error_msg = ART_FORMAT("Failed getting length of the vdex file: {}", strerror(errno)); + return nullptr; } + vdex = VdexFile::Open(vdex_fd_, + s.st_size, + filename_, + /*low_4gb=*/false, + error_msg); } else { vdex = VdexFile::Open(filename_, /*low_4gb=*/false, - &error_msg); + error_msg); } if (vdex == nullptr) { - VLOG(oat) << "unable to open vdex file " << filename_ << ": " << error_msg; - } else { - file_.reset(OatFile::OpenFromVdex(zip_fd_, - std::move(vdex), - oat_file_assistant_->dex_location_, - oat_file_assistant_->context_, - &error_msg)); + *error_msg = ART_FORMAT("Unable to open vdex file: {}", *error_msg); + return nullptr; } + return std::unique_ptr<OatFile>(OatFile::OpenFromVdex(zip_fd_, + std::move(vdex), + oat_file_assistant_->dex_location_, + oat_file_assistant_->context_, + error_msg)); } else if (filename_.ends_with(kDmExtension)) { - executable = false; // Check to see if there is a vdex file we can make use of. - std::unique_ptr<ZipArchive> dm_file(ZipArchive::Open(filename_.c_str(), &error_msg)); - if (dm_file != nullptr) { - std::unique_ptr<VdexFile> vdex(VdexFile::OpenFromDm(filename_, *dm_file, &error_msg)); - if (vdex != nullptr) { - file_.reset(OatFile::OpenFromVdex(zip_fd_, - std::move(vdex), - oat_file_assistant_->dex_location_, - oat_file_assistant_->context_, - &error_msg)); - } + std::unique_ptr<ZipArchive> dm_file(ZipArchive::Open(filename_.c_str(), error_msg)); + if (dm_file == nullptr) { + return nullptr; + } + std::unique_ptr<VdexFile> vdex(VdexFile::OpenFromDm(filename_, *dm_file, error_msg)); + if (vdex == nullptr) { + return nullptr; } + return std::unique_ptr<OatFile>(OatFile::OpenFromVdex(/*zip_fd=*/-1, + std::move(vdex), + oat_file_assistant_->dex_location_, + oat_file_assistant_->context_, + error_msg)); } else { + bool executable = oat_file_assistant_->load_executable_; if (executable && oat_file_assistant_->only_load_trusted_executable_) { executable = LocationIsTrusted(filename_, /*trust_art_apex_data_files=*/true); } - VLOG(oat) << "Loading " << filename_ << " with executable: " << executable; + if (use_fd_) { - if (oat_fd_ >= 0 && vdex_fd_ >= 0) { - ArrayRef<const std::string> dex_locations(&oat_file_assistant_->dex_location_, - /*size=*/1u); - file_.reset(OatFile::Open(zip_fd_, - vdex_fd_, - oat_fd_, - filename_, - executable, - /*low_4gb=*/false, - dex_locations, - /*dex_files=*/{}, - /*reservation=*/nullptr, - &error_msg)); + if (oat_fd_ < 0 || vdex_fd_ < 0) { + *error_msg = "oat_fd or vdex_fd not provided"; + return nullptr; } + ArrayRef<const std::string> dex_locations(&oat_file_assistant_->dex_location_, + /*size=*/1u); + return std::unique_ptr<OatFile>(OatFile::Open(zip_fd_, + vdex_fd_, + oat_fd_, + filename_, + executable, + /*low_4gb=*/false, + dex_locations, + /*dex_files=*/{}, + /*reservation=*/nullptr, + error_msg)); } else { - file_.reset(OatFile::Open(/*zip_fd=*/-1, - filename_, - filename_, - executable, - /*low_4gb=*/false, - oat_file_assistant_->dex_location_, - &error_msg)); + return std::unique_ptr<OatFile>(OatFile::Open(/*zip_fd=*/-1, + filename_, + filename_, + executable, + /*low_4gb=*/false, + oat_file_assistant_->dex_location_, + error_msg)); } } - if (file_.get() == nullptr) { - VLOG(oat) << "OatFileAssistant test for existing oat file " << filename_ << ": " << error_msg; - } else { - VLOG(oat) << "Successfully loaded " << filename_ << " with executable: " << executable; - } - return file_.get(); } bool OatFileAssistant::OatFileInfo::ShouldRecompileForFilter(CompilerFilter::Filter target, @@ -1103,7 +1118,8 @@ bool OatFileAssistant::OatFileInfo::ShouldRecompileForFilter(CompilerFilter::Fil return false; } -bool OatFileAssistant::ClassLoaderContextIsOkay(const OatFile& oat_file) const { +bool OatFileAssistant::ClassLoaderContextIsOkay(const OatFile& oat_file, + /*out*/ std::string* error_msg) const { if (context_ == nullptr) { // The caller requests to skip the check. return true; @@ -1125,9 +1141,10 @@ bool OatFileAssistant::ClassLoaderContextIsOkay(const OatFile& oat_file) const { /*verify_names=*/true, /*verify_checksums=*/true); if (matches == ClassLoaderContext::VerificationResult::kMismatch) { - VLOG(oat) << "ClassLoaderContext check failed. Context was " << oat_file.GetClassLoaderContext() - << ". The expected context is " - << context_->EncodeContextForOatFile(android::base::Dirname(dex_location_)); + *error_msg = + ART_FORMAT("ClassLoaderContext check failed. Context was {}. The expected context is {}", + oat_file.GetClassLoaderContext(), + context_->EncodeContextForOatFile(android::base::Dirname(dex_location_))); return false; } return true; @@ -1139,9 +1156,8 @@ bool OatFileAssistant::OatFileInfo::IsExecutable() { } void OatFileAssistant::OatFileInfo::Reset() { - load_attempted_ = false; - file_.reset(); - status_attempted_ = false; + file_ = std::nullopt; + status_ = std::nullopt; } void OatFileAssistant::OatFileInfo::Reset( @@ -1157,7 +1173,7 @@ void OatFileAssistant::OatFileInfo::Reset( std::unique_ptr<OatFile> OatFileAssistant::OatFileInfo::ReleaseFile() { file_released_ = true; - return std::move(file_); + return std::move(file_->first); } std::unique_ptr<OatFile> OatFileAssistant::OatFileInfo::ReleaseFileForUse() { diff --git a/runtime/oat/oat_file_assistant.h b/runtime/oat/oat_file_assistant.h index 4526843756..85021bea33 100644 --- a/runtime/oat/oat_file_assistant.h +++ b/runtime/oat/oat_file_assistant.h @@ -23,6 +23,7 @@ #include <sstream> #include <string> #include <string_view> +#include <utility> #include <variant> #include "arch/instruction_set.h" @@ -356,10 +357,11 @@ class OatFileAssistant { // anonymous dex file(s) created by AnonymousDexVdexLocation. EXPORT static bool IsAnonymousVdexBasename(const std::string& basename); - bool ClassLoaderContextIsOkay(const OatFile& oat_file) const; + bool ClassLoaderContextIsOkay(const OatFile& oat_file, /*out*/ std::string* error_msg) const; // Validates the boot class path checksum of an OatFile. - EXPORT bool ValidateBootClassPathChecksums(const OatFile& oat_file); + EXPORT bool ValidateBootClassPathChecksums(const OatFile& oat_file, + /*out*/ std::string* error_msg); // Validates the given bootclasspath and bootclasspath checksums found in an oat header. static bool ValidateBootClassPathChecksums(OatFileAssistantContext* ofa_context, @@ -393,7 +395,8 @@ class OatFileAssistant { bool IsUseable(); // Returns the status of this oat file. - OatStatus Status(); + // Optionally, returns `error_msg` showing why the status is not `kOatUpToDate`. + OatStatus Status(/*out*/ std::string* error_msg = nullptr); // Return the DexOptNeeded value for this oat file with respect to the given target compilation // filter and dexopt trigger. @@ -406,7 +409,8 @@ class OatFileAssistant { // Returns the loaded file. // Loads the file if needed. Returns null if the file failed to load. // The caller shouldn't clean up or free the returned pointer. - const OatFile* GetFile(); + // Optionally, returns `error_msg` showing why the file failed to load. + const OatFile* GetFile(/*out*/ std::string* error_msg = nullptr); // Returns true if the file is opened executable. bool IsExecutable(); @@ -439,6 +443,8 @@ class OatFileAssistant { bool CheckDisableCompactDex(); private: + std::unique_ptr<OatFile> LoadFile(std::string* error_msg) const; + // Returns true if the oat file is usable but at least one dexopt trigger is matched. This // function should only be called if the oat file is usable. bool ShouldRecompileForFilter(CompilerFilter::Filter target, @@ -463,11 +469,13 @@ class OatFileAssistant { int vdex_fd_ = -1; bool use_fd_ = false; - bool load_attempted_ = false; - std::unique_ptr<OatFile> file_; + // A pair of the loaded file and the error message, if `GetFile` has been attempted. + // `std::nullopt` if `GetFile` has not been attempted. + std::optional<std::pair<std::unique_ptr<OatFile>, std::string>> file_ = std::nullopt; - bool status_attempted_ = false; - OatStatus status_ = OatStatus::kOatCannotOpen; + // A pair of the oat status and the error message, if `Status` has been attempted. + // `std::nullopt` if `Status` has not been attempted. + std::optional<std::pair<OatStatus, std::string>> status_ = std::nullopt; // For debugging only. // If this flag is set, the file has been released to the user and the @@ -491,7 +499,7 @@ class OatFileAssistant { // Return the status for a given opened oat file with respect to the dex // location. - OatStatus GivenOatFileStatus(const OatFile& file); + OatStatus GivenOatFileStatus(const OatFile& file, /*out*/ std::string* error_msg); // Gets the dex checksum required for an up-to-date oat file. // Returns cached result from GetMultiDexChecksum. diff --git a/test/677-fsi/expected-stderr.txt b/test/677-fsi/expected-stderr.txt index 35c39182a5..2295afcf0a 100644 --- a/test/677-fsi/expected-stderr.txt +++ b/test/677-fsi/expected-stderr.txt @@ -1,2 +1,2 @@ -oat file has dex code, but APK has uncompressed dex code -oat file has dex code, but APK has uncompressed dex code +Oat file has dex code, but APK has uncompressed dex code +Oat file has dex code, but APK has uncompressed dex code diff --git a/test/677-fsi/run.py b/test/677-fsi/run.py index 357692f74e..e124eff4df 100644 --- a/test/677-fsi/run.py +++ b/test/677-fsi/run.py @@ -26,7 +26,7 @@ def run(ctx, args): # Only keep the lines we're interested in. ctx.run(fr"sed -i '/Hello World/!d' '{args.stdout_file}'") ctx.run( - fr"sed -i '/^.*: oat file has dex code, but APK has uncompressed dex code/!d' '{args.stderr_file}'" + fr"sed -i '/^.*: Oat file has dex code, but APK has uncompressed dex code/!d' '{args.stderr_file}'" ) # Remove part of message containing filename. |