AAPT2: Reformatted dump command invocations
Use with:
aapt2 dump apc [apc]
aapt2 dump configurations [apk]
aapt2 dump strings [apk]
aapt2 dump resources [apk]
aapt2 dump xmlstrings [apk] --file [file]
aapt2 dump xmltree [apk] --file [file]
Will add permissions and badging in a later commit.
Bug: 73351292
Test: Manual tests of the commands
Change-Id: I97eec01222af14053a98bd70255f1bfecd16b1c4
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index 6e9c800..b4311c5 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -24,8 +24,11 @@
#include "Debug.h"
#include "Diagnostics.h"
+#include "LoadedApk.h"
+#include "Util.h"
#include "format/Container.h"
#include "format/binary/BinaryResourceParser.h"
+#include "format/binary/XmlFlattener.h"
#include "format/proto/ProtoDeserialize.h"
#include "io/FileStream.h"
#include "io/ZipArchive.h"
@@ -70,184 +73,6 @@
printer->Println(StringPrintf("Data: offset=%" PRIi64 " length=%zd", offset, len));
}
-static bool DumpXmlFile(IAaptContext* context, io::IFile* file, bool proto,
- text::Printer* printer) {
- std::unique_ptr<xml::XmlResource> doc;
- if (proto) {
- std::unique_ptr<io::InputStream> in = file->OpenInputStream();
- if (in == nullptr) {
- context->GetDiagnostics()->Error(DiagMessage() << "failed to open file");
- return false;
- }
-
- io::ZeroCopyInputAdaptor adaptor(in.get());
- pb::XmlNode pb_node;
- if (!pb_node.ParseFromZeroCopyStream(&adaptor)) {
- context->GetDiagnostics()->Error(DiagMessage() << "failed to parse file as proto XML");
- return false;
- }
-
- std::string err;
- doc = DeserializeXmlResourceFromPb(pb_node, &err);
- if (doc == nullptr) {
- context->GetDiagnostics()->Error(DiagMessage() << "failed to deserialize proto XML");
- return false;
- }
- printer->Println("Proto XML");
- } else {
- std::unique_ptr<io::IData> data = file->OpenAsData();
- if (data == nullptr) {
- context->GetDiagnostics()->Error(DiagMessage() << "failed to open file");
- return false;
- }
-
- std::string err;
- doc = xml::Inflate(data->data(), data->size(), &err);
- if (doc == nullptr) {
- context->GetDiagnostics()->Error(DiagMessage() << "failed to parse file as binary XML");
- return false;
- }
- printer->Println("Binary XML");
- }
-
- Debug::DumpXml(*doc, printer);
- return true;
-}
-
-static bool TryDumpFile(IAaptContext* context, const std::string& file_path,
- const DumpOptions& options) {
- // Use a smaller buffer so that there is less latency for dumping to stdout.
- constexpr size_t kStdOutBufferSize = 1024u;
- io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
- Printer printer(&fout);
-
- std::string err;
- std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(file_path, &err);
- if (zip) {
- ResourceTable table(/** validate_resources **/ false);
- bool proto = false;
- if (io::IFile* file = zip->FindFile("resources.pb")) {
- proto = true;
-
- std::unique_ptr<io::IData> data = file->OpenAsData();
- if (data == nullptr) {
- context->GetDiagnostics()->Error(DiagMessage(file_path) << "failed to open resources.pb");
- return false;
- }
-
- pb::ResourceTable pb_table;
- if (!pb_table.ParseFromArray(data->data(), data->size())) {
- context->GetDiagnostics()->Error(DiagMessage(file_path) << "invalid resources.pb");
- return false;
- }
-
- if (!DeserializeTableFromPb(pb_table, zip.get(), &table, &err)) {
- context->GetDiagnostics()->Error(DiagMessage(file_path)
- << "failed to parse table: " << err);
- return false;
- }
- } else if (io::IFile* file = zip->FindFile("resources.arsc")) {
- std::unique_ptr<io::IData> data = file->OpenAsData();
- if (!data) {
- context->GetDiagnostics()->Error(DiagMessage(file_path) << "failed to open resources.arsc");
- return false;
- }
-
- BinaryResourceParser parser(context->GetDiagnostics(), &table, Source(file_path),
- data->data(), data->size());
- if (!parser.Parse()) {
- return false;
- }
- }
-
- if (!options.file_to_dump_path) {
- if (proto) {
- printer.Println("Proto APK");
- } else {
- printer.Println("Binary APK");
- }
- Debug::PrintTable(table, options.print_options, &printer);
- return true;
- }
-
- io::IFile* file = zip->FindFile(options.file_to_dump_path.value());
- if (file == nullptr) {
- context->GetDiagnostics()->Error(DiagMessage(file_path)
- << "file '" << options.file_to_dump_path.value()
- << "' not found in APK");
- return false;
- }
- return DumpXmlFile(context, file, proto, &printer);
- }
-
- err.clear();
-
- io::FileInputStream input(file_path);
- if (input.HadError()) {
- context->GetDiagnostics()->Error(DiagMessage(file_path)
- << "failed to open file: " << input.GetError());
- return false;
- }
-
- // Try as a compiled file.
- ContainerReader reader(&input);
- if (reader.HadError()) {
- context->GetDiagnostics()->Error(DiagMessage(file_path)
- << "failed to read container: " << reader.GetError());
- return false;
- }
-
- printer.Println("AAPT2 Container (APC)");
- ContainerReaderEntry* entry;
- while ((entry = reader.Next()) != nullptr) {
- if (entry->Type() == ContainerEntryType::kResTable) {
- printer.Println("kResTable");
-
- pb::ResourceTable pb_table;
- if (!entry->GetResTable(&pb_table)) {
- context->GetDiagnostics()->Error(DiagMessage(file_path)
- << "failed to parse proto table: " << entry->GetError());
- continue;
- }
-
- ResourceTable table;
- err.clear();
- if (!DeserializeTableFromPb(pb_table, nullptr /*files*/, &table, &err)) {
- context->GetDiagnostics()->Error(DiagMessage(file_path)
- << "failed to parse table: " << err);
- continue;
- }
-
- printer.Indent();
- Debug::PrintTable(table, options.print_options, &printer);
- printer.Undent();
- } else if (entry->Type() == ContainerEntryType::kResFile) {
- printer.Println("kResFile");
- pb::internal::CompiledFile pb_compiled_file;
- off64_t offset;
- size_t length;
- if (!entry->GetResFileOffsets(&pb_compiled_file, &offset, &length)) {
- context->GetDiagnostics()->Error(
- DiagMessage(file_path) << "failed to parse compiled proto file: " << entry->GetError());
- continue;
- }
-
- ResourceFile file;
- std::string error;
- if (!DeserializeCompiledFileFromPb(pb_compiled_file, &file, &error)) {
- context->GetDiagnostics()->Warn(DiagMessage(file_path)
- << "failed to parse compiled file: " << error);
- continue;
- }
-
- printer.Indent();
- DumpCompiledFile(file, Source(file_path), offset, length, &printer);
- printer.Undent();
- }
- }
- return true;
-}
-
namespace {
class DumpContext : public IAaptContext {
@@ -299,17 +124,290 @@
} // namespace
-int DumpCommand::Action(const std::vector<std::string>& args) {
+// Use a smaller buffer so that there is less latency for dumping to stdout.
+constexpr size_t kStdOutBufferSize = 1024u;
+
+int DumpAPCCommand::Action(const std::vector<std::string>& args) {
DumpContext context;
- context.SetVerbose(verbose_);
- options_.print_options.show_sources = true;
- options_.print_options.show_values = !no_values_;
- for (const std::string& arg : args) {
- if (!TryDumpFile(&context, arg, options_)) {
- return 1;
+ DebugPrintTableOptions print_options;
+ print_options.show_sources = true;
+ print_options.show_values = !no_values_;
+
+ if (args.size() < 1) {
+ diag_->Error(DiagMessage() << "No dump container specified.");
+ return 1;
+ }
+
+ io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
+ Printer printer(&fout);
+
+ for (auto container : args) {
+ io::FileInputStream input(container);
+ if (input.HadError()) {
+ context.GetDiagnostics()->Error(DiagMessage(container)
+ << "failed to open file: " << input.GetError());
+ return false;
+ }
+
+ // Try as a compiled file.
+ ContainerReader reader(&input);
+ if (reader.HadError()) {
+ context.GetDiagnostics()->Error(DiagMessage(container)
+ << "failed to read container: " << reader.GetError());
+ return false;
+ }
+
+ printer.Println("AAPT2 Container (APC)");
+ ContainerReaderEntry* entry;
+ std::string error;
+ while ((entry = reader.Next()) != nullptr) {
+ if (entry->Type() == ContainerEntryType::kResTable) {
+ printer.Println("kResTable");
+
+ pb::ResourceTable pb_table;
+ if (!entry->GetResTable(&pb_table)) {
+ context.GetDiagnostics()->Error(DiagMessage(container)
+ << "failed to parse proto table: "
+ << entry->GetError());
+ continue;
+ }
+
+ ResourceTable table;
+ error.clear();
+ if (!DeserializeTableFromPb(pb_table, nullptr /*files*/, &table, &error)) {
+ context.GetDiagnostics()->Error(DiagMessage(container)
+ << "failed to parse table: " << error);
+ continue;
+ }
+
+ printer.Indent();
+ Debug::PrintTable(table, print_options, &printer);
+ printer.Undent();
+ } else if (entry->Type() == ContainerEntryType::kResFile) {
+ printer.Println("kResFile");
+ pb::internal::CompiledFile pb_compiled_file;
+ off64_t offset;
+ size_t length;
+ if (!entry->GetResFileOffsets(&pb_compiled_file, &offset, &length)) {
+ context.GetDiagnostics()->Error(
+ DiagMessage(container) << "failed to parse compiled proto file: "
+ << entry->GetError());
+ continue;
+ }
+
+ ResourceFile file;
+ if (!DeserializeCompiledFileFromPb(pb_compiled_file, &file, &error)) {
+ context.GetDiagnostics()->Warn(DiagMessage(container)
+ << "failed to parse compiled file: " << error);
+ continue;
+ }
+
+ printer.Indent();
+ DumpCompiledFile(file, Source(container), offset, length, &printer);
+ printer.Undent();
+ }
}
}
+
return 0;
}
+int DumpConfigsCommand::Action(const std::vector<std::string>& args) {
+ if (args.size() < 1) {
+ diag_->Error(DiagMessage() << "No dump apk specified.");
+ return 1;
+ }
+
+ auto loaded_apk = LoadedApk::LoadApkFromPath(args[0], diag_);
+ if (!loaded_apk) {
+ return 1;
+ }
+
+ io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
+ Printer printer(&fout);
+
+ // Comparison function used to order configurations
+ auto compare = [](ConfigDescription c1, ConfigDescription c2) -> bool {
+ return c1.compare(c2) < 0;
+ };
+
+ // Insert the configurations into a set in order to keep every configuarion seen
+ std::set<ConfigDescription, decltype(compare)> configs(compare);
+ for (auto& package : loaded_apk->GetResourceTable()->packages) {
+ for (auto& type : package->types) {
+ for (auto& entry : type->entries) {
+ for (auto& value : entry->values) {
+ configs.insert(value->config);
+ }
+ }
+ }
+ }
+
+ // Print the configurations in order
+ for (auto& config : configs) {
+ printer.Print(StringPrintf("%s\n", config.to_string().data()));
+ }
+
+ return 0;
+}
+
+int DumpStringsCommand::Action(const std::vector<std::string>& args) {
+ DumpContext context;
+ if (args.size() < 1) {
+ diag_->Error(DiagMessage() << "No dump apk specified.");
+ return 1;
+ }
+
+ io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
+ Printer printer(&fout);
+
+ for (auto apk : args) {
+ auto loaded_apk = LoadedApk::LoadApkFromPath(apk, diag_);
+ if (!loaded_apk) {
+ return 1;
+ }
+
+ // Load the run-time xml string pool using the flattened data
+ BigBuffer buffer(4096);
+ StringPool::FlattenUtf8(&buffer, loaded_apk->GetResourceTable()->string_pool,
+ context.GetDiagnostics());
+ auto data = buffer.to_string();
+ android::ResStringPool pool(data.data(), data.size(), false);
+ Debug::DumpResStringPool(&pool, &printer);
+ }
+
+ return 0;
+}
+
+int DumpTableCommand::Action(const std::vector<std::string>& args) {
+ if (args.size() < 1) {
+ diag_->Error(DiagMessage() << "No dump apk specified.");
+ return 1;
+ }
+
+ io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
+ Printer printer(&fout);
+
+ DebugPrintTableOptions print_options;
+ print_options.show_sources = true;
+ print_options.show_values = !no_values_;
+
+ for (auto apk : args) {
+ auto loaded_apk = LoadedApk::LoadApkFromPath(apk, diag_);
+ if (!loaded_apk) {
+ return 1;
+ }
+
+ if (loaded_apk->GetApkFormat()) {
+ printer.Println("Proto APK");
+ } else {
+ printer.Println("Binary APK");
+ }
+
+ Debug::PrintTable(*loaded_apk->GetResourceTable(), print_options, &printer);
+ }
+
+ return 0;
+}
+
+int DumpXmlTreeCommand::Action(const std::vector<std::string>& args) {
+ if (args.size() < 1) {
+ diag_->Error(DiagMessage() << "No dump apk specified");
+ return 1;
+ }
+
+ auto loaded_apk = LoadedApk::LoadApkFromPath(args[0], diag_);
+ if (!loaded_apk) {
+ return 1;
+ }
+
+ io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
+ Printer printer(&fout);
+
+ // Dump the xml tree of every passed in file
+ for (auto file : files_) {
+ auto xml = loaded_apk->LoadXml(file, diag_);
+ if (!xml) {
+ return 1;
+ }
+
+ Debug::DumpXml(*xml, &printer);
+ }
+
+ return 0;
+}
+
+int DumpXmlStringsCommand::Action(const std::vector<std::string>& args) {
+ DumpContext context;
+ if (args.size() < 1) {
+ diag_->Error(DiagMessage() << "No dump apk specified.");
+ return 1;
+ }
+
+ auto loaded_apk = LoadedApk::LoadApkFromPath(args[0], diag_);
+ if (!loaded_apk) {
+ return 1;
+ }
+
+ io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
+ Printer printer(&fout);
+
+ // Dump the xml strings of every passed in file
+ for (auto xml_file : files_) {
+ android::ResXMLTree tree;
+
+ if (loaded_apk->GetApkFormat() == kProto) {
+ auto xml = loaded_apk->LoadXml(xml_file, diag_);
+ if (!xml) {
+ return 1;
+ }
+
+ // Flatten the xml document to get a binary representation of the proto xml file
+ BigBuffer buffer(4096);
+ XmlFlattenerOptions options = {};
+ options.keep_raw_values = true;
+ XmlFlattener flattener(&buffer, options);
+ if (!flattener.Consume(&context, xml.get())) {
+ return 1;
+ }
+
+ // Load the run-time xml tree using the flattened data
+ std::string data = buffer.to_string();
+ tree.setTo(data.data(), data.size(), /** copyData */ true);
+
+ } else if (loaded_apk->GetApkFormat() == kBinary) {
+ io::IFile* file = loaded_apk->GetFileCollection()->FindFile(xml_file);
+ if (!file) {
+ diag_->Error(DiagMessage(xml_file) << "file '" << xml_file << "' not found in APK");
+ return 1;
+ }
+
+ std::unique_ptr<io::IData> data = file->OpenAsData();
+ if (!data) {
+ diag_->Error(DiagMessage() << "failed to open file");
+ return 1;
+ }
+
+ // Load the run-time xml tree from the file data
+ tree.setTo(data->data(), data->size(), /** copyData */ true);
+ }
+
+ Debug::DumpResStringPool(&tree.getStrings(), &printer);
+ }
+
+ return 0;
+}
+
+/** Preform no action because a subcommand is required. */
+int DumpCommand::Action(const std::vector<std::string>& args) {
+ if (args.size() == 0) {
+ diag_->Error(DiagMessage() << "no subcommand specified");
+ } else {
+ diag_->Error(DiagMessage() << "unknown subcommand '" << args[0] << "'");
+ }
+
+ Usage(&std::cerr);
+ return 1;
+}
+
} // namespace aapt
diff --git a/tools/aapt2/cmd/Dump.h b/tools/aapt2/cmd/Dump.h
index 4893c8b..03a4fba 100644
--- a/tools/aapt2/cmd/Dump.h
+++ b/tools/aapt2/cmd/Dump.h
@@ -22,33 +22,117 @@
namespace aapt {
-struct DumpOptions {
- DebugPrintTableOptions print_options;
-
- // The path to a file within an APK to dump.
- Maybe<std::string> file_to_dump_path;
-};
-
-class DumpCommand : public Command {
+/** Command the contents of files generated from the compilation stage. */
+class DumpAPCCommand : public Command {
public:
- DumpCommand() : Command("dump", "d") {
- SetDescription("Prints resource and manifest information.");
+ explicit DumpAPCCommand(IDiagnostics* diag) : Command("apc"), diag_(diag) {
+ SetDescription("Print the contents of the AAPT2 Container (APC) generated fom compilation.");
AddOptionalSwitch("--no-values", "Suppresses output of values when displaying resource tables.",
- &no_values_);
- AddOptionalFlag("--file", "Dumps the specified file from the APK passed as arg.",
- &options_.file_to_dump_path);
- AddOptionalSwitch("-v", "increase verbosity of output", &verbose_);
+ &no_values_);
+ AddOptionalSwitch("-v", "Enables verbose logging.", &verbose_);
}
int Action(const std::vector<std::string>& args) override;
private:
- DumpOptions options_;
-
+ IDiagnostics* diag_;
bool verbose_ = false;
bool no_values_ = false;
};
+/** Prints every configuration used by a resource in an APK. */
+class DumpConfigsCommand : public Command {
+ public:
+ explicit DumpConfigsCommand(IDiagnostics* diag) : Command("configurations"), diag_(diag) {
+ SetDescription("Print every configuration used by a resource in the APK.");
+ }
+
+ int Action(const std::vector<std::string>& args) override;
+
+ private:
+ IDiagnostics* diag_;
+};
+
+/** Prints the contents of the resource table string pool in the APK. */
+class DumpStringsCommand : public Command {
+ public:
+ explicit DumpStringsCommand(IDiagnostics* diag) : Command("strings"), diag_(diag) {
+ SetDescription("Print the contents of the resource table string pool in the APK.");
+ }
+
+ int Action(const std::vector<std::string>& args) override;
+
+ private:
+ IDiagnostics* diag_;
+};
+
+/** Prints the contents of the resource table from the APK. */
+class DumpTableCommand : public Command {
+ public:
+ explicit DumpTableCommand(IDiagnostics* diag) : Command("resources"), diag_(diag) {
+ SetDescription("Print the contents of the resource table from the APK.");
+ AddOptionalSwitch("--no-values", "Suppresses output of values when displaying resource tables.",
+ &no_values_);
+ AddOptionalSwitch("-v", "Enables verbose logging.", &verbose_);
+ }
+
+ int Action(const std::vector<std::string>& args) override;
+
+ private:
+ IDiagnostics* diag_;
+ bool verbose_ = false;
+ bool no_values_ = false;
+};
+
+/** Prints the string pool of a compiled xml in an APK. */
+class DumpXmlStringsCommand : public Command {
+public:
+ explicit DumpXmlStringsCommand(IDiagnostics* diag) : Command("xmlstrings"), diag_(diag) {
+ SetDescription("Print the string pool of a compiled xml in an APK.");
+ AddRequiredFlagList("--file", "A compiled xml file to print", &files_);
+ }
+
+ int Action(const std::vector<std::string>& args) override;
+
+private:
+ IDiagnostics* diag_;
+ std::vector<std::string> files_;
+};
+
+
+/** Prints the tree of a compiled xml in an APK. */
+class DumpXmlTreeCommand : public Command {
+ public:
+ explicit DumpXmlTreeCommand(IDiagnostics* diag) : Command("xmltree"), diag_(diag) {
+ SetDescription("Print the tree of a compiled xml in an APK.");
+ AddRequiredFlagList("--file", "A compiled xml file to print", &files_);
+ }
+
+ int Action(const std::vector<std::string>& args) override;
+
+ private:
+ IDiagnostics* diag_;
+ std::vector<std::string> files_;
+};
+
+/** The default dump command. Preforms no action because a subcommand is required. */
+class DumpCommand : public Command {
+ public:
+ explicit DumpCommand(IDiagnostics* diag) : Command("dump", "d"), diag_(diag) {
+ AddOptionalSubcommand(util::make_unique<DumpAPCCommand>(diag_));
+ AddOptionalSubcommand(util::make_unique<DumpConfigsCommand>(diag_));
+ AddOptionalSubcommand(util::make_unique<DumpStringsCommand>(diag_));
+ AddOptionalSubcommand(util::make_unique<DumpTableCommand>(diag_));
+ AddOptionalSubcommand(util::make_unique<DumpXmlStringsCommand>(diag_));
+ AddOptionalSubcommand(util::make_unique<DumpXmlTreeCommand>(diag_));
+ }
+
+ int Action(const std::vector<std::string>& args) override;
+
+ private:
+ IDiagnostics* diag_;
+};
+
}// namespace aapt
#endif //AAPT2_DUMP_H