diff options
Diffstat (limited to 'tools/aapt2/cmd/Link.cpp')
-rw-r--r-- | tools/aapt2/cmd/Link.cpp | 365 |
1 files changed, 196 insertions, 169 deletions
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 40d71a3429d0..55a4c438755c 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -25,7 +25,6 @@ #include "android-base/file.h" #include "android-base/stringprintf.h" #include "androidfw/StringPiece.h" -#include "google/protobuf/io/coded_stream.h" #include "AppInfo.h" #include "Debug.h" @@ -39,13 +38,14 @@ #include "compile/IdAssigner.h" #include "filter/ConfigFilter.h" #include "format/Archive.h" +#include "format/Container.h" #include "format/binary/BinaryResourceParser.h" #include "format/binary/TableFlattener.h" #include "format/binary/XmlFlattener.h" #include "format/proto/ProtoDeserialize.h" #include "format/proto/ProtoSerialize.h" -#include "io/BigBufferInputStream.h" -#include "io/FileInputStream.h" +#include "io/BigBufferStream.h" +#include "io/FileStream.h" #include "io/FileSystem.h" #include "io/Util.h" #include "io/ZipArchive.h" @@ -71,6 +71,14 @@ using ::android::base::StringPrintf; namespace aapt { +constexpr static const char kApkResourceTablePath[] = "resources.arsc"; +constexpr static const char kProtoResourceTablePath[] = "resources.pb"; + +enum class OutputFormat { + kApk, + kProto, +}; + struct LinkOptions { std::string output_path; std::string manifest_path; @@ -79,6 +87,7 @@ struct LinkOptions { std::vector<std::string> assets_dirs; bool output_to_directory = false; bool auto_add_overlay = false; + OutputFormat output_format = OutputFormat::kApk; // Java/Proguard options. Maybe<std::string> generate_java_class_path; @@ -253,26 +262,39 @@ class FeatureSplitSymbolTableDelegate : public DefaultSymbolTableDelegate { IAaptContext* context_; }; -static bool FlattenXml(IAaptContext* context, xml::XmlResource* xml_res, const StringPiece& path, - bool keep_raw_values, bool utf16, IArchiveWriter* writer) { - BigBuffer buffer(1024); - XmlFlattenerOptions options = {}; - options.keep_raw_values = keep_raw_values; - options.use_utf16 = utf16; - XmlFlattener flattener(&buffer, options); - if (!flattener.Consume(context, xml_res)) { - return false; - } - +static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml_res, + const StringPiece& path, bool keep_raw_values, bool utf16, + OutputFormat format, IArchiveWriter* writer) { if (context->IsVerbose()) { context->GetDiagnostics()->Note(DiagMessage(path) << "writing to archive (keep_raw_values=" << (keep_raw_values ? "true" : "false") << ")"); } - io::BigBufferInputStream input_stream(&buffer); - return io::CopyInputStreamToArchive(context, &input_stream, path.to_string(), - ArchiveEntry::kCompress, writer); + switch (format) { + case OutputFormat::kApk: { + BigBuffer buffer(1024); + XmlFlattenerOptions options = {}; + options.keep_raw_values = keep_raw_values; + options.use_utf16 = utf16; + XmlFlattener flattener(&buffer, options); + if (!flattener.Consume(context, &xml_res)) { + return false; + } + + io::BigBufferInputStream input_stream(&buffer); + return io::CopyInputStreamToArchive(context, &input_stream, path.to_string(), + ArchiveEntry::kCompress, writer); + } break; + + case OutputFormat::kProto: { + pb::XmlNode pb_node; + SerializeXmlResourceToPb(xml_res, &pb_node); + return io::CopyProtoToArchive(context, &pb_node, path.to_string(), ArchiveEntry::kCompress, + writer); + } break; + } + return false; } static std::unique_ptr<ResourceTable> LoadTableFromPb(const Source& source, const void* data, @@ -310,6 +332,7 @@ struct ResourceFileFlattenerOptions { bool keep_raw_values = false; bool do_not_compress_anything = false; bool update_proguard_spec = false; + OutputFormat output_format = OutputFormat::kApk; std::unordered_set<std::string> extensions_to_not_compress; }; @@ -467,6 +490,10 @@ std::vector<std::unique_ptr<xml::XmlResource>> ResourceFileFlattener::LinkAndVer << "linking " << src.path << " (" << doc->file.name << ")"); } + // First, strip out any tools namespace attributes. AAPT stripped them out early, which means + // that existing projects have out-of-date references which pass compilation. + xml::StripAndroidStudioAttributes(doc->root.get()); + XmlReferenceLinker xml_linker; if (!xml_linker.Consume(context_, doc)) { return {}; @@ -543,9 +570,9 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv file_op.config = config_value->config; file_op.file_to_copy = file; - const StringPiece src_path = file->GetSource().path; if (type->type != ResourceType::kRaw && - (util::EndsWith(src_path, ".xml.flat") || util::EndsWith(src_path, ".xml"))) { + (file_ref->type == ResourceFile::Type::kBinaryXml || + file_ref->type == ResourceFile::Type::kProtoXml)) { std::unique_ptr<io::IData> data = file->OpenAsData(); if (!data) { context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) @@ -553,11 +580,27 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv return false; } - file_op.xml_to_flatten = xml::Inflate(data->data(), data->size(), - context_->GetDiagnostics(), file->GetSource()); + if (file_ref->type == ResourceFile::Type::kProtoXml) { + pb::XmlNode pb_xml_node; + if (!pb_xml_node.ParseFromArray(data->data(), static_cast<int>(data->size()))) { + context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) + << "failed to parse proto xml"); + return false; + } - if (!file_op.xml_to_flatten) { - return false; + std::string error; + file_op.xml_to_flatten = DeserializeXmlResourceFromPb(pb_xml_node, &error); + if (file_op.xml_to_flatten == nullptr) { + context_->GetDiagnostics()->Error(DiagMessage(file->GetSource()) + << "failed to deserialize proto xml: " << error); + return false; + } + } else { + file_op.xml_to_flatten = xml::Inflate(data->data(), data->size(), + context_->GetDiagnostics(), file->GetSource()); + if (file_op.xml_to_flatten == nullptr) { + return false; + } } file_op.xml_to_flatten->file.config = config_value->config; @@ -607,8 +650,9 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv return false; } } - error |= !FlattenXml(context_, doc.get(), dst_path, options_.keep_raw_values, - false /*utf16*/, archive_writer); + + error |= !FlattenXml(context_, *doc, dst_path, options_.keep_raw_values, + false /*utf16*/, options_.output_format, archive_writer); } } else { error |= !io::CopyFileToArchive(context_, file_op.file_to_copy, file_op.dst_path, @@ -907,23 +951,29 @@ class LinkCommand { } } - bool FlattenTable(ResourceTable* table, IArchiveWriter* writer) { - BigBuffer buffer(1024); - TableFlattener flattener(options_.table_flattener_options, &buffer); - if (!flattener.Consume(context_, table)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed to flatten resource table"); - return false; - } + bool FlattenTable(ResourceTable* table, OutputFormat format, IArchiveWriter* writer) { + switch (format) { + case OutputFormat::kApk: { + BigBuffer buffer(1024); + TableFlattener flattener(options_.table_flattener_options, &buffer); + if (!flattener.Consume(context_, table)) { + context_->GetDiagnostics()->Error(DiagMessage() << "failed to flatten resource table"); + return false; + } - io::BigBufferInputStream input_stream(&buffer); - return io::CopyInputStreamToArchive(context_, &input_stream, "resources.arsc", - ArchiveEntry::kAlign, writer); - } + io::BigBufferInputStream input_stream(&buffer); + return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath, + ArchiveEntry::kAlign, writer); + } break; - bool FlattenTableToPb(ResourceTable* table, IArchiveWriter* writer) { - pb::ResourceTable pb_table; - SerializeTableToPb(*table, &pb_table); - return io::CopyProtoToArchive(context_, &pb_table, "resources.arsc.flat", 0, writer); + case OutputFormat::kProto: { + pb::ResourceTable pb_table; + SerializeTableToPb(*table, &pb_table); + return io::CopyProtoToArchive(context_, &pb_table, kProtoResourceTablePath, + ArchiveEntry::kCompress, writer); + } break; + } + return false; } bool WriteJavaFile(ResourceTable* table, const StringPiece& package_name_to_generate, @@ -1152,7 +1202,7 @@ class LinkCommand { } std::unique_ptr<ResourceTable> LoadTablePbFromCollection(io::IFileCollection* collection) { - io::IFile* file = collection->FindFile("resources.arsc.flat"); + io::IFile* file = collection->FindFile(kProtoResourceTablePath); if (!file) { return {}; } @@ -1201,11 +1251,7 @@ class LinkCommand { // Clear the package name, so as to make the resources look like they are coming from the // local package. pkg->name = ""; - if (override) { - result = table_merger_->MergeOverlay(Source(input), table.get(), collection.get()); - } else { - result = table_merger_->Merge(Source(input), table.get(), collection.get()); - } + result = table_merger_->Merge(Source(input), table.get(), override, collection.get()); } else { // This is the proper way to merge libraries, where the package name is @@ -1241,49 +1287,34 @@ class LinkCommand { return false; } - bool result = false; - if (override) { - result = table_merger_->MergeOverlay(file->GetSource(), table.get()); - } else { - result = table_merger_->Merge(file->GetSource(), table.get()); - } - return result; + return table_merger_->Merge(file->GetSource(), table.get(), override); } - bool MergeCompiledFile(io::IFile* file, ResourceFile* file_desc, bool override) { + bool MergeCompiledFile(const ResourceFile& compiled_file, io::IFile* file, bool override) { if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note(DiagMessage() << "merging '" << file_desc->name - << "' from compiled file " - << file->GetSource()); - } - - bool result = false; - if (override) { - result = table_merger_->MergeFileOverlay(*file_desc, file); - } else { - result = table_merger_->MergeFile(*file_desc, file); + context_->GetDiagnostics()->Note(DiagMessage() + << "merging '" << compiled_file.name + << "' from compiled file " << compiled_file.source); } - if (!result) { + if (!table_merger_->MergeFile(compiled_file, override, file)) { return false; } // Add the exports of this file to the table. - for (SourcedResourceName& exported_symbol : file_desc->exported_symbols) { - if (exported_symbol.name.package.empty()) { - exported_symbol.name.package = context_->GetCompilationPackage(); + for (const SourcedResourceName& exported_symbol : compiled_file.exported_symbols) { + ResourceName res_name = exported_symbol.name; + if (res_name.package.empty()) { + res_name.package = context_->GetCompilationPackage(); } - ResourceNameRef res_name = exported_symbol.name; - - Maybe<ResourceName> mangled_name = - context_->GetNameMangler()->MangleName(exported_symbol.name); + Maybe<ResourceName> mangled_name = context_->GetNameMangler()->MangleName(res_name); if (mangled_name) { res_name = mangled_name.value(); } std::unique_ptr<Id> id = util::make_unique<Id>(); - id->SetSource(file_desc->source.WithLine(exported_symbol.line)); + id->SetSource(compiled_file.source.WithLine(exported_symbol.line)); bool result = final_table_.AddResourceAllowMangled( res_name, ConfigDescription::DefaultConfig(), std::string(), std::move(id), context_->GetDiagnostics()); @@ -1294,15 +1325,11 @@ class LinkCommand { return true; } - /** - * Takes a path to load as a ZIP file and merges the files within into the - * master ResourceTable. - * If override is true, conflicting resources are allowed to override each - * other, in order of last seen. - * - * An io::IFileCollection is created from the ZIP file and added to the set of - * io::IFileCollections that are open. - */ + // Takes a path to load as a ZIP file and merges the files within into the master ResourceTable. + // If override is true, conflicting resources are allowed to override each other, in order of last + // seen. + // An io::IFileCollection is created from the ZIP file and added to the set of + // io::IFileCollections that are open. bool MergeArchive(const std::string& input, bool override) { if (context_->IsVerbose()) { context_->GetDiagnostics()->Note(DiagMessage() << "merging archive " << input); @@ -1328,18 +1355,11 @@ class LinkCommand { return !error; } - /** - * Takes a path to load and merge into the master ResourceTable. If override - * is true, - * conflicting resources are allowed to override each other, in order of last - * seen. - * - * If the file path ends with .flata, .jar, .jack, or .zip the file is treated - * as ZIP archive - * and the files within are merged individually. - * - * Otherwise the files is processed on its own. - */ + // Takes a path to load and merge into the master ResourceTable. If override is true, + // conflicting resources are allowed to override each other, in order of last seen. + // If the file path ends with .flata, .jar, .jack, or .zip the file is treated + // as ZIP archive and the files within are merged individually. + // Otherwise the file is processed on its own. bool MergePath(const std::string& path, bool override) { if (util::EndsWith(path, ".flata") || util::EndsWith(path, ".jar") || util::EndsWith(path, ".jack") || util::EndsWith(path, ".zip")) { @@ -1352,82 +1372,87 @@ class LinkCommand { return MergeFile(file, override); } - /** - * Takes a file to load and merge into the master ResourceTable. If override - * is true, - * conflicting resources are allowed to override each other, in order of last - * seen. - * - * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and - * merged into the - * master ResourceTable. If the file ends with .flat, then it is treated like - * a compiled file - * and the header data is read and merged into the final ResourceTable. - * - * All other file types are ignored. This is because these files could be - * coming from a zip, - * where we could have other files like classes.dex. - */ + // Takes an AAPT Container file (.apc/.flat) to load and merge into the master ResourceTable. + // If override is true, conflicting resources are allowed to override each other, in order of last + // seen. + // All other file types are ignored. This is because these files could be coming from a zip, + // where we could have other files like classes.dex. bool MergeFile(io::IFile* file, bool override) { const Source& src = file->GetSource(); - if (util::EndsWith(src.path, ".arsc.flat")) { - return MergeResourceTable(file, override); - - } else if (util::EndsWith(src.path, ".flat")) { - // Try opening the file and looking for an Export header. - std::unique_ptr<io::IData> data = file->OpenAsData(); - if (!data) { - context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to open"); - return false; - } - CompiledFileInputStream input_stream(data->data(), data->size()); - uint32_t num_files = 0; - if (!input_stream.ReadLittleEndian32(&num_files)) { - context_->GetDiagnostics()->Error(DiagMessage(src) << "failed read num files"); - return false; + if (util::EndsWith(src.path, ".xml") || util::EndsWith(src.path, ".png")) { + // Since AAPT compiles these file types and appends .flat to them, seeing + // their raw extensions is a sign that they weren't compiled. + const StringPiece file_type = util::EndsWith(src.path, ".xml") ? "XML" : "PNG"; + context_->GetDiagnostics()->Error(DiagMessage(src) << "uncompiled " << file_type + << " file passed as argument. Must be " + "compiled first into .flat file."); + return false; + } else if (!util::EndsWith(src.path, ".apc") && !util::EndsWith(src.path, ".flat")) { + if (context_->IsVerbose()) { + context_->GetDiagnostics()->Warn(DiagMessage(src) << "ignoring unrecognized file"); + return true; } + } + + std::unique_ptr<io::InputStream> input_stream = file->OpenInputStream(); + if (input_stream == nullptr) { + context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to open file"); + return false; + } + + if (input_stream->HadError()) { + context_->GetDiagnostics()->Error(DiagMessage(src) + << "failed to open file: " << input_stream->GetError()); + return false; + } + + ContainerReaderEntry* entry; + ContainerReader reader(input_stream.get()); + while ((entry = reader.Next()) != nullptr) { + if (entry->Type() == ContainerEntryType::kResTable) { + pb::ResourceTable pb_table; + if (!entry->GetResTable(&pb_table)) { + context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to read resource table: " + << entry->GetError()); + return false; + } - for (uint32_t i = 0; i < num_files; i++) { - pb::internal::CompiledFile compiled_file; - if (!input_stream.ReadCompiledFile(&compiled_file)) { + ResourceTable table; + std::string error; + if (!DeserializeTableFromPb(pb_table, &table, &error)) { context_->GetDiagnostics()->Error(DiagMessage(src) - << "failed to read compiled file header"); + << "failed to deserialize resource table: " << error); return false; } - uint64_t offset, len; - if (!input_stream.ReadDataMetaData(&offset, &len)) { - context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to read data meta data"); + if (!table_merger_->Merge(src, &table, override)) { + context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to merge resource table"); + return false; + } + } else if (entry->Type() == ContainerEntryType::kResFile) { + pb::internal::CompiledFile pb_compiled_file; + off64_t offset; + size_t len; + if (!entry->GetResFileOffsets(&pb_compiled_file, &offset, &len)) { + context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to get resource file: " + << entry->GetError()); return false; } ResourceFile resource_file; std::string error; - if (!DeserializeCompiledFileFromPb(compiled_file, &resource_file, &error)) { + if (!DeserializeCompiledFileFromPb(pb_compiled_file, &resource_file, &error)) { context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to read compiled header: " << error); return false; } - if (!MergeCompiledFile(file->CreateFileSegment(offset, len), &resource_file, override)) { + if (!MergeCompiledFile(resource_file, file->CreateFileSegment(offset, len), override)) { return false; } } - return true; - } else if (util::EndsWith(src.path, ".xml") || util::EndsWith(src.path, ".png")) { - // Since AAPT compiles these file types and appends .flat to them, seeing - // their raw extensions is a sign that they weren't compiled. - const StringPiece file_type = util::EndsWith(src.path, ".xml") ? "XML" : "PNG"; - context_->GetDiagnostics()->Error(DiagMessage(src) << "uncompiled " << file_type - << " file passed as argument. Must be " - "compiled first into .flat file."); - return false; } - - // Ignore non .flat files. This could be classes.dex or something else that - // happens - // to be in an archive. return true; } @@ -1471,15 +1496,13 @@ class LinkCommand { return true; } - /** - * Writes the AndroidManifest, ResourceTable, and all XML files referenced by - * the ResourceTable to the IArchiveWriter. - */ + // Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable + // to the IArchiveWriter. bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest, ResourceTable* table) { const bool keep_raw_values = context_->GetPackageType() == PackageType::kStaticLib; - bool result = FlattenXml(context_, manifest, "AndroidManifest.xml", keep_raw_values, - true /*utf16*/, writer); + bool result = FlattenXml(context_, *manifest, "AndroidManifest.xml", keep_raw_values, + true /*utf16*/, options_.output_format, writer); if (!result) { return false; } @@ -1494,6 +1517,7 @@ class LinkCommand { file_flattener_options.no_xml_namespaces = options_.no_xml_namespaces; file_flattener_options.update_proguard_spec = static_cast<bool>(options_.generate_proguard_rules_path); + file_flattener_options.output_format = options_.output_format; ResourceFileFlattener file_flattener(file_flattener_options, context_, keep_set); @@ -1502,15 +1526,9 @@ class LinkCommand { return false; } - if (context_->GetPackageType() == PackageType::kStaticLib) { - if (!FlattenTableToPb(table, writer)) { - return false; - } - } else { - if (!FlattenTable(table, writer)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resources.arsc"); - return false; - } + if (!FlattenTable(table, options_.output_format, writer)) { + context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resource table"); + return false; } return true; } @@ -1874,6 +1892,7 @@ int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) { bool verbose = false; bool shared_lib = false; bool static_lib = false; + bool proto_format = false; Maybe<std::string> stable_id_file_path; std::vector<std::string> split_args; Flags flags = @@ -1954,6 +1973,10 @@ int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) { .OptionalSwitch("--shared-lib", "Generates a shared Android runtime library.", &shared_lib) .OptionalSwitch("--static-lib", "Generate a static Android library.", &static_lib) + .OptionalSwitch("--proto-format", + "Generates compiled resources in Protobuf format.\n" + "Suitable as input to the bundle tool for generating an App Bundle.", + &proto_format) .OptionalSwitch("--no-static-lib-packages", "Merge all library resources under the app's package.", &options.no_static_lib_packages) @@ -2040,21 +2063,25 @@ int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics) { context.SetVerbose(verbose); } - if (shared_lib && static_lib) { - context.GetDiagnostics()->Error(DiagMessage() - << "only one of --shared-lib and --static-lib can be defined"); + if (int{shared_lib} + int{static_lib} + int{proto_format} > 1) { + context.GetDiagnostics()->Error( + DiagMessage() + << "only one of --shared-lib, --static-lib, or --proto_format can be defined"); return 1; } + // The default build type. + context.SetPackageType(PackageType::kApp); + context.SetPackageId(kAppPackageId); + if (shared_lib) { context.SetPackageType(PackageType::kSharedLib); context.SetPackageId(0x00); } else if (static_lib) { context.SetPackageType(PackageType::kStaticLib); - context.SetPackageId(kAppPackageId); - } else { - context.SetPackageType(PackageType::kApp); - context.SetPackageId(kAppPackageId); + options.output_format = OutputFormat::kProto; + } else if (proto_format) { + options.output_format = OutputFormat::kProto; } if (package_id) { |