diff options
Diffstat (limited to 'tools')
161 files changed, 5402 insertions, 2734 deletions
diff --git a/tools/aapt/AaptXml.cpp b/tools/aapt/AaptXml.cpp index b04a55d91b9c..6801a4ec7325 100644 --- a/tools/aapt/AaptXml.cpp +++ b/tools/aapt/AaptXml.cpp @@ -99,24 +99,40 @@ String8 getResolvedAttribute(const ResTable& resTable, const ResXMLTree& tree, if (idx < 0) { return String8(); } + Res_value value; - if (tree.getAttributeValue(idx, &value) != NO_ERROR) { - if (value.dataType == Res_value::TYPE_STRING) { - size_t len; - const char16_t* str = tree.getAttributeStringValue(idx, &len); - return str ? String8(str, len) : String8(); + if (tree.getAttributeValue(idx, &value) == BAD_TYPE) { + if (outError != NULL) { + *outError = "attribute value is corrupt"; } - resTable.resolveReference(&value, 0); - if (value.dataType != Res_value::TYPE_STRING) { - if (outError != NULL) { - *outError = "attribute is not a string value"; - } - return String8(); + return String8(); + } + + // Check if the string is inline in the XML. + if (value.dataType == Res_value::TYPE_STRING) { + size_t len; + const char16_t* str = tree.getAttributeStringValue(idx, &len); + return str ? String8(str, len) : String8(); + } + + // Resolve the reference if there is one. + ssize_t block = resTable.resolveReference(&value, 0); + if (block < 0) { + if (outError != NULL) { + *outError = "attribute value reference does not exist"; + } + return String8(); + } + + if (value.dataType != Res_value::TYPE_STRING) { + if (outError != NULL) { + *outError = "attribute is not a string value"; } + return String8(); } + size_t len; - const Res_value* value2 = &value; - const char16_t* str = resTable.valueToString(value2, 0, NULL, &len); + const char16_t* str = resTable.valueToString(&value, static_cast<size_t>(block), NULL, &len); return str ? String8(str, len) : String8(); } diff --git a/tools/aapt/SdkConstants.h b/tools/aapt/SdkConstants.h index bf56ec024699..b982d0d2ea63 100644 --- a/tools/aapt/SdkConstants.h +++ b/tools/aapt/SdkConstants.h @@ -43,6 +43,7 @@ enum { SDK_NOUGAT_MR1 = 25, SDK_O = 26, SDK_O_MR1 = 27, + SDK_P = 10000, // STOPSHIP Replace with the real version. }; #endif // H_AAPT_SDK_CONSTANTS diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index 86f7c31b665d..43918da75ad4 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -88,7 +88,9 @@ cc_library_host_static { "flatten/XmlFlattener.cpp", "io/BigBufferStreams.cpp", "io/File.cpp", + "io/FileInputStream.cpp", "io/FileSystem.cpp", + "io/StringInputStream.cpp", "io/Util.cpp", "io/ZipArchive.cpp", "link/AutoVersioner.cpp", @@ -100,6 +102,7 @@ cc_library_host_static { "link/XmlCompatVersioner.cpp", "link/XmlNamespaceRemover.cpp", "link/XmlReferenceLinker.cpp", + "optimize/MultiApkGenerator.cpp", "optimize/ResourceDeduper.cpp", "optimize/VersionCollapser.cpp", "process/SymbolTable.cpp", @@ -136,7 +139,8 @@ cc_library_host_static { "xml/XmlDom.cpp", "xml/XmlPullParser.cpp", "xml/XmlUtil.cpp", - "Format.proto", + "Resources.proto", + "ResourcesInternal.proto", ], proto: { export_proto_headers: true, @@ -160,6 +164,7 @@ cc_library_host_shared { cc_test_host { name: "aapt2_tests", srcs: [ + "test/Builders.cpp", "test/Common.cpp", "**/*_test.cpp", ], diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp index 7ff0c7227c9c..a9278c136cff 100644 --- a/tools/aapt2/ConfigDescription.cpp +++ b/tools/aapt2/ConfigDescription.cpp @@ -70,7 +70,7 @@ static bool parseMcc(const char* name, ResTable_config* out) { static bool parseMnc(const char* name, ResTable_config* out) { if (strcmp(name, kWildcardName) == 0) { - if (out) out->mcc = 0; + if (out) out->mnc = 0; return true; } const char* c = name; @@ -967,8 +967,6 @@ bool ConfigDescription::ConflictsWith(const ConfigDescription& o) const { o.screenLayout & MASK_LAYOUTDIR) || !pred(screenLayout & MASK_SCREENLONG, o.screenLayout & MASK_SCREENLONG) || - !pred(screenLayout & MASK_UI_MODE_TYPE, - o.screenLayout & MASK_UI_MODE_TYPE) || !pred(uiMode & MASK_UI_MODE_TYPE, o.uiMode & MASK_UI_MODE_TYPE) || !pred(uiMode & MASK_UI_MODE_NIGHT, o.uiMode & MASK_UI_MODE_NIGHT) || !pred(screenLayout2 & MASK_SCREENROUND, diff --git a/tools/aapt2/ConfigDescription_test.cpp b/tools/aapt2/ConfigDescription_test.cpp index 14a565624e01..1f351bf7481d 100644 --- a/tools/aapt2/ConfigDescription_test.cpp +++ b/tools/aapt2/ConfigDescription_test.cpp @@ -140,4 +140,16 @@ TEST(ConfigDescriptionTest, ParseVrAttribute) { EXPECT_EQ(std::string("vrheadset-v26"), config.toString().string()); } +TEST(ConfigDescriptionTest, RangeQualifiersDoNotConflict) { + using test::ParseConfigOrDie; + + EXPECT_FALSE(ParseConfigOrDie("large").ConflictsWith(ParseConfigOrDie("normal-land"))); + EXPECT_FALSE(ParseConfigOrDie("long-hdpi").ConflictsWith(ParseConfigOrDie("xhdpi"))); + EXPECT_FALSE(ParseConfigOrDie("sw600dp").ConflictsWith(ParseConfigOrDie("sw700dp"))); + EXPECT_FALSE(ParseConfigOrDie("v11").ConflictsWith(ParseConfigOrDie("v21"))); + EXPECT_FALSE(ParseConfigOrDie("h600dp").ConflictsWith(ParseConfigOrDie("h300dp"))); + EXPECT_FALSE(ParseConfigOrDie("w400dp").ConflictsWith(ParseConfigOrDie("w300dp"))); + EXPECT_FALSE(ParseConfigOrDie("600x400").ConflictsWith(ParseConfigOrDie("300x200"))); +} + } // namespace aapt diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index b872ebbeb159..dba9cb5c2803 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -33,6 +33,8 @@ namespace aapt { +namespace { + class PrintVisitor : public ValueVisitor { public: using ValueVisitor::Visit; @@ -88,9 +90,13 @@ class PrintVisitor : public ValueVisitor { } } - void Visit(Array* array) override { array->Print(&std::cout); } + void Visit(Array* array) override { + array->Print(&std::cout); + } - void Visit(Plural* plural) override { plural->Print(&std::cout); } + void Visit(Plural* plural) override { + plural->Print(&std::cout); + } void Visit(Styleable* styleable) override { std::cout << "(styleable)"; @@ -110,11 +116,14 @@ class PrintVisitor : public ValueVisitor { } } - void VisitItem(Item* item) override { item->Print(&std::cout); } + void VisitItem(Item* item) override { + item->Print(&std::cout); + } }; -void Debug::PrintTable(ResourceTable* table, - const DebugPrintTableOptions& options) { +} // namespace + +void Debug::PrintTable(ResourceTable* table, const DebugPrintTableOptions& options) { PrintVisitor visitor; for (auto& package : table->packages) { @@ -148,10 +157,9 @@ void Debug::PrintTable(ResourceTable* table, } for (const ResourceEntry* entry : sorted_entries) { - ResourceId id(package->id ? package->id.value() : uint8_t(0), - type->id ? type->id.value() : uint8_t(0), - entry->id ? entry->id.value() : uint16_t(0)); - ResourceName name(package->name, type->type, entry->name); + const ResourceId id(package->id.value_or_default(0), type->id.value_or_default(0), + entry->id.value_or_default(0)); + const ResourceName name(package->name, type->type, entry->name); std::cout << " spec resource " << id << " " << name; switch (entry->symbol_status.state) { @@ -180,16 +188,14 @@ void Debug::PrintTable(ResourceTable* table, } } -static size_t GetNodeIndex(const std::vector<ResourceName>& names, - const ResourceName& name) { +static size_t GetNodeIndex(const std::vector<ResourceName>& names, const ResourceName& name) { auto iter = std::lower_bound(names.begin(), names.end(), name); CHECK(iter != names.end()); CHECK(*iter == name); return std::distance(names.begin(), iter); } -void Debug::PrintStyleGraph(ResourceTable* table, - const ResourceName& target_style) { +void Debug::PrintStyleGraph(ResourceTable* table, const ResourceName& target_style) { std::map<ResourceName, std::set<ResourceName>> graph; std::queue<ResourceName> styles_to_visit; @@ -223,8 +229,7 @@ void Debug::PrintStyleGraph(ResourceTable* table, std::cout << "digraph styles {\n"; for (const auto& name : names) { - std::cout << " node_" << GetNodeIndex(names, name) << " [label=\"" << name - << "\"];\n"; + std::cout << " node_" << GetNodeIndex(names, name) << " [label=\"" << name << "\"];\n"; } for (const auto& entry : graph) { @@ -243,8 +248,7 @@ void Debug::PrintStyleGraph(ResourceTable* table, void Debug::DumpHex(const void* data, size_t len) { const uint8_t* d = (const uint8_t*)data; for (size_t i = 0; i < len; i++) { - std::cerr << std::hex << std::setfill('0') << std::setw(2) << (uint32_t)d[i] - << " "; + std::cerr << std::hex << std::setfill('0') << std::setw(2) << (uint32_t)d[i] << " "; if (i % 8 == 7) { std::cerr << "\n"; } @@ -262,8 +266,15 @@ class XmlPrinter : public xml::Visitor { using xml::Visitor::Visit; void Visit(xml::Element* el) override { - std::cerr << prefix_; - std::cerr << "E: "; + const size_t previous_size = prefix_.size(); + + for (const xml::NamespaceDecl& decl : el->namespace_decls) { + std::cerr << prefix_ << "N: " << decl.prefix << "=" << decl.uri + << " (line=" << decl.line_number << ")\n"; + prefix_ += " "; + } + + std::cerr << prefix_ << "E: "; if (!el->namespace_uri.empty()) { std::cerr << el->namespace_uri << ":"; } @@ -280,29 +291,22 @@ class XmlPrinter : public xml::Visitor { std::cerr << "(" << attr.compiled_attribute.value().id.value_or_default(ResourceId(0x0)) << ")"; } - std::cerr << "=" << attr.value << "\n"; + std::cerr << "="; + if (attr.compiled_value != nullptr) { + std::cerr << *attr.compiled_value; + } else { + std::cerr << attr.value; + } + std::cerr << "\n"; } - const size_t previous_size = prefix_.size(); prefix_ += " "; xml::Visitor::Visit(el); prefix_.resize(previous_size); } - void Visit(xml::Namespace* ns) override { - std::cerr << prefix_; - std::cerr << "N: " << ns->namespace_prefix << "=" << ns->namespace_uri - << " (line=" << ns->line_number << ")\n"; - - const size_t previous_size = prefix_.size(); - prefix_ += " "; - xml::Visitor::Visit(ns); - prefix_.resize(previous_size); - } - void Visit(xml::Text* text) override { - std::cerr << prefix_; - std::cerr << "T: '" << text->text << "'\n"; + std::cerr << prefix_ << "T: '" << text->text << "'\n"; } private: diff --git a/tools/aapt2/Format.proto b/tools/aapt2/Format.proto deleted file mode 100644 index 870b735f70a7..000000000000 --- a/tools/aapt2/Format.proto +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -syntax = "proto2"; - -option optimize_for = LITE_RUNTIME; - -package aapt.pb; - -message ConfigDescription { - optional bytes data = 1; - optional string product = 2; -} - -message StringPool { - optional bytes data = 1; -} - -message CompiledFile { - message Symbol { - optional string resource_name = 1; - optional uint32 line_no = 2; - } - - optional string resource_name = 1; - optional ConfigDescription config = 2; - optional string source_path = 3; - repeated Symbol exported_symbols = 4; -} - -message ResourceTable { - optional StringPool string_pool = 1; - optional StringPool source_pool = 2; - optional StringPool symbol_pool = 3; - repeated Package packages = 4; -} - -message Package { - optional uint32 package_id = 1; - optional string package_name = 2; - repeated Type types = 3; -} - -message Type { - optional uint32 id = 1; - optional string name = 2; - repeated Entry entries = 3; -} - -message SymbolStatus { - enum Visibility { - Unknown = 0; - Private = 1; - Public = 2; - } - optional Visibility visibility = 1; - optional Source source = 2; - optional string comment = 3; - optional bool allow_new = 4; -} - -message Entry { - optional uint32 id = 1; - optional string name = 2; - optional SymbolStatus symbol_status = 3; - repeated ConfigValue config_values = 4; -} - -message ConfigValue { - optional ConfigDescription config = 1; - optional Value value = 2; -} - -message Source { - optional uint32 path_idx = 1; - optional uint32 line_no = 2; - optional uint32 col_no = 3; -} - -message Reference { - enum Type { - Ref = 0; - Attr = 1; - } - optional Type type = 1; - optional uint32 id = 2; - optional uint32 symbol_idx = 3; - optional bool private = 4; -} - -message Id { -} - -message String { - optional uint32 idx = 1; -} - -message RawString { - optional uint32 idx = 1; -} - -message FileReference { - optional uint32 path_idx = 1; -} - -message Primitive { - optional uint32 type = 1; - optional uint32 data = 2; -} - -message Attribute { - message Symbol { - optional Source source = 1; - optional string comment = 2; - optional Reference name = 3; - optional uint32 value = 4; - } - optional uint32 format_flags = 1; - optional int32 min_int = 2; - optional int32 max_int = 3; - repeated Symbol symbols = 4; -} - -message Style { - message Entry { - optional Source source = 1; - optional string comment = 2; - optional Reference key = 3; - optional Item item = 4; - } - - optional Reference parent = 1; - optional Source parent_source = 2; - repeated Entry entries = 3; -} - -message Styleable { - message Entry { - optional Source source = 1; - optional string comment = 2; - optional Reference attr = 3; - } - repeated Entry entries = 1; -} - -message Array { - message Entry { - optional Source source = 1; - optional string comment = 2; - optional Item item = 3; - } - repeated Entry entries = 1; -} - -message Plural { - enum Arity { - Zero = 0; - One = 1; - Two = 2; - Few = 3; - Many = 4; - Other = 5; - } - - message Entry { - optional Source source = 1; - optional string comment = 2; - optional Arity arity = 3; - optional Item item = 4; - } - repeated Entry entries = 1; -} - -message Item { - optional Reference ref = 1; - optional String str = 2; - optional RawString raw_str = 3; - optional FileReference file = 4; - optional Id id = 5; - optional Primitive prim = 6; -} - -message CompoundValue { - optional Attribute attr = 1; - optional Style style = 2; - optional Styleable styleable = 3; - optional Array array = 4; - optional Plural plural = 5; -} - -message Value { - optional Source source = 1; - optional string comment = 2; - optional bool weak = 3; - - optional Item item = 4; - optional CompoundValue compound_value = 5; -} diff --git a/tools/aapt2/LoadedApk.cpp b/tools/aapt2/LoadedApk.cpp index 7e5efa15f61b..b80780e33dfa 100644 --- a/tools/aapt2/LoadedApk.cpp +++ b/tools/aapt2/LoadedApk.cpp @@ -58,14 +58,15 @@ std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(IAaptContext* context, bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options, IArchiveWriter* writer) { FilterChain empty; - return WriteToArchive(context, options, &empty, writer); + return WriteToArchive(context, table_.get(), options, &empty, writer); } -bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options, - FilterChain* filters, IArchiveWriter* writer) { +bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table, + const TableFlattenerOptions& options, FilterChain* filters, + IArchiveWriter* writer) { std::set<std::string> referenced_resources; // List the files being referenced in the resource table. - for (auto& pkg : table_->packages) { + for (auto& pkg : split_table->packages) { for (auto& type : pkg->types) { for (auto& entry : type->entries) { for (auto& config_value : entry->values) { @@ -84,7 +85,7 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOption std::string path = file->GetSource().path; // The name of the path has the format "<zip-file-name>@<path-to-file>". - path = path.substr(path.find("@") + 1); + path = path.substr(path.find('@') + 1); // Skip resources that are not referenced if requested. if (path.find("res/") == 0 && referenced_resources.find(path) == referenced_resources.end()) { @@ -108,7 +109,7 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOption // TODO(adamlesinski): How to determine if there were sparse entries (and if to encode // with sparse entries) b/35389232. TableFlattener flattener(options, &buffer); - if (!flattener.Consume(context, table_.get())) { + if (!flattener.Consume(context, split_table)) { return false; } diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h index 8aa9674aa2ed..dacd0c2130a9 100644 --- a/tools/aapt2/LoadedApk.h +++ b/tools/aapt2/LoadedApk.h @@ -47,16 +47,17 @@ class LoadedApk { * Writes the APK on disk at the given path, while also removing the resource * files that are not referenced in the resource table. */ - bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options, - IArchiveWriter* writer); + virtual bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options, + IArchiveWriter* writer); /** * Writes the APK on disk at the given path, while also removing the resource * files that are not referenced in the resource table. The provided filter * chain is applied to each entry in the APK file. */ - bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options, - FilterChain* filters, IArchiveWriter* writer); + virtual bool WriteToArchive(IAaptContext* context, ResourceTable* split_table, + const TableFlattenerOptions& options, FilterChain* filters, + IArchiveWriter* writer); static std::unique_ptr<LoadedApk> LoadApkFromPath(IAaptContext* context, const android::StringPiece& path); diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index c5d38abcdf71..36ab30c7fb6e 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -14,12 +14,26 @@ * limitations under the License. */ +#ifdef _WIN32 +// clang-format off +#include <windows.h> +#include <shellapi.h> +// clang-format on +#endif + #include <iostream> #include <vector> +#include "android-base/stringprintf.h" +#include "android-base/utf8.h" #include "androidfw/StringPiece.h" #include "Diagnostics.h" +#include "util/Files.h" +#include "util/Util.h" + +using ::android::StringPiece; +using ::android::base::StringPrintf; namespace aapt { @@ -27,54 +41,136 @@ namespace aapt { static const char* sMajorVersion = "2"; // Update minor version whenever a feature or flag is added. -static const char* sMinorVersion = "18"; +static const char* sMinorVersion = "19"; -int PrintVersion() { - std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "." - << sMinorVersion << std::endl; - return 0; +static void PrintVersion() { + std::cerr << StringPrintf("Android Asset Packaging Tool (aapt) %s:%s", sMajorVersion, + sMinorVersion) + << std::endl; +} + +static void PrintUsage() { + std::cerr << "\nusage: aapt2 [compile|link|dump|diff|optimize|version] ..." << std::endl; } -extern int Compile(const std::vector<android::StringPiece>& args, IDiagnostics* diagnostics); -extern int Link(const std::vector<android::StringPiece>& args, IDiagnostics* diagnostics); -extern int Dump(const std::vector<android::StringPiece>& args); -extern int Diff(const std::vector<android::StringPiece>& args); -extern int Optimize(const std::vector<android::StringPiece>& args); +extern int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics); +extern int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics); +extern int Dump(const std::vector<StringPiece>& args); +extern int Diff(const std::vector<StringPiece>& args); +extern int Optimize(const std::vector<StringPiece>& args); -} // namespace aapt +static int ExecuteCommand(const StringPiece& command, const std::vector<StringPiece>& args, + IDiagnostics* diagnostics) { + if (command == "compile" || command == "c") { + return Compile(args, diagnostics); + } else if (command == "link" || command == "l") { + return Link(args, diagnostics); + } else if (command == "dump" || command == "d") { + return Dump(args); + } else if (command == "diff") { + return Diff(args); + } else if (command == "optimize") { + return Optimize(args); + } else if (command == "version") { + PrintVersion(); + return 0; + } + diagnostics->Error(DiagMessage() << "unknown command '" << command << "'"); + return -1; +} -int main(int argc, char** argv) { - if (argc >= 2) { - argv += 1; - argc -= 1; +static void RunDaemon(IDiagnostics* diagnostics) { + std::cout << "Ready" << std::endl; - std::vector<android::StringPiece> args; - for (int i = 1; i < argc; i++) { - args.push_back(argv[i]); + // Run in daemon mode. The first line of input is the command. This can be 'quit' which ends + // the daemon mode. Each subsequent line is a single parameter to the command. The end of a + // invocation is signaled by providing an empty line. At any point, an EOF signal or the + // command 'quit' will end the daemon mode. + while (true) { + std::vector<std::string> raw_args; + for (std::string line; std::getline(std::cin, line) && !line.empty();) { + raw_args.push_back(line); } - android::StringPiece command(argv[0]); - if (command == "compile" || command == "c") { - aapt::StdErrDiagnostics diagnostics; - return aapt::Compile(args, &diagnostics); - } else if (command == "link" || command == "l") { - aapt::StdErrDiagnostics diagnostics; - return aapt::Link(args, &diagnostics); - } else if (command == "dump" || command == "d") { - return aapt::Dump(args); - } else if (command == "diff") { - return aapt::Diff(args); - } else if (command == "optimize") { - return aapt::Optimize(args); - } else if (command == "version") { - return aapt::PrintVersion(); + if (!std::cin) { + break; } - std::cerr << "unknown command '" << command << "'\n"; - } else { + + // An empty command does nothing. + if (raw_args.empty()) { + continue; + } + + if (raw_args[0] == "quit") { + break; + } + + std::vector<StringPiece> args; + args.insert(args.end(), ++raw_args.begin(), raw_args.end()); + int ret = ExecuteCommand(raw_args[0], args, diagnostics); + if (ret != 0) { + std::cerr << "Error" << std::endl; + } + std::cerr << "Done" << std::endl; + } + std::cout << "Exiting daemon" << std::endl; +} + +} // namespace aapt + +int MainImpl(int argc, char** argv) { + if (argc < 2) { std::cerr << "no command specified\n"; + aapt::PrintUsage(); + return -1; } - std::cerr << "\nusage: aapt2 [compile|link|dump|diff|optimize|version] ..." - << std::endl; - return 1; + argv += 1; + argc -= 1; + + aapt::StdErrDiagnostics diagnostics; + + // Collect the arguments starting after the program name and command name. + std::vector<StringPiece> args; + for (int i = 1; i < argc; i++) { + args.push_back(argv[i]); + } + + const StringPiece command(argv[0]); + if (command != "daemon" && command != "m") { + // Single execution. + const int result = aapt::ExecuteCommand(command, args, &diagnostics); + if (result < 0) { + aapt::PrintUsage(); + } + return result; + } + + aapt::RunDaemon(&diagnostics); + return 0; +} + +int main(int argc, char** argv) { +#ifdef _WIN32 + LPWSTR* wide_argv = CommandLineToArgvW(GetCommandLineW(), &argc); + CHECK(wide_argv != nullptr) << "invalid command line parameters passed to process"; + + std::vector<std::string> utf8_args; + for (int i = 0; i < argc; i++) { + std::string utf8_arg; + if (!::android::base::WideToUTF8(wide_argv[i], &utf8_arg)) { + std::cerr << "error converting input arguments to UTF-8" << std::endl; + return 1; + } + utf8_args.push_back(std::move(utf8_arg)); + } + LocalFree(wide_argv); + + std::unique_ptr<char* []> utf8_argv(new char*[utf8_args.size()]); + for (int i = 0; i < argc; i++) { + utf8_argv[i] = const_cast<char*>(utf8_args[i].c_str()); + } + argv = utf8_argv.get(); +#endif + return MainImpl(argc, argv); } diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h index 1305a4cf0710..f1aad29a5c58 100644 --- a/tools/aapt2/NameMangler.h +++ b/tools/aapt2/NameMangler.h @@ -69,8 +69,7 @@ class NameMangler { * The mangled name should contain symbols that are illegal to define in XML, * so that there will never be name mangling collisions. */ - static std::string MangleEntry(const std::string& package, - const std::string& name) { + static std::string MangleEntry(const std::string& package, const std::string& name) { return package + "$" + name; } @@ -86,8 +85,8 @@ class NameMangler { } out_package->assign(out_name->data(), pivot); - out_name->assign(out_name->data() + pivot + 1, - out_name->size() - (pivot + 1)); + std::string new_name = out_name->substr(pivot + 1); + *out_name = std::move(new_name); return true; } diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp index 35971e7bd99b..a9f5f298e019 100644 --- a/tools/aapt2/Resource.cpp +++ b/tools/aapt2/Resource.cpp @@ -61,6 +61,8 @@ StringPiece ToString(ResourceType type) { return "menu"; case ResourceType::kMipmap: return "mipmap"; + case ResourceType::kNavigation: + return "navigation"; case ResourceType::kPlurals: return "plurals"; case ResourceType::kRaw: @@ -98,6 +100,7 @@ static const std::map<StringPiece, ResourceType> sResourceTypeMap{ {"layout", ResourceType::kLayout}, {"menu", ResourceType::kMenu}, {"mipmap", ResourceType::kMipmap}, + {"navigation", ResourceType::kNavigation}, {"plurals", ResourceType::kPlurals}, {"raw", ResourceType::kRaw}, {"string", ResourceType::kString}, diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index 0a74c1a0f77d..cbcc8fb805aa 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -59,6 +59,7 @@ enum class ResourceType { kLayout, kMenu, kMipmap, + kNavigation, kPlurals, kRaw, kString, diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index a5783a532e23..4cc60a8dbb07 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -392,6 +392,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, {"declare-styleable", std::mem_fn(&ResourceParser::ParseDeclareStyleable)}, {"integer-array", std::mem_fn(&ResourceParser::ParseIntegerArray)}, {"java-symbol", std::mem_fn(&ResourceParser::ParseSymbol)}, + {"overlayable", std::mem_fn(&ResourceParser::ParseOverlayable)}, {"plurals", std::mem_fn(&ResourceParser::ParsePlural)}, {"public", std::mem_fn(&ResourceParser::ParsePublic)}, {"public-group", std::mem_fn(&ResourceParser::ParsePublicGroup)}, @@ -498,7 +499,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser, const auto bag_iter = elToBagMap.find(resource_type); if (bag_iter != elToBagMap.end()) { // Ensure we have a name (unless this is a <public-group>). - if (resource_type != "public-group") { + if (resource_type != "public-group" && resource_type != "overlayable") { if (!maybe_name) { diag_->Error(DiagMessage(out_resource->source) << "<" << parser->element_name() << "> missing 'name' attribute"); @@ -605,7 +606,7 @@ std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser, if (processed_item) { // Fix up the reference. if (Reference* ref = ValueCast<Reference>(processed_item.get())) { - TransformReferenceFromNamespace(parser, "", ref); + ResolvePackage(parser, ref); } return processed_item; } @@ -690,6 +691,11 @@ bool ResourceParser::ParseString(xml::XmlPullParser* parser, bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource) { + if (out_resource->config != ConfigDescription::DefaultConfig()) { + diag_->Warn(DiagMessage(out_resource->source) + << "ignoring configuration '" << out_resource->config << "' for <public> tag"); + } + Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); if (!maybe_type) { diag_->Error(DiagMessage(out_resource->source) @@ -726,8 +732,13 @@ bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, return true; } -bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, - ParsedResource* out_resource) { +bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) { + if (out_resource->config != ConfigDescription::DefaultConfig()) { + diag_->Warn(DiagMessage(out_resource->source) + << "ignoring configuration '" << out_resource->config + << "' for <public-group> tag"); + } + Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); if (!maybe_type) { diag_->Error(DiagMessage(out_resource->source) @@ -842,13 +853,83 @@ bool ResourceParser::ParseSymbolImpl(xml::XmlPullParser* parser, return true; } -bool ResourceParser::ParseSymbol(xml::XmlPullParser* parser, - ParsedResource* out_resource) { - if (ParseSymbolImpl(parser, out_resource)) { - out_resource->symbol_state = SymbolState::kPrivate; - return true; +bool ResourceParser::ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource) { + if (out_resource->config != ConfigDescription::DefaultConfig()) { + diag_->Warn(DiagMessage(out_resource->source) + << "ignoring configuration '" << out_resource->config << "' for <" + << parser->element_name() << "> tag"); } - return false; + + if (!ParseSymbolImpl(parser, out_resource)) { + return false; + } + + out_resource->symbol_state = SymbolState::kPrivate; + return true; +} + +bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource) { + if (out_resource->config != ConfigDescription::DefaultConfig()) { + diag_->Warn(DiagMessage(out_resource->source) + << "ignoring configuration '" << out_resource->config << "' for <overlayable> tag"); + } + + if (Maybe<StringPiece> maybe_policy = xml::FindNonEmptyAttribute(parser, "policy")) { + const StringPiece& policy = maybe_policy.value(); + if (policy != "system") { + diag_->Error(DiagMessage(out_resource->source) + << "<overlayable> has invalid policy '" << policy << "'"); + return false; + } + } + + bool error = false; + const size_t depth = parser->depth(); + while (xml::XmlPullParser::NextChildNode(parser, depth)) { + if (parser->event() != xml::XmlPullParser::Event::kStartElement) { + // Skip text/comments. + continue; + } + + const Source item_source = source_.WithLine(parser->line_number()); + const std::string& element_namespace = parser->element_namespace(); + const std::string& element_name = parser->element_name(); + if (element_namespace.empty() && element_name == "item") { + Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name"); + if (!maybe_name) { + diag_->Error(DiagMessage(item_source) + << "<item> within an <overlayable> tag must have a 'name' attribute"); + error = true; + continue; + } + + Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type"); + if (!maybe_type) { + diag_->Error(DiagMessage(item_source) + << "<item> within an <overlayable> tag must have a 'type' attribute"); + error = true; + continue; + } + + const ResourceType* type = ParseResourceType(maybe_type.value()); + if (type == nullptr) { + diag_->Error(DiagMessage(out_resource->source) + << "invalid resource type '" << maybe_type.value() + << "' in <item> within an <overlayable>"); + error = true; + continue; + } + + // TODO(b/64980941): Mark the symbol as overlayable and allow marking which entity can overlay + // the resource (system/app). + + xml::XmlPullParser::SkipCurrentElement(parser); + } else if (!ShouldIgnoreElement(element_namespace, element_name)) { + diag_->Error(DiagMessage(item_source) << ":" << element_name << ">"); + error = true; + } + } + return !error; } bool ResourceParser::ParseAddResource(xml::XmlPullParser* parser, @@ -1074,15 +1155,13 @@ bool ResourceParser::ParseStyleItem(xml::XmlPullParser* parser, Style* style) { return false; } - Maybe<Reference> maybe_key = - ResourceUtils::ParseXmlAttributeName(maybe_name.value()); + Maybe<Reference> maybe_key = ResourceUtils::ParseXmlAttributeName(maybe_name.value()); if (!maybe_key) { - diag_->Error(DiagMessage(source) << "invalid attribute name '" - << maybe_name.value() << "'"); + diag_->Error(DiagMessage(source) << "invalid attribute name '" << maybe_name.value() << "'"); return false; } - TransformReferenceFromNamespace(parser, "", &maybe_key.value()); + ResolvePackage(parser, &maybe_key.value()); maybe_key.value().SetSource(source); std::unique_ptr<Item> value = ParseXml(parser, 0, kAllowRawString); @@ -1091,8 +1170,7 @@ bool ResourceParser::ParseStyleItem(xml::XmlPullParser* parser, Style* style) { return false; } - style->entries.push_back( - Style::Entry{std::move(maybe_key.value()), std::move(value)}); + style->entries.push_back(Style::Entry{std::move(maybe_key.value()), std::move(value)}); return true; } @@ -1104,21 +1182,18 @@ bool ResourceParser::ParseStyle(const ResourceType type, xml::XmlPullParser* par Maybe<StringPiece> maybe_parent = xml::FindAttribute(parser, "parent"); if (maybe_parent) { - // If the parent is empty, we don't have a parent, but we also don't infer - // either. + // If the parent is empty, we don't have a parent, but we also don't infer either. if (!maybe_parent.value().empty()) { std::string err_str; - style->parent = ResourceUtils::ParseStyleParentReference( - maybe_parent.value(), &err_str); + style->parent = ResourceUtils::ParseStyleParentReference(maybe_parent.value(), &err_str); if (!style->parent) { diag_->Error(DiagMessage(out_resource->source) << err_str); return false; } - // Transform the namespace prefix to the actual package name, and mark the - // reference as + // Transform the namespace prefix to the actual package name, and mark the reference as // private if appropriate. - TransformReferenceFromNamespace(parser, "", &style->parent.value()); + ResolvePackage(parser, &style->parent.value()); } } else { @@ -1127,8 +1202,7 @@ bool ResourceParser::ParseStyle(const ResourceType type, xml::XmlPullParser* par size_t pos = style_name.find_last_of(u'.'); if (pos != std::string::npos) { style->parent_inferred = true; - style->parent = Reference( - ResourceName({}, ResourceType::kStyle, style_name.substr(0, pos))); + style->parent = Reference(ResourceName({}, ResourceType::kStyle, style_name.substr(0, pos))); } } @@ -1219,7 +1293,7 @@ bool ResourceParser::ParseArrayImpl(xml::XmlPullParser* parser, continue; } item->SetSource(item_source); - array->items.emplace_back(std::move(item)); + array->elements.emplace_back(std::move(item)); } else if (!ShouldIgnoreElement(element_namespace, element_name)) { diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) @@ -1373,7 +1447,7 @@ bool ResourceParser::ParseDeclareStyleable(xml::XmlPullParser* parser, } Reference& child_ref = maybe_ref.value(); - xml::TransformReferenceFromNamespace(parser, "", &child_ref); + xml::ResolvePackage(parser, &child_ref); // Create the ParsedResource that will add the attribute to the table. ParsedResource child_resource; diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index 5631dc2ad29c..fb9dbd0cd0fd 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -91,6 +91,7 @@ class ResourceParser { bool ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource); + bool ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseAddResource(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseAttr(xml::XmlPullParser* parser, ParsedResource* out_resource); bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, bool weak); diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 1683c64a6a5c..f08b03e7b8af 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -22,9 +22,11 @@ #include "ResourceTable.h" #include "ResourceUtils.h" #include "ResourceValues.h" +#include "io/StringInputStream.h" #include "test/Test.h" #include "xml/XmlPullParser.h" +using ::aapt::io::StringInputStream; using ::aapt::test::StrValueEq; using ::aapt::test::ValueEq; using ::android::ResTable_map; @@ -43,11 +45,13 @@ constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?> TEST(ResourceParserSingleTest, FailToParseWithNoRootResourcesElement) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - std::stringstream input(kXmlPreamble); - input << R"(<attr name="foo"/>)" << std::endl; ResourceTable table; ResourceParser parser(context->GetDiagnostics(), &table, Source{"test"}, {}); - xml::XmlPullParser xml_parser(input); + + std::string input = kXmlPreamble; + input += R"(<attr name="foo"/>)"; + StringInputStream in(input); + xml::XmlPullParser xml_parser(&in); ASSERT_FALSE(parser.Parse(&xml_parser)); } @@ -62,12 +66,16 @@ class ResourceParserTest : public ::testing::Test { } ::testing::AssertionResult TestParse(const StringPiece& str, const ConfigDescription& config) { - std::stringstream input(kXmlPreamble); - input << "<resources>\n" << str << "\n</resources>" << std::endl; ResourceParserOptions parserOptions; ResourceParser parser(context_->GetDiagnostics(), &table_, Source{"test"}, config, parserOptions); - xml::XmlPullParser xmlParser(input); + + std::string input = kXmlPreamble; + input += "<resources>\n"; + input.append(str.data(), str.size()); + input += "\n</resources>"; + StringInputStream in(input); + xml::XmlPullParser xmlParser(&in); if (parser.Parse(&xmlParser)) { return ::testing::AssertionSuccess(); } @@ -532,11 +540,11 @@ TEST_F(ResourceParserTest, ParseArray) { Array* array = test::GetValue<Array>(&table_, "array/foo"); ASSERT_THAT(array, NotNull()); - ASSERT_THAT(array->items, SizeIs(3)); + ASSERT_THAT(array->elements, SizeIs(3)); - EXPECT_THAT(ValueCast<Reference>(array->items[0].get()), NotNull()); - EXPECT_THAT(ValueCast<String>(array->items[1].get()), NotNull()); - EXPECT_THAT(ValueCast<BinaryPrimitive>(array->items[2].get()), NotNull()); + EXPECT_THAT(ValueCast<Reference>(array->elements[0].get()), NotNull()); + EXPECT_THAT(ValueCast<String>(array->elements[1].get()), NotNull()); + EXPECT_THAT(ValueCast<BinaryPrimitive>(array->elements[2].get()), NotNull()); } TEST_F(ResourceParserTest, ParseStringArray) { @@ -557,9 +565,9 @@ TEST_F(ResourceParserTest, ParseArrayWithFormat) { Array* array = test::GetValue<Array>(&table_, "array/foo"); ASSERT_THAT(array, NotNull()); - ASSERT_THAT(array->items, SizeIs(1)); + ASSERT_THAT(array->elements, SizeIs(1)); - String* str = ValueCast<String>(array->items[0].get()); + String* str = ValueCast<String>(array->elements[0].get()); ASSERT_THAT(str, NotNull()); EXPECT_THAT(*str, StrValueEq("100")); } @@ -782,4 +790,49 @@ TEST_F(ResourceParserTest, ParsePlatformIndependentNewline) { ASSERT_TRUE(TestParse(R"(<string name="foo">%1$s %n %2$s</string>)")); } +TEST_F(ResourceParserTest, ParseOverlayableTagWithSystemPolicy) { + std::string input = R"( + <overlayable policy="illegal_policy"> + <item type="string" name="foo" /> + </overlayable>)"; + EXPECT_FALSE(TestParse(input)); + + input = R"( + <overlayable policy="system"> + <item name="foo" /> + </overlayable>)"; + EXPECT_FALSE(TestParse(input)); + + input = R"( + <overlayable policy="system"> + <item type="attr" /> + </overlayable>)"; + EXPECT_FALSE(TestParse(input)); + + input = R"( + <overlayable policy="system"> + <item type="bad_type" name="foo" /> + </overlayable>)"; + EXPECT_FALSE(TestParse(input)); + + input = R"(<overlayable policy="system" />)"; + EXPECT_TRUE(TestParse(input)); + + input = R"(<overlayable />)"; + EXPECT_TRUE(TestParse(input)); + + input = R"( + <overlayable policy="system"> + <item type="string" name="foo" /> + <item type="dimen" name="foo" /> + </overlayable>)"; + ASSERT_TRUE(TestParse(input)); + + input = R"( + <overlayable> + <item type="string" name="bar" /> + </overlayable>)"; + ASSERT_TRUE(TestParse(input)); +} + } // namespace aapt diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index ab59560d33a3..0304e21698df 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -546,4 +546,34 @@ Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNam return SearchResult{package, type, entry}; } +std::unique_ptr<ResourceTable> ResourceTable::Clone() const { + std::unique_ptr<ResourceTable> new_table = util::make_unique<ResourceTable>(); + for (const auto& pkg : packages) { + ResourceTablePackage* new_pkg = new_table->CreatePackage(pkg->name, pkg->id); + for (const auto& type : pkg->types) { + ResourceTableType* new_type = new_pkg->FindOrCreateType(type->type); + if (!new_type->id) { + new_type->id = type->id; + new_type->symbol_status = type->symbol_status; + } + + for (const auto& entry : type->entries) { + ResourceEntry* new_entry = new_type->FindOrCreateEntry(entry->name); + if (!new_entry->id) { + new_entry->id = entry->id; + new_entry->symbol_status = entry->symbol_status; + } + + for (const auto& config_value : entry->values) { + ResourceConfigValue* new_value = + new_entry->FindOrCreateValue(config_value->config, config_value->product); + Value* value = config_value->value->Clone(&new_table->string_pool); + new_value->value = std::unique_ptr<Value>(value); + } + } + } + } + return new_table; +} + } // namespace aapt diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index 4295d0674774..d5db67e77f51 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -251,6 +251,8 @@ class ResourceTable { ResourceTablePackage* CreatePackage(const android::StringPiece& name, Maybe<uint8_t> id = {}); + std::unique_ptr<ResourceTable> Clone() const; + /** * The string pool used by this resource table. Values that reference strings * must use diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index eb59175edf3b..1cba19462839 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -805,13 +805,12 @@ bool Array::Equals(const Value* value) const { return false; } - if (items.size() != other->items.size()) { + if (elements.size() != other->elements.size()) { return false; } - return std::equal(items.begin(), items.end(), other->items.begin(), - [](const std::unique_ptr<Item>& a, - const std::unique_ptr<Item>& b) -> bool { + return std::equal(elements.begin(), elements.end(), other->elements.begin(), + [](const std::unique_ptr<Item>& a, const std::unique_ptr<Item>& b) -> bool { return a->Equals(b.get()); }); } @@ -820,14 +819,14 @@ Array* Array::Clone(StringPool* new_pool) const { Array* array = new Array(); array->comment_ = comment_; array->source_ = source_; - for (auto& item : items) { - array->items.emplace_back(std::unique_ptr<Item>(item->Clone(new_pool))); + for (auto& item : elements) { + array->elements.emplace_back(std::unique_ptr<Item>(item->Clone(new_pool))); } return array; } void Array::Print(std::ostream* out) const { - *out << "(array) [" << util::Joiner(items, ", ") << "]"; + *out << "(array) [" << util::Joiner(elements, ", ") << "]"; } bool Plural::Equals(const Value* value) const { diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index 7e7547fc1b94..275864bbcd3e 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -292,7 +292,7 @@ struct Style : public BaseValue<Style> { }; struct Array : public BaseValue<Array> { - std::vector<std::unique_ptr<Item>> items; + std::vector<std::unique_ptr<Item>> elements; bool Equals(const Value* value) const override; Array* Clone(StringPool* new_pool) const override; diff --git a/tools/aapt2/ResourceValues_test.cpp b/tools/aapt2/ResourceValues_test.cpp index 06c3404561de..10f9b55ede08 100644 --- a/tools/aapt2/ResourceValues_test.cpp +++ b/tools/aapt2/ResourceValues_test.cpp @@ -54,19 +54,19 @@ TEST(ResourceValuesTest, ArrayEquals) { StringPool pool; Array a; - a.items.push_back(util::make_unique<String>(pool.MakeRef("one"))); - a.items.push_back(util::make_unique<String>(pool.MakeRef("two"))); + a.elements.push_back(util::make_unique<String>(pool.MakeRef("one"))); + a.elements.push_back(util::make_unique<String>(pool.MakeRef("two"))); Array b; - b.items.push_back(util::make_unique<String>(pool.MakeRef("une"))); - b.items.push_back(util::make_unique<String>(pool.MakeRef("deux"))); + b.elements.push_back(util::make_unique<String>(pool.MakeRef("une"))); + b.elements.push_back(util::make_unique<String>(pool.MakeRef("deux"))); Array c; - c.items.push_back(util::make_unique<String>(pool.MakeRef("uno"))); + c.elements.push_back(util::make_unique<String>(pool.MakeRef("uno"))); Array d; - d.items.push_back(util::make_unique<String>(pool.MakeRef("one"))); - d.items.push_back(util::make_unique<String>(pool.MakeRef("two"))); + d.elements.push_back(util::make_unique<String>(pool.MakeRef("one"))); + d.elements.push_back(util::make_unique<String>(pool.MakeRef("two"))); EXPECT_FALSE(a.Equals(&b)); EXPECT_FALSE(a.Equals(&c)); @@ -78,8 +78,8 @@ TEST(ResourceValuesTest, ArrayClone) { StringPool pool; Array a; - a.items.push_back(util::make_unique<String>(pool.MakeRef("one"))); - a.items.push_back(util::make_unique<String>(pool.MakeRef("two"))); + a.elements.push_back(util::make_unique<String>(pool.MakeRef("one"))); + a.elements.push_back(util::make_unique<String>(pool.MakeRef("two"))); std::unique_ptr<Array> b(a.Clone(&pool)); EXPECT_TRUE(a.Equals(b.get())); diff --git a/tools/aapt2/Resource_test.cpp b/tools/aapt2/Resource_test.cpp index ad4e3ce02b32..c557f3c77654 100644 --- a/tools/aapt2/Resource_test.cpp +++ b/tools/aapt2/Resource_test.cpp @@ -93,6 +93,10 @@ TEST(ResourceTypeTest, ParseResourceTypes) { ASSERT_NE(type, nullptr); EXPECT_EQ(*type, ResourceType::kMipmap); + type = ParseResourceType("navigation"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kNavigation); + type = ParseResourceType("plurals"); ASSERT_NE(type, nullptr); EXPECT_EQ(*type, ResourceType::kPlurals); diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto new file mode 100644 index 000000000000..71f33b0853ad --- /dev/null +++ b/tools/aapt2/Resources.proto @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Keep proto2 syntax because we require the distinction between fields that +// are set and unset. +syntax = "proto2"; + +option java_package = "com.android.aapt"; +option optimize_for = LITE_RUNTIME; + +package aapt.pb; + +// A configuration description that wraps the binary form of the C++ class +// aapt::ConfigDescription, with an added product definition. +// TODO(adamlesinski): Flesh this out to be represented in proto. +message ConfigDescription { + optional bytes data = 1; + optional string product = 2; +} + +// A string pool that wraps the binary form of the C++ class android::ResStringPool. +message StringPool { + optional bytes data = 1; +} + +// The position of a declared entity within a file. +message SourcePosition { + optional uint32 line_number = 1; + optional uint32 column_number = 2; +} + +// Developer friendly source file information for an entity in the resource table. +message Source { + // The index of the string path within the source string pool of a ResourceTable. + optional uint32 path_idx = 1; + optional SourcePosition position = 2; +} + +// Top level message representing a resource table. +message ResourceTable { + // The string pool containing source paths referenced throughout the resource table. This does + // not end up in the final binary ARSC file. + optional StringPool source_pool = 1; + + // Resource definitions corresponding to an Android package. + repeated Package package = 2; +} + +// Defines resources for an Android package. +message Package { + // The package ID of this package, in the range [0x00, 0xff]. + // The ID 0x00 is reserved for shared libraries, or when the ID is assigned at run-time. + // The ID 0x01 is reserved for the 'android' package (framework). + // The ID range [0x02, 0x7f) is reserved for auto-assignment to shared libraries at run-time. + // The ID 0x7f is reserved for the application package. + // IDs > 0x7f are reserved for the application as well and are treated as feature splits. + optional uint32 package_id = 1; + + // The Java compatible Android package name of the app. + optional string package_name = 2; + + // The series of types defined by the package. + repeated Type type = 3; +} + +// A set of resources grouped under a common type. Such types include string, layout, xml, dimen, +// attr, etc. This maps to the second part of a resource identifier in Java (R.type.entry). +message Type { + // The ID of the type. This may be 0, which indicates no ID is set. + optional uint32 id = 1; + + // The name of the type. This corresponds to the 'type' part of a full resource name of the form + // package:type/entry. The set of legal type names is listed in Resource.cpp. + optional string name = 2; + + // The entries defined for this type. + repeated Entry entry = 3; +} + +// The status of a symbol/entry. This contains information like visibility (public/private), +// comments, and whether the entry can be overridden. +message SymbolStatus { + // The visibility of the resource outside of its package. + enum Visibility { + // No visibility was explicitly specified. This is typically treated as private. + // The distinction is important when two separate R.java files are generated: a public and + // private one. An unknown visibility, in this case, would cause the resource to be omitted + // from either R.java. + UNKNOWN = 0; + + // A resource was explicitly marked as private. This means the resource can not be accessed + // outside of its package unless the @*package:type/entry notation is used (the asterisk being + // the private accessor). If two R.java files are generated (private + public), the resource + // will only be emitted to the private R.java file. + PRIVATE = 1; + + // A resource was explicitly marked as public. This means the resource can be accessed + // from any package, and is emitted into all R.java files, public and private. + PUBLIC = 2; + } + + optional Visibility visibility = 1; + + // The path at which this entry's visibility was defined (eg. public.xml). + optional Source source = 2; + + // The comment associated with the <public> tag. + optional string comment = 3; + + // Whether the symbol can be merged into another resource table without there being an existing + // definition to override. Used for overlays and set to true when <add-resource> is specified. + optional bool allow_new = 4; +} + +// An entry declaration. An entry has a full resource ID that is the combination of package ID, +// type ID, and its own entry ID. An entry on its own has no value, but values are defined for +// various configurations/variants. +message Entry { + // The ID of this entry. Together with the package ID and type ID, this forms a full resource ID + // of the form 0xPPTTEEEE, where PP is the package ID, TT is the type ID, and EEEE is the entry + // ID. + optional uint32 id = 1; + + // The name of this entry. This corresponds to the 'entry' part of a full resource name of the + // form package:type/entry. + optional string name = 2; + + // The symbol status of this entry, which includes visibility information. + optional SymbolStatus symbol_status = 3; + + // The set of values defined for this entry, each corresponding to a different + // configuration/variant. + repeated ConfigValue config_value = 4; +} + +// A Configuration/Value pair. +message ConfigValue { + optional ConfigDescription config = 1; + optional Value value = 2; +} + +// The generic meta-data for every value in a resource table. +message Value { + // Where the value was defined. + optional Source source = 1; + + // Any comment associated with the value. + optional string comment = 2; + + // Whether the value can be overridden. + optional bool weak = 3; + + // If the value is an Item, this is set. + optional Item item = 4; + + // If the value is a CompoundValue, this is set. + optional CompoundValue compound_value = 5; +} + +// An Item is an abstract type. It represents a value that can appear inline in many places, such +// as XML attribute values or on the right hand side of style attribute definitions. The concrete +// type is one of the types below. Only one can be set. +message Item { + optional Reference ref = 1; + optional String str = 2; + optional RawString raw_str = 3; + optional StyledString styled_str = 4; + optional FileReference file = 5; + optional Id id = 6; + optional Primitive prim = 7; +} + +// A CompoundValue is an abstract type. It represents a value that is a made of other values. +// These can only usually appear as top-level resources. The concrete type is one of the types +// below. Only one can be set. +message CompoundValue { + optional Attribute attr = 1; + optional Style style = 2; + optional Styleable styleable = 3; + optional Array array = 4; + optional Plural plural = 5; +} + +// A value that is a reference to another resource. This reference can be by name or resource ID. +message Reference { + enum Type { + // A plain reference (@package:type/entry). + REFERENCE = 0; + + // A reference to a theme attribute (?package:type/entry). + ATTRIBUTE = 1; + } + + optional Type type = 1; + + // The resource ID (0xPPTTEEEE) of the resource being referred. + optional uint32 id = 2; + + // The optional resource name. + optional string name = 3; + + // Whether this reference is referencing a private resource (@*package:type/entry). + optional bool private = 4; +} + +// A value that represents an ID. This is just a placeholder, as ID values are used to occupy a +// resource ID (0xPPTTEEEE) as a unique identifier. Their value is unimportant. +message Id { +} + +// A value that is a string. +message String { + optional string value = 1; +} + +// A value that is a raw string, which is unescaped/uninterpreted. This is typically used to +// represent the value of a style attribute before the attribute is compiled and the set of +// allowed values is known. +message RawString { + optional string value = 1; +} + +// A string with styling information, like html tags that specify boldness, italics, etc. +message StyledString { + // The raw text of the string. + optional string value = 1; + + // A Span marks a region of the string text that is styled. + message Span { + // The name of the tag, and its attributes, encoded as follows: + // tag_name;attr1=value1;attr2=value2;[...] + optional string tag = 1; + + // The first character position this span applies to, in UTF-16 offset. + optional uint32 first_char = 2; + + // The last character position this span applies to, in UTF-16 offset. + optional uint32 last_char = 3; + } + + repeated Span span = 2; +} + +// A value that is a reference to an external entity, like an XML file or a PNG. +message FileReference { + // Path to a file within the APK (typically res/type-config/entry.ext). + optional string path = 1; +} + +// A value that represents a primitive data type (float, int, boolean, etc.). +// Corresponds to the fields (type/data) of the C struct android::Res_value. +message Primitive { + optional uint32 type = 1; + optional uint32 data = 2; +} + +// A value that represents an XML attribute and what values it accepts. +message Attribute { + // A Symbol used to represent an enum or a flag. + message Symbol { + // Where the enum/flag item was defined. + optional Source source = 1; + + // Any comments associated with the enum or flag. + optional string comment = 2; + + // The name of the enum/flag as a reference. Enums/flag items are generated as ID resource + // values. + optional Reference name = 3; + + // The value of the enum/flag. + optional uint32 value = 4; + } + + // Bitmask of formats allowed for an attribute. + enum FormatFlags { + ANY = 0x0000ffff; // Allows any type except ENUM and FLAGS. + REFERENCE = 0x01; // Allows Reference values. + STRING = 0x02; // Allows String/StyledString values. + INTEGER = 0x04; // Allows any integer BinaryPrimitive values. + BOOLEAN = 0x08; // Allows any boolean BinaryPrimitive values. + COLOR = 0x010; // Allows any color BinaryPrimitive values. + FLOAT = 0x020; // Allows any float BinaryPrimitive values. + DIMENSION = 0x040; // Allows any dimension BinaryPrimitive values. + FRACTION = 0x080; // Allows any fraction BinaryPrimitive values. + ENUM = 0x00010000; // Allows enums that are defined in the Attribute's symbols. + // ENUM and FLAGS cannot BOTH be set. + FLAGS = 0x00020000; // Allows flags that are defined in the Attribute's symbols. + // ENUM and FLAGS cannot BOTH be set. + } + + // A bitmask of types that this XML attribute accepts. Corresponds to the flags in the + // enum FormatFlags. + optional uint32 format_flags = 1; + + // The smallest integer allowed for this XML attribute. Only makes sense if the format includes + // FormatFlags::INTEGER. + optional int32 min_int = 2; + + // The largest integer allowed for this XML attribute. Only makes sense if the format includes + // FormatFlags::INTEGER. + optional int32 max_int = 3; + + // The set of enums/flags defined in this attribute. Only makes sense if the format includes + // either FormatFlags::ENUM or FormatFlags::FLAGS. Having both is an error. + repeated Symbol symbol = 4; +} + +// A value that represents a style. +message Style { + // An XML attribute/value pair defined in the style. + message Entry { + // Where the entry was defined. + optional Source source = 1; + + // Any comments associated with the entry. + optional string comment = 2; + + // A reference to the XML attribute. + optional Reference key = 3; + + // The Item defined for this XML attribute. + optional Item item = 4; + } + + // The optinal style from which this style inherits attributes. + optional Reference parent = 1; + + // The source file information of the parent inheritance declaration. + optional Source parent_source = 2; + + // The set of XML attribute/value pairs for this style. + repeated Entry entry = 3; +} + +// A value that represents a <declare-styleable> XML resource. These are not real resources and +// only end up as Java fields in the generated R.java. They do not end up in the binary ARSC file. +message Styleable { + // An attribute defined for this styleable. + message Entry { + // Where the attribute was defined within the <declare-styleable> block. + optional Source source = 1; + + // Any comments associated with the declaration. + optional string comment = 2; + + // The reference to the attribute. + optional Reference attr = 3; + } + + // The set of attribute declarations. + repeated Entry entry = 1; +} + +// A value that represents an array of resource values. +message Array { + // A single element of the array. + message Element { + // Where the element was defined. + optional Source source = 1; + + // Any comments associated with the element. + optional string comment = 2; + + // The value assigned to this element. + optional Item item = 3; + } + + // The list of array elements. + repeated Element element = 1; +} + +// A value that represents a string and its many variations based on plurality. +message Plural { + // The arity of the plural. + enum Arity { + ZERO = 0; + ONE = 1; + TWO = 2; + FEW = 3; + MANY = 4; + OTHER = 5; + } + + // The plural value for a given arity. + message Entry { + // Where the plural was defined. + optional Source source = 1; + + // Any comments associated with the plural. + optional string comment = 2; + + // The arity of the plural. + optional Arity arity = 3; + + // The value assigned to this plural. + optional Item item = 4; + } + + // The set of arity/plural mappings. + repeated Entry entry = 1; +} + +// Defines an abstract XmlNode that must be either an XmlElement, or +// a text node represented by a string. +message XmlNode { + // If set, this node is an element/tag. + optional XmlElement element = 1; + + // If set, this node is a chunk of text. + optional string text = 2; + + // Source line and column info. + optional SourcePosition source = 3; +} + +// An <element> in an XML document. +message XmlElement { + // Namespaces defined on this element. + repeated XmlNamespace namespace_declaration = 1; + + // The namespace URI of this element. + optional string namespace_uri = 2; + + // The name of this element. + optional string name = 3; + + // The attributes of this element. + repeated XmlAttribute attribute = 4; + + // The children of this element. + repeated XmlNode child = 5; +} + +// A namespace declaration on an XmlElement (xmlns:android="http://..."). +message XmlNamespace { + optional string prefix = 1; + optional string uri = 2; + + // Source line and column info. + optional SourcePosition source = 3; +} + +// An attribute defined on an XmlElement (android:text="..."). +message XmlAttribute { + optional string namespace_uri = 1; + optional string name = 2; + optional string value = 3; + + // Source line and column info. + optional SourcePosition source = 4; + + // The resource ID (0xPPTTEEEE) of the attribute. + optional uint32 resource_id = 5; + + // The interpreted/compiled version of the `value` string. + optional Item compiled_item = 6; +} diff --git a/tools/aapt2/ResourcesInternal.proto b/tools/aapt2/ResourcesInternal.proto new file mode 100644 index 000000000000..31179174b843 --- /dev/null +++ b/tools/aapt2/ResourcesInternal.proto @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +option java_package = "android.aapt.pb.internal"; +option optimize_for = LITE_RUNTIME; + +import "frameworks/base/tools/aapt2/Resources.proto"; + +package aapt.pb.internal; + +// The top level message representing an external resource file (layout XML, PNG, etc). +// This is used to represent a compiled file before it is linked. Only useful to aapt2. +message CompiledFile { + message Symbol { + // The name of the symbol (in the form package:type/name). + optional string resource_name = 1; + + // The position in the file at which this symbol is defined. For debug use. + optional aapt.pb.SourcePosition source = 2; + } + + // The name of the resource (in the form package:type/name). + optional string resource_name = 1; + + // The configuration for which the resource is defined. + optional aapt.pb.ConfigDescription config = 2; + + // The filesystem path to where the source file originated. + // Mainly used to display helpful error messages. + optional string source_path = 3; + + // Any symbols this file auto-generates/exports (eg. @+id/foo in an XML file). + repeated Symbol exported_symbol = 4; + + // If this is a compiled XML file, this is the root node. + optional aapt.pb.XmlNode xml_root = 5; +} diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h index 5c32ed4fd849..13584c0bf840 100644 --- a/tools/aapt2/SdkConstants.h +++ b/tools/aapt2/SdkConstants.h @@ -53,6 +53,7 @@ enum : ApiVersion { SDK_NOUGAT_MR1 = 25, SDK_O = 26, SDK_O_MR1 = 27, + SDK_P = 10000, // STOPSHIP Replace with the real version. }; ApiVersion FindAttributeSdkLevel(const ResourceId& id); diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h index 2763d49f15c4..eb4fa494e53f 100644 --- a/tools/aapt2/ValueVisitor.h +++ b/tools/aapt2/ValueVisitor.h @@ -80,7 +80,7 @@ struct ValueVisitor : public RawValueVisitor { } void VisitSubValues(Array* array) { - for (std::unique_ptr<Item>& item : array->items) { + for (std::unique_ptr<Item>& item : array->elements) { item->Accept(this); } } diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index b64cd8c432d4..7f5bbf042766 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -16,11 +16,11 @@ #include <dirent.h> -#include <fstream> #include <string> #include "android-base/errors.h" #include "android-base/file.h" +#include "android-base/utf8.h" #include "androidfw/StringPiece.h" #include "google/protobuf/io/coded_stream.h" #include "google/protobuf/io/zero_copy_stream_impl_lite.h" @@ -38,6 +38,7 @@ #include "flatten/Archive.h" #include "flatten/XmlFlattener.h" #include "io/BigBufferOutputStream.h" +#include "io/FileInputStream.h" #include "io/Util.h" #include "proto/ProtoSerialize.h" #include "util/Files.h" @@ -46,8 +47,9 @@ #include "xml/XmlDom.h" #include "xml/XmlPullParser.h" -using android::StringPiece; -using google::protobuf::io::CopyingOutputStreamAdaptor; +using ::aapt::io::FileInputStream; +using ::android::StringPiece; +using ::google::protobuf::io::CopyingOutputStreamAdaptor; namespace aapt { @@ -57,19 +59,14 @@ struct ResourcePathData { std::string name; std::string extension; - // Original config str. We keep this because when we parse the config, we may - // add on - // version qualifiers. We want to preserve the original input so the output is - // easily + // Original config str. We keep this because when we parse the config, we may add on + // version qualifiers. We want to preserve the original input so the output is easily // computed before hand. std::string config_str; ConfigDescription config; }; -/** - * Resource file paths are expected to look like: - * [--/res/]type[-config]/name - */ +// Resource file paths are expected to look like: [--/res/]type[-config]/name static Maybe<ResourcePathData> ExtractResourcePathData(const std::string& path, std::string* out_error) { std::vector<std::string> parts = util::Split(path, file::sDirSep); @@ -137,9 +134,7 @@ static bool IsHidden(const StringPiece& filename) { return util::StartsWith(filename, "."); } -/** - * Walks the res directory structure, looking for resource files. - */ +// Walks the res directory structure, looking for resource files. static bool LoadInputFilesFromDir(IAaptContext* context, const CompileOptions& options, std::vector<ResourcePathData>* out_path_data) { const std::string& root_dir = options.res_dir.value(); @@ -195,22 +190,20 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, const std::string& output_path) { ResourceTable table; { - std::ifstream fin(path_data.source.path, std::ifstream::binary); - if (!fin) { + FileInputStream fin(path_data.source.path); + if (fin.HadError()) { context->GetDiagnostics()->Error(DiagMessage(path_data.source) - << "failed to open file: " - << android::base::SystemErrorCodeToString(errno)); + << "failed to open file: " << fin.GetError()); return false; } // Parse the values file from XML. - xml::XmlPullParser xml_parser(fin); + xml::XmlPullParser xml_parser(&fin); ResourceParserOptions parser_options; parser_options.error_on_positional_arguments = !options.legacy_mode; - // If the filename includes donottranslate, then the default translatable is - // false. + // If the filename includes donottranslate, then the default translatable is false. parser_options.translatable = path_data.name.find("donottranslate") == std::string::npos; ResourceParser res_parser(context->GetDiagnostics(), &table, path_data.source, path_data.config, @@ -218,8 +211,6 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, if (!res_parser.Parse(&xml_parser)) { return false; } - - fin.close(); } if (options.pseudolocalize) { @@ -239,8 +230,7 @@ static bool CompileTable(IAaptContext* context, const CompileOptions& options, // Assign an ID to any package that has resources. for (auto& pkg : table.packages) { if (!pkg->id) { - // If no package ID was set while parsing (public identifiers), auto - // assign an ID. + // If no package ID was set while parsing (public identifiers), auto assign an ID. pkg->id = context->GetPackageId(); } } @@ -292,7 +282,7 @@ static bool WriteHeaderAndBufferToWriter(const StringPiece& output_path, const R // Number of CompiledFiles. output_stream.WriteLittleEndian32(1); - std::unique_ptr<pb::CompiledFile> compiled_file = SerializeCompiledFileToPb(file); + std::unique_ptr<pb::internal::CompiledFile> compiled_file = SerializeCompiledFileToPb(file); output_stream.WriteCompiledFile(compiled_file.get()); output_stream.WriteData(&buffer); @@ -329,7 +319,7 @@ static bool WriteHeaderAndMmapToWriter(const StringPiece& output_path, const Res // Number of CompiledFiles. output_stream.WriteLittleEndian32(1); - std::unique_ptr<pb::CompiledFile> compiled_file = SerializeCompiledFileToPb(file); + std::unique_ptr<pb::internal::CompiledFile> compiled_file = SerializeCompiledFileToPb(file); output_stream.WriteCompiledFile(compiled_file.get()); output_stream.WriteData(map.getDataPtr(), map.getDataLength()); @@ -356,7 +346,8 @@ static bool FlattenXmlToOutStream(IAaptContext* context, const StringPiece& outp return false; } - std::unique_ptr<pb::CompiledFile> pb_compiled_file = SerializeCompiledFileToPb(xmlres->file); + std::unique_ptr<pb::internal::CompiledFile> pb_compiled_file = + SerializeCompiledFileToPb(xmlres->file); out->WriteCompiledFile(pb_compiled_file.get()); out->WriteData(&buffer); @@ -367,7 +358,7 @@ static bool FlattenXmlToOutStream(IAaptContext* context, const StringPiece& outp return true; } -static bool IsValidFile(IAaptContext* context, const StringPiece& input_path) { +static bool IsValidFile(IAaptContext* context, const std::string& input_path) { const file::FileType file_type = file::GetFileType(input_path); if (file_type != file::FileType::kRegular && file_type != file::FileType::kSymlink) { if (file_type == file::FileType::kDirectory) { @@ -393,17 +384,14 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options, std::unique_ptr<xml::XmlResource> xmlres; { - std::ifstream fin(path_data.source.path, std::ifstream::binary); - if (!fin) { + FileInputStream fin(path_data.source.path); + if (fin.HadError()) { context->GetDiagnostics()->Error(DiagMessage(path_data.source) - << "failed to open file: " - << android::base::SystemErrorCodeToString(errno)); + << "failed to open file: " << fin.GetError()); return false; } xmlres = xml::Inflate(&fin, context->GetDiagnostics(), path_data.source); - - fin.close(); } if (!xmlres) { @@ -432,12 +420,9 @@ static bool CompileXml(IAaptContext* context, const CompileOptions& options, return false; } - // Make sure CopyingOutputStreamAdaptor is deleted before we call - // writer->FinishEntry(). + // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->FinishEntry(). { - // Wrap our IArchiveWriter with an adaptor that implements the - // ZeroCopyOutputStream - // interface. + // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream interface. CopyingOutputStreamAdaptor copying_adaptor(writer); CompiledFileOutputStream output_stream(©ing_adaptor); diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp index aa9472361def..0965910ca853 100644 --- a/tools/aapt2/cmd/Dump.cpp +++ b/tools/aapt2/cmd/Dump.cpp @@ -27,11 +27,11 @@ #include "unflatten/BinaryResourceParser.h" #include "util/Files.h" -using android::StringPiece; +using ::android::StringPiece; namespace aapt { -bool DumpCompiledFile(const pb::CompiledFile& pb_file, const void* data, size_t len, +bool DumpCompiledFile(const pb::internal::CompiledFile& pb_file, const void* data, size_t len, const Source& source, IAaptContext* context) { std::unique_ptr<ResourceFile> file = DeserializeCompiledFileFromPb(pb_file, source, context->GetDiagnostics()); @@ -118,7 +118,7 @@ bool TryDumpFile(IAaptContext* context, const std::string& file_path) { } for (uint32_t i = 0; i < num_files; i++) { - pb::CompiledFile compiled_file; + pb::internal::CompiledFile compiled_file; if (!input.ReadCompiledFile(&compiled_file)) { context->GetDiagnostics()->Warn(DiagMessage() << "failed to read compiled file"); return false; diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 5ad0cdd995ed..3a2faa9f0368 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -40,6 +40,7 @@ #include "flatten/TableFlattener.h" #include "flatten/XmlFlattener.h" #include "io/BigBufferInputStream.h" +#include "io/FileInputStream.h" #include "io/FileSystem.h" #include "io/Util.h" #include "io/ZipArchive.h" @@ -61,8 +62,9 @@ #include "util/Files.h" #include "xml/XmlDom.h" -using android::StringPiece; -using android::base::StringPrintf; +using ::aapt::io::FileInputStream; +using ::android::StringPiece; +using ::android::base::StringPrintf; namespace aapt { @@ -284,13 +286,11 @@ static std::unique_ptr<ResourceTable> LoadTableFromPb(const Source& source, cons return table; } -/** - * Inflates an XML file from the source path. - */ +// Inflates an XML file from the source path. static std::unique_ptr<xml::XmlResource> LoadXml(const std::string& path, IDiagnostics* diag) { - std::ifstream fin(path, std::ifstream::binary); - if (!fin) { - diag->Error(DiagMessage(path) << strerror(errno)); + FileInputStream fin(path); + if (fin.HadError()) { + diag->Error(DiagMessage(path) << "failed to load XML file: " << fin.GetError()); return {}; } return xml::Inflate(&fin, diag, Source(path)); @@ -457,7 +457,8 @@ std::vector<std::unique_ptr<xml::XmlResource>> ResourceFileFlattener::LinkAndVer const Source& src = doc->file.source; if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note(DiagMessage() << "linking " << src.path); + context_->GetDiagnostics()->Note(DiagMessage() + << "linking " << src.path << " (" << doc->file.name << ")"); } XmlReferenceLinker xml_linker; @@ -482,7 +483,7 @@ std::vector<std::unique_ptr<xml::XmlResource>> ResourceFileFlattener::LinkAndVer if (options_.no_version_vectors || options_.no_version_transitions) { // Skip this if it is a vector or animated-vector. - xml::Element* el = xml::FindRootElement(doc); + xml::Element* el = doc->root.get(); if (el && el->namespace_uri.empty()) { if ((options_.no_version_vectors && IsVectorElement(el->name)) || (options_.no_version_transitions && IsTransitionElement(el->name))) { @@ -505,6 +506,8 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv std::map<std::pair<ConfigDescription, StringPiece>, FileOperation> config_sorted_files; for (auto& pkg : table->packages) { + CHECK(!pkg->name.empty()) << "Packages must have names when being linked"; + for (auto& type : pkg->types) { // Sort by config and name, so that we get better locality in the zip file. config_sorted_files.clear(); @@ -701,7 +704,7 @@ class LinkCommand { util::make_unique<AssetManagerSymbolSource>(); for (const std::string& path : options_.include_paths) { if (context_->IsVerbose()) { - context_->GetDiagnostics()->Note(DiagMessage(path) << "loading include path"); + context_->GetDiagnostics()->Note(DiagMessage() << "including " << path); } // First try to load the file as a static lib. @@ -819,11 +822,9 @@ class LinkCommand { return app_info; } - /** - * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked. - * Postcondition: ResourceTable has only one package left. All others are - * stripped, or there is an error and false is returned. - */ + // Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked. + // Postcondition: ResourceTable has only one package left. All others are + // stripped, or there is an error and false is returned. bool VerifyNoExternalPackages() { auto is_ext_package_func = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool { return context_->GetCompilationPackage() != pkg->name || !pkg->id || @@ -920,7 +921,7 @@ class LinkCommand { bool WriteJavaFile(ResourceTable* table, const StringPiece& package_name_to_generate, const StringPiece& out_package, const JavaClassGeneratorOptions& java_options, - const Maybe<std::string> out_text_symbols_path = {}) { + const Maybe<std::string>& out_text_symbols_path = {}) { if (!options_.generate_java_class_path) { return true; } @@ -965,10 +966,94 @@ class LinkCommand { context_->GetDiagnostics()->Error(DiagMessage() << "failed writing to '" << out_path << "': " << android::base::SystemErrorCodeToString(errno)); + return false; } return true; } + bool GenerateJavaClasses() { + // The set of packages whose R class to call in the main classes onResourcesLoaded callback. + std::vector<std::string> packages_to_callback; + + JavaClassGeneratorOptions template_options; + template_options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; + template_options.javadoc_annotations = options_.javadoc_annotations; + + if (context_->GetPackageType() == PackageType::kStaticLib || options_.generate_non_final_ids) { + template_options.use_final = false; + } + + if (context_->GetPackageType() == PackageType::kSharedLib) { + template_options.use_final = false; + template_options.rewrite_callback_options = OnResourcesLoadedCallbackOptions{}; + } + + const StringPiece actual_package = context_->GetCompilationPackage(); + StringPiece output_package = context_->GetCompilationPackage(); + if (options_.custom_java_package) { + // Override the output java package to the custom one. + output_package = options_.custom_java_package.value(); + } + + // Generate the private symbols if required. + if (options_.private_symbols) { + packages_to_callback.push_back(options_.private_symbols.value()); + + // If we defined a private symbols package, we only emit Public symbols + // to the original package, and private and public symbols to the private package. + JavaClassGeneratorOptions options = template_options; + options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate; + if (!WriteJavaFile(&final_table_, actual_package, options_.private_symbols.value(), + options)) { + return false; + } + } + + // Generate copies of the original package R class but with different package names. + // This is to support non-namespaced builds. + for (const std::string& extra_package : options_.extra_java_packages) { + packages_to_callback.push_back(extra_package); + + JavaClassGeneratorOptions options = template_options; + options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; + if (!WriteJavaFile(&final_table_, actual_package, extra_package, options)) { + return false; + } + } + + // Generate R classes for each package that was merged (static library). + // Use the actual package's resources only. + for (const std::string& package : table_merger_->merged_packages()) { + packages_to_callback.push_back(package); + + JavaClassGeneratorOptions options = template_options; + options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; + if (!WriteJavaFile(&final_table_, package, package, options)) { + return false; + } + } + + // Generate the main public R class. + JavaClassGeneratorOptions options = template_options; + + // Only generate public symbols if we have a private package. + if (options_.private_symbols) { + options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic; + } + + if (options.rewrite_callback_options) { + options.rewrite_callback_options.value().packages_to_callback = + std::move(packages_to_callback); + } + + if (!WriteJavaFile(&final_table_, actual_package, output_package, options, + options_.generate_text_symbols_path)) { + return false; + } + + return true; + } + bool WriteManifestJavaFile(xml::XmlResource* manifest_xml) { if (!options_.generate_java_class_path) { return true; @@ -1097,15 +1182,17 @@ class LinkCommand { bool result; if (options_.no_static_lib_packages) { - // Merge all resources as if they were in the compilation package. This is - // the old behavior of aapt. + // Merge all resources as if they were in the compilation package. This is the old behavior + // of aapt. - // Add the package to the set of --extra-packages so we emit an R.java for - // each library package. + // Add the package to the set of --extra-packages so we emit an R.java for each library + // package. if (!pkg->name.empty()) { options_.extra_java_packages.insert(pkg->name); } + // 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()); @@ -1295,7 +1382,7 @@ class LinkCommand { } for (uint32_t i = 0; i < num_files; i++) { - pb::CompiledFile compiled_file; + pb::internal::CompiledFile compiled_file; if (!input_stream.ReadCompiledFile(&compiled_file)) { context_->GetDiagnostics()->Error(DiagMessage(src) << "failed to read compiled file header"); @@ -1673,8 +1760,7 @@ class LinkCommand { bool error = false; { - // AndroidManifest.xml has no resource name, but the CallSite is built - // from the name + // AndroidManifest.xml has no resource name, but the CallSite is built from the name // (aka, which package the AndroidManifest.xml is coming from). // So we give it a package name so it can see local resources. manifest_xml->file.name.package = context_->GetCompilationPackage(); @@ -1727,72 +1813,7 @@ class LinkCommand { } if (options_.generate_java_class_path) { - // The set of packages whose R class to call in the main classes - // onResourcesLoaded callback. - std::vector<std::string> packages_to_callback; - - JavaClassGeneratorOptions template_options; - template_options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; - template_options.javadoc_annotations = options_.javadoc_annotations; - - if (context_->GetPackageType() == PackageType::kStaticLib || - options_.generate_non_final_ids) { - template_options.use_final = false; - } - - if (context_->GetPackageType() == PackageType::kSharedLib) { - template_options.use_final = false; - template_options.rewrite_callback_options = OnResourcesLoadedCallbackOptions{}; - } - - const StringPiece actual_package = context_->GetCompilationPackage(); - StringPiece output_package = context_->GetCompilationPackage(); - if (options_.custom_java_package) { - // Override the output java package to the custom one. - output_package = options_.custom_java_package.value(); - } - - // Generate the private symbols if required. - if (options_.private_symbols) { - packages_to_callback.push_back(options_.private_symbols.value()); - - // If we defined a private symbols package, we only emit Public symbols - // to the original package, and private and public symbols to the - // private package. - JavaClassGeneratorOptions options = template_options; - options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate; - if (!WriteJavaFile(&final_table_, actual_package, options_.private_symbols.value(), - options)) { - return 1; - } - } - - // Generate all the symbols for all extra packages. - for (const std::string& extra_package : options_.extra_java_packages) { - packages_to_callback.push_back(extra_package); - - JavaClassGeneratorOptions options = template_options; - options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; - if (!WriteJavaFile(&final_table_, actual_package, extra_package, options)) { - return 1; - } - } - - // Generate the main public R class. - JavaClassGeneratorOptions options = template_options; - - // Only generate public symbols if we have a private package. - if (options_.private_symbols) { - options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic; - } - - if (options.rewrite_callback_options) { - options.rewrite_callback_options.value().packages_to_callback = - std::move(packages_to_callback); - } - - if (!WriteJavaFile(&final_table_, actual_package, output_package, options, - options_.generate_text_symbols_path)) { + if (!GenerateJavaClasses()) { return 1; } } diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp index 9d71775889d4..33a1d3a704f1 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -18,6 +18,7 @@ #include <vector> #include "android-base/stringprintf.h" +#include "androidfw/ResourceTypes.h" #include "androidfw/StringPiece.h" #include "Diagnostics.h" @@ -33,15 +34,19 @@ #include "flatten/XmlFlattener.h" #include "io/BigBufferInputStream.h" #include "io/Util.h" +#include "optimize/MultiApkGenerator.h" #include "optimize/ResourceDeduper.h" #include "optimize/VersionCollapser.h" #include "split/TableSplitter.h" #include "util/Files.h" +#include "util/Util.h" using ::aapt::configuration::Abi; using ::aapt::configuration::Artifact; using ::aapt::configuration::PostProcessingConfiguration; +using ::android::ResTable_config; using ::android::StringPiece; +using ::android::base::StringAppendF; using ::android::base::StringPrintf; namespace aapt { @@ -188,42 +193,12 @@ class OptimizeCommand { } if (options_.configuration && options_.output_dir) { - PostProcessingConfiguration& config = options_.configuration.value(); - - // For now, just write out the stripped APK since ABI splitting doesn't modify anything else. - for (const Artifact& artifact : config.artifacts) { - if (artifact.abi_group) { - const std::string& group = artifact.abi_group.value(); - - auto abi_group = config.abi_groups.find(group); - // TODO: Remove validation when configuration parser ensures referential integrity. - if (abi_group == config.abi_groups.end()) { - context_->GetDiagnostics()->Note( - DiagMessage() << "could not find referenced ABI group '" << group << "'"); - return 1; - } - FilterChain filters; - filters.AddFilter(AbiFilter::FromAbiList(abi_group->second)); - - const std::string& path = apk->GetSource().path; - const StringPiece ext = file::GetExtension(path); - const std::string name = path.substr(0, path.rfind(ext.to_string())); - - // Name is hard coded for now since only one split dimension is supported. - // TODO: Incorporate name generation into the configuration objects. - const std::string file_name = - StringPrintf("%s.%s%s", name.c_str(), group.c_str(), ext.data()); - std::string out = options_.output_dir.value(); - file::AppendPath(&out, file_name); - - std::unique_ptr<IArchiveWriter> writer = - CreateZipFileArchiveWriter(context_->GetDiagnostics(), out); - - if (!apk->WriteToArchive(context_, options_.table_flattener_options, &filters, - writer.get())) { - return 1; - } - } + MultiApkGenerator generator{apk.get(), context_}; + MultiApkGeneratorOptions generator_options = {options_.output_dir.value(), + options_.configuration.value(), + options_.table_flattener_options}; + if (!generator.FromBaseApk(generator_options)) { + return 1; } } @@ -260,7 +235,7 @@ class OptimizeCommand { for (auto& entry : type->entries) { for (auto& config_value : entry->values) { - FileReference* file_ref = ValueCast<FileReference>(config_value->value.get()); + auto* file_ref = ValueCast<FileReference>(config_value->value.get()); if (file_ref == nullptr) { continue; } @@ -297,11 +272,8 @@ class OptimizeCommand { } io::BigBufferInputStream table_buffer_in(&table_buffer); - if (!io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc", - ArchiveEntry::kAlign, writer)) { - return false; - } - return true; + return io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc", + ArchiveEntry::kAlign, writer); } OptimizeOptions options_; @@ -349,20 +321,29 @@ int Optimize(const std::vector<StringPiece>& args) { OptimizeOptions options; Maybe<std::string> config_path; Maybe<std::string> target_densities; + Maybe<std::string> target_abis; std::vector<std::string> configs; std::vector<std::string> split_args; bool verbose = false; + bool print_only = false; Flags flags = Flags() .OptionalFlag("-o", "Path to the output APK.", &options.output_path) .OptionalFlag("-d", "Path to the output directory (for splits).", &options.output_dir) .OptionalFlag("-x", "Path to XML configuration file.", &config_path) + .OptionalSwitch("-p", "Print the multi APK artifacts and exit.", &print_only) .OptionalFlag( "--target-densities", "Comma separated list of the screen densities that the APK will be optimized for.\n" "All the resources that would be unused on devices of the given densities will be \n" "removed from the APK.", &target_densities) + .OptionalFlag( + "--target-abis", + "Comma separated list of the CPU ABIs that the APK will be optimized for.\n" + "All the native libraries that would be unused on devices of the given ABIs will \n" + "be removed from the APK.", + &target_abis) .OptionalFlagList("-c", "Comma separated list of configurations to include. The default\n" "is all configurations.", @@ -388,18 +369,19 @@ int Optimize(const std::vector<StringPiece>& args) { return 1; } - std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[0]); + const std::string& apk_path = flags.GetArgs()[0]; + std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(&context, apk_path); if (!apk) { return 1; } context.SetVerbose(verbose); + IDiagnostics* diag = context.GetDiagnostics(); if (target_densities) { // Parse the target screen densities. for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) { - Maybe<uint16_t> target_density = - ParseTargetDensityParameter(config_str, context.GetDiagnostics()); + Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag); if (!target_density) { return 1; } @@ -409,7 +391,7 @@ int Optimize(const std::vector<StringPiece>& args) { std::unique_ptr<IConfigFilter> filter; if (!configs.empty()) { - filter = ParseConfigFilterParameters(configs, context.GetDiagnostics()); + filter = ParseConfigFilterParameters(configs, diag); if (filter == nullptr) { return 1; } @@ -418,28 +400,47 @@ int Optimize(const std::vector<StringPiece>& args) { // Parse the split parameters. for (const std::string& split_arg : split_args) { - options.split_paths.push_back({}); - options.split_constraints.push_back({}); - if (!ParseSplitParameter(split_arg, context.GetDiagnostics(), &options.split_paths.back(), + options.split_paths.emplace_back(); + options.split_constraints.emplace_back(); + if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(), &options.split_constraints.back())) { return 1; } } if (config_path) { - if (!options.output_dir) { - context.GetDiagnostics()->Error( - DiagMessage() << "Output directory is required when using a configuration file"); - return 1; - } std::string& path = config_path.value(); Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path); if (for_path) { - options.configuration = for_path.value().WithDiagnostics(context.GetDiagnostics()).Parse(); + options.configuration = for_path.value().WithDiagnostics(diag).Parse(); } else { - context.GetDiagnostics()->Error(DiagMessage() << "Could not parse config file " << path); + diag->Error(DiagMessage() << "Could not parse config file " << path); return 1; } + + if (print_only) { + std::vector<std::string> names; + const PostProcessingConfiguration& config = options.configuration.value(); + if (!config.AllArtifactNames(file::GetFilename(apk_path), &names, diag)) { + diag->Error(DiagMessage() << "Failed to generate output artifact list"); + return 1; + } + + for (const auto& name : names) { + std::cout << name << std::endl; + } + return 0; + } + + // Since we know that we are going to process the APK (not just print targets), make sure we + // have somewhere to write them to. + if (!options.output_dir) { + diag->Error(DiagMessage() << "Output directory is required when using a configuration file"); + return 1; + } + } else if (print_only) { + diag->Error(DiagMessage() << "Asked to print artifacts without providing a configurations"); + return 1; } if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) { diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp index e1c45d68f611..d17858d45d08 100644 --- a/tools/aapt2/cmd/Util.cpp +++ b/tools/aapt2/cmd/Util.cpp @@ -28,7 +28,7 @@ #include "util/Maybe.h" #include "util/Util.h" -using android::StringPiece; +using ::android::StringPiece; namespace aapt { @@ -134,19 +134,21 @@ static xml::AaptAttribute CreateAttributeWithId(const ResourceId& id) { return xml::AaptAttribute(Attribute(), id); } +static xml::NamespaceDecl CreateAndroidNamespaceDecl() { + xml::NamespaceDecl decl; + decl.prefix = "android"; + decl.uri = xml::kSchemaAndroid; + return decl; +} + std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info, const SplitConstraints& constraints) { const ResourceId kVersionCode(0x0101021b); const ResourceId kRevisionCode(0x010104d5); const ResourceId kHasCode(0x0101000c); - std::unique_ptr<xml::XmlResource> doc = util::make_unique<xml::XmlResource>(); - - std::unique_ptr<xml::Namespace> namespace_android = util::make_unique<xml::Namespace>(); - namespace_android->namespace_uri = xml::kSchemaAndroid; - namespace_android->namespace_prefix = "android"; - std::unique_ptr<xml::Element> manifest_el = util::make_unique<xml::Element>(); + manifest_el->namespace_decls.push_back(CreateAndroidNamespaceDecl()); manifest_el->name = "manifest"; manifest_el->attributes.push_back(xml::Attribute{"", "package", app_info.package}); @@ -179,8 +181,8 @@ std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info, xml::Attribute{"", "configForSplit", app_info.split_name.value()}); } - // Splits may contain more configurations than originally desired (fallback densities, etc.). - // This makes programmatic discovery of split targetting difficult. Encode the original + // Splits may contain more configurations than originally desired (fall-back densities, etc.). + // This makes programmatic discovery of split targeting difficult. Encode the original // split constraints intended for this split. std::stringstream target_config_str; target_config_str << util::Joiner(constraints.configs, ","); @@ -193,8 +195,9 @@ std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info, util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_BOOLEAN, 0u)}); manifest_el->AppendChild(std::move(application_el)); - namespace_android->AppendChild(std::move(manifest_el)); - doc->root = std::move(namespace_android); + + std::unique_ptr<xml::XmlResource> doc = util::make_unique<xml::XmlResource>(); + doc->root = std::move(manifest_el); return doc; } @@ -284,7 +287,7 @@ static Maybe<int> ExtractSdkVersion(xml::Attribute* attr, std::string* out_error Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(xml::XmlResource* xml_res, IDiagnostics* diag) { // Make sure the first element is <manifest> with package attribute. - xml::Element* manifest_el = xml::FindRootElement(xml_res->root.get()); + xml::Element* manifest_el = xml_res->root.get(); if (manifest_el == nullptr) { return {}; } diff --git a/tools/aapt2/compile/InlineXmlFormatParser.cpp b/tools/aapt2/compile/InlineXmlFormatParser.cpp index 786494b6ad1c..a17926067a0b 100644 --- a/tools/aapt2/compile/InlineXmlFormatParser.cpp +++ b/tools/aapt2/compile/InlineXmlFormatParser.cpp @@ -16,12 +16,8 @@ #include "compile/InlineXmlFormatParser.h" -#include <sstream> #include <string> -#include "android-base/macros.h" - -#include "Debug.h" #include "ResourceUtils.h" #include "util/Util.h" #include "xml/XmlDom.h" @@ -31,19 +27,17 @@ namespace aapt { namespace { -/** - * XML Visitor that will find all <aapt:attr> elements for extraction. - */ +struct InlineDeclaration { + xml::Element* el; + std::string attr_namespace_uri; + std::string attr_name; +}; + +// XML Visitor that will find all <aapt:attr> elements for extraction. class Visitor : public xml::PackageAwareVisitor { public: using xml::PackageAwareVisitor::Visit; - struct InlineDeclaration { - xml::Element* el; - std::string attr_namespace_uri; - std::string attr_name; - }; - explicit Visitor(IAaptContext* context, xml::XmlResource* xml_resource) : context_(context), xml_resource_(xml_resource) {} @@ -53,51 +47,44 @@ class Visitor : public xml::PackageAwareVisitor { return; } - const Source& src = xml_resource_->file.source.WithLine(el->line_number); + const Source src = xml_resource_->file.source.WithLine(el->line_number); xml::Attribute* attr = el->FindAttribute({}, "name"); if (!attr) { - context_->GetDiagnostics()->Error(DiagMessage(src) - << "missing 'name' attribute"); + context_->GetDiagnostics()->Error(DiagMessage(src) << "missing 'name' attribute"); error_ = true; return; } Maybe<Reference> ref = ResourceUtils::ParseXmlAttributeName(attr->value); if (!ref) { - context_->GetDiagnostics()->Error( - DiagMessage(src) << "invalid XML attribute '" << attr->value << "'"); + context_->GetDiagnostics()->Error(DiagMessage(src) << "invalid XML attribute '" << attr->value + << "'"); error_ = true; return; } const ResourceName& name = ref.value().name.value(); - // Use an empty string for the compilation package because we don't want to - // default to - // the local package if the user specified name="style" or something. This - // should just + // Use an empty string for the compilation package because we don't want to default to + // the local package if the user specified name="style" or something. This should just // be the default namespace. - Maybe<xml::ExtractedPackage> maybe_pkg = - TransformPackageAlias(name.package, {}); + Maybe<xml::ExtractedPackage> maybe_pkg = TransformPackageAlias(name.package); if (!maybe_pkg) { context_->GetDiagnostics()->Error(DiagMessage(src) - << "invalid namespace prefix '" - << name.package << "'"); + << "invalid namespace prefix '" << name.package << "'"); error_ = true; return; } const xml::ExtractedPackage& pkg = maybe_pkg.value(); - const bool private_namespace = - pkg.private_namespace || ref.value().private_reference; + const bool private_namespace = pkg.private_namespace || ref.value().private_reference; InlineDeclaration decl; decl.el = el; decl.attr_name = name.entry; if (!pkg.package.empty()) { - decl.attr_namespace_uri = - xml::BuildPackageNamespace(pkg.package, private_namespace); + decl.attr_namespace_uri = xml::BuildPackageNamespace(pkg.package, private_namespace); } inline_declarations_.push_back(std::move(decl)); @@ -107,7 +94,9 @@ class Visitor : public xml::PackageAwareVisitor { return inline_declarations_; } - bool HasError() const { return error_; } + bool HasError() const { + return error_; + } private: DISALLOW_COPY_AND_ASSIGN(Visitor); @@ -120,8 +109,7 @@ class Visitor : public xml::PackageAwareVisitor { } // namespace -bool InlineXmlFormatParser::Consume(IAaptContext* context, - xml::XmlResource* doc) { +bool InlineXmlFormatParser::Consume(IAaptContext* context, xml::XmlResource* doc) { Visitor visitor(context, doc); doc->root->Accept(&visitor); if (visitor.HasError()) { @@ -129,69 +117,53 @@ bool InlineXmlFormatParser::Consume(IAaptContext* context, } size_t name_suffix_counter = 0; - for (const Visitor::InlineDeclaration& decl : - visitor.GetInlineDeclarations()) { + for (const InlineDeclaration& decl : visitor.GetInlineDeclarations()) { auto new_doc = util::make_unique<xml::XmlResource>(); new_doc->file.config = doc->file.config; new_doc->file.source = doc->file.source.WithLine(decl.el->line_number); new_doc->file.name = doc->file.name; // Modify the new entry name. We need to suffix the entry with a number to - // avoid - // local collisions, then mangle it with the empty package, such that it - // won't show up + // avoid local collisions, then mangle it with the empty package, such that it won't show up // in R.java. - - new_doc->file.name.entry = - NameMangler::MangleEntry({}, new_doc->file.name.entry + "__" + - std::to_string(name_suffix_counter)); + new_doc->file.name.entry = NameMangler::MangleEntry( + {}, new_doc->file.name.entry + "__" + std::to_string(name_suffix_counter)); // Extracted elements must be the only child of <aapt:attr>. // Make sure there is one root node in the children (ignore empty text). - for (auto& child : decl.el->children) { + for (std::unique_ptr<xml::Node>& child : decl.el->children) { const Source child_source = doc->file.source.WithLine(child->line_number); if (xml::Text* t = xml::NodeCast<xml::Text>(child.get())) { if (!util::TrimWhitespace(t->text).empty()) { - context->GetDiagnostics()->Error( - DiagMessage(child_source) - << "can't extract text into its own resource"); + context->GetDiagnostics()->Error(DiagMessage(child_source) + << "can't extract text into its own resource"); return false; } } else if (new_doc->root) { - context->GetDiagnostics()->Error( - DiagMessage(child_source) - << "inline XML resources must have a single root"); + context->GetDiagnostics()->Error(DiagMessage(child_source) + << "inline XML resources must have a single root"); return false; } else { - new_doc->root = std::move(child); + new_doc->root.reset(static_cast<xml::Element*>(child.release())); new_doc->root->parent = nullptr; } } - // Walk up and find the parent element. - xml::Node* node = decl.el; - xml::Element* parent_el = nullptr; - while (node->parent && - (parent_el = xml::NodeCast<xml::Element>(node->parent)) == nullptr) { - node = node->parent; - } - + // Get the parent element of <aapt:attr> + xml::Element* parent_el = decl.el->parent; if (!parent_el) { - context->GetDiagnostics()->Error( - DiagMessage(new_doc->file.source) - << "no suitable parent for inheriting attribute"); + context->GetDiagnostics()->Error(DiagMessage(new_doc->file.source) + << "no suitable parent for inheriting attribute"); return false; } // Add the inline attribute to the parent. - parent_el->attributes.push_back( - xml::Attribute{decl.attr_namespace_uri, decl.attr_name, - "@" + new_doc->file.name.ToString()}); + parent_el->attributes.push_back(xml::Attribute{decl.attr_namespace_uri, decl.attr_name, + "@" + new_doc->file.name.ToString()}); // Delete the subtree. - for (auto iter = parent_el->children.begin(); - iter != parent_el->children.end(); ++iter) { - if (iter->get() == node) { + for (auto iter = parent_el->children.begin(); iter != parent_el->children.end(); ++iter) { + if (iter->get() == decl.el) { parent_el->children.erase(iter); break; } diff --git a/tools/aapt2/compile/InlineXmlFormatParser.h b/tools/aapt2/compile/InlineXmlFormatParser.h index 1a658fd6a180..4300023e7726 100644 --- a/tools/aapt2/compile/InlineXmlFormatParser.h +++ b/tools/aapt2/compile/InlineXmlFormatParser.h @@ -26,35 +26,30 @@ namespace aapt { -/** - * Extracts Inline XML definitions into their own xml::XmlResource objects. - * - * Inline XML looks like: - * - * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" - * xmlns:aapt="http://schemas.android.com/aapt" > - * <aapt:attr name="android:drawable" > - * <vector - * android:height="64dp" - * android:width="64dp" - * android:viewportHeight="600" - * android:viewportWidth="600"/> - * </aapt:attr> - * </animated-vector> - * - * The <vector> will be extracted into its own XML file and <animated-vector> - * will - * gain an attribute 'android:drawable' set to a reference to the extracted - * <vector> resource. - */ +// Extracts Inline XML definitions into their own xml::XmlResource objects. +// +// Inline XML looks like: +// +// <animated-vector xmlns:android="http://schemas.android.com/apk/res/android" +// xmlns:aapt="http://schemas.android.com/aapt" > +// <aapt:attr name="android:drawable" > +// <vector +// android:height="64dp" +// android:width="64dp" +// android:viewportHeight="600" +// android:viewportWidth="600"/> +// </aapt:attr> +// </animated-vector> +// +// The <vector> will be extracted into its own XML file and <animated-vector> will +// gain an attribute 'android:drawable' set to a reference to the extracted <vector> resource. class InlineXmlFormatParser : public IXmlResourceConsumer { public: explicit InlineXmlFormatParser() = default; bool Consume(IAaptContext* context, xml::XmlResource* doc) override; - std::vector<std::unique_ptr<xml::XmlResource>>& - GetExtractedInlineXmlDocuments() { + std::vector<std::unique_ptr<xml::XmlResource>>& GetExtractedInlineXmlDocuments() { return queue_; } diff --git a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp index 348796c98c22..de7739ada407 100644 --- a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp +++ b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp @@ -18,25 +18,32 @@ #include "test/Test.h" +using ::testing::Eq; +using ::testing::IsNull; +using ::testing::Not; +using ::testing::NotNull; +using ::testing::SizeIs; +using ::testing::StrEq; + namespace aapt { TEST(InlineXmlFormatParserTest, PassThrough) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"( <View xmlns:android="http://schemas.android.com/apk/res/android"> <View android:text="hey"> <View android:id="hi" /> </View> - </View>)EOF"); + </View>)"); InlineXmlFormatParser parser; ASSERT_TRUE(parser.Consume(context.get(), doc.get())); - EXPECT_EQ(0u, parser.GetExtractedInlineXmlDocuments().size()); + EXPECT_THAT(parser.GetExtractedInlineXmlDocuments(), SizeIs(0u)); } TEST(InlineXmlFormatParserTest, ExtractOneXmlResource) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"( <View1 xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"> <aapt:attr name="android:text"> @@ -44,7 +51,7 @@ TEST(InlineXmlFormatParserTest, ExtractOneXmlResource) { <View3 android:id="hi" /> </View2> </aapt:attr> - </View1>)EOF"); + </View1>)"); doc->file.name = test::ParseNameOrDie("layout/main"); @@ -52,42 +59,38 @@ TEST(InlineXmlFormatParserTest, ExtractOneXmlResource) { ASSERT_TRUE(parser.Consume(context.get(), doc.get())); // One XML resource should have been extracted. - EXPECT_EQ(1u, parser.GetExtractedInlineXmlDocuments().size()); - - xml::Element* el = xml::FindRootElement(doc.get()); - ASSERT_NE(nullptr, el); + EXPECT_THAT(parser.GetExtractedInlineXmlDocuments(), SizeIs(1u)); - EXPECT_EQ("View1", el->name); + xml::Element* el = doc->root.get(); + ASSERT_THAT(el, NotNull()); + EXPECT_THAT(el->name, StrEq("View1")); // The <aapt:attr> tag should be extracted. - EXPECT_EQ(nullptr, el->FindChild(xml::kSchemaAapt, "attr")); + EXPECT_THAT(el->FindChild(xml::kSchemaAapt, "attr"), IsNull()); // The 'android:text' attribute should be set with a reference. xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "text"); - ASSERT_NE(nullptr, attr); + ASSERT_THAT(attr, NotNull()); ResourceNameRef name_ref; ASSERT_TRUE(ResourceUtils::ParseReference(attr->value, &name_ref)); - xml::XmlResource* extracted_doc = - parser.GetExtractedInlineXmlDocuments()[0].get(); - ASSERT_NE(nullptr, extracted_doc); + xml::XmlResource* extracted_doc = parser.GetExtractedInlineXmlDocuments()[0].get(); + ASSERT_THAT(extracted_doc, NotNull()); // Make sure the generated reference is correct. - EXPECT_EQ(name_ref.package, extracted_doc->file.name.package); - EXPECT_EQ(name_ref.type, extracted_doc->file.name.type); - EXPECT_EQ(name_ref.entry, extracted_doc->file.name.entry); + EXPECT_THAT(extracted_doc->file.name, Eq(name_ref)); // Verify the structure of the extracted XML. - el = xml::FindRootElement(extracted_doc); - ASSERT_NE(nullptr, el); - EXPECT_EQ("View2", el->name); - EXPECT_NE(nullptr, el->FindChild({}, "View3")); + el = extracted_doc->root.get(); + ASSERT_THAT(el, NotNull()); + EXPECT_THAT(el->name, StrEq("View2")); + EXPECT_THAT(el->FindChild({}, "View3"), NotNull()); } TEST(InlineXmlFormatParserTest, ExtractTwoXmlResources) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"( <View1 xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"> <aapt:attr name="android:text"> @@ -99,45 +102,39 @@ TEST(InlineXmlFormatParserTest, ExtractTwoXmlResources) { <aapt:attr name="android:drawable"> <vector /> </aapt:attr> - </View1>)EOF"); + </View1>)"); doc->file.name = test::ParseNameOrDie("layout/main"); InlineXmlFormatParser parser; ASSERT_TRUE(parser.Consume(context.get(), doc.get())); - ASSERT_EQ(2u, parser.GetExtractedInlineXmlDocuments().size()); - - xml::Element* el = xml::FindRootElement(doc.get()); - ASSERT_NE(nullptr, el); + ASSERT_THAT(parser.GetExtractedInlineXmlDocuments(), SizeIs(2u)); - EXPECT_EQ("View1", el->name); + xml::Element* el = doc->root.get(); + ASSERT_THAT(el, NotNull()); + EXPECT_THAT(el->name, StrEq("View1")); xml::Attribute* attr_text = el->FindAttribute(xml::kSchemaAndroid, "text"); - ASSERT_NE(nullptr, attr_text); + ASSERT_THAT(attr_text, NotNull()); - xml::Attribute* attr_drawable = - el->FindAttribute(xml::kSchemaAndroid, "drawable"); - ASSERT_NE(nullptr, attr_drawable); + xml::Attribute* attr_drawable = el->FindAttribute(xml::kSchemaAndroid, "drawable"); + ASSERT_THAT(attr_drawable, NotNull()); // The two extracted resources should have different names. - EXPECT_NE(attr_text->value, attr_drawable->value); + EXPECT_THAT(attr_text->value, Not(Eq(attr_drawable->value))); // The child <aapt:attr> elements should be gone. - EXPECT_EQ(nullptr, el->FindChild(xml::kSchemaAapt, "attr")); - - xml::XmlResource* extracted_doc_text = - parser.GetExtractedInlineXmlDocuments()[0].get(); - ASSERT_NE(nullptr, extracted_doc_text); - el = xml::FindRootElement(extracted_doc_text); - ASSERT_NE(nullptr, el); - EXPECT_EQ("View2", el->name); - - xml::XmlResource* extracted_doc_drawable = - parser.GetExtractedInlineXmlDocuments()[1].get(); - ASSERT_NE(nullptr, extracted_doc_drawable); - el = xml::FindRootElement(extracted_doc_drawable); - ASSERT_NE(nullptr, el); - EXPECT_EQ("vector", el->name); + EXPECT_THAT(el->FindChild(xml::kSchemaAapt, "attr"), IsNull()); + + xml::XmlResource* extracted_doc_text = parser.GetExtractedInlineXmlDocuments()[0].get(); + ASSERT_THAT(extracted_doc_text, NotNull()); + ASSERT_THAT(extracted_doc_text->root, NotNull()); + EXPECT_THAT(extracted_doc_text->root->name, StrEq("View2")); + + xml::XmlResource* extracted_doc_drawable = parser.GetExtractedInlineXmlDocuments()[1].get(); + ASSERT_THAT(extracted_doc_drawable, NotNull()); + ASSERT_THAT(extracted_doc_drawable->root, NotNull()); + EXPECT_THAT(extracted_doc_drawable->root->name, StrEq("vector")); } } // namespace aapt diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp index d051120b9445..9d6d3286f0ef 100644 --- a/tools/aapt2/configuration/ConfigurationParser.cpp +++ b/tools/aapt2/configuration/ConfigurationParser.cpp @@ -22,13 +22,15 @@ #include <memory> #include <utility> -#include <android-base/file.h> -#include <android-base/logging.h> +#include "android-base/file.h" +#include "android-base/logging.h" #include "ConfigDescription.h" #include "Diagnostics.h" #include "io/File.h" #include "io/FileSystem.h" +#include "io/StringInputStream.h" +#include "util/Files.h" #include "util/Maybe.h" #include "util/Util.h" #include "xml/XmlActionExecutor.h" @@ -49,14 +51,15 @@ using ::aapt::configuration::Group; using ::aapt::configuration::Locale; using ::aapt::io::IFile; using ::aapt::io::RegularFile; +using ::aapt::io::StringInputStream; using ::aapt::util::TrimWhitespace; using ::aapt::xml::Element; -using ::aapt::xml::FindRootElement; using ::aapt::xml::NodeCast; using ::aapt::xml::XmlActionExecutor; using ::aapt::xml::XmlActionExecutorPolicy; using ::aapt::xml::XmlNodeAction; using ::android::base::ReadFileToString; +using ::android::StringPiece; const std::unordered_map<std::string, Abi> kStringToAbiMap = { {"armeabi", Abi::kArmeV6}, {"armeabi-v7a", Abi::kArmV7a}, {"arm64-v8a", Abi::kArm64V8a}, @@ -115,56 +118,141 @@ const std::string& AbiToString(Abi abi) { * success, or false if the either the placeholder is not found in the name, or the value is not * present and the placeholder was. */ -static bool ReplacePlaceholder(const std::string& placeholder, const Maybe<std::string>& value, +static bool ReplacePlaceholder(const StringPiece& placeholder, const Maybe<StringPiece>& value, std::string* name, IDiagnostics* diag) { - size_t offset = name->find(placeholder); - if (value) { - if (offset == std::string::npos) { + size_t offset = name->find(placeholder.data()); + bool found = (offset != std::string::npos); + + // Make sure the placeholder was present if the desired value is present. + if (!found) { + if (value) { diag->Error(DiagMessage() << "Missing placeholder for artifact: " << placeholder); return false; } - name->replace(offset, placeholder.length(), value.value()); return true; } + DCHECK(found) << "Missing return path for placeholder not found"; + // Make sure the placeholder was not present if the desired value was not present. - bool result = (offset == std::string::npos); - if (!result) { + if (!value) { diag->Error(DiagMessage() << "Placeholder present but no value for artifact: " << placeholder); + return false; + } + + name->replace(offset, placeholder.length(), value.value().data()); + + // Make sure there was only one instance of the placeholder. + if (name->find(placeholder.data()) != std::string::npos) { + diag->Error(DiagMessage() << "Placeholder present multiple times: " << placeholder); + return false; + } + return true; +} + +/** + * Returns the common artifact base name from a template string. + */ +Maybe<std::string> ToBaseName(std::string result, const StringPiece& apk_name, IDiagnostics* diag) { + const StringPiece ext = file::GetExtension(apk_name); + size_t end_index = apk_name.to_string().rfind(ext.to_string()); + const std::string base_name = + (end_index != std::string::npos) ? std::string{apk_name.begin(), end_index} : ""; + + // Base name is optional. + if (result.find("${basename}") != std::string::npos) { + Maybe<StringPiece> maybe_base_name = + base_name.empty() ? Maybe<StringPiece>{} : Maybe<StringPiece>{base_name}; + if (!ReplacePlaceholder("${basename}", maybe_base_name, &result, diag)) { + return {}; + } } + + // Extension is optional. + if (result.find("${ext}") != std::string::npos) { + // Make sure we disregard the '.' in the extension when replacing the placeholder. + if (!ReplacePlaceholder("${ext}", {ext.substr(1)}, &result, diag)) { + return {}; + } + } else { + // If no extension is specified, and the name template does not end in the current extension, + // add the existing extension. + if (!util::EndsWith(result, ext)) { + result.append(ext.to_string()); + } + } + return result; } -Maybe<std::string> Artifact::ToArtifactName(const std::string& format, IDiagnostics* diag) const { - std::string result = format; +Maybe<std::string> Artifact::ToArtifactName(const StringPiece& format, const StringPiece& apk_name, + IDiagnostics* diag) const { + Maybe<std::string> base = ToBaseName(format.to_string(), apk_name, diag); + if (!base) { + return {}; + } + std::string result = std::move(base.value()); - if (!ReplacePlaceholder("{abi}", abi_group, &result, diag)) { + if (!ReplacePlaceholder("${abi}", abi_group, &result, diag)) { return {}; } - if (!ReplacePlaceholder("{density}", screen_density_group, &result, diag)) { + if (!ReplacePlaceholder("${density}", screen_density_group, &result, diag)) { return {}; } - if (!ReplacePlaceholder("{locale}", locale_group, &result, diag)) { + if (!ReplacePlaceholder("${locale}", locale_group, &result, diag)) { return {}; } - if (!ReplacePlaceholder("{sdk}", android_sdk_group, &result, diag)) { + if (!ReplacePlaceholder("${sdk}", android_sdk_group, &result, diag)) { return {}; } - if (!ReplacePlaceholder("{feature}", device_feature_group, &result, diag)) { + if (!ReplacePlaceholder("${feature}", device_feature_group, &result, diag)) { return {}; } - if (!ReplacePlaceholder("{gl}", gl_texture_group, &result, diag)) { + if (!ReplacePlaceholder("${gl}", gl_texture_group, &result, diag)) { return {}; } return result; } +Maybe<std::string> Artifact::Name(const StringPiece& apk_name, IDiagnostics* diag) const { + if (!name) { + return {}; + } + + return ToBaseName(name.value(), apk_name, diag); +} + +bool PostProcessingConfiguration::AllArtifactNames(const StringPiece& apk_name, + std::vector<std::string>* artifact_names, + IDiagnostics* diag) const { + for (const auto& artifact : artifacts) { + Maybe<std::string> name; + if (artifact.name) { + name = artifact.Name(apk_name, diag); + } else { + if (!artifact_format) { + diag->Error(DiagMessage() << "No global artifact template and an artifact name is missing"); + return false; + } + name = artifact.ToArtifactName(artifact_format.value(), apk_name, diag); + } + + if (!name) { + return false; + } + + artifact_names->push_back(std::move(name.value())); + } + + return true; +} + } // namespace configuration /** Returns a ConfigurationParser for the file located at the provided path. */ @@ -182,15 +270,14 @@ ConfigurationParser::ConfigurationParser(std::string contents) } Maybe<PostProcessingConfiguration> ConfigurationParser::Parse() { - std::istringstream in(contents_); - - auto doc = xml::Inflate(&in, diag_, Source("config.xml")); + StringInputStream in(contents_); + std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, diag_, Source("config.xml")); if (!doc) { return {}; } // Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace. - auto* root = FindRootElement(doc.get()); + Element* root = doc->root.get(); if (root == nullptr) { diag_->Error(DiagMessage() << "Could not find the root element in the XML document"); return {}; @@ -333,7 +420,10 @@ ConfigurationParser::ActionHandler ConfigurationParser::screen_density_group_han if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { ConfigDescription config_descriptor; const android::StringPiece& text = TrimWhitespace(t->text); - if (ConfigDescription::Parse(text, &config_descriptor)) { + bool parsed = ConfigDescription::Parse(text, &config_descriptor); + if (parsed && + (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) == + android::ResTable_config::CONFIG_DENSITY)) { // Copy the density with the minimum SDK version stripped out. group.push_back(config_descriptor.CopyWithoutSdkVersion()); } else { @@ -366,17 +456,25 @@ ConfigurationParser::ActionHandler ConfigurationParser::locale_group_handler_ = << child->name); valid = false; } else { - Locale entry; - for (const auto& attr : child->attributes) { - if (attr.name == "lang") { - entry.lang = {attr.value}; - } else if (attr.name == "region") { - entry.region = {attr.value}; - } else { - diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value); + for (auto& node : child->children) { + xml::Text* t; + if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { + ConfigDescription config_descriptor; + const android::StringPiece& text = TrimWhitespace(t->text); + bool parsed = ConfigDescription::Parse(text, &config_descriptor); + if (parsed && + (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) == + android::ResTable_config::CONFIG_LOCALE)) { + // Copy the locale with the minimum SDK version stripped out. + group.push_back(config_descriptor.CopyWithoutSdkVersion()); + } else { + diag->Error(DiagMessage() + << "Could not parse config descriptor for screen-density: " << text); + valid = false; + } + break; } } - group.push_back(entry); } } @@ -390,8 +488,8 @@ ConfigurationParser::ActionHandler ConfigurationParser::android_sdk_group_handle return false; } - auto& group = config->android_sdk_groups[label]; bool valid = true; + bool found = false; for (auto* child : root_element->GetChildElements()) { if (child->name != "android-sdk") { @@ -422,7 +520,11 @@ ConfigurationParser::ActionHandler ConfigurationParser::android_sdk_group_handle } } - group.push_back(entry); + config->android_sdk_groups[label] = entry; + if (found) { + valid = false; + } + found = true; } } diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h index 28c355e39643..9bc9081b8ae4 100644 --- a/tools/aapt2/configuration/ConfigurationParser.h +++ b/tools/aapt2/configuration/ConfigurationParser.h @@ -33,10 +33,14 @@ namespace configuration { template<class T> using Group = std::unordered_map<std::string, std::vector<T>>; +/** A mapping of group label to a single configuration item. */ +template <class T> +using Entry = std::unordered_map<std::string, T>; + /** Output artifact configuration options. */ struct Artifact { /** Name to use for output of processing foo.apk -> foo.<name>.apk. */ - std::string name; + Maybe<std::string> name; /** If present, uses the ABI group with this name. */ Maybe<std::string> abi_group; /** If present, uses the screen density group with this name. */ @@ -51,7 +55,11 @@ struct Artifact { Maybe<std::string> gl_texture_group; /** Convert an artifact name template into a name string based on configuration contents. */ - Maybe<std::string> ToArtifactName(const std::string& format, IDiagnostics* diag) const; + Maybe<std::string> ToArtifactName(const android::StringPiece& format, + const android::StringPiece& apk_name, IDiagnostics* diag) const; + + /** Convert an artifact name template into a name string based on configuration contents. */ + Maybe<std::string> Name(const android::StringPiece& apk_name, IDiagnostics* diag) const; }; /** Enumeration of currently supported ABIs. */ @@ -100,6 +108,12 @@ struct AndroidSdk { Maybe<std::string> max_sdk_version; Maybe<AndroidManifest> manifest; + static AndroidSdk ForMinSdk(std::string min_sdk) { + AndroidSdk sdk; + sdk.min_sdk_version = {std::move(min_sdk)}; + return sdk; + } + inline friend bool operator==(const AndroidSdk& lhs, const AndroidSdk& rhs) { return lhs.min_sdk_version == rhs.min_sdk_version && lhs.target_sdk_version == rhs.target_sdk_version && @@ -129,10 +143,14 @@ struct PostProcessingConfiguration { Group<Abi> abi_groups; Group<ConfigDescription> screen_density_groups; - Group<Locale> locale_groups; - Group<AndroidSdk> android_sdk_groups; + Group<ConfigDescription> locale_groups; + Entry<AndroidSdk> android_sdk_groups; Group<DeviceFeature> device_feature_groups; Group<GlTexture> gl_texture_groups; + + /** Helper method that generates a list of artifact names and returns true on success. */ + bool AllArtifactNames(const android::StringPiece& apk_name, + std::vector<std::string>* artifact_names, IDiagnostics* diag) const; }; } // namespace configuration diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp index fb71e98d2fb5..7ffb3d515079 100644 --- a/tools/aapt2/configuration/ConfigurationParser_test.cpp +++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp @@ -18,9 +18,6 @@ #include <string> -#include <gmock/gmock.h> -#include <gtest/gtest.h> - #include "androidfw/ResourceTypes.h" #include "test/Test.h" @@ -29,7 +26,7 @@ namespace aapt { namespace { -using android::ResTable_config; +using ::android::ResTable_config; using configuration::Abi; using configuration::AndroidSdk; using configuration::Artifact; @@ -38,7 +35,7 @@ using configuration::DeviceFeature; using configuration::GlTexture; using configuration::Locale; using configuration::AndroidManifest; -using testing::ElementsAre; +using ::testing::ElementsAre; using xml::Element; using xml::NodeCast; @@ -67,24 +64,21 @@ constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?> <screen-density>xxxhdpi</screen-density> </screen-density-group> <locale-group label="europe"> - <locale lang="en"/> - <locale lang="es"/> - <locale lang="fr"/> - <locale lang="de"/> + <locale>en</locale> + <locale>es</locale> + <locale>fr</locale> + <locale>de</locale> </locale-group> <locale-group label="north-america"> - <locale lang="en"/> - <locale lang="es" region="MX"/> - <locale lang="fr" region="CA"/> - </locale-group> - <locale-group label="all"> - <locale/> + <locale>en</locale> + <locale>es-rMX</locale> + <locale>fr-rCA</locale> </locale-group> - <android-sdk-group label="19"> + <android-sdk-group label="v19"> <android-sdk - minSdkVersion="19" - targetSdkVersion="24" - maxSdkVersion="25"> + minSdkVersion="v19" + targetSdkVersion="v24" + maxSdkVersion="v25"> <manifest> <!--- manifest additions here XSLT? TODO --> </manifest> @@ -108,7 +102,7 @@ constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?> abi-group="arm" screen-density-group="large" locale-group="europe" - android-sdk-group="19" + android-sdk-group="v19" gl-texture-group="dxt1" device-feature-group="low-latency"/> <artifact @@ -116,7 +110,7 @@ constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?> abi-group="other" screen-density-group="alldpi" locale-group="north-america" - android-sdk-group="19" + android-sdk-group="v19" gl-texture-group="dxt1" device-feature-group="low-latency"/> </artifacts> @@ -156,13 +150,13 @@ TEST_F(ConfigurationParserTest, ValidateFile) { EXPECT_EQ(3ul, value.screen_density_groups["large"].size()); EXPECT_EQ(6ul, value.screen_density_groups["alldpi"].size()); - EXPECT_EQ(3ul, value.locale_groups.size()); + EXPECT_EQ(2ul, value.locale_groups.size()); EXPECT_EQ(4ul, value.locale_groups["europe"].size()); EXPECT_EQ(3ul, value.locale_groups["north-america"].size()); - EXPECT_EQ(1ul, value.locale_groups["all"].size()); EXPECT_EQ(1ul, value.android_sdk_groups.size()); - EXPECT_EQ(1ul, value.android_sdk_groups["19"].size()); + EXPECT_TRUE(value.android_sdk_groups["v19"].min_sdk_version); + EXPECT_EQ("v19", value.android_sdk_groups["v19"].min_sdk_version.value()); EXPECT_EQ(1ul, value.gl_texture_groups.size()); EXPECT_EQ(1ul, value.gl_texture_groups["dxt1"].size()); @@ -185,24 +179,24 @@ TEST_F(ConfigurationParserTest, ArtifactAction) { abi-group="arm" screen-density-group="large" locale-group="europe" - android-sdk-group="19" + android-sdk-group="v19" gl-texture-group="dxt1" device-feature-group="low-latency"/>)xml"; auto doc = test::BuildXmlDom(xml); PostProcessingConfiguration config; - bool ok = artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); + bool ok = artifact_handler_(&config, NodeCast<Element>(doc->root.get()), &diag_); ASSERT_TRUE(ok); EXPECT_EQ(1ul, config.artifacts.size()); auto& artifact = config.artifacts.front(); - EXPECT_EQ("", artifact.name); // TODO: make this fail. + EXPECT_FALSE(artifact.name); // TODO: make this fail. EXPECT_EQ("arm", artifact.abi_group.value()); EXPECT_EQ("large", artifact.screen_density_group.value()); EXPECT_EQ("europe", artifact.locale_group.value()); - EXPECT_EQ("19", artifact.android_sdk_group.value()); + EXPECT_EQ("v19", artifact.android_sdk_group.value()); EXPECT_EQ("dxt1", artifact.gl_texture_group.value()); EXPECT_EQ("low-latency", artifact.device_feature_group.value()); @@ -212,7 +206,7 @@ TEST_F(ConfigurationParserTest, ArtifactAction) { abi-group="other" screen-density-group="large" locale-group="europe" - android-sdk-group="19" + android-sdk-group="v19" gl-texture-group="dxt1" device-feature-group="low-latency"/>)xml"; doc = test::BuildXmlDom(second); @@ -298,10 +292,10 @@ TEST_F(ConfigurationParserTest, ScreenDensityGroupAction) { TEST_F(ConfigurationParserTest, LocaleGroupAction) { static constexpr const char* xml = R"xml( <locale-group label="europe"> - <locale lang="en"/> - <locale lang="es"/> - <locale lang="fr"/> - <locale lang="de"/> + <locale>en</locale> + <locale>es</locale> + <locale>fr</locale> + <locale>de</locale> </locale-group>)xml"; auto doc = test::BuildXmlDom(xml); @@ -313,27 +307,23 @@ TEST_F(ConfigurationParserTest, LocaleGroupAction) { ASSERT_EQ(1ul, config.locale_groups.size()); ASSERT_EQ(1u, config.locale_groups.count("europe")); - auto& out = config.locale_groups["europe"]; + const auto& out = config.locale_groups["europe"]; - Locale en; - en.lang = std::string("en"); - Locale es; - es.lang = std::string("es"); - Locale fr; - fr.lang = std::string("fr"); - Locale de; - de.lang = std::string("de"); + ConfigDescription en = test::ParseConfigOrDie("en"); + ConfigDescription es = test::ParseConfigOrDie("es"); + ConfigDescription fr = test::ParseConfigOrDie("fr"); + ConfigDescription de = test::ParseConfigOrDie("de"); ASSERT_THAT(out, ElementsAre(en, es, fr, de)); } TEST_F(ConfigurationParserTest, AndroidSdkGroupAction) { static constexpr const char* xml = R"xml( - <android-sdk-group label="19"> + <android-sdk-group label="v19"> <android-sdk - minSdkVersion="19" - targetSdkVersion="24" - maxSdkVersion="25"> + minSdkVersion="v19" + targetSdkVersion="v24" + maxSdkVersion="v25"> <manifest> <!--- manifest additions here XSLT? TODO --> </manifest> @@ -347,18 +337,17 @@ TEST_F(ConfigurationParserTest, AndroidSdkGroupAction) { ASSERT_TRUE(ok); ASSERT_EQ(1ul, config.android_sdk_groups.size()); - ASSERT_EQ(1u, config.android_sdk_groups.count("19")); + ASSERT_EQ(1u, config.android_sdk_groups.count("v19")); - auto& out = config.android_sdk_groups["19"]; + auto& out = config.android_sdk_groups["v19"]; AndroidSdk sdk; - sdk.min_sdk_version = std::string("19"); - sdk.target_sdk_version = std::string("24"); - sdk.max_sdk_version = std::string("25"); + sdk.min_sdk_version = std::string("v19"); + sdk.target_sdk_version = std::string("v24"); + sdk.max_sdk_version = std::string("v25"); sdk.manifest = AndroidManifest(); - ASSERT_EQ(1ul, out.size()); - ASSERT_EQ(sdk, out[0]); + ASSERT_EQ(sdk, out); } TEST_F(ConfigurationParserTest, GlTextureGroupAction) { @@ -418,21 +407,43 @@ TEST_F(ConfigurationParserTest, DeviceFeatureGroupAction) { ASSERT_THAT(out, ElementsAre(low_latency, pro)); } +// Artifact name parser test cases. + TEST(ArtifactTest, Simple) { StdErrDiagnostics diag; Artifact x86; x86.abi_group = {"x86"}; - auto x86_result = x86.ToArtifactName("something.{abi}.apk", &diag); + auto x86_result = x86.ToArtifactName("something.${abi}.apk", "", &diag); ASSERT_TRUE(x86_result); EXPECT_EQ(x86_result.value(), "something.x86.apk"); Artifact arm; arm.abi_group = {"armeabi-v7a"}; - auto arm_result = arm.ToArtifactName("app.{abi}.apk", &diag); - ASSERT_TRUE(arm_result); - EXPECT_EQ(arm_result.value(), "app.armeabi-v7a.apk"); + { + auto arm_result = arm.ToArtifactName("app.${abi}.apk", "", &diag); + ASSERT_TRUE(arm_result); + EXPECT_EQ(arm_result.value(), "app.armeabi-v7a.apk"); + } + + { + auto arm_result = arm.ToArtifactName("app.${abi}.apk", "different_name.apk", &diag); + ASSERT_TRUE(arm_result); + EXPECT_EQ(arm_result.value(), "app.armeabi-v7a.apk"); + } + + { + auto arm_result = arm.ToArtifactName("${basename}.${abi}.apk", "app.apk", &diag); + ASSERT_TRUE(arm_result); + EXPECT_EQ(arm_result.value(), "app.armeabi-v7a.apk"); + } + + { + auto arm_result = arm.ToArtifactName("app.${abi}.${ext}", "app.apk", &diag); + ASSERT_TRUE(arm_result); + EXPECT_EQ(arm_result.value(), "app.armeabi-v7a.apk"); + } } TEST(ArtifactTest, Complex) { @@ -443,12 +454,42 @@ TEST(ArtifactTest, Complex) { artifact.device_feature_group = {"df1"}; artifact.gl_texture_group = {"glx1"}; artifact.locale_group = {"en-AU"}; - artifact.android_sdk_group = {"26"}; - - auto result = - artifact.ToArtifactName("app.{density}_{locale}_{feature}_{gl}.sdk{sdk}.{abi}.apk", &diag); - ASSERT_TRUE(result); - EXPECT_EQ(result.value(), "app.ldpi_en-AU_df1_glx1.sdk26.mips64.apk"); + artifact.android_sdk_group = {"v26"}; + + { + auto result = artifact.ToArtifactName( + "app.${density}_${locale}_${feature}_${gl}.${sdk}.${abi}.apk", "", &diag); + ASSERT_TRUE(result); + EXPECT_EQ(result.value(), "app.ldpi_en-AU_df1_glx1.v26.mips64.apk"); + } + + { + auto result = artifact.ToArtifactName( + "app.${density}_${locale}_${feature}_${gl}.${sdk}.${abi}.apk", "app.apk", &diag); + ASSERT_TRUE(result); + EXPECT_EQ(result.value(), "app.ldpi_en-AU_df1_glx1.v26.mips64.apk"); + } + + { + auto result = artifact.ToArtifactName( + "${basename}.${density}_${locale}_${feature}_${gl}.${sdk}.${abi}.apk", "app.apk", &diag); + ASSERT_TRUE(result); + EXPECT_EQ(result.value(), "app.ldpi_en-AU_df1_glx1.v26.mips64.apk"); + } + + { + auto result = artifact.ToArtifactName( + "app.${density}_${locale}_${feature}_${gl}.${sdk}.${abi}.${ext}", "app.apk", &diag); + ASSERT_TRUE(result); + EXPECT_EQ(result.value(), "app.ldpi_en-AU_df1_glx1.v26.mips64.apk"); + } + + { + auto result = artifact.ToArtifactName( + "${basename}.${density}_${locale}_${feature}_${gl}.${sdk}.${abi}", "app.apk", &diag); + ASSERT_TRUE(result); + EXPECT_EQ(result.value(), "app.ldpi_en-AU_df1_glx1.v26.mips64.apk"); + } } TEST(ArtifactTest, Missing) { @@ -456,16 +497,69 @@ TEST(ArtifactTest, Missing) { Artifact x86; x86.abi_group = {"x86"}; - EXPECT_FALSE(x86.ToArtifactName("something.{density}.apk", &diag)); - EXPECT_FALSE(x86.ToArtifactName("something.apk", &diag)); + EXPECT_FALSE(x86.ToArtifactName("something.${density}.apk", "", &diag)); + EXPECT_FALSE(x86.ToArtifactName("something.apk", "", &diag)); + EXPECT_FALSE(x86.ToArtifactName("something.${density}.apk", "something.apk", &diag)); + EXPECT_FALSE(x86.ToArtifactName("something.apk", "something.apk", &diag)); } TEST(ArtifactTest, Empty) { StdErrDiagnostics diag; Artifact artifact; - EXPECT_FALSE(artifact.ToArtifactName("something.{density}.apk", &diag)); - EXPECT_TRUE(artifact.ToArtifactName("something.apk", &diag)); + EXPECT_FALSE(artifact.ToArtifactName("something.${density}.apk", "", &diag)); + EXPECT_TRUE(artifact.ToArtifactName("something.apk", "", &diag)); + EXPECT_FALSE(artifact.ToArtifactName("something.${density}.apk", "something.apk", &diag)); + EXPECT_TRUE(artifact.ToArtifactName("something.apk", "something.apk", &diag)); +} + +TEST(ArtifactTest, Repeated) { + StdErrDiagnostics diag; + Artifact artifact; + artifact.screen_density_group = {"mdpi"}; + + ASSERT_TRUE(artifact.ToArtifactName("something.${density}.apk", "", &diag)); + EXPECT_FALSE(artifact.ToArtifactName("something.${density}.${density}.apk", "", &diag)); + ASSERT_TRUE(artifact.ToArtifactName("something.${density}.apk", "something.apk", &diag)); +} + +TEST(ArtifactTest, Nesting) { + StdErrDiagnostics diag; + Artifact x86; + x86.abi_group = {"x86"}; + + EXPECT_FALSE(x86.ToArtifactName("something.${abi${density}}.apk", "", &diag)); + + const Maybe<std::string>& name = x86.ToArtifactName("something.${abi${abi}}.apk", "", &diag); + ASSERT_TRUE(name); + EXPECT_EQ(name.value(), "something.${abix86}.apk"); +} + +TEST(ArtifactTest, Recursive) { + StdErrDiagnostics diag; + Artifact artifact; + artifact.device_feature_group = {"${gl}"}; + artifact.gl_texture_group = {"glx1"}; + + EXPECT_FALSE(artifact.ToArtifactName("app.${feature}.${gl}.apk", "", &diag)); + + artifact.device_feature_group = {"df1"}; + artifact.gl_texture_group = {"${feature}"}; + { + const auto& result = artifact.ToArtifactName("app.${feature}.${gl}.apk", "", &diag); + ASSERT_TRUE(result); + EXPECT_EQ(result.value(), "app.df1.${feature}.apk"); + } + + // This is an invalid case, but should be the only possible case due to the ordering of + // replacement. + artifact.device_feature_group = {"${gl}"}; + artifact.gl_texture_group = {"glx1"}; + { + const auto& result = artifact.ToArtifactName("app.${feature}.apk", "", &diag); + ASSERT_TRUE(result); + EXPECT_EQ(result.value(), "app.glx1.apk"); + } } } // namespace diff --git a/tools/aapt2/filter/ConfigFilter.h b/tools/aapt2/filter/ConfigFilter.h index 3f1341684912..ebb81519dceb 100644 --- a/tools/aapt2/filter/ConfigFilter.h +++ b/tools/aapt2/filter/ConfigFilter.h @@ -38,13 +38,9 @@ class IConfigFilter { }; /** - * Implements config axis matching. An axis is one component of a configuration, - * like screen - * density or locale. If an axis is specified in the filter, and the axis is - * specified in - * the configuration to match, they must be compatible. Otherwise the - * configuration to match is - * accepted. + * Implements config axis matching. An axis is one component of a configuration, like screen density + * or locale. If an axis is specified in the filter, and the axis is specified in the configuration + * to match, they must be compatible. Otherwise the configuration to match is accepted. * * Used when handling "-c" options. */ diff --git a/tools/aapt2/filter/Filter_test.cpp b/tools/aapt2/filter/Filter_test.cpp index fb75a4b4d7c1..db2e69fc90d3 100644 --- a/tools/aapt2/filter/Filter_test.cpp +++ b/tools/aapt2/filter/Filter_test.cpp @@ -25,22 +25,16 @@ namespace aapt { namespace { -TEST(FilterChainTest, EmptyChain) { +TEST(FilterTest, FilterChain) { FilterChain chain; ASSERT_TRUE(chain.Keep("some/random/path")); -} -TEST(FilterChainTest, SingleFilter) { - FilterChain chain; chain.AddFilter(util::make_unique<PrefixFilter>("keep/")); ASSERT_FALSE(chain.Keep("removed/path")); ASSERT_TRUE(chain.Keep("keep/path/1")); ASSERT_TRUE(chain.Keep("keep/path/2")); -} -TEST(FilterChainTest, MultipleFilters) { - FilterChain chain; chain.AddFilter(util::make_unique<PrefixFilter>("keep/")); chain.AddFilter(util::make_unique<PrefixFilter>("keep/really/")); diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp index 826f91b4a2fd..5f8bd063f9b0 100644 --- a/tools/aapt2/flatten/Archive.cpp +++ b/tools/aapt2/flatten/Archive.cpp @@ -23,12 +23,14 @@ #include "android-base/errors.h" #include "android-base/macros.h" +#include "android-base/utf8.h" #include "androidfw/StringPiece.h" #include "ziparchive/zip_writer.h" #include "util/Files.h" -using android::StringPiece; +using ::android::StringPiece; +using ::android::base::SystemErrorCodeToString; namespace aapt { @@ -58,11 +60,11 @@ class DirectoryWriter : public IArchiveWriter { std::string full_path = dir_; file::AppendPath(&full_path, path); - file::mkdirs(file::GetStem(full_path)); + file::mkdirs(file::GetStem(full_path).to_string()); - file_ = {fopen(full_path.data(), "wb"), fclose}; + file_ = {::android::base::utf8::fopen(full_path.c_str(), "wb"), fclose}; if (!file_) { - error_ = android::base::SystemErrorCodeToString(errno); + error_ = SystemErrorCodeToString(errno); return false; } return true; @@ -74,7 +76,7 @@ class DirectoryWriter : public IArchiveWriter { } if (fwrite(data, 1, len, file_.get()) != static_cast<size_t>(len)) { - error_ = android::base::SystemErrorCodeToString(errno); + error_ = SystemErrorCodeToString(errno); file_.reset(nullptr); return false; } @@ -121,9 +123,9 @@ class ZipFileWriter : public IArchiveWriter { ZipFileWriter() = default; bool Open(const StringPiece& path) { - file_ = {fopen(path.data(), "w+b"), fclose}; + file_ = {::android::base::utf8::fopen(path.to_string().c_str(), "w+b"), fclose}; if (!file_) { - error_ = android::base::SystemErrorCodeToString(errno); + error_ = SystemErrorCodeToString(errno); return false; } writer_ = util::make_unique<ZipWriter>(file_.get()); diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp index e5993a65366d..14b776b1bd99 100644 --- a/tools/aapt2/flatten/TableFlattener.cpp +++ b/tools/aapt2/flatten/TableFlattener.cpp @@ -133,7 +133,7 @@ class MapFlattenVisitor : public RawValueVisitor { } void Visit(Array* array) override { - for (auto& item : array->items) { + for (auto& item : array->elements) { ResTable_map* out_entry = buffer_->NextBlock<ResTable_map>(); FlattenValue(item.get(), out_entry); out_entry->value.size = util::HostToDevice16(sizeof(out_entry->value)); diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp index 331ef784a7da..b3b308a29fc5 100644 --- a/tools/aapt2/flatten/XmlFlattener.cpp +++ b/tools/aapt2/flatten/XmlFlattener.cpp @@ -38,12 +38,10 @@ namespace { constexpr uint32_t kLowPriority = 0xffffffffu; -static bool cmp_xml_attribute_by_id(const xml::Attribute* a, - const xml::Attribute* b) { +static bool cmp_xml_attribute_by_id(const xml::Attribute* a, const xml::Attribute* b) { if (a->compiled_attribute && a->compiled_attribute.value().id) { if (b->compiled_attribute && b->compiled_attribute.value().id) { - return a->compiled_attribute.value().id.value() < - b->compiled_attribute.value().id.value(); + return a->compiled_attribute.value().id.value() < b->compiled_attribute.value().id.value(); } return true; } else if (!b->compiled_attribute) { @@ -75,17 +73,6 @@ class XmlFlattenerVisitor : public xml::Visitor { XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options) : buffer_(buffer), options_(options) {} - void Visit(xml::Namespace* node) override { - if (node->namespace_uri == xml::kSchemaTools) { - // Skip dedicated tools namespace. - xml::Visitor::Visit(node); - } else { - WriteNamespace(node, android::RES_XML_START_NAMESPACE_TYPE); - xml::Visitor::Visit(node); - WriteNamespace(node, android::RES_XML_END_NAMESPACE_TYPE); - } - } - void Visit(xml::Text* node) override { if (util::TrimWhitespace(node->text).empty()) { // Skip whitespace only text nodes. @@ -93,8 +80,7 @@ class XmlFlattenerVisitor : public xml::Visitor { } ChunkWriter writer(buffer_); - ResXMLTree_node* flat_node = - writer.StartChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE); + ResXMLTree_node* flat_node = writer.StartChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE); flat_node->lineNumber = util::HostToDevice32(node->line_number); flat_node->comment.index = util::HostToDevice32(-1); @@ -109,6 +95,13 @@ class XmlFlattenerVisitor : public xml::Visitor { } void Visit(xml::Element* node) override { + for (const xml::NamespaceDecl& decl : node->namespace_decls) { + // Skip dedicated tools namespace. + if (decl.uri != xml::kSchemaTools) { + WriteNamespace(decl, android::RES_XML_START_NAMESPACE_TYPE); + } + } + { ChunkWriter start_writer(buffer_); ResXMLTree_node* flat_node = @@ -116,19 +109,15 @@ class XmlFlattenerVisitor : public xml::Visitor { flat_node->lineNumber = util::HostToDevice32(node->line_number); flat_node->comment.index = util::HostToDevice32(-1); - ResXMLTree_attrExt* flat_elem = - start_writer.NextBlock<ResXMLTree_attrExt>(); + ResXMLTree_attrExt* flat_elem = start_writer.NextBlock<ResXMLTree_attrExt>(); - // A missing namespace must be null, not an empty string. Otherwise the - // runtime complains. + // A missing namespace must be null, not an empty string. Otherwise the runtime complains. AddString(node->namespace_uri, kLowPriority, &flat_elem->ns, true /* treat_empty_string_as_null */); - AddString(node->name, kLowPriority, &flat_elem->name, - true /* treat_empty_string_as_null */); + AddString(node->name, kLowPriority, &flat_elem->name, true /* treat_empty_string_as_null */); flat_elem->attributeStart = util::HostToDevice16(sizeof(*flat_elem)); - flat_elem->attributeSize = - util::HostToDevice16(sizeof(ResXMLTree_attribute)); + flat_elem->attributeSize = util::HostToDevice16(sizeof(ResXMLTree_attribute)); WriteAttributes(node, flat_elem, &start_writer); @@ -144,14 +133,20 @@ class XmlFlattenerVisitor : public xml::Visitor { flat_end_node->lineNumber = util::HostToDevice32(node->line_number); flat_end_node->comment.index = util::HostToDevice32(-1); - ResXMLTree_endElementExt* flat_end_elem = - end_writer.NextBlock<ResXMLTree_endElementExt>(); + ResXMLTree_endElementExt* flat_end_elem = end_writer.NextBlock<ResXMLTree_endElementExt>(); AddString(node->namespace_uri, kLowPriority, &flat_end_elem->ns, true /* treat_empty_string_as_null */); AddString(node->name, kLowPriority, &flat_end_elem->name); end_writer.Finish(); } + + for (auto iter = node->namespace_decls.rbegin(); iter != node->namespace_decls.rend(); ++iter) { + // Skip dedicated tools namespace. + if (iter->uri != xml::kSchemaTools) { + WriteNamespace(*iter, android::RES_XML_END_NAMESPACE_TYPE); + } + } } private: @@ -173,16 +168,16 @@ class XmlFlattenerVisitor : public xml::Visitor { string_refs.push_back(StringFlattenDest{ref, dest}); } - void WriteNamespace(xml::Namespace* node, uint16_t type) { + void WriteNamespace(const xml::NamespaceDecl& decl, uint16_t type) { ChunkWriter writer(buffer_); ResXMLTree_node* flatNode = writer.StartChunk<ResXMLTree_node>(type); - flatNode->lineNumber = util::HostToDevice32(node->line_number); + flatNode->lineNumber = util::HostToDevice32(decl.line_number); flatNode->comment.index = util::HostToDevice32(-1); ResXMLTree_namespaceExt* flat_ns = writer.NextBlock<ResXMLTree_namespaceExt>(); - AddString(node->namespace_prefix, kLowPriority, &flat_ns->prefix); - AddString(node->namespace_uri, kLowPriority, &flat_ns->uri); + AddString(decl.prefix, kLowPriority, &flat_ns->prefix); + AddString(decl.uri, kLowPriority, &flat_ns->uri); writer.Finish(); } diff --git a/tools/aapt2/integration-tests/NamespaceTest/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/Android.mk new file mode 100644 index 000000000000..6361f9b8ae7d --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/Android.mk @@ -0,0 +1,2 @@ +LOCAL_PATH := $(call my-dir) +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk new file mode 100644 index 000000000000..6ed07b0c5c73 --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/App/Android.mk @@ -0,0 +1,29 @@ +# +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_USE_AAPT2 := true +LOCAL_AAPT_NAMESPACES := true +LOCAL_PACKAGE_NAME := AaptTestNamespace_App +LOCAL_MODULE_TAGS := tests +LOCAL_SRC_FILES := $(call all-java-files-under,src) +LOCAL_STATIC_ANDROID_LIBRARIES := \ + AaptTestNamespace_LibOne \ + AaptTestNamespace_LibTwo +LOCAL_AAPT_FLAGS := -v +include $(BUILD_PACKAGE) diff --git a/tools/aapt2/integration-tests/NamespaceTest/App/AndroidManifest.xml b/tools/aapt2/integration-tests/NamespaceTest/App/AndroidManifest.xml new file mode 100644 index 000000000000..6398a83ea1d2 --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/App/AndroidManifest.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.aapt.namespace.app"> + + <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="26"/> + + <application android:theme="@style/AppTheme" android:label="@string/app_name"> + <activity android:name=".MainActivity"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/tools/aapt2/integration-tests/NamespaceTest/App/res/layout/activity_main.xml b/tools/aapt2/integration-tests/NamespaceTest/App/res/layout/activity_main.xml new file mode 100644 index 000000000000..19bfd942a5bd --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/App/res/layout/activity_main.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:libone="http://schemas.android.com/apk/res/com.android.aapt.namespace.libone" + xmlns:libtwo="http://schemas.android.com/apk/res/com.android.aapt.namespace.libtwo" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <com.android.aapt.namespace.libtwo.TextView + android:id="@+id/textview" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerInParent="@bool/always_true" + android:text="@libone:string/textview_text" + libtwo:textview_attr="?libone:theme_attr" /> +</RelativeLayout>
\ No newline at end of file diff --git a/tools/aapt2/integration-tests/NamespaceTest/App/res/values/values.xml b/tools/aapt2/integration-tests/NamespaceTest/App/res/values/values.xml new file mode 100644 index 000000000000..1b80d9542881 --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/App/res/values/values.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <string name="app_name">Namespace App</string> + <bool name="always_true">true</bool> + + <style name="AppTheme" parent="com.android.aapt.namespace.libone:style/Theme"> + <item name="android:colorPrimary">#3F51B5</item> + <item name="android:colorPrimaryDark">#303F9F</item> + <item name="android:colorAccent">#FF4081</item> + <item name="com.android.aapt.namespace.libone:theme_attr"> + @com.android.aapt.namespace.libtwo:string/public_string + </item> + </style> +</resources>
\ No newline at end of file diff --git a/tools/aapt2/integration-tests/NamespaceTest/App/src/com/android/aapt/namespace/app/MainActivity.java b/tools/aapt2/integration-tests/NamespaceTest/App/src/com/android/aapt/namespace/app/MainActivity.java new file mode 100644 index 000000000000..fcb4c3c12f81 --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/App/src/com/android/aapt/namespace/app/MainActivity.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.aapt.namespace.app; + +import android.app.Activity; +import android.os.Bundle; +import android.widget.Toast; + +public class MainActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + com.android.aapt.namespace.libtwo.TextView tv = findViewById(R.id.textview); + + + + Toast.makeText(this, tv.getTextViewAttr(), Toast.LENGTH_LONG).show(); + } +} diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk new file mode 100644 index 000000000000..b1cac68dae7a --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/LibOne/Android.mk @@ -0,0 +1,28 @@ +# +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_USE_AAPT2 := true +LOCAL_AAPT_NAMESPACES := true +LOCAL_MODULE := AaptTestNamespace_LibOne +LOCAL_MODULE_TAGS := tests +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +# We need this to retain the R.java generated for this library. +LOCAL_JAR_EXCLUDE_FILES := none +include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibOne/AndroidManifest.xml b/tools/aapt2/integration-tests/NamespaceTest/LibOne/AndroidManifest.xml new file mode 100644 index 000000000000..70b4b226624b --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/LibOne/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.aapt.namespace.libone"> + + <uses-sdk android:minSdkVersion="21" /> +</manifest> diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibOne/res/values/values.xml b/tools/aapt2/integration-tests/NamespaceTest/LibOne/res/values/values.xml new file mode 100644 index 000000000000..d2dcea0c0b1e --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/LibOne/res/values/values.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <public type="string" name="textview_text" /> + <string name="textview_text">LibOne\'s textview_text string!</string> + + <public type="attr" name="theme_attr" /> + <attr name="theme_attr" format="string" /> + + <public type="style" name="Theme" /> + <style name="Theme" parent="android:Theme.Material.Light.DarkActionBar"> + <item name="theme_attr">[Please override with your own value]</item> + </style> +</resources>
\ No newline at end of file diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk new file mode 100644 index 000000000000..dc16d1bbb420 --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/Android.mk @@ -0,0 +1,29 @@ +# +# Copyright (C) 2017 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_USE_AAPT2 := true +LOCAL_AAPT_NAMESPACES := true +LOCAL_MODULE := AaptTestNamespace_LibTwo +LOCAL_MODULE_TAGS := tests +LOCAL_SRC_FILES := $(call all-java-files-under,src) +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res + +# We need this to retain the R.java generated for this library. +LOCAL_JAR_EXCLUDE_FILES := none +include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibTwo/AndroidManifest.xml b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/AndroidManifest.xml new file mode 100644 index 000000000000..32944a9b8f0b --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.aapt.namespace.libtwo"> + + <uses-sdk android:minSdkVersion="21" /> +</manifest> diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibTwo/res/values/values.xml b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/res/values/values.xml new file mode 100644 index 000000000000..0c5f5d8d55e0 --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/res/values/values.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <public type="string" name="public_string" /> + <string name="public_string">LibTwo\'s public string!</string> + + <public type="attr" name="textview_attr" /> + <attr name="textview_attr" format="string" /> + + <declare-styleable name="TextView"> + <attr name="textview_attr" /> + </declare-styleable> +</resources>
\ No newline at end of file diff --git a/tools/aapt2/integration-tests/NamespaceTest/LibTwo/src/com/android/aapt/namespace/libtwo/TextView.java b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/src/com/android/aapt/namespace/libtwo/TextView.java new file mode 100644 index 000000000000..0f8024e58797 --- /dev/null +++ b/tools/aapt2/integration-tests/NamespaceTest/LibTwo/src/com/android/aapt/namespace/libtwo/TextView.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.aapt.namespace.libtwo; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; + +public class TextView extends android.widget.TextView { + + private String mTextViewAttr; + + public TextView(Context context) { + this(context, null); + } + + public TextView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public TextView(Context context, AttributeSet attrs, int defStyleAttr) { + this(context, attrs, defStyleAttr, 0); + } + + public TextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + + final TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.TextView, + 0, 0); + try { + mTextViewAttr = ta.getString(R.styleable.TextView_textview_attr); + } finally { + ta.recycle(); + } + } + + public String getTextViewAttr() { + return mTextViewAttr; + } +} diff --git a/tools/aapt2/integration-tests/StaticLibTest/Android.mk b/tools/aapt2/integration-tests/StaticLibTest/Android.mk new file mode 100644 index 000000000000..6361f9b8ae7d --- /dev/null +++ b/tools/aapt2/integration-tests/StaticLibTest/Android.mk @@ -0,0 +1,2 @@ +LOCAL_PATH := $(call my-dir) +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tools/aapt2/integration-tests/AppOne/Android.mk b/tools/aapt2/integration-tests/StaticLibTest/App/Android.mk index 38bd5b5e3275..4d0c01d565a5 100644 --- a/tools/aapt2/integration-tests/AppOne/Android.mk +++ b/tools/aapt2/integration-tests/StaticLibTest/App/Android.mk @@ -18,12 +18,12 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_USE_AAPT2 := true -LOCAL_PACKAGE_NAME := AaptTestAppOne +LOCAL_PACKAGE_NAME := AaptTestStaticLib_App LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-java-files-under,src) LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets $(LOCAL_PATH)/assets2 LOCAL_STATIC_ANDROID_LIBRARIES := \ - AaptTestStaticLibOne \ - AaptTestStaticLibTwo + AaptTestStaticLib_LibOne \ + AaptTestStaticLib_LibTwo LOCAL_AAPT_FLAGS := --no-version-vectors --no-version-transitions include $(BUILD_PACKAGE) diff --git a/tools/aapt2/integration-tests/AppOne/AndroidManifest.xml b/tools/aapt2/integration-tests/StaticLibTest/App/AndroidManifest.xml index a5f202dd22fc..a5f202dd22fc 100644 --- a/tools/aapt2/integration-tests/AppOne/AndroidManifest.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/AndroidManifest.xml diff --git a/tools/aapt2/integration-tests/AppOne/assets/subdir/subsubdir/test.txt b/tools/aapt2/integration-tests/StaticLibTest/App/assets/subdir/subsubdir/test.txt index 125194943ec8..125194943ec8 100644 --- a/tools/aapt2/integration-tests/AppOne/assets/subdir/subsubdir/test.txt +++ b/tools/aapt2/integration-tests/StaticLibTest/App/assets/subdir/subsubdir/test.txt diff --git a/tools/aapt2/integration-tests/AppOne/assets/test.txt b/tools/aapt2/integration-tests/StaticLibTest/App/assets/test.txt index 88266de2b4d4..88266de2b4d4 100644 --- a/tools/aapt2/integration-tests/AppOne/assets/test.txt +++ b/tools/aapt2/integration-tests/StaticLibTest/App/assets/test.txt diff --git a/tools/aapt2/integration-tests/AppOne/assets2/new.txt b/tools/aapt2/integration-tests/StaticLibTest/App/assets2/new.txt index f4963a95503a..f4963a95503a 100644 --- a/tools/aapt2/integration-tests/AppOne/assets2/new.txt +++ b/tools/aapt2/integration-tests/StaticLibTest/App/assets2/new.txt diff --git a/tools/aapt2/integration-tests/AppOne/assets2/test.txt b/tools/aapt2/integration-tests/StaticLibTest/App/assets2/test.txt index 5d8b36c0d52d..5d8b36c0d52d 100644 --- a/tools/aapt2/integration-tests/AppOne/assets2/test.txt +++ b/tools/aapt2/integration-tests/StaticLibTest/App/assets2/test.txt diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/cheap_transparency.png b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/cheap_transparency.png Binary files differindex 0522a9979db9..0522a9979db9 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/cheap_transparency.png +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/cheap_transparency.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/complex.9.png b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/complex.9.png Binary files differindex baf9fff13ab5..baf9fff13ab5 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/complex.9.png +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/complex.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/icon.png b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/icon.png Binary files differindex 4bff9b900643..4bff9b900643 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/icon.png +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/icon.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/image.xml b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/image.xml index 6132a75d85d0..6132a75d85d0 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/image.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/image.xml diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/outline_8x8.9.png b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/outline_8x8.9.png Binary files differindex 7b331e16fcbd..7b331e16fcbd 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/outline_8x8.9.png +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/outline_8x8.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_off_center_outline_32x16.9.png b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/round_rect_off_center_outline_32x16.9.png Binary files differindex 0ec6c70a2b9f..0ec6c70a2b9f 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_off_center_outline_32x16.9.png +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/round_rect_off_center_outline_32x16.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_outline_32x16.9.png b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/round_rect_outline_32x16.9.png Binary files differindex e05708a089a3..e05708a089a3 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/round_rect_outline_32x16.9.png +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/round_rect_outline_32x16.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/test.9.png b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/test.9.png Binary files differindex 33daa117ea9d..33daa117ea9d 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/test.9.png +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/test.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_3x3.9.png b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/transparent_3x3.9.png Binary files differindex a11377a0d670..a11377a0d670 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_3x3.9.png +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/transparent_3x3.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_optical_bounds_3x3.9.png b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/transparent_optical_bounds_3x3.9.png Binary files differindex 6803e4243484..6803e4243484 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/transparent_optical_bounds_3x3.9.png +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/transparent_optical_bounds_3x3.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/white_3x3.9.png b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/white_3x3.9.png Binary files differindex 1a3731bbc8b8..1a3731bbc8b8 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/white_3x3.9.png +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/white_3x3.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/drawable/white_optical_bounds_3x3.9.png b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/white_optical_bounds_3x3.9.png Binary files differindex 489ace292e1f..489ace292e1f 100644 --- a/tools/aapt2/integration-tests/AppOne/res/drawable/white_optical_bounds_3x3.9.png +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/drawable/white_optical_bounds_3x3.9.png diff --git a/tools/aapt2/integration-tests/AppOne/res/font/myfont-italic.ttf b/tools/aapt2/integration-tests/StaticLibTest/App/res/font/myfont-italic.ttf index e69de29bb2d1..e69de29bb2d1 100644 --- a/tools/aapt2/integration-tests/AppOne/res/font/myfont-italic.ttf +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/font/myfont-italic.ttf diff --git a/tools/aapt2/integration-tests/AppOne/res/font/myfont-normal.ttf b/tools/aapt2/integration-tests/StaticLibTest/App/res/font/myfont-normal.ttf index e69de29bb2d1..e69de29bb2d1 100644 --- a/tools/aapt2/integration-tests/AppOne/res/font/myfont-normal.ttf +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/font/myfont-normal.ttf diff --git a/tools/aapt2/integration-tests/AppOne/res/font/myfont.xml b/tools/aapt2/integration-tests/StaticLibTest/App/res/font/myfont.xml index 1fb67914894e..1fb67914894e 100644 --- a/tools/aapt2/integration-tests/AppOne/res/font/myfont.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/font/myfont.xml diff --git a/tools/aapt2/integration-tests/AppOne/res/layout-v21/main.xml b/tools/aapt2/integration-tests/StaticLibTest/App/res/layout-v21/main.xml index 9f5a4a85cbcf..724bfe4a9536 100644 --- a/tools/aapt2/integration-tests/AppOne/res/layout-v21/main.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/layout-v21/main.xml @@ -15,7 +15,6 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:support="http://schemas.android.com/apk/res/android.appcompat" android:id="@+id/view" android:layout_width="match_parent" android:layout_height="wrap_content"> diff --git a/tools/aapt2/integration-tests/AppOne/res/layout/main.xml b/tools/aapt2/integration-tests/StaticLibTest/App/res/layout/main.xml index ab1a251a7d56..aaa884bf7084 100644 --- a/tools/aapt2/integration-tests/AppOne/res/layout/main.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/layout/main.xml @@ -15,7 +15,6 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:support="http://schemas.android.com/apk/res/android.appcompat" android:id="@+id/view" android:layout_width="match_parent" android:layout_height="wrap_content"> diff --git a/tools/aapt2/integration-tests/AppOne/res/layout/special.xml b/tools/aapt2/integration-tests/StaticLibTest/App/res/layout/special.xml index 28c85ca92019..28c85ca92019 100644 --- a/tools/aapt2/integration-tests/AppOne/res/layout/special.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/layout/special.xml diff --git a/tools/aapt2/integration-tests/StaticLibTest/App/res/navigation/home.xml b/tools/aapt2/integration-tests/StaticLibTest/App/res/navigation/home.xml new file mode 100644 index 000000000000..ade271d60ab6 --- /dev/null +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/navigation/home.xml @@ -0,0 +1,2 @@ +<?xml version="1.0" encoding="utf-8"?> +<navigation /> diff --git a/tools/aapt2/integration-tests/AppOne/res/raw/test.txt b/tools/aapt2/integration-tests/StaticLibTest/App/res/raw/test.txt index b14df6442ea5..b14df6442ea5 100644 --- a/tools/aapt2/integration-tests/AppOne/res/raw/test.txt +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/raw/test.txt diff --git a/tools/aapt2/integration-tests/AppOne/res/transition/transition_set.xml b/tools/aapt2/integration-tests/StaticLibTest/App/res/transition/transition_set.xml index e10e6c2f53da..e10e6c2f53da 100644 --- a/tools/aapt2/integration-tests/AppOne/res/transition/transition_set.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/transition/transition_set.xml diff --git a/tools/aapt2/integration-tests/AppOne/res/values-v4/styles.xml b/tools/aapt2/integration-tests/StaticLibTest/App/res/values-v4/styles.xml index d8c11e210eda..d8c11e210eda 100644 --- a/tools/aapt2/integration-tests/AppOne/res/values-v4/styles.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/values-v4/styles.xml diff --git a/tools/aapt2/integration-tests/AppOne/res/values/colors.xml b/tools/aapt2/integration-tests/StaticLibTest/App/res/values/colors.xml index 4df5077051d2..4df5077051d2 100644 --- a/tools/aapt2/integration-tests/AppOne/res/values/colors.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/values/colors.xml diff --git a/tools/aapt2/integration-tests/AppOne/res/values/styles.xml b/tools/aapt2/integration-tests/StaticLibTest/App/res/values/styles.xml index 19d96c0809db..a088e5d0e1a2 100644 --- a/tools/aapt2/integration-tests/AppOne/res/values/styles.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/values/styles.xml @@ -14,7 +14,7 @@ limitations under the License. --> -<resources xmlns:lib="http://schemas.android.com/apk/res/android.appcompat"> +<resources> <style name="App"> <item name="android:background">@color/primary</item> <item name="android:colorPrimary">@color/primary</item> diff --git a/tools/aapt2/integration-tests/AppOne/res/values/test.xml b/tools/aapt2/integration-tests/StaticLibTest/App/res/values/test.xml index 2c9e8b877565..2c9e8b877565 100644 --- a/tools/aapt2/integration-tests/AppOne/res/values/test.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/App/res/values/test.xml diff --git a/tools/aapt2/integration-tests/AppOne/src/com/android/aapt/app/one/AppOne.java b/tools/aapt2/integration-tests/StaticLibTest/App/src/com/android/aapt/app/one/AppOne.java index 472b35a781fe..472b35a781fe 100644 --- a/tools/aapt2/integration-tests/AppOne/src/com/android/aapt/app/one/AppOne.java +++ b/tools/aapt2/integration-tests/StaticLibTest/App/src/com/android/aapt/app/one/AppOne.java diff --git a/tools/aapt2/integration-tests/StaticLibOne/Android.mk b/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.mk index 0b7129aa0d38..0c828b80b3b3 100644 --- a/tools/aapt2/integration-tests/StaticLibOne/Android.mk +++ b/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.mk @@ -18,11 +18,11 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_USE_AAPT2 := true -LOCAL_MODULE := AaptTestStaticLibOne +LOCAL_MODULE := AaptTestStaticLib_LibOne LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-java-files-under,src) LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res -# We need this to compile the Java sources of AaptTestStaticLibTwo using javac. +# We need this to compile the Java sources of AaptTestStaticLib_LibTwo using javac. LOCAL_JAR_EXCLUDE_FILES := none include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/tools/aapt2/integration-tests/StaticLibOne/AndroidManifest.xml b/tools/aapt2/integration-tests/StaticLibTest/LibOne/AndroidManifest.xml index 705047e71300..705047e71300 100644 --- a/tools/aapt2/integration-tests/StaticLibOne/AndroidManifest.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/LibOne/AndroidManifest.xml diff --git a/tools/aapt2/integration-tests/StaticLibOne/res/layout/layout.xml b/tools/aapt2/integration-tests/StaticLibTest/LibOne/res/layout/layout.xml index 683c91cd9cf5..683c91cd9cf5 100644 --- a/tools/aapt2/integration-tests/StaticLibOne/res/layout/layout.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/LibOne/res/layout/layout.xml diff --git a/tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml b/tools/aapt2/integration-tests/StaticLibTest/LibOne/res/values/values.xml index b4dc90b3e640..b4dc90b3e640 100644 --- a/tools/aapt2/integration-tests/StaticLibOne/res/values/values.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/LibOne/res/values/values.xml diff --git a/tools/aapt2/integration-tests/StaticLibOne/src/com/android/aapt/staticlib/one/StaticLibOne.java b/tools/aapt2/integration-tests/StaticLibTest/LibOne/src/com/android/aapt/staticlib/one/StaticLibOne.java index cf48f67056cf..cf48f67056cf 100644 --- a/tools/aapt2/integration-tests/StaticLibOne/src/com/android/aapt/staticlib/one/StaticLibOne.java +++ b/tools/aapt2/integration-tests/StaticLibTest/LibOne/src/com/android/aapt/staticlib/one/StaticLibOne.java diff --git a/tools/aapt2/integration-tests/StaticLibTwo/Android.mk b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.mk index 8b6eb41b08cd..538d5251b203 100644 --- a/tools/aapt2/integration-tests/StaticLibTwo/Android.mk +++ b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.mk @@ -18,10 +18,10 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_USE_AAPT2 := true -LOCAL_MODULE := AaptTestStaticLibTwo +LOCAL_MODULE := AaptTestStaticLib_LibTwo LOCAL_MODULE_TAGS := tests LOCAL_SRC_FILES := $(call all-java-files-under,src) LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res -LOCAL_SHARED_ANDROID_LIBRARIES := AaptTestStaticLibOne +LOCAL_SHARED_ANDROID_LIBRARIES := AaptTestStaticLib_LibOne include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/tools/aapt2/integration-tests/StaticLibTwo/AndroidManifest.xml b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/AndroidManifest.xml index 28f069932452..28f069932452 100644 --- a/tools/aapt2/integration-tests/StaticLibTwo/AndroidManifest.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/AndroidManifest.xml diff --git a/tools/aapt2/integration-tests/StaticLibTwo/res/drawable/vector.xml b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/res/drawable/vector.xml index dd5979f7e838..dd5979f7e838 100644 --- a/tools/aapt2/integration-tests/StaticLibTwo/res/drawable/vector.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/res/drawable/vector.xml diff --git a/tools/aapt2/integration-tests/StaticLibTwo/res/layout/layout_two.xml b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/res/layout/layout_two.xml index ba9830708eb0..ba9830708eb0 100644 --- a/tools/aapt2/integration-tests/StaticLibTwo/res/layout/layout_two.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/res/layout/layout_two.xml diff --git a/tools/aapt2/integration-tests/StaticLibTwo/res/values/values.xml b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/res/values/values.xml index 97bb2a53d9f6..97bb2a53d9f6 100644 --- a/tools/aapt2/integration-tests/StaticLibTwo/res/values/values.xml +++ b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/res/values/values.xml diff --git a/tools/aapt2/integration-tests/StaticLibTwo/src/com/android/aapt/staticlib/two/StaticLibTwo.java b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/src/com/android/aapt/staticlib/two/StaticLibTwo.java index 7110dcdd017a..7110dcdd017a 100644 --- a/tools/aapt2/integration-tests/StaticLibTwo/src/com/android/aapt/staticlib/two/StaticLibTwo.java +++ b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/src/com/android/aapt/staticlib/two/StaticLibTwo.java diff --git a/tools/aapt2/io/FileInputStream.cpp b/tools/aapt2/io/FileInputStream.cpp new file mode 100644 index 000000000000..07dbb5a98add --- /dev/null +++ b/tools/aapt2/io/FileInputStream.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "io/FileInputStream.h" + +#include <errno.h> // for errno +#include <fcntl.h> // for O_RDONLY +#include <unistd.h> // for read + +#include "android-base/errors.h" +#include "android-base/file.h" // for O_BINARY +#include "android-base/macros.h" +#include "android-base/utf8.h" + +using ::android::base::SystemErrorCodeToString; + +namespace aapt { +namespace io { + +FileInputStream::FileInputStream(const std::string& path, size_t buffer_capacity) + : FileInputStream(::android::base::utf8::open(path.c_str(), O_RDONLY | O_BINARY), + buffer_capacity) { +} + +FileInputStream::FileInputStream(int fd, size_t buffer_capacity) + : fd_(fd), + buffer_capacity_(buffer_capacity), + buffer_offset_(0u), + buffer_size_(0u), + total_byte_count_(0u) { + if (fd_ == -1) { + error_ = SystemErrorCodeToString(errno); + } else { + buffer_.reset(new uint8_t[buffer_capacity_]); + } +} + +bool FileInputStream::Next(const void** data, size_t* size) { + if (HadError()) { + return false; + } + + // Deal with any remaining bytes after BackUp was called. + if (buffer_offset_ != buffer_size_) { + *data = buffer_.get() + buffer_offset_; + *size = buffer_size_ - buffer_offset_; + total_byte_count_ += buffer_size_ - buffer_offset_; + buffer_offset_ = buffer_size_; + return true; + } + + ssize_t n = TEMP_FAILURE_RETRY(read(fd_, buffer_.get(), buffer_capacity_)); + if (n < 0) { + error_ = SystemErrorCodeToString(errno); + fd_.reset(); + return false; + } + + buffer_size_ = static_cast<size_t>(n); + buffer_offset_ = buffer_size_; + total_byte_count_ += buffer_size_; + + *data = buffer_.get(); + *size = buffer_size_; + return buffer_size_ != 0u; +} + +void FileInputStream::BackUp(size_t count) { + if (count > buffer_offset_) { + count = buffer_offset_; + } + buffer_offset_ -= count; + total_byte_count_ -= count; +} + +size_t FileInputStream::ByteCount() const { + return total_byte_count_; +} + +bool FileInputStream::HadError() const { + return !error_.empty(); +} + +std::string FileInputStream::GetError() const { + return error_; +} + +} // namespace io +} // namespace aapt diff --git a/tools/aapt2/io/FileInputStream.h b/tools/aapt2/io/FileInputStream.h new file mode 100644 index 000000000000..6beb9a186ce5 --- /dev/null +++ b/tools/aapt2/io/FileInputStream.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_IO_FILEINPUTSTREAM_H +#define AAPT_IO_FILEINPUTSTREAM_H + +#include "io/Io.h" + +#include <memory> +#include <string> + +#include "android-base/macros.h" +#include "android-base/unique_fd.h" + +namespace aapt { +namespace io { + +class FileInputStream : public InputStream { + public: + explicit FileInputStream(const std::string& path, size_t buffer_capacity = 4096); + + // Takes ownership of `fd`. + explicit FileInputStream(int fd, size_t buffer_capacity = 4096); + + bool Next(const void** data, size_t* size) override; + + void BackUp(size_t count) override; + + size_t ByteCount() const override; + + bool HadError() const override; + + std::string GetError() const override; + + private: + DISALLOW_COPY_AND_ASSIGN(FileInputStream); + + android::base::unique_fd fd_; + std::string error_; + std::unique_ptr<uint8_t[]> buffer_; + size_t buffer_capacity_; + size_t buffer_offset_; + size_t buffer_size_; + size_t total_byte_count_; +}; + +} // namespace io +} // namespace aapt + +#endif // AAPT_IO_FILEINPUTSTREAM_H diff --git a/tools/aapt2/io/FileInputStream_test.cpp b/tools/aapt2/io/FileInputStream_test.cpp new file mode 100644 index 000000000000..7314ab7beeba --- /dev/null +++ b/tools/aapt2/io/FileInputStream_test.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "io/FileInputStream.h" + +#include "android-base/macros.h" +#include "android-base/test_utils.h" + +#include "test/Test.h" + +using ::android::StringPiece; +using ::testing::Eq; +using ::testing::NotNull; +using ::testing::StrEq; + +namespace aapt { +namespace io { + +TEST(FileInputStreamTest, NextAndBackup) { + std::string input = "this is a cool string"; + TemporaryFile file; + ASSERT_THAT(TEMP_FAILURE_RETRY(write(file.fd, input.c_str(), input.size())), Eq(21)); + lseek64(file.fd, 0, SEEK_SET); + + // Use a small buffer size so that we can call Next() a few times. + FileInputStream in(file.fd, 10u); + ASSERT_FALSE(in.HadError()); + EXPECT_THAT(in.ByteCount(), Eq(0u)); + + const char* buffer; + size_t size; + ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)) << in.GetError(); + ASSERT_THAT(size, Eq(10u)); + ASSERT_THAT(buffer, NotNull()); + EXPECT_THAT(in.ByteCount(), Eq(10u)); + EXPECT_THAT(StringPiece(buffer, size), Eq("this is a ")); + + ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); + ASSERT_THAT(size, Eq(10u)); + ASSERT_THAT(buffer, NotNull()); + EXPECT_THAT(in.ByteCount(), Eq(20u)); + EXPECT_THAT(StringPiece(buffer, size), Eq("cool strin")); + + in.BackUp(5u); + EXPECT_THAT(in.ByteCount(), Eq(15u)); + + ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); + ASSERT_THAT(size, Eq(5u)); + ASSERT_THAT(buffer, NotNull()); + ASSERT_THAT(in.ByteCount(), Eq(20u)); + EXPECT_THAT(StringPiece(buffer, size), Eq("strin")); + + // Backup 1 more than possible. Should clamp. + in.BackUp(11u); + EXPECT_THAT(in.ByteCount(), Eq(10u)); + + ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); + ASSERT_THAT(size, Eq(10u)); + ASSERT_THAT(buffer, NotNull()); + ASSERT_THAT(in.ByteCount(), Eq(20u)); + EXPECT_THAT(StringPiece(buffer, size), Eq("cool strin")); + + ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); + ASSERT_THAT(size, Eq(1u)); + ASSERT_THAT(buffer, NotNull()); + ASSERT_THAT(in.ByteCount(), Eq(21u)); + EXPECT_THAT(StringPiece(buffer, size), Eq("g")); + + EXPECT_FALSE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); + EXPECT_FALSE(in.HadError()); +} + +} // namespace io +} // namespace aapt diff --git a/tools/aapt2/io/StringInputStream.cpp b/tools/aapt2/io/StringInputStream.cpp new file mode 100644 index 000000000000..51a18a7d8a9f --- /dev/null +++ b/tools/aapt2/io/StringInputStream.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "io/StringInputStream.h" + +using ::android::StringPiece; + +namespace aapt { +namespace io { + +StringInputStream::StringInputStream(const StringPiece& str) : str_(str), offset_(0u) { +} + +bool StringInputStream::Next(const void** data, size_t* size) { + if (offset_ == str_.size()) { + return false; + } + + *data = str_.data() + offset_; + *size = str_.size() - offset_; + offset_ = str_.size(); + return true; +} + +void StringInputStream::BackUp(size_t count) { + if (count > offset_) { + count = offset_; + } + offset_ -= count; +} + +size_t StringInputStream::ByteCount() const { + return offset_; +} + +} // namespace io +} // namespace aapt diff --git a/tools/aapt2/io/StringInputStream.h b/tools/aapt2/io/StringInputStream.h new file mode 100644 index 000000000000..ff5b112ef274 --- /dev/null +++ b/tools/aapt2/io/StringInputStream.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_IO_STRINGINPUTSTREAM_H +#define AAPT_IO_STRINGINPUTSTREAM_H + +#include "io/Io.h" + +#include "android-base/macros.h" +#include "androidfw/StringPiece.h" + +namespace aapt { +namespace io { + +class StringInputStream : public InputStream { + public: + explicit StringInputStream(const android::StringPiece& str); + + bool Next(const void** data, size_t* size) override; + + void BackUp(size_t count) override; + + size_t ByteCount() const override; + + inline bool HadError() const override { + return false; + } + + inline std::string GetError() const override { + return {}; + } + + private: + DISALLOW_COPY_AND_ASSIGN(StringInputStream); + + android::StringPiece str_; + size_t offset_; +}; + +} // namespace io +} // namespace aapt + +#endif // AAPT_IO_STRINGINPUTSTREAM_H diff --git a/tools/aapt2/io/StringInputStream_test.cpp b/tools/aapt2/io/StringInputStream_test.cpp new file mode 100644 index 000000000000..cc57bc498313 --- /dev/null +++ b/tools/aapt2/io/StringInputStream_test.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "io/StringInputStream.h" + +#include "test/Test.h" + +using ::android::StringPiece; +using ::testing::Eq; +using ::testing::NotNull; +using ::testing::StrEq; + +namespace aapt { +namespace io { + +TEST(StringInputStreamTest, OneCallToNextShouldReturnEntireBuffer) { + constexpr const size_t kCount = 1000; + std::string input; + input.resize(kCount, 0x7f); + input[0] = 0x00; + input[kCount - 1] = 0xff; + StringInputStream in(input); + + const char* buffer; + size_t size; + ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); + ASSERT_THAT(size, Eq(kCount)); + ASSERT_THAT(buffer, NotNull()); + + EXPECT_THAT(buffer[0], Eq(0x00)); + EXPECT_THAT(buffer[kCount - 1], Eq('\xff')); + + EXPECT_FALSE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); + EXPECT_FALSE(in.HadError()); +} + +TEST(StringInputStreamTest, BackUp) { + std::string input = "hello this is a string"; + StringInputStream in(input); + + const char* buffer; + size_t size; + ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); + ASSERT_THAT(size, Eq(input.size())); + ASSERT_THAT(buffer, NotNull()); + EXPECT_THAT(in.ByteCount(), Eq(input.size())); + + in.BackUp(6u); + EXPECT_THAT(in.ByteCount(), Eq(input.size() - 6u)); + + ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); + ASSERT_THAT(size, Eq(6u)); + ASSERT_THAT(buffer, NotNull()); + ASSERT_THAT(buffer, StrEq("string")); + EXPECT_THAT(in.ByteCount(), Eq(input.size())); +} + +} // namespace io +} // namespace aapt diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp index 1f83fa098d74..c93461a66899 100644 --- a/tools/aapt2/java/AnnotationProcessor.cpp +++ b/tools/aapt2/java/AnnotationProcessor.cpp @@ -17,6 +17,7 @@ #include "java/AnnotationProcessor.h" #include <algorithm> +#include <array> #include "text/Unicode.h" #include "text/Utf8Iterator.h" @@ -41,30 +42,54 @@ StringPiece AnnotationProcessor::ExtractFirstSentence(const StringPiece& comment return comment; } -void AnnotationProcessor::AppendCommentLine(std::string& comment) { +struct AnnotationRule { + enum : uint32_t { + kDeprecated = 0x01, + kSystemApi = 0x02, + kTestApi = 0x04, + }; + + StringPiece doc_str; + uint32_t bit_mask; + StringPiece annotation; +}; + +static std::array<AnnotationRule, 2> sAnnotationRules = {{ + {"@SystemApi", AnnotationRule::kSystemApi, "@android.annotation.SystemApi"}, + {"@TestApi", AnnotationRule::kTestApi, "@android.annotation.TestApi"}, +}}; + +void AnnotationProcessor::AppendCommentLine(std::string comment) { static const std::string sDeprecated = "@deprecated"; - static const std::string sSystemApi = "@SystemApi"; + // Treat deprecated specially, since we don't remove it from the source comment. if (comment.find(sDeprecated) != std::string::npos) { - annotation_bit_mask_ |= kDeprecated; + annotation_bit_mask_ |= AnnotationRule::kDeprecated; } - std::string::size_type idx = comment.find(sSystemApi); - if (idx != std::string::npos) { - annotation_bit_mask_ |= kSystemApi; - comment.erase(comment.begin() + idx, - comment.begin() + idx + sSystemApi.size()); + for (const AnnotationRule& rule : sAnnotationRules) { + std::string::size_type idx = comment.find(rule.doc_str.data()); + if (idx != std::string::npos) { + annotation_bit_mask_ |= rule.bit_mask; + comment.erase(comment.begin() + idx, comment.begin() + idx + rule.doc_str.size()); + } } - if (util::TrimWhitespace(comment).empty()) { + // Check if after removal of annotations the line is empty. + const StringPiece trimmed = util::TrimWhitespace(comment); + if (trimmed.empty()) { return; } + // If there was trimming to do, copy the string. + if (trimmed.size() != comment.size()) { + comment = trimmed.to_string(); + } + if (!has_comments_) { has_comments_ = true; comment_ << "/**"; } - comment_ << "\n * " << std::move(comment); } @@ -73,16 +98,18 @@ void AnnotationProcessor::AppendComment(const StringPiece& comment) { for (StringPiece line : util::Tokenize(comment, '\n')) { line = util::TrimWhitespace(line); if (!line.empty()) { - std::string lineCopy = line.to_string(); - AppendCommentLine(lineCopy); + AppendCommentLine(line.to_string()); } } } -void AnnotationProcessor::AppendNewLine() { comment_ << "\n *"; } +void AnnotationProcessor::AppendNewLine() { + if (has_comments_) { + comment_ << "\n *"; + } +} -void AnnotationProcessor::WriteToStream(std::ostream* out, - const StringPiece& prefix) const { +void AnnotationProcessor::WriteToStream(const StringPiece& prefix, std::ostream* out) const { if (has_comments_) { std::string result = comment_.str(); for (StringPiece line : util::Tokenize(result, '\n')) { @@ -92,12 +119,14 @@ void AnnotationProcessor::WriteToStream(std::ostream* out, << "\n"; } - if (annotation_bit_mask_ & kDeprecated) { + if (annotation_bit_mask_ & AnnotationRule::kDeprecated) { *out << prefix << "@Deprecated\n"; } - if (annotation_bit_mask_ & kSystemApi) { - *out << prefix << "@android.annotation.SystemApi\n"; + for (const AnnotationRule& rule : sAnnotationRules) { + if (annotation_bit_mask_ & rule.bit_mask) { + *out << prefix << rule.annotation << "\n"; + } } } diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h index a06eda0f9c5c..a7bf73f50de5 100644 --- a/tools/aapt2/java/AnnotationProcessor.h +++ b/tools/aapt2/java/AnnotationProcessor.h @@ -24,64 +24,53 @@ namespace aapt { -/** - * Builds a JavaDoc comment from a set of XML comments. - * This will also look for instances of @SystemApi and convert them to - * actual Java annotations. - * - * Example: - * - * Input XML: - * - * <!-- This is meant to be hidden because - * It is system api. Also it is @deprecated - * @SystemApi - * --> - * - * Output JavaDoc: - * - * /\* - * * This is meant to be hidden because - * * It is system api. Also it is @deprecated - * *\/ - * - * Output Annotations: - * - * @Deprecated - * @android.annotation.SystemApi - * - */ +// Builds a JavaDoc comment from a set of XML comments. +// This will also look for instances of @SystemApi and convert them to +// actual Java annotations. +// +// Example: +// +// Input XML: +// +// <!-- This is meant to be hidden because +// It is system api. Also it is @deprecated +// @SystemApi +// --> +// +// Output JavaDoc: +// +// /** +// * This is meant to be hidden because +// * It is system api. Also it is @deprecated +// */ +// +// Output Annotations: +// +// @Deprecated +// @android.annotation.SystemApi class AnnotationProcessor { public: + // Extracts the first sentence of a comment. The algorithm selects the substring starting from + // the beginning of the string, and ending at the first '.' character that is followed by a + // whitespace character. If these requirements are not met, the whole string is returned. static android::StringPiece ExtractFirstSentence(const android::StringPiece& comment); - /** - * Adds more comments. Since resources can have various values with different - * configurations, - * we need to collect all the comments. - */ + // Adds more comments. Resources can have value definitions for various configurations, and + // each of the definitions may have comments that need to be processed. void AppendComment(const android::StringPiece& comment); void AppendNewLine(); - /** - * Writes the comments and annotations to the stream, with the given prefix - * before each line. - */ - void WriteToStream(std::ostream* out, const android::StringPiece& prefix) const; + // Writes the comments and annotations to the stream, with the given prefix before each line. + void WriteToStream(const android::StringPiece& prefix, std::ostream* out) const; private: - enum : uint32_t { - kDeprecated = 0x01, - kSystemApi = 0x02, - }; - std::stringstream comment_; std::stringstream mAnnotations; bool has_comments_ = false; uint32_t annotation_bit_mask_ = 0; - void AppendCommentLine(std::string& line); + void AppendCommentLine(std::string line); }; } // namespace aapt diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp index 9ccac8888426..856f4ccbd7f0 100644 --- a/tools/aapt2/java/AnnotationProcessor_test.cpp +++ b/tools/aapt2/java/AnnotationProcessor_test.cpp @@ -34,7 +34,7 @@ TEST(AnnotationProcessorTest, EmitsDeprecated) { processor.AppendComment(comment); std::stringstream result; - processor.WriteToStream(&result, ""); + processor.WriteToStream("", &result); std::string annotations = result.str(); EXPECT_THAT(annotations, HasSubstr("@Deprecated")); @@ -45,7 +45,7 @@ TEST(AnnotationProcessorTest, EmitsSystemApiAnnotationAndRemovesFromComment) { processor.AppendComment("@SystemApi This is a system API"); std::stringstream result; - processor.WriteToStream(&result, ""); + processor.WriteToStream("", &result); std::string annotations = result.str(); EXPECT_THAT(annotations, HasSubstr("@android.annotation.SystemApi")); @@ -53,6 +53,19 @@ TEST(AnnotationProcessorTest, EmitsSystemApiAnnotationAndRemovesFromComment) { EXPECT_THAT(annotations, HasSubstr("This is a system API")); } +TEST(AnnotationProcessorTest, EmitsTestApiAnnotationAndRemovesFromComment) { + AnnotationProcessor processor; + processor.AppendComment("@TestApi This is a test API"); + + std::stringstream result; + processor.WriteToStream("", &result); + std::string annotations = result.str(); + + EXPECT_THAT(annotations, HasSubstr("@android.annotation.TestApi")); + EXPECT_THAT(annotations, Not(HasSubstr("@TestApi"))); + EXPECT_THAT(annotations, HasSubstr("This is a test API")); +} + TEST(AnnotationProcessor, ExtractsFirstSentence) { EXPECT_THAT(AnnotationProcessor::ExtractFirstSentence("This is the only sentence"), Eq("This is the only sentence")); diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp index 0cec9ae407f5..6ad0dd68cb6a 100644 --- a/tools/aapt2/java/ClassDefinition.cpp +++ b/tools/aapt2/java/ClassDefinition.cpp @@ -18,12 +18,12 @@ #include "androidfw/StringPiece.h" -using android::StringPiece; +using ::android::StringPiece; namespace aapt { void ClassMember::WriteToStream(const StringPiece& prefix, bool final, std::ostream* out) const { - processor_.WriteToStream(out, prefix); + processor_.WriteToStream(prefix, out); } void MethodDefinition::AppendStatement(const StringPiece& statement) { @@ -39,6 +39,17 @@ void MethodDefinition::WriteToStream(const StringPiece& prefix, bool final, *out << prefix << "}"; } +ClassDefinition::Result ClassDefinition::AddMember(std::unique_ptr<ClassMember> member) { + Result result = Result::kAdded; + auto iter = members_.find(member); + if (iter != members_.end()) { + members_.erase(iter); + result = Result::kOverridden; + } + members_.insert(std::move(member)); + return result; +} + bool ClassDefinition::empty() const { for (const std::unique_ptr<ClassMember>& member : members_) { if (!member->empty()) { @@ -81,9 +92,8 @@ constexpr static const char* sWarningHeader = " * should not be modified by hand.\n" " */\n\n"; -bool ClassDefinition::WriteJavaFile(const ClassDefinition* def, - const StringPiece& package, bool final, - std::ostream* out) { +bool ClassDefinition::WriteJavaFile(const ClassDefinition* def, const StringPiece& package, + bool final, std::ostream* out) { *out << sWarningHeader << "package " << package << ";\n\n"; def->WriteToStream("", final, out); return bool(*out); diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h index ca76421390d6..6c4bcad83d2a 100644 --- a/tools/aapt2/java/ClassDefinition.h +++ b/tools/aapt2/java/ClassDefinition.h @@ -18,6 +18,7 @@ #define AAPT_JAVA_CLASSDEFINITION_H #include <ostream> +#include <set> #include <string> #include "android-base/macros.h" @@ -37,10 +38,14 @@ class ClassMember { public: virtual ~ClassMember() = default; - AnnotationProcessor* GetCommentBuilder() { return &processor_; } + AnnotationProcessor* GetCommentBuilder() { + return &processor_; + } virtual bool empty() const = 0; + virtual const std::string& GetName() const = 0; + // Writes the class member to the out stream. Subclasses should derive this method // to write their own data. Call this base method from the subclass to write out // this member's comments/annotations. @@ -57,7 +62,13 @@ class PrimitiveMember : public ClassMember { PrimitiveMember(const android::StringPiece& name, const T& val) : name_(name.to_string()), val_(val) {} - bool empty() const override { return false; } + bool empty() const override { + return false; + } + + const std::string& GetName() const override { + return name_; + } void WriteToStream(const android::StringPiece& prefix, bool final, std::ostream* out) const override { @@ -83,7 +94,13 @@ class PrimitiveMember<std::string> : public ClassMember { PrimitiveMember(const android::StringPiece& name, const std::string& val) : name_(name.to_string()), val_(val) {} - bool empty() const override { return false; } + bool empty() const override { + return false; + } + + const std::string& GetName() const override { + return name_; + } void WriteToStream(const android::StringPiece& prefix, bool final, std::ostream* out) const override { @@ -109,9 +126,17 @@ class PrimitiveArrayMember : public ClassMember { public: explicit PrimitiveArrayMember(const android::StringPiece& name) : name_(name.to_string()) {} - void AddElement(const T& val) { elements_.push_back(val); } + void AddElement(const T& val) { + elements_.push_back(val); + } - bool empty() const override { return false; } + bool empty() const override { + return false; + } + + const std::string& GetName() const override { + return name_; + } void WriteToStream(const android::StringPiece& prefix, bool final, std::ostream* out) const override { @@ -154,6 +179,11 @@ class MethodDefinition : public ClassMember { // formatting may be broken. void AppendStatement(const android::StringPiece& statement); + // Not quite the same as a name, but good enough. + const std::string& GetName() const override { + return signature_; + } + // Even if the method is empty, we always want to write the method signature. bool empty() const override { return false; } @@ -175,19 +205,34 @@ class ClassDefinition : public ClassMember { ClassDefinition(const android::StringPiece& name, ClassQualifier qualifier, bool createIfEmpty) : name_(name.to_string()), qualifier_(qualifier), create_if_empty_(createIfEmpty) {} - void AddMember(std::unique_ptr<ClassMember> member) { - members_.push_back(std::move(member)); - } + enum class Result { + kAdded, + kOverridden, + }; + + Result AddMember(std::unique_ptr<ClassMember> member); bool empty() const override; + + const std::string& GetName() const override { + return name_; + } + void WriteToStream(const android::StringPiece& prefix, bool final, std::ostream* out) const override; private: + struct ClassMemberCompare { + using T = std::unique_ptr<ClassMember>; + bool operator()(const T& a, const T& b) const { + return a->GetName() < b->GetName(); + } + }; + std::string name_; ClassQualifier qualifier_; bool create_if_empty_; - std::vector<std::unique_ptr<ClassMember>> members_; + std::set<std::unique_ptr<ClassMember>, ClassMemberCompare> members_; DISALLOW_COPY_AND_ASSIGN(ClassDefinition); }; diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp index 44fa0f19a0e5..8da9106aa8d7 100644 --- a/tools/aapt2/java/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -480,7 +480,7 @@ Maybe<std::string> JavaClassGenerator::UnmangleResource(const StringPiece& packa if (NameMangler::Unmangle(&unmangled_name, &unmangled_package)) { // The entry name was mangled, and we successfully unmangled it. // Check that we want to emit this symbol. - if (package_name != unmangled_package) { + if (package_name_to_generate != unmangled_package) { // Skip the entry if it doesn't belong to the package we're writing. return {}; } @@ -579,8 +579,7 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, continue; } - // Stay consistent with AAPT and generate an empty type class if the R class - // is public. + // Stay consistent with AAPT and generate an empty type class if the R class is public. const bool force_creation_if_empty = (options_.types == JavaClassGeneratorOptions::SymbolTypes::kPublic); diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp index 271279ff5e92..4f449f0db41a 100644 --- a/tools/aapt2/java/JavaClassGenerator_test.cpp +++ b/tools/aapt2/java/JavaClassGenerator_test.cpp @@ -22,7 +22,9 @@ #include "test/Test.h" #include "util/Util.h" -using android::StringPiece; +using ::android::StringPiece; +using ::testing::HasSubstr; +using ::testing::Not; namespace aapt { @@ -52,17 +54,15 @@ TEST(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) { .AddSimple("android:id/hey-man", ResourceId(0x01020000)) .AddValue("android:attr/cool.attr", ResourceId(0x01010000), test::AttributeBuilder(false).Build()) - .AddValue( - "android:styleable/hey.dude", ResourceId(0x01030000), - test::StyleableBuilder() - .AddItem("android:attr/cool.attr", ResourceId(0x01010000)) - .Build()) + .AddValue("android:styleable/hey.dude", ResourceId(0x01030000), + test::StyleableBuilder() + .AddItem("android:attr/cool.attr", ResourceId(0x01010000)) + .Build()) .Build(); std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGenerator generator(context.get(), table.get(), {}); @@ -72,14 +72,9 @@ TEST(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) { std::string output = out.str(); - EXPECT_NE(std::string::npos, - output.find("public static final int hey_man=0x01020000;")); - - EXPECT_NE(std::string::npos, - output.find("public static final int[] hey_dude={")); - - EXPECT_NE(std::string::npos, - output.find("public static final int hey_dude_cool_attr=0;")); + EXPECT_THAT(output, HasSubstr("public static final int hey_man=0x01020000;")); + EXPECT_THAT(output, HasSubstr("public static final int[] hey_dude={")); + EXPECT_THAT(output, HasSubstr("public static final int hey_dude_cool_attr=0;")); } TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) { @@ -92,8 +87,7 @@ TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGenerator generator(context.get(), table.get(), {}); @@ -101,11 +95,10 @@ TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) { ASSERT_TRUE(generator.Generate("android", "com.android.internal", &out)); std::string output = out.str(); - EXPECT_NE(std::string::npos, output.find("package com.android.internal;")); - EXPECT_NE(std::string::npos, - output.find("public static final int one=0x01020000;")); - EXPECT_EQ(std::string::npos, output.find("two")); - EXPECT_EQ(std::string::npos, output.find("com_foo$two")); + EXPECT_THAT(output, HasSubstr("package com.android.internal;")); + EXPECT_THAT(output, HasSubstr("public static final int one=0x01020000;")); + EXPECT_THAT(output, Not(HasSubstr("two"))); + EXPECT_THAT(output, Not(HasSubstr("com_foo$two"))); } TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) { @@ -118,8 +111,7 @@ TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGenerator generator(context.get(), table.get(), {}); @@ -127,9 +119,8 @@ TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) { ASSERT_TRUE(generator.Generate("android", &out)); std::string output = out.str(); - EXPECT_NE(std::string::npos, output.find("public static final class attr")); - EXPECT_EQ(std::string::npos, - output.find("public static final class ^attr-private")); + EXPECT_THAT(output, HasSubstr("public static final class attr")); + EXPECT_THAT(output, Not(HasSubstr("public static final class ^attr-private"))); } TEST(JavaClassGeneratorTest, OnlyWritePublicResources) { @@ -140,16 +131,13 @@ TEST(JavaClassGeneratorTest, OnlyWritePublicResources) { .AddSimple("android:id/one", ResourceId(0x01020000)) .AddSimple("android:id/two", ResourceId(0x01020001)) .AddSimple("android:id/three", ResourceId(0x01020002)) - .SetSymbolState("android:id/one", ResourceId(0x01020000), - SymbolState::kPublic) - .SetSymbolState("android:id/two", ResourceId(0x01020001), - SymbolState::kPrivate) + .SetSymbolState("android:id/one", ResourceId(0x01020000), SymbolState::kPublic) + .SetSymbolState("android:id/two", ResourceId(0x01020001), SymbolState::kPrivate) .Build(); std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); @@ -160,10 +148,9 @@ TEST(JavaClassGeneratorTest, OnlyWritePublicResources) { std::stringstream out; ASSERT_TRUE(generator.Generate("android", &out)); std::string output = out.str(); - EXPECT_NE(std::string::npos, - output.find("public static final int one=0x01020000;")); - EXPECT_EQ(std::string::npos, output.find("two")); - EXPECT_EQ(std::string::npos, output.find("three")); + EXPECT_THAT(output, HasSubstr("public static final int one=0x01020000;")); + EXPECT_THAT(output, Not(HasSubstr("two"))); + EXPECT_THAT(output, Not(HasSubstr("three"))); } options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate; @@ -172,11 +159,9 @@ TEST(JavaClassGeneratorTest, OnlyWritePublicResources) { std::stringstream out; ASSERT_TRUE(generator.Generate("android", &out)); std::string output = out.str(); - EXPECT_NE(std::string::npos, - output.find("public static final int one=0x01020000;")); - EXPECT_NE(std::string::npos, - output.find("public static final int two=0x01020001;")); - EXPECT_EQ(std::string::npos, output.find("three")); + EXPECT_THAT(output, HasSubstr("public static final int one=0x01020000;")); + EXPECT_THAT(output, HasSubstr("public static final int two=0x01020001;")); + EXPECT_THAT(output, Not(HasSubstr("three"))); } options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; @@ -185,12 +170,9 @@ TEST(JavaClassGeneratorTest, OnlyWritePublicResources) { std::stringstream out; ASSERT_TRUE(generator.Generate("android", &out)); std::string output = out.str(); - EXPECT_NE(std::string::npos, - output.find("public static final int one=0x01020000;")); - EXPECT_NE(std::string::npos, - output.find("public static final int two=0x01020001;")); - EXPECT_NE(std::string::npos, - output.find("public static final int three=0x01020002;")); + EXPECT_THAT(output, HasSubstr("public static final int one=0x01020000;")); + EXPECT_THAT(output, HasSubstr("public static final int two=0x01020001;")); + EXPECT_THAT(output, HasSubstr("public static final int three=0x01020002;")); } } @@ -246,8 +228,7 @@ TEST(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGenerator generator(context.get(), table.get(), {}); @@ -256,8 +237,8 @@ TEST(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) { EXPECT_TRUE(generator.Generate("android", &out)); std::string output = out.str(); - EXPECT_NE(std::string::npos, output.find("int foo_bar=")); - EXPECT_NE(std::string::npos, output.find("int foo_com_lib_bar=")); + EXPECT_THAT(output, HasSubstr("int foo_bar=")); + EXPECT_THAT(output, HasSubstr("int foo_com_lib_bar=")); } TEST(JavaClassGeneratorTest, CommentsForSimpleResourcesArePresent) { @@ -271,24 +252,22 @@ TEST(JavaClassGeneratorTest, CommentsForSimpleResourcesArePresent) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGenerator generator(context.get(), table.get(), {}); std::stringstream out; ASSERT_TRUE(generator.Generate("android", &out)); - std::string actual = out.str(); + std::string output = out.str(); - const char* expectedText = + const char* expected_text = R"EOF(/** * This is a comment * @deprecated */ @Deprecated public static final int foo=0x01010000;)EOF"; - - EXPECT_NE(std::string::npos, actual.find(expectedText)); + EXPECT_THAT(output, HasSubstr(expected_text)); } TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) {} @@ -298,8 +277,7 @@ TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) attr.SetComment(StringPiece("This is an attribute")); Styleable styleable; - styleable.entries.push_back( - Reference(test::ParseNameOrDie("android:attr/one"))); + styleable.entries.push_back(Reference(test::ParseNameOrDie("android:attr/one"))); styleable.SetComment(StringPiece("This is a styleable")); std::unique_ptr<ResourceTable> table = @@ -312,8 +290,7 @@ TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGeneratorOptions options; @@ -321,12 +298,12 @@ TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) JavaClassGenerator generator(context.get(), table.get(), options); std::stringstream out; ASSERT_TRUE(generator.Generate("android", &out)); - std::string actual = out.str(); + std::string output = out.str(); - EXPECT_NE(std::string::npos, actual.find("attr name android:one")); - EXPECT_NE(std::string::npos, actual.find("attr description")); - EXPECT_NE(std::string::npos, actual.find(attr.GetComment().data())); - EXPECT_NE(std::string::npos, actual.find(styleable.GetComment().data())); + EXPECT_THAT(output, HasSubstr("attr name android:one")); + EXPECT_THAT(output, HasSubstr("attr description")); + EXPECT_THAT(output, HasSubstr(attr.GetComment())); + EXPECT_THAT(output, HasSubstr(styleable.GetComment())); } TEST(JavaClassGeneratorTest, CommentsForRemovedAttributesAreNotPresentInClass) { @@ -341,8 +318,7 @@ TEST(JavaClassGeneratorTest, CommentsForRemovedAttributesAreNotPresentInClass) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGeneratorOptions options; @@ -350,17 +326,17 @@ TEST(JavaClassGeneratorTest, CommentsForRemovedAttributesAreNotPresentInClass) { JavaClassGenerator generator(context.get(), table.get(), options); std::stringstream out; ASSERT_TRUE(generator.Generate("android", &out)); - std::string actual = out.str(); + std::string output = out.str(); - EXPECT_EQ(std::string::npos, actual.find("@attr name android:one")); - EXPECT_EQ(std::string::npos, actual.find("@attr description")); + EXPECT_THAT(output, Not(HasSubstr("@attr name android:one"))); + EXPECT_THAT(output, Not(HasSubstr("@attr description"))); // We should find @removed only in the attribute javadoc and not anywhere else - // (i.e. the class - // javadoc). - const size_t pos = actual.find("removed"); - EXPECT_NE(std::string::npos, pos); - EXPECT_EQ(std::string::npos, actual.find("removed", pos + 1)); + // (i.e. the class javadoc). + const std::string kRemoved("removed"); + ASSERT_THAT(output, HasSubstr(kRemoved)); + std::string after_first_match = output.substr(output.find(kRemoved) + kRemoved.size()); + EXPECT_THAT(after_first_match, Not(HasSubstr(kRemoved))); } TEST(JavaClassGeneratorTest, GenerateOnResourcesLoadedCallbackForSharedLibrary) { @@ -381,19 +357,17 @@ TEST(JavaClassGeneratorTest, GenerateOnResourcesLoadedCallbackForSharedLibrary) JavaClassGeneratorOptions options; options.use_final = false; - options.rewrite_callback_options = OnResourcesLoadedCallbackOptions{ - {"com.foo", "com.boo"}, - }; + options.rewrite_callback_options = OnResourcesLoadedCallbackOptions{{"com.foo", "com.boo"}}; JavaClassGenerator generator(context.get(), table.get(), options); std::stringstream out; ASSERT_TRUE(generator.Generate("android", &out)); - std::string actual = out.str(); + std::string output = out.str(); - EXPECT_NE(std::string::npos, actual.find("void onResourcesLoaded")); - EXPECT_NE(std::string::npos, actual.find("com.foo.R.onResourcesLoaded")); - EXPECT_NE(std::string::npos, actual.find("com.boo.R.onResourcesLoaded")); + EXPECT_THAT(output, HasSubstr("void onResourcesLoaded")); + EXPECT_THAT(output, HasSubstr("com.foo.R.onResourcesLoaded")); + EXPECT_THAT(output, HasSubstr("com.boo.R.onResourcesLoaded")); } } // namespace aapt diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp index f49e4985fcf1..c4b36176aa71 100644 --- a/tools/aapt2/java/ManifestClassGenerator.cpp +++ b/tools/aapt2/java/ManifestClassGenerator.cpp @@ -21,24 +21,21 @@ #include "Source.h" #include "java/AnnotationProcessor.h" #include "java/ClassDefinition.h" +#include "text/Unicode.h" #include "util/Maybe.h" #include "xml/XmlDom.h" -using android::StringPiece; +using ::android::StringPiece; +using ::aapt::text::IsJavaIdentifier; namespace aapt { -static Maybe<StringPiece> ExtractJavaIdentifier(IDiagnostics* diag, - const Source& source, - const StringPiece& value) { - const StringPiece sep = "."; - auto iter = std::find_end(value.begin(), value.end(), sep.begin(), sep.end()); - - StringPiece result; - if (iter != value.end()) { - result.assign(iter + sep.size(), value.end() - (iter + sep.size())); - } else { - result = value; +static Maybe<StringPiece> ExtractJavaIdentifier(IDiagnostics* diag, const Source& source, + const std::string& value) { + StringPiece result = value; + size_t pos = value.rfind('.'); + if (pos != std::string::npos) { + result = result.substr(pos + 1); } if (result.empty()) { @@ -46,33 +43,23 @@ static Maybe<StringPiece> ExtractJavaIdentifier(IDiagnostics* diag, return {}; } - iter = util::FindNonAlphaNumericAndNotInSet(result, "_"); - if (iter != result.end()) { - diag->Error(DiagMessage(source) << "invalid character '" - << StringPiece(iter, 1) << "' in '" - << result << "'"); + if (!IsJavaIdentifier(result)) { + diag->Error(DiagMessage(source) << "invalid Java identifier '" << result << "'"); return {}; } - - if (*result.begin() >= '0' && *result.begin() <= '9') { - diag->Error(DiagMessage(source) << "symbol can not start with a digit"); - return {}; - } - return result; } -static bool WriteSymbol(const Source& source, IDiagnostics* diag, - xml::Element* el, ClassDefinition* class_def) { +static bool WriteSymbol(const Source& source, IDiagnostics* diag, xml::Element* el, + ClassDefinition* class_def) { xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name"); if (!attr) { - diag->Error(DiagMessage(source) << "<" << el->name - << "> must define 'android:name'"); + diag->Error(DiagMessage(source) << "<" << el->name << "> must define 'android:name'"); return false; } - Maybe<StringPiece> result = ExtractJavaIdentifier( - diag, source.WithLine(el->line_number), attr->value); + Maybe<StringPiece> result = + ExtractJavaIdentifier(diag, source.WithLine(el->line_number), attr->value); if (!result) { return false; } @@ -81,12 +68,14 @@ static bool WriteSymbol(const Source& source, IDiagnostics* diag, util::make_unique<StringMember>(result.value(), attr->value); string_member->GetCommentBuilder()->AppendComment(el->comment); - class_def->AddMember(std::move(string_member)); + if (class_def->AddMember(std::move(string_member)) == ClassDefinition::Result::kOverridden) { + diag->Warn(DiagMessage(source.WithLine(el->line_number)) + << "duplicate definitions of '" << result.value() << "', overriding previous"); + } return true; } -std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, - xml::XmlResource* res) { +std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, xml::XmlResource* res) { xml::Element* el = xml::FindRootElement(res->root.get()); if (!el) { diag->Error(DiagMessage(res->file.source) << "no root tag defined"); @@ -94,8 +83,7 @@ std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, } if (el->name != "manifest" && !el->namespace_uri.empty()) { - diag->Error(DiagMessage(res->file.source) - << "no <manifest> root tag defined"); + diag->Error(DiagMessage(res->file.source) << "no <manifest> root tag defined"); return {}; } @@ -109,11 +97,9 @@ std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, for (xml::Element* child_el : children) { if (child_el->namespace_uri.empty()) { if (child_el->name == "permission") { - error |= !WriteSymbol(res->file.source, diag, child_el, - permission_class.get()); + error |= !WriteSymbol(res->file.source, diag, child_el, permission_class.get()); } else if (child_el->name == "permission-group") { - error |= !WriteSymbol(res->file.source, diag, child_el, - permission_group_class.get()); + error |= !WriteSymbol(res->file.source, diag, child_el, permission_group_class.get()); } } } diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp index 9f6ec210a6a7..ada563409d19 100644 --- a/tools/aapt2/java/ManifestClassGenerator_test.cpp +++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp @@ -84,6 +84,8 @@ TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) { @hide @SystemApi --> <permission android:name="android.permission.SECRET" /> + <!-- @TestApi This is a test only permission. --> + <permission android:name="android.permission.TEST_ONLY" /> </manifest>)"); std::string actual; @@ -110,6 +112,28 @@ TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) { @android.annotation.SystemApi public static final String SECRET="android.permission.SECRET";)"; EXPECT_THAT(actual, HasSubstr(expected_secret)); + + const char* expected_test = R"( /** + * This is a test only permission. + */ + @android.annotation.TestApi + public static final String TEST_ONLY="android.permission.TEST_ONLY";)"; + EXPECT_THAT(actual, HasSubstr(expected_test)); +} + +// This is bad but part of public API behaviour so we need to preserve it. +TEST(ManifestClassGeneratorTest, LastSeenPermissionWithSameLeafNameTakesPrecedence) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> manifest = test::BuildXmlDom(R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android"> + <permission android:name="android.permission.ACCESS_INTERNET" /> + <permission android:name="com.android.aapt.test.ACCESS_INTERNET" /> + </manifest>)"); + + std::string actual; + ASSERT_TRUE(GetManifestClassText(context.get(), manifest.get(), &actual)); + EXPECT_THAT(actual, HasSubstr("ACCESS_INTERNET=\"com.android.aapt.test.ACCESS_INTERNET\";")); + EXPECT_THAT(actual, Not(HasSubstr("ACCESS_INTERNET=\"android.permission.ACCESS_INTERNET\";"))); } static ::testing::AssertionResult GetManifestClassText(IAaptContext* context, xml::XmlResource* res, diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp index 5f61faeeebe7..10c46101123c 100644 --- a/tools/aapt2/java/ProguardRules.cpp +++ b/tools/aapt2/java/ProguardRules.cpp @@ -29,18 +29,12 @@ namespace proguard { class BaseVisitor : public xml::Visitor { public: - BaseVisitor(const Source& source, KeepSet* keep_set) - : source_(source), keep_set_(keep_set) {} + using xml::Visitor::Visit; - virtual void Visit(xml::Text*) override{}; - - virtual void Visit(xml::Namespace* node) override { - for (const auto& child : node->children) { - child->Accept(this); - } + BaseVisitor(const Source& source, KeepSet* keep_set) : source_(source), keep_set_(keep_set) { } - virtual void Visit(xml::Element* node) override { + void Visit(xml::Element* node) override { if (!node->namespace_uri.empty()) { Maybe<xml::ExtractedPackage> maybe_package = xml::ExtractPackageFromNamespace(node->namespace_uri); @@ -78,10 +72,10 @@ class BaseVisitor : public xml::Visitor { class LayoutVisitor : public BaseVisitor { public: - LayoutVisitor(const Source& source, KeepSet* keep_set) - : BaseVisitor(source, keep_set) {} + LayoutVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) { + } - virtual void Visit(xml::Element* node) override { + void Visit(xml::Element* node) override { bool check_class = false; bool check_name = false; if (node->namespace_uri.empty()) { @@ -119,7 +113,7 @@ class MenuVisitor : public BaseVisitor { MenuVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) { } - virtual void Visit(xml::Element* node) override { + void Visit(xml::Element* node) override { if (node->namespace_uri.empty() && node->name == "item") { for (const auto& attr : node->attributes) { if (attr.namespace_uri == xml::kSchemaAndroid) { @@ -142,10 +136,10 @@ class MenuVisitor : public BaseVisitor { class XmlResourceVisitor : public BaseVisitor { public: - XmlResourceVisitor(const Source& source, KeepSet* keep_set) - : BaseVisitor(source, keep_set) {} + XmlResourceVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) { + } - virtual void Visit(xml::Element* node) override { + void Visit(xml::Element* node) override { bool check_fragment = false; if (node->namespace_uri.empty()) { check_fragment = @@ -169,13 +163,12 @@ class XmlResourceVisitor : public BaseVisitor { class TransitionVisitor : public BaseVisitor { public: - TransitionVisitor(const Source& source, KeepSet* keep_set) - : BaseVisitor(source, keep_set) {} + TransitionVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) { + } - virtual void Visit(xml::Element* node) override { + void Visit(xml::Element* node) override { bool check_class = - node->namespace_uri.empty() && - (node->name == "transition" || node->name == "pathMotion"); + node->namespace_uri.empty() && (node->name == "transition" || node->name == "pathMotion"); if (check_class) { xml::Attribute* attr = node->FindAttribute({}, "class"); if (attr && util::IsJavaClassName(attr->value)) { @@ -195,7 +188,7 @@ class ManifestVisitor : public BaseVisitor { ManifestVisitor(const Source& source, KeepSet* keep_set, bool main_dex_only) : BaseVisitor(source, keep_set), main_dex_only_(main_dex_only) {} - virtual void Visit(xml::Element* node) override { + void Visit(xml::Element* node) override { if (node->namespace_uri.empty()) { bool get_name = false; if (node->name == "manifest") { @@ -205,18 +198,15 @@ class ManifestVisitor : public BaseVisitor { } } else if (node->name == "application") { get_name = true; - xml::Attribute* attr = - node->FindAttribute(xml::kSchemaAndroid, "backupAgent"); + xml::Attribute* attr = node->FindAttribute(xml::kSchemaAndroid, "backupAgent"); if (attr) { - Maybe<std::string> result = - util::GetFullyQualifiedClassName(package_, attr->value); + Maybe<std::string> result = util::GetFullyQualifiedClassName(package_, attr->value); if (result) { AddClass(node->line_number, result.value()); } } if (main_dex_only_) { - xml::Attribute* default_process = - node->FindAttribute(xml::kSchemaAndroid, "process"); + xml::Attribute* default_process = node->FindAttribute(xml::kSchemaAndroid, "process"); if (default_process) { default_process_ = default_process->value; } @@ -226,8 +216,7 @@ class ManifestVisitor : public BaseVisitor { get_name = true; if (main_dex_only_) { - xml::Attribute* component_process = - node->FindAttribute(xml::kSchemaAndroid, "process"); + xml::Attribute* component_process = node->FindAttribute(xml::kSchemaAndroid, "process"); const std::string& process = component_process ? component_process->value : default_process_; @@ -242,8 +231,7 @@ class ManifestVisitor : public BaseVisitor { get_name = attr != nullptr; if (get_name) { - Maybe<std::string> result = - util::GetFullyQualifiedClassName(package_, attr->value); + Maybe<std::string> result = util::GetFullyQualifiedClassName(package_, attr->value); if (result) { AddClass(node->line_number, result.value()); } @@ -261,8 +249,7 @@ class ManifestVisitor : public BaseVisitor { std::string default_process_; }; -bool CollectProguardRulesForManifest(const Source& source, - xml::XmlResource* res, KeepSet* keep_set, +bool CollectProguardRulesForManifest(const Source& source, xml::XmlResource* res, KeepSet* keep_set, bool main_dex_only) { ManifestVisitor visitor(source, keep_set, main_dex_only); if (res->root) { @@ -272,8 +259,7 @@ bool CollectProguardRulesForManifest(const Source& source, return false; } -bool CollectProguardRules(const Source& source, xml::XmlResource* res, - KeepSet* keep_set) { +bool CollectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keep_set) { if (!res->root) { return false; } @@ -321,8 +307,7 @@ bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set) { for (const Source& source : entry.second) { *out << "# Referenced at " << source << "\n"; } - *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" - << std::endl; + *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl; } return true; } diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h index 5527f9092c87..3c9c4767b3d1 100644 --- a/tools/aapt2/link/Linkers.h +++ b/tools/aapt2/link/Linkers.h @@ -21,6 +21,7 @@ #include <unordered_set> #include "android-base/macros.h" +#include "androidfw/StringPiece.h" #include "Resource.h" #include "SdkConstants.h" @@ -33,18 +34,15 @@ class ResourceTable; class ResourceEntry; struct ConfigDescription; -/** - * Defines the location in which a value exists. This determines visibility of - * other package's private symbols. - */ +// Defines the context in which a resource value is defined. Most resources are defined with the +// implicit package name of their compilation context. Understanding the package name of a resource +// allows to determine visibility of other symbols which may or may not have their packages defined. struct CallSite { - ResourceNameRef resource; + std::string package; }; -/** - * Determines whether a versioned resource should be created. If a versioned - * resource already exists, it takes precedence. - */ +// Determines whether a versioned resource should be created. If a versioned resource already +// exists, it takes precedence. bool ShouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config, const ApiVersion sdk_version_to_generate); @@ -62,39 +60,26 @@ class AutoVersioner : public IResourceTableConsumer { DISALLOW_COPY_AND_ASSIGN(AutoVersioner); }; -/** - * If any attribute resource values are defined as public, this consumer will - * move all private - * attribute resource values to a private ^private-attr type, avoiding backwards - * compatibility - * issues with new apps running on old platforms. - * - * The Android platform ignores resource attributes it doesn't recognize, so an - * app developer can - * use new attributes in their layout XML files without worrying about - * versioning. This assumption - * actually breaks on older platforms. OEMs may add private attributes that are - * used internally. - * AAPT originally assigned all private attributes IDs immediately proceeding - * the public attributes' - * IDs. - * - * This means that on a newer Android platform, an ID previously assigned to a - * private attribute - * may end up assigned to a public attribute. - * - * App developers assume using the newer attribute is safe on older platforms - * because it will - * be ignored. Instead, the platform thinks the new attribute is an older, - * private attribute and - * will interpret it as such. This leads to unintended styling and exceptions - * thrown due to - * unexpected types. - * - * By moving the private attributes to a completely different type, this ID - * conflict will never - * occur. - */ +// If any attribute resource values are defined as public, this consumer will move all private +// attribute resource values to a private ^private-attr type, avoiding backwards compatibility +// issues with new apps running on old platforms. +// +// The Android platform ignores resource attributes it doesn't recognize, so an app developer can +// use new attributes in their layout XML files without worrying about versioning. This assumption +// actually breaks on older platforms. OEMs may add private attributes that are used internally. +// AAPT originally assigned all private attributes IDs immediately proceeding the public attributes' +// IDs. +// +// This means that on a newer Android platform, an ID previously assigned to a private attribute +// may end up assigned to a public attribute. +// +// App developers assume using the newer attribute is safe on older platforms because it will +// be ignored. Instead, the platform thinks the new attribute is an older, private attribute and +// will interpret it as such. This leads to unintended styling and exceptions thrown due to +// unexpected types. +// +// By moving the private attributes to a completely different type, this ID conflict will never +// occur. class PrivateAttributeMover : public IResourceTableConsumer { public: PrivateAttributeMover() = default; @@ -126,14 +111,10 @@ class ProductFilter : public IResourceTableConsumer { std::unordered_set<std::string> products_; }; -/** - * Removes namespace nodes and URI information from the XmlResource. - * - * Once an XmlResource is processed by this consumer, it is no longer able to - * have its attributes - * parsed. As such, this XmlResource must have already been processed by - * XmlReferenceLinker. - */ +// Removes namespace nodes and URI information from the XmlResource. +// +// Once an XmlResource is processed by this consumer, it is no longer able to have its attributes +// parsed. As such, this XmlResource must have already been processed by XmlReferenceLinker. class XmlNamespaceRemover : public IXmlResourceConsumer { public: explicit XmlNamespaceRemover(bool keep_uris = false) : keep_uris_(keep_uris){}; @@ -146,11 +127,8 @@ class XmlNamespaceRemover : public IXmlResourceConsumer { bool keep_uris_; }; -/** - * Resolves attributes in the XmlResource and compiles string values to resource - * values. - * Once an XmlResource is processed by this linker, it is ready to be flattened. - */ +// Resolves attributes in the XmlResource and compiles string values to resource values. +// Once an XmlResource is processed by this linker, it is ready to be flattened. class XmlReferenceLinker : public IXmlResourceConsumer { public: XmlReferenceLinker() = default; diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp index 80edb352f42c..da7f410b8b08 100644 --- a/tools/aapt2/link/ManifestFixer_test.cpp +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -122,7 +122,7 @@ TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) { xml::Element* el; xml::Attribute* attr; - el = xml::FindRootElement(doc.get()); + el = doc->root.get(); ASSERT_NE(nullptr, el); el = el->FindChild({}, "uses-sdk"); ASSERT_NE(nullptr, el); @@ -141,7 +141,7 @@ TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) { options); ASSERT_NE(nullptr, doc); - el = xml::FindRootElement(doc.get()); + el = doc->root.get(); ASSERT_NE(nullptr, el); el = el->FindChild({}, "uses-sdk"); ASSERT_NE(nullptr, el); @@ -160,7 +160,7 @@ TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) { options); ASSERT_NE(nullptr, doc); - el = xml::FindRootElement(doc.get()); + el = doc->root.get(); ASSERT_NE(nullptr, el); el = el->FindChild({}, "uses-sdk"); ASSERT_NE(nullptr, el); @@ -177,7 +177,7 @@ TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) { options); ASSERT_NE(nullptr, doc); - el = xml::FindRootElement(doc.get()); + el = doc->root.get(); ASSERT_NE(nullptr, el); el = el->FindChild({}, "uses-sdk"); ASSERT_NE(nullptr, el); @@ -199,7 +199,7 @@ TEST_F(ManifestFixerTest, UsesSdkMustComeBeforeApplication) { options); ASSERT_NE(nullptr, doc); - xml::Element* manifest_el = xml::FindRootElement(doc.get()); + xml::Element* manifest_el = doc->root.get(); ASSERT_NE(nullptr, manifest_el); ASSERT_EQ("manifest", manifest_el->name); @@ -248,7 +248,7 @@ TEST_F(ManifestFixerTest, RenameManifestPackageAndFullyQualifyClasses) { options); ASSERT_NE(nullptr, doc); - xml::Element* manifestEl = xml::FindRootElement(doc.get()); + xml::Element* manifestEl = doc->root.get(); ASSERT_NE(nullptr, manifestEl); xml::Attribute* attr = nullptr; @@ -297,7 +297,7 @@ TEST_F(ManifestFixerTest, options); ASSERT_NE(nullptr, doc); - xml::Element* manifest_el = xml::FindRootElement(doc.get()); + xml::Element* manifest_el = doc->root.get(); ASSERT_NE(nullptr, manifest_el); xml::Element* instrumentation_el = @@ -321,7 +321,7 @@ TEST_F(ManifestFixerTest, UseDefaultVersionNameAndCode) { options); ASSERT_NE(nullptr, doc); - xml::Element* manifest_el = xml::FindRootElement(doc.get()); + xml::Element* manifest_el = doc->root.get(); ASSERT_NE(nullptr, manifest_el); xml::Attribute* attr = @@ -344,7 +344,7 @@ TEST_F(ManifestFixerTest, EnsureManifestAttributesAreTyped) { Verify("<manifest package=\"android\" coreApp=\"true\" />"); ASSERT_NE(nullptr, doc); - xml::Element* el = xml::FindRootElement(doc.get()); + xml::Element* el = doc->root.get(); ASSERT_NE(nullptr, el); EXPECT_EQ("manifest", el->name); diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp index 414e56eb5dcc..71e828b039e1 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -30,23 +30,18 @@ #include "util/Util.h" #include "xml/XmlUtil.h" -using android::StringPiece; +using ::android::StringPiece; namespace aapt { namespace { -/** - * The ReferenceLinkerVisitor will follow all references and make sure they - * point - * to resources that actually exist, either in the local resource table, or as - * external - * symbols. Once the target resource has been found, the ID of the resource will - * be assigned - * to the reference object. - * - * NOTE: All of the entries in the ResourceTable must be assigned IDs. - */ +// The ReferenceLinkerVisitor will follow all references and make sure they point +// to resources that actually exist, either in the local resource table, or as external +// symbols. Once the target resource has been found, the ID of the resource will be assigned +// to the reference object. +// +// NOTE: All of the entries in the ResourceTable must be assigned IDs. class ReferenceLinkerVisitor : public ValueVisitor { public: using ValueVisitor::Visit; @@ -65,14 +60,9 @@ class ReferenceLinkerVisitor : public ValueVisitor { } } - /** - * We visit the Style specially because during this phase, values of - * attributes are - * all RawString values. Now that we are expected to resolve all symbols, we - * can - * lookup the attributes to find out which types are allowed for the - * attributes' values. - */ + // We visit the Style specially because during this phase, values of attributes are + // all RawString values. Now that we are expected to resolve all symbols, we can + // lookup the attributes to find out which types are allowed for the attributes' values. void Visit(Style* style) override { if (style->parent) { Visit(&style->parent.value()); @@ -81,28 +71,21 @@ class ReferenceLinkerVisitor : public ValueVisitor { for (Style::Entry& entry : style->entries) { std::string err_str; - // Transform the attribute reference so that it is using the fully - // qualified package - // name. This will also mark the reference as being able to see private - // resources if - // there was a '*' in the reference or if the package came from the - // private namespace. + // Transform the attribute reference so that it is using the fully qualified package + // name. This will also mark the reference as being able to see private resources if + // there was a '*' in the reference or if the package came from the private namespace. Reference transformed_reference = entry.key; - TransformReferenceFromNamespace(package_decls_, - context_->GetCompilationPackage(), - &transformed_reference); + ResolvePackage(package_decls_, &transformed_reference); - // Find the attribute in the symbol table and check if it is visible from - // this callsite. + // Find the attribute in the symbol table and check if it is visible from this callsite. const SymbolTable::Symbol* symbol = ReferenceLinker::ResolveAttributeCheckVisibility( transformed_reference, callsite_, symbols_, &err_str); if (symbol) { - // Assign our style key the correct ID. - // The ID may not exist. + // Assign our style key the correct ID. The ID may not exist. entry.key.id = symbol->id; - // Try to convert the value to a more specific, typed value based on the - // attribute it is set to. + // Try to convert the value to a more specific, typed value based on the attribute it is + // set to. entry.value = ParseValueWithAttribute(std::move(entry.value), symbol->attribute.get()); // Link/resolve the final value (mostly if it's a reference). @@ -115,8 +98,8 @@ class ReferenceLinkerVisitor : public ValueVisitor { // The actual type of this item is incompatible with the attribute. DiagMessage msg(entry.key.GetSource()); - // Call the matches method again, this time with a DiagMessage so we - // fill in the actual error message. + // Call the matches method again, this time with a DiagMessage so we fill in the actual + // error message. symbol->attribute->Matches(*entry.value, &msg); context_->GetDiagnostics()->Error(msg); error_ = true; @@ -125,7 +108,7 @@ class ReferenceLinkerVisitor : public ValueVisitor { } else { DiagMessage msg(entry.key.GetSource()); msg << "style attribute '"; - ReferenceLinker::WriteResourceName(&msg, entry.key, transformed_reference); + ReferenceLinker::WriteResourceName(entry.key, callsite_, package_decls_, &msg); msg << "' " << err_str; context_->GetDiagnostics()->Error(msg); error_ = true; @@ -133,17 +116,15 @@ class ReferenceLinkerVisitor : public ValueVisitor { } } - bool HasError() { return error_; } + bool HasError() { + return error_; + } private: DISALLOW_COPY_AND_ASSIGN(ReferenceLinkerVisitor); - /** - * Transform a RawString value into a more specific, appropriate value, based - * on the - * Attribute. If a non RawString value is passed in, this is an identity - * transform. - */ + // Transform a RawString value into a more specific, appropriate value, based on the + // Attribute. If a non RawString value is passed in, this is an identity transform. std::unique_ptr<Item> ParseValueWithAttribute(std::unique_ptr<Item> value, const Attribute* attr) { if (RawString* raw_string = ValueCast<RawString>(value.get())) { @@ -178,11 +159,9 @@ class EmptyDeclStack : public xml::IPackageDeclStack { public: EmptyDeclStack() = default; - Maybe<xml::ExtractedPackage> TransformPackageAlias( - const StringPiece& alias, - const StringPiece& local_package) const override { + Maybe<xml::ExtractedPackage> TransformPackageAlias(const StringPiece& alias) const override { if (alias.empty()) { - return xml::ExtractedPackage{local_package.to_string(), true /* private */}; + return xml::ExtractedPackage{{}, true /*private*/}; } return {}; } @@ -191,32 +170,44 @@ class EmptyDeclStack : public xml::IPackageDeclStack { DISALLOW_COPY_AND_ASSIGN(EmptyDeclStack); }; -} // namespace +// The symbol is visible if it is public, or if the reference to it is requesting private access +// or if the callsite comes from the same package. +bool IsSymbolVisible(const SymbolTable::Symbol& symbol, const Reference& ref, + const CallSite& callsite) { + if (symbol.is_public || ref.private_reference) { + return true; + } -/** - * The symbol is visible if it is public, or if the reference to it is - * requesting private access - * or if the callsite comes from the same package. - */ -bool ReferenceLinker::IsSymbolVisible(const SymbolTable::Symbol& symbol, - const Reference& ref, - const CallSite& callsite) { - if (!symbol.is_public && !ref.private_reference) { - if (ref.name) { - return callsite.resource.package == ref.name.value().package; - } else if (ref.id && symbol.id) { - return ref.id.value().package_id() == symbol.id.value().package_id(); - } else { - return false; + if (ref.name) { + const ResourceName& name = ref.name.value(); + if (name.package.empty()) { + // If the symbol was found, and the package is empty, that means it was found in the local + // scope, which is always visible (private local). + return true; } + + // The symbol is visible if the reference is local to the same package it is defined in. + return callsite.package == name.package; } - return true; + + if (ref.id && symbol.id) { + return ref.id.value().package_id() == symbol.id.value().package_id(); + } + return false; } +} // namespace + const SymbolTable::Symbol* ReferenceLinker::ResolveSymbol(const Reference& reference, + const CallSite& callsite, SymbolTable* symbols) { if (reference.name) { - return symbols->FindByName(reference.name.value()); + const ResourceName& name = reference.name.value(); + if (name.package.empty()) { + // Use the callsite's package name if no package name was defined. + return symbols->FindByName(ResourceName(callsite.package, name.type, name.entry)); + } + return symbols->FindByName(name); } else if (reference.id) { return symbols->FindById(reference.id.value()); } else { @@ -228,7 +219,7 @@ const SymbolTable::Symbol* ReferenceLinker::ResolveSymbolCheckVisibility(const R const CallSite& callsite, SymbolTable* symbols, std::string* out_error) { - const SymbolTable::Symbol* symbol = ResolveSymbol(reference, symbols); + const SymbolTable::Symbol* symbol = ResolveSymbol(reference, callsite, symbols); if (!symbol) { if (out_error) *out_error = "not found"; return nullptr; @@ -274,24 +265,62 @@ Maybe<xml::AaptAttribute> ReferenceLinker::CompileXmlAttribute(const Reference& return xml::AaptAttribute(*symbol->attribute, symbol->id); } -void ReferenceLinker::WriteResourceName(DiagMessage* out_msg, - const Reference& orig, - const Reference& transformed) { +void ReferenceLinker::WriteResourceName(const Reference& ref, const CallSite& callsite, + const xml::IPackageDeclStack* decls, DiagMessage* out_msg) { CHECK(out_msg != nullptr); + if (!ref.name) { + *out_msg << ref.id.value(); + return; + } - if (orig.name) { - *out_msg << orig.name.value(); - if (transformed.name.value() != orig.name.value()) { - *out_msg << " (aka " << transformed.name.value() << ")"; - } - } else { - *out_msg << orig.id.value(); + *out_msg << ref.name.value(); + + Reference fully_qualified = ref; + xml::ResolvePackage(decls, &fully_qualified); + + ResourceName& full_name = fully_qualified.name.value(); + if (full_name.package.empty()) { + full_name.package = callsite.package; + } + + if (full_name != ref.name.value()) { + *out_msg << " (aka " << full_name << ")"; + } +} + +void ReferenceLinker::WriteAttributeName(const Reference& ref, const CallSite& callsite, + const xml::IPackageDeclStack* decls, + DiagMessage* out_msg) { + CHECK(out_msg != nullptr); + if (!ref.name) { + *out_msg << ref.id.value(); + return; + } + + const ResourceName& ref_name = ref.name.value(); + CHECK_EQ(ref_name.type, ResourceType::kAttr); + + if (!ref_name.package.empty()) { + *out_msg << ref_name.package << ":"; + } + *out_msg << ref_name.entry; + + Reference fully_qualified = ref; + xml::ResolvePackage(decls, &fully_qualified); + + ResourceName& full_name = fully_qualified.name.value(); + if (full_name.package.empty()) { + full_name.package = callsite.package; + } + + if (full_name != ref.name.value()) { + *out_msg << " (aka " << full_name.package << ":" << full_name.entry << ")"; } } bool ReferenceLinker::LinkReference(const CallSite& callsite, Reference* reference, IAaptContext* context, SymbolTable* symbols, - xml::IPackageDeclStack* decls) { + const xml::IPackageDeclStack* decls) { CHECK(reference != nullptr); if (!reference->name && !reference->id) { // This is @null. @@ -299,7 +328,7 @@ bool ReferenceLinker::LinkReference(const CallSite& callsite, Reference* referen } Reference transformed_reference = *reference; - TransformReferenceFromNamespace(decls, context->GetCompilationPackage(), &transformed_reference); + xml::ResolvePackage(decls, &transformed_reference); std::string err_str; const SymbolTable::Symbol* s = @@ -314,7 +343,7 @@ bool ReferenceLinker::LinkReference(const CallSite& callsite, Reference* referen DiagMessage error_msg(reference->GetSource()); error_msg << "resource "; - WriteResourceName(&error_msg, *reference, transformed_reference); + WriteResourceName(*reference, callsite, decls, &error_msg); error_msg << " " << err_str; context->GetDiagnostics()->Error(error_msg); return false; @@ -324,21 +353,24 @@ bool ReferenceLinker::Consume(IAaptContext* context, ResourceTable* table) { EmptyDeclStack decl_stack; bool error = false; for (auto& package : table->packages) { + // Since we're linking, each package must have a name. + CHECK(!package->name.empty()) << "all packages being linked must have a name"; + for (auto& type : package->types) { for (auto& entry : type->entries) { - // Symbol state information may be lost if there is no value for the - // resource. - if (entry->symbol_status.state != SymbolState::kUndefined && - entry->values.empty()) { - context->GetDiagnostics()->Error( - DiagMessage(entry->symbol_status.source) - << "no definition for declared symbol '" - << ResourceNameRef(package->name, type->type, entry->name) - << "'"); + // First, unmangle the name if necessary. + ResourceName name(package->name, type->type, entry->name); + NameMangler::Unmangle(&name.entry, &name.package); + + // Symbol state information may be lost if there is no value for the resource. + if (entry->symbol_status.state != SymbolState::kUndefined && entry->values.empty()) { + context->GetDiagnostics()->Error(DiagMessage(entry->symbol_status.source) + << "no definition for declared symbol '" << name << "'"); error = true; } - CallSite callsite = {ResourceNameRef(package->name, type->type, entry->name)}; + // The context of this resource is the package in which it is defined. + const CallSite callsite{name.package}; ReferenceLinkerVisitor visitor(callsite, context, context->GetExternalSymbols(), &table->string_pool, &decl_stack); diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h index b3d0196d9e7c..3b11bee0acc9 100644 --- a/tools/aapt2/link/ReferenceLinker.h +++ b/tools/aapt2/link/ReferenceLinker.h @@ -29,83 +29,58 @@ namespace aapt { -/** - * Resolves all references to resources in the ResourceTable and assigns them - * IDs. - * The ResourceTable must already have IDs assigned to each resource. - * Once the ResourceTable is processed by this linker, it is ready to be - * flattened. - */ +// Resolves all references to resources in the ResourceTable and assigns them IDs. +// The ResourceTable must already have IDs assigned to each resource. +// Once the ResourceTable is processed by this linker, it is ready to be flattened. class ReferenceLinker : public IResourceTableConsumer { public: ReferenceLinker() = default; - /** - * Returns true if the symbol is visible by the reference and from the - * callsite. - */ - static bool IsSymbolVisible(const SymbolTable::Symbol& symbol, - const Reference& ref, const CallSite& callsite); - - /** - * Performs name mangling and looks up the resource in the symbol table. - * Returns nullptr if the symbol was not found. - */ - static const SymbolTable::Symbol* ResolveSymbol(const Reference& reference, SymbolTable* symbols); - - /** - * Performs name mangling and looks up the resource in the symbol table. If - * the symbol is not visible by the reference at the callsite, nullptr is - * returned. out_error holds the error message. - */ + // Performs name mangling and looks up the resource in the symbol table. Uses the callsite's + // package if the reference has no package name defined (implicit). + // Returns nullptr if the symbol was not found. + static const SymbolTable::Symbol* ResolveSymbol(const Reference& reference, + const CallSite& callsite, SymbolTable* symbols); + + // Performs name mangling and looks up the resource in the symbol table. If the symbol is not + // visible by the reference at the callsite, nullptr is returned. + // `out_error` holds the error message. static const SymbolTable::Symbol* ResolveSymbolCheckVisibility(const Reference& reference, const CallSite& callsite, SymbolTable* symbols, std::string* out_error); - /** - * Same as resolveSymbolCheckVisibility(), but also makes sure the symbol is - * an attribute. - * That is, the return value will have a non-null value for - * ISymbolTable::Symbol::attribute. - */ + // Same as ResolveSymbolCheckVisibility(), but also makes sure the symbol is an attribute. + // That is, the return value will have a non-null value for ISymbolTable::Symbol::attribute. static const SymbolTable::Symbol* ResolveAttributeCheckVisibility(const Reference& reference, const CallSite& callsite, SymbolTable* symbols, std::string* out_error); - /** - * Resolves the attribute reference and returns an xml::AaptAttribute if - * successful. - * If resolution fails, outError holds the error message. - */ + // Resolves the attribute reference and returns an xml::AaptAttribute if successful. + // If resolution fails, outError holds the error message. static Maybe<xml::AaptAttribute> CompileXmlAttribute(const Reference& reference, const CallSite& callsite, SymbolTable* symbols, std::string* out_error); - /** - * Writes the resource name to the DiagMessage, using the - * "orig_name (aka <transformed_name>)" syntax. - */ - static void WriteResourceName(DiagMessage* out_msg, const Reference& orig, - const Reference& transformed); - - /** - * Transforms the package name of the reference to the fully qualified package - * name using - * the xml::IPackageDeclStack, then mangles and looks up the symbol. If the - * symbol is visible - * to the reference at the callsite, the reference is updated with an ID. - * Returns false on failure, and an error message is logged to the - * IDiagnostics in the context. - */ + // Writes the resource name to the DiagMessage, using the + // "orig_name (aka <transformed_name>)" syntax. + static void WriteResourceName(const Reference& orig, const CallSite& callsite, + const xml::IPackageDeclStack* decls, DiagMessage* out_msg); + + // Same as WriteResourceName but omits the 'attr' part. + static void WriteAttributeName(const Reference& ref, const CallSite& callsite, + const xml::IPackageDeclStack* decls, DiagMessage* out_msg); + + // Transforms the package name of the reference to the fully qualified package name using + // the xml::IPackageDeclStack, then mangles and looks up the symbol. If the symbol is visible + // to the reference at the callsite, the reference is updated with an ID. + // Returns false on failure, and an error message is logged to the IDiagnostics in the context. static bool LinkReference(const CallSite& callsite, Reference* reference, IAaptContext* context, - SymbolTable* symbols, xml::IPackageDeclStack* decls); + SymbolTable* symbols, const xml::IPackageDeclStack* decls); - /** - * Links all references in the ResourceTable. - */ + // Links all references in the ResourceTable. bool Consume(IAaptContext* context, ResourceTable* table) override; private: diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp index 72a91689e392..be38b967c986 100644 --- a/tools/aapt2/link/ReferenceLinker_test.cpp +++ b/tools/aapt2/link/ReferenceLinker_test.cpp @@ -18,7 +18,9 @@ #include "test/Test.h" -using android::ResTable_map; +using ::android::ResTable_map; +using ::testing::Eq; +using ::testing::IsNull; using ::testing::NotNull; namespace aapt { @@ -263,7 +265,7 @@ TEST(ReferenceLinkerTest, AppsWithSamePackageButDifferentIdAreVisibleNonPublic) .Build()); std::string error; - const CallSite call_site{ResourceNameRef("com.app.test", ResourceType::kString, "foo")}; + const CallSite call_site{"com.app.test"}; const SymbolTable::Symbol* symbol = ReferenceLinker::ResolveSymbolCheckVisibility( *test::BuildReference("com.app.test:string/foo"), call_site, &table, &error); ASSERT_THAT(symbol, NotNull()); @@ -281,7 +283,7 @@ TEST(ReferenceLinkerTest, AppsWithDifferentPackageCanNotUseEachOthersAttribute) .Build()); std::string error; - const CallSite call_site{ResourceNameRef("com.app.ext", ResourceType::kLayout, "foo")}; + const CallSite call_site{"com.app.ext"}; EXPECT_FALSE(ReferenceLinker::CompileXmlAttribute( *test::BuildReference("com.app.test:attr/foo"), call_site, &table, &error)); @@ -293,4 +295,27 @@ TEST(ReferenceLinkerTest, AppsWithDifferentPackageCanNotUseEachOthersAttribute) EXPECT_TRUE(error.empty()); } +TEST(ReferenceLinkerTest, ReferenceWithNoPackageUsesCallSitePackage) { + NameMangler mangler(NameManglerPolicy{"com.app.test"}); + SymbolTable table(&mangler); + table.AppendSource(test::StaticSymbolSourceBuilder() + .AddSymbol("com.app.test:string/foo", ResourceId(0x7f010000)) + .AddSymbol("com.app.lib:string/foo", ResourceId(0x7f010001)) + .Build()); + + const SymbolTable::Symbol* s = ReferenceLinker::ResolveSymbol(*test::BuildReference("string/foo"), + CallSite{"com.app.test"}, &table); + ASSERT_THAT(s, NotNull()); + EXPECT_THAT(s->id, Eq(make_value<ResourceId>(0x7f010000))); + + s = ReferenceLinker::ResolveSymbol(*test::BuildReference("string/foo"), CallSite{"com.app.lib"}, + &table); + ASSERT_THAT(s, NotNull()); + EXPECT_THAT(s->id, Eq(make_value<ResourceId>(0x7f010001))); + + EXPECT_THAT(ReferenceLinker::ResolveSymbol(*test::BuildReference("string/foo"), + CallSite{"com.app.bad"}, &table), + IsNull()); +} + } // namespace aapt diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp index 10e837c725e5..93c904f1a743 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -24,7 +24,7 @@ #include "ValueVisitor.h" #include "util/Util.h" -using android::StringPiece; +using ::android::StringPiece; namespace aapt { @@ -32,27 +32,23 @@ TableMerger::TableMerger(IAaptContext* context, ResourceTable* out_table, const TableMergerOptions& options) : context_(context), master_table_(out_table), options_(options) { // Create the desired package that all tables will be merged into. - master_package_ = master_table_->CreatePackage( - context_->GetCompilationPackage(), context_->GetPackageId()); + master_package_ = + master_table_->CreatePackage(context_->GetCompilationPackage(), context_->GetPackageId()); CHECK(master_package_ != nullptr) << "package name or ID already taken"; } -bool TableMerger::Merge(const Source& src, ResourceTable* table, - io::IFileCollection* collection) { - return MergeImpl(src, table, collection, false /* overlay */, true /* allow new */); +bool TableMerger::Merge(const Source& src, ResourceTable* table, io::IFileCollection* collection) { + return MergeImpl(src, table, collection, false /*overlay*/, true /*allow_new*/); } bool TableMerger::MergeOverlay(const Source& src, ResourceTable* table, io::IFileCollection* collection) { - return MergeImpl(src, table, collection, true /* overlay */, options_.auto_add_overlay); + return MergeImpl(src, table, collection, true /*overlay*/, options_.auto_add_overlay); } -/** - * This will merge packages with the same package name (or no package name). - */ +// This will merge packages with the same package name (or no package name). bool TableMerger::MergeImpl(const Source& src, ResourceTable* table, - io::IFileCollection* collection, bool overlay, - bool allow_new) { + io::IFileCollection* collection, bool overlay, bool allow_new) { bool error = false; for (auto& package : table->packages) { // Only merge an empty package or the package we're building. @@ -62,9 +58,8 @@ bool TableMerger::MergeImpl(const Source& src, ResourceTable* table, if (package->name.empty() || context_->GetCompilationPackage() == package->name) { FileMergeCallback callback; if (collection) { - callback = [&](const ResourceNameRef& name, - const ConfigDescription& config, FileReference* new_file, - FileReference* old_file) -> bool { + callback = [&](const ResourceNameRef& name, const ConfigDescription& config, + FileReference* new_file, FileReference* old_file) -> bool { // The old file's path points inside the APK, so we can use it as is. io::IFile* f = collection->FindFile(*old_file->path); if (!f) { @@ -78,45 +73,38 @@ bool TableMerger::MergeImpl(const Source& src, ResourceTable* table, }; } - // Merge here. Once the entries are merged and mangled, any references to - // them are still valid. This is because un-mangled references are - // mangled, then looked up at resolution time. - // Also, when linking, we convert references with no package name to use - // the compilation package name. - error |= !DoMerge(src, table, package.get(), false /* mangle */, overlay, - allow_new, callback); + // Merge here. Once the entries are merged and mangled, any references to them are still + // valid. This is because un-mangled references are mangled, then looked up at resolution + // time. Also, when linking, we convert references with no package name to use the compilation + // package name. + error |= + !DoMerge(src, table, package.get(), false /* mangle */, overlay, allow_new, callback); } } return !error; } -/** - * This will merge and mangle resources from a static library. - */ -bool TableMerger::MergeAndMangle(const Source& src, - const StringPiece& package_name, - ResourceTable* table, - io::IFileCollection* collection) { +// This will merge and mangle resources from a static library. +bool TableMerger::MergeAndMangle(const Source& src, const StringPiece& package_name, + ResourceTable* table, io::IFileCollection* collection) { bool error = false; for (auto& package : table->packages) { // Warn of packages with an unrelated ID. if (package_name != package->name) { - context_->GetDiagnostics()->Warn(DiagMessage(src) << "ignoring package " - << package->name); + context_->GetDiagnostics()->Warn(DiagMessage(src) << "ignoring package " << package->name); continue; } bool mangle = package_name != context_->GetCompilationPackage(); merged_packages_.insert(package->name); - auto callback = [&]( - const ResourceNameRef& name, const ConfigDescription& config, - FileReference* new_file, FileReference* old_file) -> bool { + auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config, + FileReference* new_file, FileReference* old_file) -> bool { // The old file's path points inside the APK, so we can use it as is. io::IFile* f = collection->FindFile(*old_file->path); if (!f) { - context_->GetDiagnostics()->Error( - DiagMessage(src) << "file '" << *old_file->path << "' not found"); + context_->GetDiagnostics()->Error(DiagMessage(src) + << "file '" << *old_file->path << "' not found"); return false; } @@ -124,21 +112,18 @@ bool TableMerger::MergeAndMangle(const Source& src, return true; }; - error |= !DoMerge(src, table, package.get(), mangle, false /* overlay */, - true /* allow new */, callback); + error |= !DoMerge(src, table, package.get(), mangle, false /*overlay*/, true /*allow_new*/, + callback); } return !error; } -static bool MergeType(IAaptContext* context, const Source& src, - ResourceTableType* dst_type, +static bool MergeType(IAaptContext* context, const Source& src, ResourceTableType* dst_type, ResourceTableType* src_type) { if (dst_type->symbol_status.state < src_type->symbol_status.state) { - // The incoming type's visibility is stronger, so we should override - // the visibility. + // The incoming type's visibility is stronger, so we should override the visibility. if (src_type->symbol_status.state == SymbolState::kPublic) { - // Only copy the ID if the source is public, or else the ID is - // meaningless. + // Only copy the ID if the source is public, or else the ID is meaningless. dst_type->id = src_type->id; } dst_type->symbol_status = std::move(src_type->symbol_status); @@ -155,14 +140,12 @@ static bool MergeType(IAaptContext* context, const Source& src, return true; } -static bool MergeEntry(IAaptContext* context, const Source& src, - ResourceEntry* dst_entry, ResourceEntry* src_entry) { +static bool MergeEntry(IAaptContext* context, const Source& src, ResourceEntry* dst_entry, + ResourceEntry* src_entry) { if (dst_entry->symbol_status.state < src_entry->symbol_status.state) { - // The incoming type's visibility is stronger, so we should override - // the visibility. + // The incoming type's visibility is stronger, so we should override the visibility. if (src_entry->symbol_status.state == SymbolState::kPublic) { - // Only copy the ID if the source is public, or else the ID is - // meaningless. + // Only copy the ID if the source is public, or else the ID is meaningless. dst_entry->id = src_entry->id; } dst_entry->symbol_status = std::move(src_entry->symbol_status); @@ -171,9 +154,8 @@ static bool MergeEntry(IAaptContext* context, const Source& src, dst_entry->id && src_entry->id && dst_entry->id.value() != src_entry->id.value()) { // Both entries are public and have different IDs. - context->GetDiagnostics()->Error( - DiagMessage(src) << "cannot merge entry '" << src_entry->name - << "': conflicting public IDs"); + context->GetDiagnostics()->Error(DiagMessage(src) << "cannot merge entry '" << src_entry->name + << "': conflicting public IDs"); return false; } return true; @@ -181,12 +163,10 @@ static bool MergeEntry(IAaptContext* context, const Source& src, // Modified CollisionResolver which will merge Styleables and Styles. Used with overlays. // -// Styleables are not actual resources, but they are treated as such during the -// compilation phase. +// Styleables are not actual resources, but they are treated as such during the compilation phase. // -// Styleables and Styles don't simply overlay each other, their definitions merge -// and accumulate. If both values are Styleables/Styles, we just merge them into the -// existing value. +// Styleables and Styles don't simply overlay each other, their definitions merge and accumulate. +// If both values are Styleables/Styles, we just merge them into the existing value. static ResourceTable::CollisionResult ResolveMergeCollision(Value* existing, Value* incoming, StringPool* pool) { if (Styleable* existing_styleable = ValueCast<Styleable>(existing)) { diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h index c96b1b0b4dfb..81518ffb2441 100644 --- a/tools/aapt2/link/TableMerger.h +++ b/tools/aapt2/link/TableMerger.h @@ -33,81 +33,49 @@ namespace aapt { struct TableMergerOptions { - /** - * If true, resources in overlays can be added without previously having - * existed. - */ + // If true, resources in overlays can be added without previously having existed. bool auto_add_overlay = false; }; -/** - * TableMerger takes resource tables and merges all packages within the tables - * that have the same - * package ID. - * - * If a package has a different name, all the entries in that table have their - * names mangled - * to include the package name. This way there are no collisions. In order to do - * this correctly, - * the TableMerger needs to also mangle any FileReference paths. Once these are - * mangled, - * the original source path of the file, along with the new destination path is - * recorded in the - * queue returned from getFileMergeQueue(). - * - * Once the merging is complete, a separate process can go collect the files - * from the various - * source APKs and either copy or process their XML and put them in the correct - * location in - * the final APK. - */ +// TableMerger takes resource tables and merges all packages within the tables that have the same +// package ID. +// +// If a package has a different name, all the entries in that table have their names mangled +// to include the package name. This way there are no collisions. In order to do this correctly, +// the TableMerger needs to also mangle any FileReference paths. Once these are mangled, the +// `IFile` pointer in `FileReference` will point to the original file. +// +// Once the merging is complete, a separate phase can go collect the files from the various +// source APKs and either copy or process their XML and put them in the correct location in the +// final APK. class TableMerger { public: - /** - * Note: The out_table ResourceTable must live longer than this TableMerger. - * References are made to this ResourceTable for efficiency reasons. - */ - TableMerger(IAaptContext* context, ResourceTable* out_table, - const TableMergerOptions& options); - - const std::set<std::string>& merged_packages() const { + // Note: The out_table ResourceTable must live longer than this TableMerger. + // References are made to this ResourceTable for efficiency reasons. + TableMerger(IAaptContext* context, ResourceTable* out_table, const TableMergerOptions& options); + + inline const std::set<std::string>& merged_packages() const { return merged_packages_; } - /** - * Merges resources from the same or empty package. This is for local sources. - * An io::IFileCollection is optional and used to find the referenced Files - * and process them. - */ - bool Merge(const Source& src, ResourceTable* table, - io::IFileCollection* collection = nullptr); - - /** - * Merges resources from an overlay ResourceTable. - * An io::IFileCollection is optional and used to find the referenced Files - * and process them. - */ + // Merges resources from the same or empty package. This is for local sources. + // An io::IFileCollection is optional and used to find the referenced Files and process them. + bool Merge(const Source& src, ResourceTable* table, io::IFileCollection* collection = nullptr); + + // Merges resources from an overlay ResourceTable. + // An io::IFileCollection is optional and used to find the referenced Files and process them. bool MergeOverlay(const Source& src, ResourceTable* table, io::IFileCollection* collection = nullptr); - /** - * Merges resources from the given package, mangling the name. This is for - * static libraries. - * An io::IFileCollection is needed in order to find the referenced Files and - * process them. - */ + // Merges resources from the given package, mangling the name. This is for static libraries. + // An io::IFileCollection is needed in order to find the referenced Files and process them. bool MergeAndMangle(const Source& src, const android::StringPiece& package, ResourceTable* table, io::IFileCollection* collection); - /** - * Merges a compiled file that belongs to this same or empty package. This is - * for local sources. - */ + // Merges a compiled file that belongs to this same or empty package. This is for local sources. bool MergeFile(const ResourceFile& fileDesc, io::IFile* file); - /** - * Merges a compiled file from an overlay, overriding an existing definition. - */ + // Merges a compiled file from an overlay, overriding an existing definition. bool MergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file); private: diff --git a/tools/aapt2/link/XmlCompatVersioner.cpp b/tools/aapt2/link/XmlCompatVersioner.cpp index f1f4e3b7f05f..20ebdc696814 100644 --- a/tools/aapt2/link/XmlCompatVersioner.cpp +++ b/tools/aapt2/link/XmlCompatVersioner.cpp @@ -107,7 +107,7 @@ std::unique_ptr<xml::XmlResource> XmlCompatVersioner::ProcessDoc( std::unique_ptr<xml::XmlResource> cloned_doc = util::make_unique<xml::XmlResource>(doc->file); cloned_doc->file.config.sdkVersion = static_cast<uint16_t>(target_api); - cloned_doc->root = doc->root->Clone([&](const xml::Element& el, xml::Element* out_el) { + cloned_doc->root = doc->root->CloneElement([&](const xml::Element& el, xml::Element* out_el) { for (const auto& attr : el.attributes) { if (!attr.compiled_attribute) { // Just copy if this isn't a compiled attribute. diff --git a/tools/aapt2/link/XmlCompatVersioner_test.cpp b/tools/aapt2/link/XmlCompatVersioner_test.cpp index ce6605c6ad97..29ad25f79ab9 100644 --- a/tools/aapt2/link/XmlCompatVersioner_test.cpp +++ b/tools/aapt2/link/XmlCompatVersioner_test.cpp @@ -19,6 +19,12 @@ #include "Linkers.h" #include "test/Test.h" +using ::aapt::test::ValueEq; +using ::testing::Eq; +using ::testing::IsNull; +using ::testing::NotNull; +using ::testing::SizeIs; + namespace aapt { constexpr auto TYPE_DIMENSION = android::ResTable_map::TYPE_DIMENSION; @@ -68,12 +74,12 @@ class XmlCompatVersionerTest : public ::testing::Test { }; TEST_F(XmlCompatVersionerTest, NoRulesOnlyStripsAndCopies) { - auto doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( + auto doc = test::BuildXmlDomForPackageName(context_.get(), R"( <View xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:paddingHorizontal="24dp" app:foo="16dp" - foo="bar"/>)EOF"); + foo="bar"/>)"); XmlReferenceLinker linker; ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); @@ -84,35 +90,35 @@ TEST_F(XmlCompatVersionerTest, NoRulesOnlyStripsAndCopies) { XmlCompatVersioner versioner(&rules); std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs = versioner.Process(context_.get(), doc.get(), api_range); - ASSERT_EQ(2u, versioned_docs.size()); + ASSERT_THAT(versioned_docs, SizeIs(2u)); xml::Element* el; // Source XML file's sdkVersion == 0, so the first one must also have the same sdkVersion. - EXPECT_EQ(static_cast<uint16_t>(0), versioned_docs[0]->file.config.sdkVersion); - el = xml::FindRootElement(versioned_docs[0].get()); - ASSERT_NE(nullptr, el); - EXPECT_EQ(2u, el->attributes.size()); - EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")); - EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAuto, "foo")); - EXPECT_NE(nullptr, el->FindAttribute({}, "foo")); - - EXPECT_EQ(static_cast<uint16_t>(SDK_LOLLIPOP_MR1), versioned_docs[1]->file.config.sdkVersion); - el = xml::FindRootElement(versioned_docs[1].get()); - ASSERT_NE(nullptr, el); - EXPECT_EQ(3u, el->attributes.size()); - EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")); - EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAuto, "foo")); - EXPECT_NE(nullptr, el->FindAttribute({}, "foo")); + EXPECT_THAT(versioned_docs[0]->file.config.sdkVersion, Eq(0u)); + el = versioned_docs[0]->root.get(); + ASSERT_THAT(el, NotNull()); + EXPECT_THAT(el->attributes, SizeIs(2u)); + EXPECT_THAT(el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"), IsNull()); + EXPECT_THAT(el->FindAttribute(xml::kSchemaAuto, "foo"), NotNull()); + EXPECT_THAT(el->FindAttribute({}, "foo"), NotNull()); + + EXPECT_THAT(versioned_docs[1]->file.config.sdkVersion, Eq(SDK_LOLLIPOP_MR1)); + el = versioned_docs[1]->root.get(); + ASSERT_THAT(el, NotNull()); + EXPECT_THAT(el->attributes, SizeIs(3u)); + EXPECT_THAT(el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"), NotNull()); + EXPECT_THAT(el->FindAttribute(xml::kSchemaAuto, "foo"), NotNull()); + EXPECT_THAT(el->FindAttribute({}, "foo"), NotNull()); } TEST_F(XmlCompatVersionerTest, SingleRule) { - auto doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( + auto doc = test::BuildXmlDomForPackageName(context_.get(), R"( <View xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:paddingHorizontal="24dp" app:foo="16dp" - foo="bar"/>)EOF"); + foo="bar"/>)"); XmlReferenceLinker linker; ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); @@ -129,51 +135,51 @@ TEST_F(XmlCompatVersionerTest, SingleRule) { XmlCompatVersioner versioner(&rules); std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs = versioner.Process(context_.get(), doc.get(), api_range); - ASSERT_EQ(2u, versioned_docs.size()); + ASSERT_THAT(versioned_docs, SizeIs(2u)); xml::Element* el; - EXPECT_EQ(static_cast<uint16_t>(0), versioned_docs[0]->file.config.sdkVersion); - el = xml::FindRootElement(versioned_docs[0].get()); - ASSERT_NE(nullptr, el); - EXPECT_EQ(4u, el->attributes.size()); - EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")); - EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAuto, "foo")); - EXPECT_NE(nullptr, el->FindAttribute({}, "foo")); + EXPECT_THAT(versioned_docs[0]->file.config.sdkVersion, Eq(0u)); + el = versioned_docs[0]->root.get(); + ASSERT_THAT(el, NotNull()); + EXPECT_THAT(el->attributes, SizeIs(4u)); + EXPECT_THAT(el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"), IsNull()); + EXPECT_THAT(el->FindAttribute(xml::kSchemaAuto, "foo"), NotNull()); + EXPECT_THAT(el->FindAttribute({}, "foo"), NotNull()); xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"); - ASSERT_NE(nullptr, attr); - ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_THAT(attr, NotNull()); + ASSERT_THAT(attr->compiled_value, NotNull()); ASSERT_TRUE(attr->compiled_attribute); attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight"); - ASSERT_NE(nullptr, attr); - ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_THAT(attr, NotNull()); + ASSERT_THAT(attr->compiled_value, NotNull()); ASSERT_TRUE(attr->compiled_attribute); - EXPECT_EQ(static_cast<uint16_t>(SDK_LOLLIPOP_MR1), versioned_docs[1]->file.config.sdkVersion); - el = xml::FindRootElement(versioned_docs[1].get()); - ASSERT_NE(nullptr, el); - EXPECT_EQ(5u, el->attributes.size()); - EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")); - EXPECT_NE(nullptr, el->FindAttribute(xml::kSchemaAuto, "foo")); - EXPECT_NE(nullptr, el->FindAttribute({}, "foo")); + EXPECT_THAT(versioned_docs[1]->file.config.sdkVersion, Eq(SDK_LOLLIPOP_MR1)); + el = versioned_docs[1]->root.get(); + ASSERT_THAT(el, NotNull()); + EXPECT_THAT(el->attributes, SizeIs(5u)); + EXPECT_THAT(el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"), NotNull()); + EXPECT_THAT(el->FindAttribute(xml::kSchemaAuto, "foo"), NotNull()); + EXPECT_THAT(el->FindAttribute({}, "foo"), NotNull()); attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"); - ASSERT_NE(nullptr, attr); - ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_THAT(attr, NotNull()); + ASSERT_THAT(attr->compiled_value, NotNull()); ASSERT_TRUE(attr->compiled_attribute); attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight"); - ASSERT_NE(nullptr, attr); - ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_THAT(attr, NotNull()); + ASSERT_THAT(attr->compiled_value, NotNull()); ASSERT_TRUE(attr->compiled_attribute); } TEST_F(XmlCompatVersionerTest, ChainedRule) { - auto doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( + auto doc = test::BuildXmlDomForPackageName(context_.get(), R"( <View xmlns:android="http://schemas.android.com/apk/res/android" - android:paddingHorizontal="24dp" />)EOF"); + android:paddingHorizontal="24dp" />)"); XmlReferenceLinker linker; ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); @@ -193,71 +199,70 @@ TEST_F(XmlCompatVersionerTest, ChainedRule) { XmlCompatVersioner versioner(&rules); std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs = versioner.Process(context_.get(), doc.get(), api_range); - ASSERT_EQ(3u, versioned_docs.size()); + ASSERT_THAT(versioned_docs, SizeIs(3u)); xml::Element* el; - EXPECT_EQ(static_cast<uint16_t>(0), versioned_docs[0]->file.config.sdkVersion); - el = xml::FindRootElement(versioned_docs[0].get()); - ASSERT_NE(nullptr, el); - EXPECT_EQ(2u, el->attributes.size()); - EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")); + EXPECT_THAT(versioned_docs[0]->file.config.sdkVersion, Eq(0u)); + el = versioned_docs[0]->root.get(); + ASSERT_THAT(el, NotNull()); + EXPECT_THAT(el->attributes, SizeIs(2u)); + EXPECT_THAT(el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"), IsNull()); xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"); - ASSERT_NE(nullptr, attr); - ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_THAT(attr, NotNull()); + ASSERT_THAT(attr->compiled_value, NotNull()); ASSERT_TRUE(attr->compiled_attribute); attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight"); - ASSERT_NE(nullptr, attr); - ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_THAT(attr, NotNull()); + ASSERT_THAT(attr->compiled_value, NotNull()); ASSERT_TRUE(attr->compiled_attribute); - EXPECT_EQ(static_cast<uint16_t>(SDK_HONEYCOMB), versioned_docs[1]->file.config.sdkVersion); - el = xml::FindRootElement(versioned_docs[1].get()); - ASSERT_NE(nullptr, el); - EXPECT_EQ(1u, el->attributes.size()); - EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")); - EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingLeft")); - EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingRight")); + EXPECT_THAT(versioned_docs[1]->file.config.sdkVersion, Eq(SDK_HONEYCOMB)); + el = versioned_docs[1]->root.get(); + ASSERT_THAT(el, NotNull()); + EXPECT_THAT(el->attributes, SizeIs(1u)); + EXPECT_THAT(el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"), IsNull()); + EXPECT_THAT(el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"), IsNull()); + EXPECT_THAT(el->FindAttribute(xml::kSchemaAndroid, "paddingRight"), IsNull()); attr = el->FindAttribute(xml::kSchemaAndroid, "progressBarPadding"); - ASSERT_NE(nullptr, attr); - ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_THAT(attr, NotNull()); + ASSERT_THAT(attr->compiled_value, NotNull()); ASSERT_TRUE(attr->compiled_attribute); - EXPECT_EQ(static_cast<uint16_t>(SDK_LOLLIPOP_MR1), versioned_docs[2]->file.config.sdkVersion); - el = xml::FindRootElement(versioned_docs[2].get()); - ASSERT_NE(nullptr, el); - EXPECT_EQ(2u, el->attributes.size()); - EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingLeft")); - EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingRight")); + EXPECT_THAT(versioned_docs[2]->file.config.sdkVersion, Eq(SDK_LOLLIPOP_MR1)); + el = versioned_docs[2]->root.get(); + ASSERT_THAT(el, NotNull()); + EXPECT_THAT(el->attributes, SizeIs(2u)); + EXPECT_THAT(el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"), IsNull()); + EXPECT_THAT(el->FindAttribute(xml::kSchemaAndroid, "paddingRight"), IsNull()); attr = el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"); - ASSERT_NE(nullptr, attr); - ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_THAT(attr, NotNull()); + ASSERT_THAT(attr->compiled_value, NotNull()); ASSERT_TRUE(attr->compiled_attribute); attr = el->FindAttribute(xml::kSchemaAndroid, "progressBarPadding"); - ASSERT_NE(nullptr, attr); - ASSERT_NE(nullptr, attr->compiled_value); + ASSERT_THAT(attr, NotNull()); + ASSERT_THAT(attr->compiled_value, NotNull()); ASSERT_TRUE(attr->compiled_attribute); } TEST_F(XmlCompatVersionerTest, DegradeRuleOverridesExistingAttribute) { - auto doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( + auto doc = test::BuildXmlDomForPackageName(context_.get(), R"( <View xmlns:android="http://schemas.android.com/apk/res/android" android:paddingHorizontal="24dp" android:paddingLeft="16dp" - android:paddingRight="16dp"/>)EOF"); + android:paddingRight="16dp"/>)"); XmlReferenceLinker linker; ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); - Item* padding_horizontal_value = xml::FindRootElement(doc.get()) - ->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal") - ->compiled_value.get(); - ASSERT_NE(nullptr, padding_horizontal_value); + Item* padding_horizontal_value = + doc->root->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")->compiled_value.get(); + ASSERT_THAT(padding_horizontal_value, NotNull()); XmlCompatVersioner::Rules rules; rules[R::attr::paddingHorizontal] = @@ -271,50 +276,50 @@ TEST_F(XmlCompatVersionerTest, DegradeRuleOverridesExistingAttribute) { XmlCompatVersioner versioner(&rules); std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs = versioner.Process(context_.get(), doc.get(), api_range); - ASSERT_EQ(2u, versioned_docs.size()); + ASSERT_THAT(versioned_docs, SizeIs(2u)); xml::Element* el; - EXPECT_EQ(static_cast<uint16_t>(0), versioned_docs[0]->file.config.sdkVersion); - el = xml::FindRootElement(versioned_docs[0].get()); - ASSERT_NE(nullptr, el); - EXPECT_EQ(2u, el->attributes.size()); - EXPECT_EQ(nullptr, el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal")); + EXPECT_THAT(versioned_docs[0]->file.config.sdkVersion, Eq(0u)); + el = versioned_docs[0]->root.get(); + ASSERT_THAT(el, NotNull()); + EXPECT_THAT(el->attributes, SizeIs(2u)); + EXPECT_THAT(el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"), IsNull()); xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"); - ASSERT_NE(nullptr, attr); - ASSERT_NE(nullptr, attr->compiled_value); - ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get())); + ASSERT_THAT(attr, NotNull()); + ASSERT_THAT(attr->compiled_value, NotNull()); ASSERT_TRUE(attr->compiled_attribute); + ASSERT_THAT(*attr->compiled_value, ValueEq(padding_horizontal_value)); attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight"); - ASSERT_NE(nullptr, attr); - ASSERT_NE(nullptr, attr->compiled_value); - ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get())); + ASSERT_THAT(attr, NotNull()); + ASSERT_THAT(attr->compiled_value, NotNull()); ASSERT_TRUE(attr->compiled_attribute); + ASSERT_THAT(*attr->compiled_value, ValueEq(padding_horizontal_value)); - EXPECT_EQ(static_cast<uint16_t>(SDK_LOLLIPOP_MR1), versioned_docs[1]->file.config.sdkVersion); - el = xml::FindRootElement(versioned_docs[1].get()); - ASSERT_NE(nullptr, el); - EXPECT_EQ(3u, el->attributes.size()); + EXPECT_THAT(versioned_docs[1]->file.config.sdkVersion, Eq(SDK_LOLLIPOP_MR1)); + el = versioned_docs[1]->root.get(); + ASSERT_THAT(el, NotNull()); + EXPECT_THAT(el->attributes, SizeIs(3u)); attr = el->FindAttribute(xml::kSchemaAndroid, "paddingHorizontal"); - ASSERT_NE(nullptr, attr); - ASSERT_NE(nullptr, attr->compiled_value); - ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get())); + ASSERT_THAT(attr, NotNull()); + ASSERT_THAT(attr->compiled_value, NotNull()); ASSERT_TRUE(attr->compiled_attribute); + ASSERT_THAT(*attr->compiled_value, ValueEq(padding_horizontal_value)); attr = el->FindAttribute(xml::kSchemaAndroid, "paddingLeft"); - ASSERT_NE(nullptr, attr); - ASSERT_NE(nullptr, attr->compiled_value); - ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get())); + ASSERT_THAT(attr, NotNull()); + ASSERT_THAT(attr->compiled_value, NotNull()); ASSERT_TRUE(attr->compiled_attribute); + ASSERT_THAT(*attr->compiled_value, ValueEq(padding_horizontal_value)); attr = el->FindAttribute(xml::kSchemaAndroid, "paddingRight"); - ASSERT_NE(nullptr, attr); - ASSERT_NE(nullptr, attr->compiled_value); - ASSERT_TRUE(padding_horizontal_value->Equals(attr->compiled_value.get())); + ASSERT_THAT(attr, NotNull()); + ASSERT_THAT(attr->compiled_value, NotNull()); ASSERT_TRUE(attr->compiled_attribute); + ASSERT_THAT(*attr->compiled_value, ValueEq(padding_horizontal_value)); } } // namespace aapt diff --git a/tools/aapt2/link/XmlNamespaceRemover.cpp b/tools/aapt2/link/XmlNamespaceRemover.cpp index 24aa5660ae30..b5e2423d58dc 100644 --- a/tools/aapt2/link/XmlNamespaceRemover.cpp +++ b/tools/aapt2/link/XmlNamespaceRemover.cpp @@ -24,37 +24,19 @@ namespace aapt { namespace { -/** - * Visits each xml Node, removing URI references and nested namespaces. - */ +// Visits each xml Node, removing URI references and nested namespaces. class XmlVisitor : public xml::Visitor { public: explicit XmlVisitor(bool keep_uris) : keep_uris_(keep_uris) {} void Visit(xml::Element* el) override { - // Strip namespaces - for (auto& child : el->children) { - while (child && xml::NodeCast<xml::Namespace>(child.get())) { - if (child->children.empty()) { - child = {}; - } else { - child = std::move(child->children.front()); - child->parent = el; - } - } - } - el->children.erase( - std::remove_if(el->children.begin(), el->children.end(), - [](const std::unique_ptr<xml::Node>& child) -> bool { - return child == nullptr; - }), - el->children.end()); + el->namespace_decls.clear(); if (!keep_uris_) { for (xml::Attribute& attr : el->attributes) { - attr.namespace_uri = std::string(); + attr.namespace_uri.clear(); } - el->namespace_uri = std::string(); + el->namespace_uri.clear(); } xml::Visitor::Visit(el); } @@ -67,19 +49,11 @@ class XmlVisitor : public xml::Visitor { } // namespace -bool XmlNamespaceRemover::Consume(IAaptContext* context, - xml::XmlResource* resource) { +bool XmlNamespaceRemover::Consume(IAaptContext* context, xml::XmlResource* resource) { if (!resource->root) { return false; } - // Replace any root namespaces until the root is a non-namespace node - while (xml::NodeCast<xml::Namespace>(resource->root.get())) { - if (resource->root->children.empty()) { - break; - } - resource->root = std::move(resource->root->children.front()); - resource->root->parent = nullptr; - } + XmlVisitor visitor(keep_uris_); resource->root->Accept(&visitor); return true; diff --git a/tools/aapt2/link/XmlNamespaceRemover_test.cpp b/tools/aapt2/link/XmlNamespaceRemover_test.cpp index a176c03a6432..df4920fde37f 100644 --- a/tools/aapt2/link/XmlNamespaceRemover_test.cpp +++ b/tools/aapt2/link/XmlNamespaceRemover_test.cpp @@ -18,6 +18,10 @@ #include "test/Test.h" +using ::testing::NotNull; +using ::testing::SizeIs; +using ::testing::StrEq; + namespace aapt { class XmlUriTestVisitor : public xml::Visitor { @@ -25,16 +29,14 @@ class XmlUriTestVisitor : public xml::Visitor { XmlUriTestVisitor() = default; void Visit(xml::Element* el) override { + EXPECT_THAT(el->namespace_decls, SizeIs(0u)); + for (const auto& attr : el->attributes) { - EXPECT_EQ(std::string(), attr.namespace_uri); + EXPECT_THAT(attr.namespace_uri, StrEq("")); } - EXPECT_EQ(std::string(), el->namespace_uri); - xml::Visitor::Visit(el); - } + EXPECT_THAT(el->namespace_uri, StrEq("")); - void Visit(xml::Namespace* ns) override { - EXPECT_EQ(std::string(), ns->namespace_uri); - xml::Visitor::Visit(ns); + xml::Visitor::Visit(el); } private: @@ -45,10 +47,9 @@ class XmlNamespaceTestVisitor : public xml::Visitor { public: XmlNamespaceTestVisitor() = default; - void Visit(xml::Namespace* ns) override { - ADD_FAILURE() << "Detected namespace: " << ns->namespace_prefix << "=\"" - << ns->namespace_uri << "\""; - xml::Visitor::Visit(ns); + void Visit(xml::Element* el) override { + EXPECT_THAT(el->namespace_decls, SizeIs(0u)); + xml::Visitor::Visit(el); } private: @@ -58,8 +59,7 @@ class XmlNamespaceTestVisitor : public xml::Visitor { class XmlNamespaceRemoverTest : public ::testing::Test { public: void SetUp() override { - context_ = - test::ContextBuilder().SetCompilationPackage("com.app.test").Build(); + context_ = test::ContextBuilder().SetCompilationPackage("com.app.test").Build(); } protected: @@ -75,8 +75,8 @@ TEST_F(XmlNamespaceRemoverTest, RemoveUris) { XmlNamespaceRemover remover; ASSERT_TRUE(remover.Consume(context_.get(), doc.get())); - xml::Node* root = doc.get()->root.get(); - ASSERT_NE(root, nullptr); + xml::Node* root = doc->root.get(); + ASSERT_THAT(root, NotNull()); XmlUriTestVisitor visitor; root->Accept(&visitor); @@ -93,8 +93,8 @@ TEST_F(XmlNamespaceRemoverTest, RemoveNamespaces) { XmlNamespaceRemover remover; ASSERT_TRUE(remover.Consume(context_.get(), doc.get())); - xml::Node* root = doc.get()->root.get(); - ASSERT_NE(root, nullptr); + xml::Node* root = doc->root.get(); + ASSERT_THAT(root, NotNull()); XmlNamespaceTestVisitor visitor; root->Accept(&visitor); @@ -112,8 +112,8 @@ TEST_F(XmlNamespaceRemoverTest, RemoveNestedNamespaces) { XmlNamespaceRemover remover; ASSERT_TRUE(remover.Consume(context_.get(), doc.get())); - xml::Node* root = doc.get()->root.get(); - ASSERT_NE(root, nullptr); + xml::Node* root = doc->root.get(); + ASSERT_THAT(root, NotNull()); XmlNamespaceTestVisitor visitor; root->Accept(&visitor); diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp index 721fc26399bc..6ebb80f4be8f 100644 --- a/tools/aapt2/link/XmlReferenceLinker.cpp +++ b/tools/aapt2/link/XmlReferenceLinker.cpp @@ -31,13 +31,9 @@ namespace aapt { namespace { -/** - * Visits all references (including parents of styles, references in styles, - * arrays, etc) and - * links their symbolic name to their Resource ID, performing mangling and - * package aliasing - * as needed. - */ +// Visits all references (including parents of styles, references in styles, arrays, etc) and +// links their symbolic name to their Resource ID, performing mangling and package aliasing +// as needed. class ReferenceVisitor : public ValueVisitor { public: using ValueVisitor::Visit; @@ -52,7 +48,9 @@ class ReferenceVisitor : public ValueVisitor { } } - bool HasError() const { return error_; } + bool HasError() const { + return error_; + } private: DISALLOW_COPY_AND_ASSIGN(ReferenceVisitor); @@ -64,9 +62,7 @@ class ReferenceVisitor : public ValueVisitor { bool error_; }; -/** - * Visits each xml Element and compiles the attributes within. - */ +// Visits each xml Element and compiles the attributes within. class XmlVisitor : public xml::PackageAwareVisitor { public: using xml::PackageAwareVisitor::Visit; @@ -92,18 +88,12 @@ class XmlVisitor : public xml::PackageAwareVisitor { // they were assigned to the default Attribute. const Attribute* attribute = &kDefaultAttribute; - std::string attribute_package; if (Maybe<xml::ExtractedPackage> maybe_package = xml::ExtractPackageFromNamespace(attr.namespace_uri)) { // There is a valid package name for this attribute. We will look this up. - attribute_package = maybe_package.value().package; - if (attribute_package.empty()) { - // Empty package means the 'current' or 'local' package. - attribute_package = context_->GetCompilationPackage(); - } - - Reference attr_ref(ResourceNameRef(attribute_package, ResourceType::kAttr, attr.name)); + Reference attr_ref( + ResourceNameRef(maybe_package.value().package, ResourceType::kAttr, attr.name)); attr_ref.private_reference = maybe_package.value().private_namespace; std::string err_str; @@ -111,9 +101,11 @@ class XmlVisitor : public xml::PackageAwareVisitor { ReferenceLinker::CompileXmlAttribute(attr_ref, callsite_, symbols_, &err_str); if (!attr.compiled_attribute) { - context_->GetDiagnostics()->Error(DiagMessage(source) << "attribute '" - << attribute_package << ":" - << attr.name << "' " << err_str); + DiagMessage error_msg(source); + error_msg << "attribute "; + ReferenceLinker::WriteAttributeName(attr_ref, callsite_, this, &error_msg); + error_msg << " " << err_str; + context_->GetDiagnostics()->Error(error_msg); error_ = true; continue; } @@ -129,12 +121,8 @@ class XmlVisitor : public xml::PackageAwareVisitor { } else if ((attribute->type_mask & android::ResTable_map::TYPE_STRING) == 0) { // We won't be able to encode this as a string. DiagMessage msg(source); - msg << "'" << attr.value << "' " - << "is incompatible with attribute "; - if (!attribute_package.empty()) { - msg << attribute_package << ":"; - } - msg << attr.name << " " << *attribute; + msg << "'" << attr.value << "' is incompatible with attribute " << attr.name << " " + << *attribute; context_->GetDiagnostics()->Error(msg); error_ = true; } @@ -144,7 +132,9 @@ class XmlVisitor : public xml::PackageAwareVisitor { xml::PackageAwareVisitor::Visit(el); } - bool HasError() { return error_ || reference_visitor_.HasError(); } + bool HasError() { + return error_ || reference_visitor_.HasError(); + } private: DISALLOW_COPY_AND_ASSIGN(XmlVisitor); @@ -161,7 +151,17 @@ class XmlVisitor : public xml::PackageAwareVisitor { } // namespace bool XmlReferenceLinker::Consume(IAaptContext* context, xml::XmlResource* resource) { - const CallSite callsite = {resource->file.name}; + CallSite callsite{resource->file.name.package}; + + std::string out_name = resource->file.name.entry; + NameMangler::Unmangle(&out_name, &callsite.package); + + if (callsite.package.empty()) { + // Assume an empty package means that the XML file is local. This is true of AndroidManifest.xml + // for example. + callsite.package = context->GetCompilationPackage(); + } + XmlVisitor visitor(resource->file.source, callsite, context, context->GetExternalSymbols()); if (resource->root) { resource->root->Accept(&visitor); diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp index 228cfb9e3d66..ef99355e5b5f 100644 --- a/tools/aapt2/link/XmlReferenceLinker_test.cpp +++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp @@ -79,20 +79,20 @@ class XmlReferenceLinkerTest : public ::testing::Test { }; TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( - <View xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:background="@color/green" - android:text="hello" - android:attr="\?hello" - nonAaptAttr="1" - nonAaptAttrRef="@id/id" - class="hello" />)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( + <View xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:background="@color/green" + android:text="hello" + android:attr="\?hello" + nonAaptAttr="1" + nonAaptAttrRef="@id/id" + class="hello" />)"); XmlReferenceLinker linker; ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); - xml::Element* view_el = xml::FindRootElement(doc.get()); + xml::Element* view_el = doc->root.get(); ASSERT_THAT(view_el, NotNull()); xml::Attribute* xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "layout_width"); @@ -138,32 +138,32 @@ TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) { } TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreNotLinked) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( - <View xmlns:android="http://schemas.android.com/apk/res/android" - android:colorAccent="@android:color/hidden" />)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( + <View xmlns:android="http://schemas.android.com/apk/res/android" + android:colorAccent="@android:color/hidden" />)"); XmlReferenceLinker linker; ASSERT_FALSE(linker.Consume(context_.get(), doc.get())); } TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreLinkedWhenReferenceHasStarPrefix) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( <View xmlns:android="http://schemas.android.com/apk/res/android" - android:colorAccent="@*android:color/hidden" />)EOF"); + android:colorAccent="@*android:color/hidden" />)"); XmlReferenceLinker linker; ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); } TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( - <View xmlns:support="http://schemas.android.com/apk/res/com.android.support" - support:colorAccent="#ff0000" />)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( + <View xmlns:support="http://schemas.android.com/apk/res/com.android.support" + support:colorAccent="#ff0000" />)"); XmlReferenceLinker linker; ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); - xml::Element* view_el = xml::FindRootElement(doc.get()); + xml::Element* view_el = doc->root.get(); ASSERT_THAT(view_el, NotNull()); xml::Attribute* xml_attr = @@ -175,14 +175,14 @@ TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) { } TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( - <View xmlns:app="http://schemas.android.com/apk/res-auto" - app:colorAccent="@app:color/red" />)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( + <View xmlns:app="http://schemas.android.com/apk/res-auto" + app:colorAccent="@app:color/red" />)"); XmlReferenceLinker linker; ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); - xml::Element* view_el = xml::FindRootElement(doc.get()); + xml::Element* view_el = doc->root.get(); ASSERT_THAT(view_el, NotNull()); xml::Attribute* xml_attr = view_el->FindAttribute(xml::kSchemaAuto, "colorAccent"); @@ -196,17 +196,15 @@ TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) { } TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( - <View xmlns:app="http://schemas.android.com/apk/res/android" - app:attr="@app:id/id"> - <View xmlns:app="http://schemas.android.com/apk/res/com.app.test" - app:attr="@app:id/id"/> - </View>)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( + <View xmlns:app="http://schemas.android.com/apk/res/android" app:attr="@app:id/id"> + <View xmlns:app="http://schemas.android.com/apk/res/com.app.test" app:attr="@app:id/id"/> + </View>)"); XmlReferenceLinker linker; ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); - xml::Element* view_el = xml::FindRootElement(doc.get()); + xml::Element* view_el = doc->root.get(); ASSERT_THAT(view_el, NotNull()); // All attributes and references in this element should be referring to @@ -235,14 +233,14 @@ TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) { } TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( - <View xmlns:android="http://schemas.android.com/apk/res/com.app.test" - android:attr="@id/id"/>)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( + <View xmlns:android="http://schemas.android.com/apk/res/com.app.test" + android:attr="@id/id"/>)"); XmlReferenceLinker linker; ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); - xml::Element* view_el = xml::FindRootElement(doc.get()); + xml::Element* view_el = doc->root.get(); ASSERT_THAT(view_el, NotNull()); // All attributes and references in this element should be referring to diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp new file mode 100644 index 000000000000..5ff890832371 --- /dev/null +++ b/tools/aapt2/optimize/MultiApkGenerator.cpp @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MultiApkGenerator.h" + +#include <algorithm> +#include <string> + +#include "androidfw/StringPiece.h" + +#include "LoadedApk.h" +#include "configuration/ConfigurationParser.h" +#include "filter/AbiFilter.h" +#include "filter/Filter.h" +#include "flatten/Archive.h" +#include "optimize/VersionCollapser.h" +#include "process/IResourceTableConsumer.h" +#include "split/TableSplitter.h" +#include "util/Files.h" + +namespace aapt { + +using ::aapt::configuration::AndroidSdk; +using ::aapt::configuration::Artifact; +using ::aapt::configuration::PostProcessingConfiguration; +using ::android::StringPiece; + +/** + * Context wrapper that allows the min Android SDK value to be overridden. + */ +class ContextWrapper : public IAaptContext { + public: + explicit ContextWrapper(IAaptContext* context) + : context_(context), min_sdk_(context_->GetMinSdkVersion()) { + } + + PackageType GetPackageType() override { + return context_->GetPackageType(); + } + + SymbolTable* GetExternalSymbols() override { + return context_->GetExternalSymbols(); + } + + IDiagnostics* GetDiagnostics() override { + return context_->GetDiagnostics(); + } + + const std::string& GetCompilationPackage() override { + return context_->GetCompilationPackage(); + } + + uint8_t GetPackageId() override { + return context_->GetPackageId(); + } + + NameMangler* GetNameMangler() override { + return context_->GetNameMangler(); + } + + bool IsVerbose() override { + return context_->IsVerbose(); + } + + int GetMinSdkVersion() override { + return min_sdk_; + } + + void SetMinSdkVersion(int min_sdk) { + min_sdk_ = min_sdk; + } + + private: + IAaptContext* context_; + + int min_sdk_ = -1; +}; + +MultiApkGenerator::MultiApkGenerator(LoadedApk* apk, IAaptContext* context) + : apk_(apk), context_(context) { +} + +bool MultiApkGenerator::FromBaseApk(const MultiApkGeneratorOptions& options) { + // TODO(safarmer): Handle APK version codes for the generated APKs. + IDiagnostics* diag = context_->GetDiagnostics(); + const PostProcessingConfiguration& config = options.config; + + const std::string& apk_name = file::GetFilename(apk_->GetSource().path).to_string(); + const StringPiece ext = file::GetExtension(apk_name); + const std::string base_name = apk_name.substr(0, apk_name.rfind(ext.to_string())); + + // For now, just write out the stripped APK since ABI splitting doesn't modify anything else. + for (const Artifact& artifact : config.artifacts) { + FilterChain filters; + + if (!artifact.name && !config.artifact_format) { + diag->Error( + DiagMessage() << "Artifact does not have a name and no global name template defined"); + return false; + } + + Maybe<std::string> artifact_name = + (artifact.name) ? artifact.Name(apk_name, diag) + : artifact.ToArtifactName(config.artifact_format.value(), apk_name, diag); + + if (!artifact_name) { + diag->Error(DiagMessage() << "Could not determine split APK artifact name"); + return false; + } + + std::unique_ptr<ResourceTable> table = + FilterTable(artifact, config, *apk_->GetResourceTable(), &filters); + if (!table) { + return false; + } + + std::string out = options.out_dir; + if (!file::mkdirs(out)) { + context_->GetDiagnostics()->Warn(DiagMessage() << "could not create out dir: " << out); + } + file::AppendPath(&out, artifact_name.value()); + + if (context_->IsVerbose()) { + context_->GetDiagnostics()->Note(DiagMessage() << "Generating split: " << out); + } + + std::unique_ptr<IArchiveWriter> writer = + CreateZipFileArchiveWriter(context_->GetDiagnostics(), out); + + if (context_->IsVerbose()) { + diag->Note(DiagMessage() << "Writing output: " << out); + } + + if (!apk_->WriteToArchive(context_, table.get(), options.table_flattener_options, &filters, + writer.get())) { + return false; + } + } + + return true; +} + +std::unique_ptr<ResourceTable> MultiApkGenerator::FilterTable( + const configuration::Artifact& artifact, + const configuration::PostProcessingConfiguration& config, const ResourceTable& old_table, + FilterChain* filters) { + TableSplitterOptions splits; + AxisConfigFilter axis_filter; + ContextWrapper wrappedContext{context_}; + + if (artifact.abi_group) { + const std::string& group_name = artifact.abi_group.value(); + + auto group = config.abi_groups.find(group_name); + // TODO: Remove validation when configuration parser ensures referential integrity. + if (group == config.abi_groups.end()) { + context_->GetDiagnostics()->Error(DiagMessage() << "could not find referenced ABI group '" + << group_name << "'"); + return {}; + } + filters->AddFilter(AbiFilter::FromAbiList(group->second)); + } + + if (artifact.screen_density_group) { + const std::string& group_name = artifact.screen_density_group.value(); + + auto group = config.screen_density_groups.find(group_name); + // TODO: Remove validation when configuration parser ensures referential integrity. + if (group == config.screen_density_groups.end()) { + context_->GetDiagnostics()->Error(DiagMessage() << "could not find referenced group '" + << group_name << "'"); + return {}; + } + + const std::vector<ConfigDescription>& densities = group->second; + for(const auto& density_config : densities) { + splits.preferred_densities.push_back(density_config.density); + } + } + + if (artifact.locale_group) { + const std::string& group_name = artifact.locale_group.value(); + auto group = config.locale_groups.find(group_name); + // TODO: Remove validation when configuration parser ensures referential integrity. + if (group == config.locale_groups.end()) { + context_->GetDiagnostics()->Error(DiagMessage() << "could not find referenced group '" + << group_name << "'"); + return {}; + } + + const std::vector<ConfigDescription>& locales = group->second; + for (const auto& locale : locales) { + axis_filter.AddConfig(locale); + } + splits.config_filter = &axis_filter; + } + + if (artifact.android_sdk_group) { + const std::string& group_name = artifact.android_sdk_group.value(); + auto group = config.android_sdk_groups.find(group_name); + // TODO: Remove validation when configuration parser ensures referential integrity. + if (group == config.android_sdk_groups.end()) { + context_->GetDiagnostics()->Error(DiagMessage() << "could not find referenced group '" + << group_name << "'"); + return {}; + } + + const AndroidSdk& sdk = group->second; + if (!sdk.min_sdk_version) { + context_->GetDiagnostics()->Error(DiagMessage() + << "skipping SDK version. No min SDK: " << group_name); + return {}; + } + + ConfigDescription c; + const std::string& version = sdk.min_sdk_version.value(); + if (!ConfigDescription::Parse(version, &c)) { + context_->GetDiagnostics()->Error(DiagMessage() << "could not parse min SDK: " << version); + return {}; + } + + wrappedContext.SetMinSdkVersion(c.sdkVersion); + } + + std::unique_ptr<ResourceTable> table = old_table.Clone(); + + VersionCollapser collapser; + if (!collapser.Consume(context_, table.get())) { + context_->GetDiagnostics()->Error(DiagMessage() << "Failed to strip versioned resources"); + return {}; + } + + TableSplitter splitter{{}, splits}; + splitter.SplitTable(table.get()); + return table; +} + +} // namespace aapt diff --git a/tools/aapt2/optimize/MultiApkGenerator.h b/tools/aapt2/optimize/MultiApkGenerator.h new file mode 100644 index 000000000000..b06440014be6 --- /dev/null +++ b/tools/aapt2/optimize/MultiApkGenerator.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT2_APKSPLITTER_H +#define AAPT2_APKSPLITTER_H + +#include "Diagnostics.h" +#include "LoadedApk.h" +#include "configuration/ConfigurationParser.h" + +namespace aapt { + +struct MultiApkGeneratorOptions { + std::string out_dir; + configuration::PostProcessingConfiguration config; + TableFlattenerOptions table_flattener_options; +}; + +/** + * Generates a set of APKs that are a subset of the original base APKs. Each of the new APKs contain + * only the resources and assets for an artifact in the configuration file. + */ +class MultiApkGenerator { + public: + MultiApkGenerator(LoadedApk* apk, IAaptContext* context); + + /** + * Writes a set of APKs to the provided output directory. Each APK is a subset fo the base APK and + * represents an artifact in the post processing configuration. + */ + bool FromBaseApk(const MultiApkGeneratorOptions& options); + + protected: + virtual std::unique_ptr<ResourceTable> FilterTable( + const configuration::Artifact& artifact, + const configuration::PostProcessingConfiguration& config, const ResourceTable& old_table, + FilterChain* chain); + + private: + IDiagnostics* GetDiagnostics() { + return context_->GetDiagnostics(); + } + + LoadedApk* apk_; + IAaptContext* context_; +}; + +} // namespace aapt + +#endif // AAPT2_APKSPLITTER_H diff --git a/tools/aapt2/optimize/MultiApkGenerator_test.cpp b/tools/aapt2/optimize/MultiApkGenerator_test.cpp new file mode 100644 index 000000000000..23f573cd52a5 --- /dev/null +++ b/tools/aapt2/optimize/MultiApkGenerator_test.cpp @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "optimize/MultiApkGenerator.h" + +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "LoadedApk.h" +#include "ResourceTable.h" +#include "configuration/ConfigurationParser.h" +#include "filter/Filter.h" +#include "flatten/Archive.h" +#include "flatten/TableFlattener.h" +#include "process/IResourceTableConsumer.h" +#include "test/Context.h" +#include "test/Test.h" + +namespace aapt { +namespace { + +using ::aapt::configuration::Abi; +using ::aapt::configuration::AndroidSdk; +using ::aapt::configuration::Artifact; +using ::aapt::configuration::PostProcessingConfiguration; +using ::aapt::test::GetValue; +using ::aapt::test::GetValueForConfig; +using ::aapt::test::ParseConfigOrDie; +using ::testing::Eq; +using ::testing::IsNull; +using ::testing::Not; +using ::testing::NotNull; +using ::testing::PrintToString; +using ::testing::Return; +using ::testing::Test; +using ::testing::_; + +/** Subclass the LoadedApk class so that we can mock the WriteToArchive method. */ +class MockApk : public LoadedApk { + public: + MockApk(std::unique_ptr<ResourceTable> table) : LoadedApk({"test.apk"}, {}, std::move(table)){}; + MOCK_METHOD5(WriteToArchive, bool(IAaptContext*, ResourceTable*, const TableFlattenerOptions&, + FilterChain*, IArchiveWriter*)); +}; + +/** + * Subclass the MultiApkGenerator class so that we can access the protected FilterTable method to + * directly test table filter. + */ +class MultiApkGeneratorWrapper : public MultiApkGenerator { + public: + MultiApkGeneratorWrapper(LoadedApk* apk, IAaptContext* context) + : MultiApkGenerator(apk, context) { + } + + std::unique_ptr<ResourceTable> FilterTable( + const configuration::Artifact& artifact, + const configuration::PostProcessingConfiguration& config, const ResourceTable& old_table, + FilterChain* pChain) override { + return MultiApkGenerator::FilterTable(artifact, config, old_table, pChain); + } +}; + +/** MultiApkGenerator test fixture. */ +class MultiApkGeneratorTest : public ::testing::Test { + public: + std::unique_ptr<ResourceTable> BuildTable() { + return test::ResourceTableBuilder() + .AddFileReference(kResourceName, "res/drawable-mdpi/icon.png", mdpi_) + .AddFileReference(kResourceName, "res/drawable-hdpi/icon.png", hdpi_) + .AddFileReference(kResourceName, "res/drawable-xhdpi/icon.png", xhdpi_) + .AddFileReference(kResourceName, "res/drawable-xxhdpi/icon.png", xxhdpi_) + .AddFileReference(kResourceName, "res/drawable-v19/icon.xml", v19_) + .AddFileReference(kResourceName, "res/drawable-v21/icon.xml", v21_) + .AddSimple("android:string/one") + .Build(); + } + + inline FileReference* ValueForConfig(ResourceTable* table, const ConfigDescription& config) { + return GetValueForConfig<FileReference>(table, kResourceName, config); + }; + + void SetUp() override { + } + + protected: + static constexpr const char* kResourceName = "android:drawable/icon"; + + ConfigDescription default_ = ParseConfigOrDie("").CopyWithoutSdkVersion(); + ConfigDescription mdpi_ = ParseConfigOrDie("mdpi").CopyWithoutSdkVersion(); + ConfigDescription hdpi_ = ParseConfigOrDie("hdpi").CopyWithoutSdkVersion(); + ConfigDescription xhdpi_ = ParseConfigOrDie("xhdpi").CopyWithoutSdkVersion(); + ConfigDescription xxhdpi_ = ParseConfigOrDie("xxhdpi").CopyWithoutSdkVersion(); + ConfigDescription xxxhdpi_ = ParseConfigOrDie("xxxhdpi").CopyWithoutSdkVersion(); + ConfigDescription v19_ = ParseConfigOrDie("v19"); + ConfigDescription v21_ = ParseConfigOrDie("v21"); +}; + +TEST_F(MultiApkGeneratorTest, FromBaseApk) { + std::unique_ptr<ResourceTable> table = BuildTable(); + + MockApk apk{std::move(table)}; + + EXPECT_CALL(apk, WriteToArchive(_, _, _, _, _)).Times(0); + + test::Context ctx; + PostProcessingConfiguration empty_config; + TableFlattenerOptions table_flattener_options; + + MultiApkGenerator generator{&apk, &ctx}; + EXPECT_TRUE(generator.FromBaseApk({"out", empty_config, table_flattener_options})); + + Artifact x64 = test::ArtifactBuilder() + .SetName("${basename}.x64.apk") + .SetAbiGroup("x64") + .SetLocaleGroup("en") + .SetDensityGroup("xhdpi") + .Build(); + + Artifact intel = test::ArtifactBuilder() + .SetName("${basename}.intel.apk") + .SetAbiGroup("intel") + .SetLocaleGroup("europe") + .SetDensityGroup("large") + .Build(); + + auto config = test::PostProcessingConfigurationBuilder() + .SetLocaleGroup("en", {"en"}) + .SetLocaleGroup("europe", {"en", "fr", "de", "es"}) + .SetAbiGroup("x64", {Abi::kX86_64}) + .SetAbiGroup("intel", {Abi::kX86_64, Abi::kX86}) + .SetDensityGroup("xhdpi", {"xhdpi"}) + .SetDensityGroup("large", {"xhdpi", "xxhdpi", "xxxhdpi"}) + .AddArtifact(x64) + .AddArtifact(intel) + .Build(); + + // Called once for each artifact. + EXPECT_CALL(apk, WriteToArchive(Eq(&ctx), _, _, _, _)).Times(2).WillRepeatedly(Return(true)); + EXPECT_TRUE(generator.FromBaseApk({"out", config, table_flattener_options})); +} + +TEST_F(MultiApkGeneratorTest, VersionFilterNewerVersion) { + std::unique_ptr<ResourceTable> table = BuildTable(); + + MockApk apk{std::move(table)}; + std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(19).Build(); + PostProcessingConfiguration empty_config; + TableFlattenerOptions table_flattener_options; + FilterChain chain; + + Artifact x64 = test::ArtifactBuilder() + .SetName("${basename}.${sdk}.apk") + .SetDensityGroup("xhdpi") + .SetAndroidSdk("v23") + .Build(); + + auto config = test::PostProcessingConfigurationBuilder() + .SetLocaleGroup("en", {"en"}) + .SetAbiGroup("x64", {Abi::kX86_64}) + .SetDensityGroup("xhdpi", {"xhdpi"}) + .SetAndroidSdk("v23", AndroidSdk::ForMinSdk("v23")) + .AddArtifact(x64) + .Build(); + + MultiApkGeneratorWrapper generator{&apk, ctx.get()}; + std::unique_ptr<ResourceTable> split = + generator.FilterTable(x64, config, *apk.GetResourceTable(), &chain); + + ResourceTable* new_table = split.get(); + EXPECT_THAT(ValueForConfig(new_table, mdpi_), IsNull()); + EXPECT_THAT(ValueForConfig(new_table, hdpi_), IsNull()); + EXPECT_THAT(ValueForConfig(new_table, xxhdpi_), IsNull()); + EXPECT_THAT(ValueForConfig(new_table, xxxhdpi_), IsNull()); + EXPECT_THAT(ValueForConfig(new_table, v19_), IsNull()); + + // xhdpi directly matches one of the required dimensions. + EXPECT_THAT(ValueForConfig(new_table, xhdpi_), NotNull()); + // drawable-v21 was converted to drawable. + EXPECT_THAT(ValueForConfig(new_table, default_), NotNull()); + EXPECT_THAT(GetValue<Id>(new_table, "android:string/one"), NotNull()); +} + +TEST_F(MultiApkGeneratorTest, VersionFilterOlderVersion) { + std::unique_ptr<ResourceTable> table = BuildTable(); + + MockApk apk{std::move(table)}; + std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build(); + PostProcessingConfiguration empty_config; + TableFlattenerOptions table_flattener_options; + FilterChain chain; + + Artifact x64 = test::ArtifactBuilder() + .SetName("${basename}.${sdk}.apk") + .SetDensityGroup("xhdpi") + .SetAndroidSdk("v4") + .Build(); + + auto config = test::PostProcessingConfigurationBuilder() + .SetLocaleGroup("en", {"en"}) + .SetAbiGroup("x64", {Abi::kX86_64}) + .SetDensityGroup("xhdpi", {"xhdpi"}) + .SetAndroidSdk("v4", AndroidSdk::ForMinSdk("v4")) + .AddArtifact(x64) + .Build(); + + MultiApkGeneratorWrapper generator{&apk, ctx.get()};; + std::unique_ptr<ResourceTable> split = + generator.FilterTable(x64, config, *apk.GetResourceTable(), &chain); + + ResourceTable* new_table = split.get(); + EXPECT_THAT(ValueForConfig(new_table, mdpi_), IsNull()); + EXPECT_THAT(ValueForConfig(new_table, hdpi_), IsNull()); + EXPECT_THAT(ValueForConfig(new_table, xxhdpi_), IsNull()); + EXPECT_THAT(ValueForConfig(new_table, xxxhdpi_), IsNull()); + + EXPECT_THAT(ValueForConfig(new_table, xhdpi_), NotNull()); + EXPECT_THAT(ValueForConfig(new_table, v19_), NotNull()); + EXPECT_THAT(ValueForConfig(new_table, v21_), NotNull()); + EXPECT_THAT(GetValue<Id>(new_table, "android:string/one"), NotNull()); +} + +TEST_F(MultiApkGeneratorTest, VersionFilterNoVersion) { + std::unique_ptr<ResourceTable> table = BuildTable(); + + MockApk apk{std::move(table)}; + std::unique_ptr<IAaptContext> ctx = test::ContextBuilder().SetMinSdkVersion(1).Build(); + PostProcessingConfiguration empty_config; + TableFlattenerOptions table_flattener_options; + FilterChain chain; + + Artifact x64 = + test::ArtifactBuilder().SetName("${basename}.${sdk}.apk").SetDensityGroup("xhdpi").Build(); + + auto config = test::PostProcessingConfigurationBuilder() + .SetLocaleGroup("en", {"en"}) + .SetAbiGroup("x64", {Abi::kX86_64}) + .SetDensityGroup("xhdpi", {"xhdpi"}) + .AddArtifact(x64) + .Build(); + + MultiApkGeneratorWrapper generator{&apk, ctx.get()}; + std::unique_ptr<ResourceTable> split = + generator.FilterTable(x64, config, *apk.GetResourceTable(), &chain); + + ResourceTable* new_table = split.get(); + EXPECT_THAT(ValueForConfig(new_table, mdpi_), IsNull()); + EXPECT_THAT(ValueForConfig(new_table, hdpi_), IsNull()); + EXPECT_THAT(ValueForConfig(new_table, xxhdpi_), IsNull()); + EXPECT_THAT(ValueForConfig(new_table, xxxhdpi_), IsNull()); + + EXPECT_THAT(ValueForConfig(new_table, xhdpi_), NotNull()); + EXPECT_THAT(ValueForConfig(new_table, v19_), NotNull()); + EXPECT_THAT(ValueForConfig(new_table, v21_), NotNull()); + EXPECT_THAT(GetValue<Id>(new_table, "android:string/one"), NotNull()); +} + +} // namespace +} // namespace aapt diff --git a/tools/aapt2/optimize/VersionCollapser.cpp b/tools/aapt2/optimize/VersionCollapser.cpp index d941b487e439..cc1fc1e6910b 100644 --- a/tools/aapt2/optimize/VersionCollapser.cpp +++ b/tools/aapt2/optimize/VersionCollapser.cpp @@ -63,11 +63,9 @@ FilterIterator<Iterator, Pred> make_filter_iterator(Iterator begin, } /** - * Every Configuration with an SDK version specified that is less than minSdk - * will be removed. - * The exception is when there is no exact matching resource for the minSdk. The - * next smallest - * one will be kept. + * Every Configuration with an SDK version specified that is less than minSdk will be removed. The + * exception is when there is no exact matching resource for the minSdk. The next smallest one will + * be kept. */ static void CollapseVersions(int min_sdk, ResourceEntry* entry) { // First look for all sdks less than minSdk. @@ -80,11 +78,9 @@ static void CollapseVersions(int min_sdk, ResourceEntry* entry) { const ConfigDescription& config = (*iter)->config; if (config.sdkVersion <= min_sdk) { - // This is the first configuration we've found with a smaller or equal SDK - // level - // to the minimum. We MUST keep this one, but remove all others we find, - // which get - // overridden by this one. + // This is the first configuration we've found with a smaller or equal SDK level to the + // minimum. We MUST keep this one, but remove all others we find, which get overridden by this + // one. ConfigDescription config_without_sdk = config.CopyWithoutSdkVersion(); auto pred = [&](const std::unique_ptr<ResourceConfigValue>& val) -> bool { @@ -115,11 +111,9 @@ static void CollapseVersions(int min_sdk, ResourceEntry* entry) { -> bool { return val == nullptr; }), entry->values.end()); - // Strip the version qualifiers for every resource with version <= minSdk. - // This will ensure - // that the resource entries are all packed together in the same ResTable_type - // struct - // and take up less space in the resources.arsc table. + // Strip the version qualifiers for every resource with version <= minSdk. This will ensure that + // the resource entries are all packed together in the same ResTable_type struct and take up less + // space in the resources.arsc table. bool modified = false; for (std::unique_ptr<ResourceConfigValue>& config_value : entry->values) { if (config_value->config.sdkVersion != 0 && @@ -137,8 +131,8 @@ static void CollapseVersions(int min_sdk, ResourceEntry* entry) { } if (modified) { - // We've modified the keys (ConfigDescription) by changing the sdkVersion to - // 0. We MUST re-sort to ensure ordering guarantees hold. + // We've modified the keys (ConfigDescription) by changing the sdkVersion to 0. We MUST re-sort + // to ensure ordering guarantees hold. std::sort(entry->values.begin(), entry->values.end(), [](const std::unique_ptr<ResourceConfigValue>& a, const std::unique_ptr<ResourceConfigValue>& b) -> bool { diff --git a/tools/aapt2/proto/ProtoHelpers.cpp b/tools/aapt2/proto/ProtoHelpers.cpp index 6b21364b5eb2..aa99c982f6ae 100644 --- a/tools/aapt2/proto/ProtoHelpers.cpp +++ b/tools/aapt2/proto/ProtoHelpers.cpp @@ -36,7 +36,7 @@ void SerializeSourceToPb(const Source& source, StringPool* src_pool, pb::Source* StringPool::Ref ref = src_pool->MakeRef(source.path); out_pb_source->set_path_idx(static_cast<uint32_t>(ref.index())); if (source.line) { - out_pb_source->set_line_no(static_cast<uint32_t>(source.line.value())); + out_pb_source->mutable_position()->set_line_number(static_cast<uint32_t>(source.line.value())); } } @@ -46,29 +46,28 @@ void DeserializeSourceFromPb(const pb::Source& pb_source, const android::ResStri out_source->path = util::GetString(src_pool, pb_source.path_idx()); } - if (pb_source.has_line_no()) { - out_source->line = static_cast<size_t>(pb_source.line_no()); + if (pb_source.has_position()) { + out_source->line = static_cast<size_t>(pb_source.position().line_number()); } } pb::SymbolStatus_Visibility SerializeVisibilityToPb(SymbolState state) { switch (state) { case SymbolState::kPrivate: - return pb::SymbolStatus_Visibility_Private; + return pb::SymbolStatus_Visibility_PRIVATE; case SymbolState::kPublic: - return pb::SymbolStatus_Visibility_Public; + return pb::SymbolStatus_Visibility_PUBLIC; default: break; } - return pb::SymbolStatus_Visibility_Unknown; + return pb::SymbolStatus_Visibility_UNKNOWN; } -SymbolState DeserializeVisibilityFromPb( - pb::SymbolStatus_Visibility pb_visibility) { +SymbolState DeserializeVisibilityFromPb(pb::SymbolStatus_Visibility pb_visibility) { switch (pb_visibility) { - case pb::SymbolStatus_Visibility_Private: + case pb::SymbolStatus_Visibility_PRIVATE: return SymbolState::kPrivate; - case pb::SymbolStatus_Visibility_Public: + case pb::SymbolStatus_Visibility_PUBLIC: return SymbolState::kPublic; default: break; @@ -102,20 +101,20 @@ bool DeserializeConfigDescriptionFromPb(const pb::ConfigDescription& pb_config, pb::Reference_Type SerializeReferenceTypeToPb(Reference::Type type) { switch (type) { case Reference::Type::kResource: - return pb::Reference_Type_Ref; + return pb::Reference_Type_REFERENCE; case Reference::Type::kAttribute: - return pb::Reference_Type_Attr; + return pb::Reference_Type_ATTRIBUTE; default: break; } - return pb::Reference_Type_Ref; + return pb::Reference_Type_REFERENCE; } Reference::Type DeserializeReferenceTypeFromPb(pb::Reference_Type pb_type) { switch (pb_type) { - case pb::Reference_Type_Ref: + case pb::Reference_Type_REFERENCE: return Reference::Type::kResource; - case pb::Reference_Type_Attr: + case pb::Reference_Type_ATTRIBUTE: return Reference::Type::kAttribute; default: break; @@ -126,32 +125,32 @@ Reference::Type DeserializeReferenceTypeFromPb(pb::Reference_Type pb_type) { pb::Plural_Arity SerializePluralEnumToPb(size_t plural_idx) { switch (plural_idx) { case Plural::Zero: - return pb::Plural_Arity_Zero; + return pb::Plural_Arity_ZERO; case Plural::One: - return pb::Plural_Arity_One; + return pb::Plural_Arity_ONE; case Plural::Two: - return pb::Plural_Arity_Two; + return pb::Plural_Arity_TWO; case Plural::Few: - return pb::Plural_Arity_Few; + return pb::Plural_Arity_FEW; case Plural::Many: - return pb::Plural_Arity_Many; + return pb::Plural_Arity_MANY; default: break; } - return pb::Plural_Arity_Other; + return pb::Plural_Arity_OTHER; } size_t DeserializePluralEnumFromPb(pb::Plural_Arity arity) { switch (arity) { - case pb::Plural_Arity_Zero: + case pb::Plural_Arity_ZERO: return Plural::Zero; - case pb::Plural_Arity_One: + case pb::Plural_Arity_ONE: return Plural::One; - case pb::Plural_Arity_Two: + case pb::Plural_Arity_TWO: return Plural::Two; - case pb::Plural_Arity_Few: + case pb::Plural_Arity_FEW: return Plural::Few; - case pb::Plural_Arity_Many: + case pb::Plural_Arity_MANY: return Plural::Many; default: break; diff --git a/tools/aapt2/proto/ProtoHelpers.h b/tools/aapt2/proto/ProtoHelpers.h index 344e9477ea71..2f268f44752c 100644 --- a/tools/aapt2/proto/ProtoHelpers.h +++ b/tools/aapt2/proto/ProtoHelpers.h @@ -23,27 +23,23 @@ #include "ResourceTable.h" #include "Source.h" #include "StringPool.h" -#include "Format.pb.h" +#include "Resources.pb.h" +#include "ResourcesInternal.pb.h" namespace aapt { -void SerializeStringPoolToPb(const StringPool& pool, - pb::StringPool* out_pb_pool); +void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool); -void SerializeSourceToPb(const Source& source, StringPool* src_pool, - pb::Source* out_pb_source); +void SerializeSourceToPb(const Source& source, StringPool* src_pool, pb::Source* out_pb_source); -void DeserializeSourceFromPb(const pb::Source& pb_source, - const android::ResStringPool& src_pool, +void DeserializeSourceFromPb(const pb::Source& pb_source, const android::ResStringPool& src_pool, Source* out_source); pb::SymbolStatus_Visibility SerializeVisibilityToPb(SymbolState state); -SymbolState DeserializeVisibilityFromPb( - pb::SymbolStatus_Visibility pb_visibility); +SymbolState DeserializeVisibilityFromPb(pb::SymbolStatus_Visibility pb_visibility); -void SerializeConfig(const ConfigDescription& config, - pb::ConfigDescription* out_pb_config); +void SerializeConfig(const ConfigDescription& config, pb::ConfigDescription* out_pb_config); bool DeserializeConfigDescriptionFromPb(const pb::ConfigDescription& pb_config, ConfigDescription* out_config); diff --git a/tools/aapt2/proto/ProtoSerialize.h b/tools/aapt2/proto/ProtoSerialize.h index 39c50038d599..8c46642e9090 100644 --- a/tools/aapt2/proto/ProtoSerialize.h +++ b/tools/aapt2/proto/ProtoSerialize.h @@ -30,11 +30,10 @@ namespace aapt { class CompiledFileOutputStream { public: - explicit CompiledFileOutputStream( - google::protobuf::io::ZeroCopyOutputStream* out); + explicit CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out); void WriteLittleEndian32(uint32_t value); - void WriteCompiledFile(const pb::CompiledFile* compiledFile); + void WriteCompiledFile(const pb::internal::CompiledFile* compiledFile); void WriteData(const BigBuffer* buffer); void WriteData(const void* data, size_t len); bool HadError(); @@ -52,7 +51,7 @@ class CompiledFileInputStream { explicit CompiledFileInputStream(const void* data, size_t size); bool ReadLittleEndian32(uint32_t* outVal); - bool ReadCompiledFile(pb::CompiledFile* outVal); + bool ReadCompiledFile(pb::internal::CompiledFile* outVal); bool ReadDataMetaData(uint64_t* outOffset, uint64_t* outLen); private: @@ -64,13 +63,12 @@ class CompiledFileInputStream { }; std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table); -std::unique_ptr<ResourceTable> DeserializeTableFromPb( - const pb::ResourceTable& pbTable, const Source& source, IDiagnostics* diag); +std::unique_ptr<ResourceTable> DeserializeTableFromPb(const pb::ResourceTable& pbTable, + const Source& source, IDiagnostics* diag); -std::unique_ptr<pb::CompiledFile> SerializeCompiledFileToPb( - const ResourceFile& file); +std::unique_ptr<pb::internal::CompiledFile> SerializeCompiledFileToPb(const ResourceFile& file); std::unique_ptr<ResourceFile> DeserializeCompiledFileFromPb( - const pb::CompiledFile& pbFile, const Source& source, IDiagnostics* diag); + const pb::internal::CompiledFile& pbFile, const Source& source, IDiagnostics* diag); } // namespace aapt diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp index 37d5ed0cf59d..b9d5878f2f71 100644 --- a/tools/aapt2/proto/TableProtoDeserializer.cpp +++ b/tools/aapt2/proto/TableProtoDeserializer.cpp @@ -55,66 +55,61 @@ class ReferenceIdToNameVisitor : public ValueVisitor { class PackagePbDeserializer { public: - PackagePbDeserializer(const android::ResStringPool* valuePool, - const android::ResStringPool* sourcePool, - const android::ResStringPool* symbolPool, - const Source& source, IDiagnostics* diag) - : value_pool_(valuePool), - source_pool_(sourcePool), - symbol_pool_(symbolPool), - source_(source), - diag_(diag) {} + PackagePbDeserializer(const android::ResStringPool* sourcePool, const Source& source, + IDiagnostics* diag) + : source_pool_(sourcePool), source_(source), diag_(diag) { + } public: - bool DeserializeFromPb(const pb::Package& pbPackage, ResourceTable* table) { + bool DeserializeFromPb(const pb::Package& pb_package, ResourceTable* table) { Maybe<uint8_t> id; - if (pbPackage.has_package_id()) { - id = static_cast<uint8_t>(pbPackage.package_id()); + if (pb_package.has_package_id()) { + id = static_cast<uint8_t>(pb_package.package_id()); } - std::map<ResourceId, ResourceNameRef> idIndex; + std::map<ResourceId, ResourceNameRef> id_index; - ResourceTablePackage* pkg = table->CreatePackage(pbPackage.package_name(), id); - for (const pb::Type& pbType : pbPackage.types()) { - const ResourceType* resType = ParseResourceType(pbType.name()); - if (!resType) { - diag_->Error(DiagMessage(source_) << "unknown type '" << pbType.name() << "'"); + ResourceTablePackage* pkg = table->CreatePackage(pb_package.package_name(), id); + for (const pb::Type& pb_type : pb_package.type()) { + const ResourceType* res_type = ParseResourceType(pb_type.name()); + if (res_type == nullptr) { + diag_->Error(DiagMessage(source_) << "unknown type '" << pb_type.name() << "'"); return {}; } - ResourceTableType* type = pkg->FindOrCreateType(*resType); + ResourceTableType* type = pkg->FindOrCreateType(*res_type); - for (const pb::Entry& pbEntry : pbType.entries()) { - ResourceEntry* entry = type->FindOrCreateEntry(pbEntry.name()); + for (const pb::Entry& pb_entry : pb_type.entry()) { + ResourceEntry* entry = type->FindOrCreateEntry(pb_entry.name()); - // Deserialize the symbol status (public/private with source and - // comments). - if (pbEntry.has_symbol_status()) { - const pb::SymbolStatus& pbStatus = pbEntry.symbol_status(); - if (pbStatus.has_source()) { - DeserializeSourceFromPb(pbStatus.source(), *source_pool_, &entry->symbol_status.source); + // Deserialize the symbol status (public/private with source and comments). + if (pb_entry.has_symbol_status()) { + const pb::SymbolStatus& pb_status = pb_entry.symbol_status(); + if (pb_status.has_source()) { + DeserializeSourceFromPb(pb_status.source(), *source_pool_, + &entry->symbol_status.source); } - if (pbStatus.has_comment()) { - entry->symbol_status.comment = pbStatus.comment(); + if (pb_status.has_comment()) { + entry->symbol_status.comment = pb_status.comment(); } - entry->symbol_status.allow_new = pbStatus.allow_new(); + entry->symbol_status.allow_new = pb_status.allow_new(); - SymbolState visibility = DeserializeVisibilityFromPb(pbStatus.visibility()); + SymbolState visibility = DeserializeVisibilityFromPb(pb_status.visibility()); entry->symbol_status.state = visibility; if (visibility == SymbolState::kPublic) { // This is a public symbol, we must encode the ID now if there is one. - if (pbEntry.has_id()) { - entry->id = static_cast<uint16_t>(pbEntry.id()); + if (pb_entry.has_id()) { + entry->id = static_cast<uint16_t>(pb_entry.id()); } if (type->symbol_status.state != SymbolState::kPublic) { // If the type has not been made public, do so now. type->symbol_status.state = SymbolState::kPublic; - if (pbType.has_id()) { - type->id = static_cast<uint8_t>(pbType.id()); + if (pb_type.has_id()) { + type->id = static_cast<uint8_t>(pb_type.id()); } } } else if (visibility == SymbolState::kPrivate) { @@ -124,45 +119,44 @@ class PackagePbDeserializer { } } - ResourceId resId(pbPackage.package_id(), pbType.id(), pbEntry.id()); - if (resId.is_valid()) { - idIndex[resId] = ResourceNameRef(pkg->name, type->type, entry->name); + ResourceId resid(pb_package.package_id(), pb_type.id(), pb_entry.id()); + if (resid.is_valid()) { + id_index[resid] = ResourceNameRef(pkg->name, type->type, entry->name); } - for (const pb::ConfigValue& pbConfigValue : pbEntry.config_values()) { - const pb::ConfigDescription& pbConfig = pbConfigValue.config(); + for (const pb::ConfigValue& pb_config_value : pb_entry.config_value()) { + const pb::ConfigDescription& pb_config = pb_config_value.config(); ConfigDescription config; - if (!DeserializeConfigDescriptionFromPb(pbConfig, &config)) { + if (!DeserializeConfigDescriptionFromPb(pb_config, &config)) { diag_->Error(DiagMessage(source_) << "invalid configuration"); return {}; } - ResourceConfigValue* configValue = entry->FindOrCreateValue(config, pbConfig.product()); - if (configValue->value) { + ResourceConfigValue* config_value = entry->FindOrCreateValue(config, pb_config.product()); + if (config_value->value) { // Duplicate config. diag_->Error(DiagMessage(source_) << "duplicate configuration"); return {}; } - configValue->value = - DeserializeValueFromPb(pbConfigValue.value(), config, &table->string_pool); - if (!configValue->value) { + config_value->value = + DeserializeValueFromPb(pb_config_value.value(), config, &table->string_pool); + if (!config_value->value) { return {}; } } } } - ReferenceIdToNameVisitor visitor(&idIndex); + ReferenceIdToNameVisitor visitor(&id_index); VisitAllValuesInPackage(pkg, &visitor); return true; } private: std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item, - const ConfigDescription& config, - StringPool* pool) { + const ConfigDescription& config, StringPool* pool) { if (pb_item.has_ref()) { const pb::Reference& pb_ref = pb_item.ref(); std::unique_ptr<Reference> ref = util::make_unique<Reference>(); @@ -173,45 +167,32 @@ class PackagePbDeserializer { } else if (pb_item.has_prim()) { const pb::Primitive& pb_prim = pb_item.prim(); - android::Res_value prim = {}; - prim.dataType = static_cast<uint8_t>(pb_prim.type()); - prim.data = pb_prim.data(); - return util::make_unique<BinaryPrimitive>(prim); + return util::make_unique<BinaryPrimitive>(static_cast<uint8_t>(pb_prim.type()), + pb_prim.data()); } else if (pb_item.has_id()) { return util::make_unique<Id>(); } else if (pb_item.has_str()) { - const uint32_t idx = pb_item.str().idx(); - const std::string str = util::GetString(*value_pool_, idx); - - const android::ResStringPool_span* spans = value_pool_->styleAt(idx); - if (spans && spans->name.index != android::ResStringPool_span::END) { - StyleString style_str = {str}; - while (spans->name.index != android::ResStringPool_span::END) { - style_str.spans.push_back( - Span{util::GetString(*value_pool_, spans->name.index), - spans->firstChar, spans->lastChar}); - spans++; - } - return util::make_unique<StyledString>(pool->MakeRef( - style_str, StringPool::Context(StringPool::Context::kNormalPriority, config))); - } return util::make_unique<String>( - pool->MakeRef(str, StringPool::Context(config))); + pool->MakeRef(pb_item.str().value(), StringPool::Context(config))); } else if (pb_item.has_raw_str()) { - const uint32_t idx = pb_item.raw_str().idx(); - const std::string str = util::GetString(*value_pool_, idx); return util::make_unique<RawString>( - pool->MakeRef(str, StringPool::Context(config))); + pool->MakeRef(pb_item.raw_str().value(), StringPool::Context(config))); + + } else if (pb_item.has_styled_str()) { + const pb::StyledString& pb_str = pb_item.styled_str(); + StyleString style_str{pb_str.value()}; + for (const pb::StyledString::Span& pb_span : pb_str.span()) { + style_str.spans.push_back(Span{pb_span.tag(), pb_span.first_char(), pb_span.last_char()}); + } + return util::make_unique<StyledString>(pool->MakeRef( + style_str, StringPool::Context(StringPool::Context::kNormalPriority, config))); } else if (pb_item.has_file()) { - const uint32_t idx = pb_item.file().path_idx(); - const std::string str = util::GetString(*value_pool_, idx); return util::make_unique<FileReference>(pool->MakeRef( - str, - StringPool::Context(StringPool::Context::kHighPriority, config))); + pb_item.file().path(), StringPool::Context(StringPool::Context::kHighPriority, config))); } else { diag_->Error(DiagMessage(source_) << "unknown item"); @@ -222,8 +203,6 @@ class PackagePbDeserializer { std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value, const ConfigDescription& config, StringPool* pool) { - const bool is_weak = pb_value.has_weak() ? pb_value.weak() : false; - std::unique_ptr<Value> value; if (pb_value.has_item()) { value = DeserializeItemFromPb(pb_value.item(), config, pool); @@ -235,11 +214,11 @@ class PackagePbDeserializer { const pb::CompoundValue& pb_compound_value = pb_value.compound_value(); if (pb_compound_value.has_attr()) { const pb::Attribute& pb_attr = pb_compound_value.attr(); - std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(is_weak); + std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(); attr->type_mask = pb_attr.format_flags(); attr->min_int = pb_attr.min_int(); attr->max_int = pb_attr.max_int(); - for (const pb::Attribute_Symbol& pb_symbol : pb_attr.symbols()) { + for (const pb::Attribute_Symbol& pb_symbol : pb_attr.symbol()) { Attribute::Symbol symbol; DeserializeItemCommon(pb_symbol, &symbol.symbol); if (!DeserializeReferenceFromPb(pb_symbol.name(), &symbol.symbol)) { @@ -255,20 +234,18 @@ class PackagePbDeserializer { std::unique_ptr<Style> style = util::make_unique<Style>(); if (pb_style.has_parent()) { style->parent = Reference(); - if (!DeserializeReferenceFromPb(pb_style.parent(), - &style->parent.value())) { + if (!DeserializeReferenceFromPb(pb_style.parent(), &style->parent.value())) { return {}; } if (pb_style.has_parent_source()) { Source parent_source; - DeserializeSourceFromPb(pb_style.parent_source(), *source_pool_, - &parent_source); + DeserializeSourceFromPb(pb_style.parent_source(), *source_pool_, &parent_source); style->parent.value().SetSource(std::move(parent_source)); } } - for (const pb::Style_Entry& pb_entry : pb_style.entries()) { + for (const pb::Style_Entry& pb_entry : pb_style.entry()) { Style::Entry entry; DeserializeItemCommon(pb_entry, &entry.key); if (!DeserializeReferenceFromPb(pb_entry.key(), &entry.key)) { @@ -288,7 +265,7 @@ class PackagePbDeserializer { } else if (pb_compound_value.has_styleable()) { const pb::Styleable& pb_styleable = pb_compound_value.styleable(); std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); - for (const pb::Styleable_Entry& pb_entry : pb_styleable.entries()) { + for (const pb::Styleable_Entry& pb_entry : pb_styleable.entry()) { Reference attr_ref; DeserializeItemCommon(pb_entry, &attr_ref); DeserializeReferenceFromPb(pb_entry.attr(), &attr_ref); @@ -299,25 +276,23 @@ class PackagePbDeserializer { } else if (pb_compound_value.has_array()) { const pb::Array& pb_array = pb_compound_value.array(); std::unique_ptr<Array> array = util::make_unique<Array>(); - for (const pb::Array_Entry& pb_entry : pb_array.entries()) { - std::unique_ptr<Item> item = - DeserializeItemFromPb(pb_entry.item(), config, pool); + for (const pb::Array_Element& pb_entry : pb_array.element()) { + std::unique_ptr<Item> item = DeserializeItemFromPb(pb_entry.item(), config, pool); if (!item) { return {}; } DeserializeItemCommon(pb_entry, item.get()); - array->items.push_back(std::move(item)); + array->elements.push_back(std::move(item)); } value = std::move(array); } else if (pb_compound_value.has_plural()) { const pb::Plural& pb_plural = pb_compound_value.plural(); std::unique_ptr<Plural> plural = util::make_unique<Plural>(); - for (const pb::Plural_Entry& pb_entry : pb_plural.entries()) { + for (const pb::Plural_Entry& pb_entry : pb_plural.entry()) { size_t pluralIdx = DeserializePluralEnumFromPb(pb_entry.arity()); - plural->values[pluralIdx] = - DeserializeItemFromPb(pb_entry.item(), config, pool); + plural->values[pluralIdx] = DeserializeItemFromPb(pb_entry.item(), config, pool); if (!plural->values[pluralIdx]) { return {}; } @@ -337,7 +312,7 @@ class PackagePbDeserializer { CHECK(value) << "forgot to set value"; - value->SetWeak(is_weak); + value->SetWeak(pb_value.weak()); DeserializeItemCommon(pb_value, value.get()); return value; } @@ -350,11 +325,10 @@ class PackagePbDeserializer { out_ref->id = ResourceId(pb_ref.id()); } - if (pb_ref.has_symbol_idx()) { - const std::string str_symbol = util::GetString(*symbol_pool_, pb_ref.symbol_idx()); + if (pb_ref.has_name()) { ResourceNameRef name_ref; - if (!ResourceUtils::ParseResourceName(str_symbol, &name_ref, nullptr)) { - diag_->Error(DiagMessage(source_) << "invalid reference name '" << str_symbol << "'"); + if (!ResourceUtils::ParseResourceName(pb_ref.name(), &name_ref, nullptr)) { + diag_->Error(DiagMessage(source_) << "invalid reference name '" << pb_ref.name() << "'"); return false; } @@ -377,61 +351,33 @@ class PackagePbDeserializer { } private: - const android::ResStringPool* value_pool_; const android::ResStringPool* source_pool_; - const android::ResStringPool* symbol_pool_; const Source source_; IDiagnostics* diag_; }; } // namespace -std::unique_ptr<ResourceTable> DeserializeTableFromPb( - const pb::ResourceTable& pb_table, const Source& source, - IDiagnostics* diag) { - // We import the android namespace because on Windows NO_ERROR is a macro, not - // an enum, which +std::unique_ptr<ResourceTable> DeserializeTableFromPb(const pb::ResourceTable& pb_table, + const Source& source, IDiagnostics* diag) { + // We import the android namespace because on Windows NO_ERROR is a macro, not an enum, which // causes errors when qualifying it with android:: using namespace android; std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); - if (!pb_table.has_string_pool()) { - diag->Error(DiagMessage(source) << "no string pool found"); - return {}; - } - - ResStringPool value_pool; - status_t result = value_pool.setTo(pb_table.string_pool().data().data(), - pb_table.string_pool().data().size()); - if (result != NO_ERROR) { - diag->Error(DiagMessage(source) << "invalid string pool"); - return {}; - } - ResStringPool source_pool; if (pb_table.has_source_pool()) { - result = source_pool.setTo(pb_table.source_pool().data().data(), - pb_table.source_pool().data().size()); + status_t result = source_pool.setTo(pb_table.source_pool().data().data(), + pb_table.source_pool().data().size()); if (result != NO_ERROR) { diag->Error(DiagMessage(source) << "invalid source pool"); return {}; } } - ResStringPool symbol_pool; - if (pb_table.has_symbol_pool()) { - result = symbol_pool.setTo(pb_table.symbol_pool().data().data(), - pb_table.symbol_pool().data().size()); - if (result != NO_ERROR) { - diag->Error(DiagMessage(source) << "invalid symbol pool"); - return {}; - } - } - - PackagePbDeserializer package_pb_deserializer(&value_pool, &source_pool, - &symbol_pool, source, diag); - for (const pb::Package& pb_package : pb_table.packages()) { + PackagePbDeserializer package_pb_deserializer(&source_pool, source, diag); + for (const pb::Package& pb_package : pb_table.package()) { if (!package_pb_deserializer.DeserializeFromPb(pb_package, table.get())) { return {}; } @@ -440,7 +386,7 @@ std::unique_ptr<ResourceTable> DeserializeTableFromPb( } std::unique_ptr<ResourceFile> DeserializeCompiledFileFromPb( - const pb::CompiledFile& pb_file, const Source& source, IDiagnostics* diag) { + const pb::internal::CompiledFile& pb_file, const Source& source, IDiagnostics* diag) { std::unique_ptr<ResourceFile> file = util::make_unique<ResourceFile>(); ResourceNameRef name_ref; @@ -456,19 +402,20 @@ std::unique_ptr<ResourceFile> DeserializeCompiledFileFromPb( file->source.path = pb_file.source_path(); DeserializeConfigDescriptionFromPb(pb_file.config(), &file->config); - for (const pb::CompiledFile_Symbol& pb_symbol : pb_file.exported_symbols()) { - // Need to create an lvalue here so that nameRef can point to something - // real. - if (!ResourceUtils::ParseResourceName(pb_symbol.resource_name(), - &name_ref)) { + for (const pb::internal::CompiledFile_Symbol& pb_symbol : pb_file.exported_symbol()) { + // Need to create an lvalue here so that nameRef can point to something real. + if (!ResourceUtils::ParseResourceName(pb_symbol.resource_name(), &name_ref)) { diag->Error(DiagMessage(source) << "invalid resource name for exported symbol in " "compiled file header: " << pb_file.resource_name()); return {}; } - file->exported_symbols.push_back( - SourcedResourceName{name_ref.ToResourceName(), pb_symbol.line_no()}); + size_t line = 0u; + if (pb_symbol.has_source()) { + line = pb_symbol.source().line_number(); + } + file->exported_symbols.push_back(SourcedResourceName{name_ref.ToResourceName(), line}); } return file; } diff --git a/tools/aapt2/proto/TableProtoSerializer.cpp b/tools/aapt2/proto/TableProtoSerializer.cpp index 730442c62836..a08df71eae1e 100644 --- a/tools/aapt2/proto/TableProtoSerializer.cpp +++ b/tools/aapt2/proto/TableProtoSerializer.cpp @@ -24,9 +24,9 @@ #include "android-base/logging.h" -using google::protobuf::io::CodedOutputStream; -using google::protobuf::io::CodedInputStream; -using google::protobuf::io::ZeroCopyOutputStream; +using ::google::protobuf::io::CodedInputStream; +using ::google::protobuf::io::CodedOutputStream; +using ::google::protobuf::io::ZeroCopyOutputStream; namespace aapt { @@ -36,46 +36,46 @@ class PbSerializerVisitor : public RawValueVisitor { public: using RawValueVisitor::Visit; - /** - * Constructor to use when expecting to serialize any value. - */ - PbSerializerVisitor(StringPool* source_pool, StringPool* symbol_pool, - pb::Value* out_pb_value) - : source_pool_(source_pool), - symbol_pool_(symbol_pool), - out_pb_value_(out_pb_value), - out_pb_item_(nullptr) {} - - /** - * Constructor to use when expecting to serialize an Item. - */ - PbSerializerVisitor(StringPool* sourcePool, StringPool* symbolPool, - pb::Item* outPbItem) - : source_pool_(sourcePool), - symbol_pool_(symbolPool), - out_pb_value_(nullptr), - out_pb_item_(outPbItem) {} + // Constructor to use when expecting to serialize any value. + PbSerializerVisitor(StringPool* source_pool, pb::Value* out_pb_value) + : source_pool_(source_pool), out_pb_value_(out_pb_value), out_pb_item_(nullptr) { + } + + // Constructor to use when expecting to serialize an Item. + PbSerializerVisitor(StringPool* sourcePool, pb::Item* outPbItem) + : source_pool_(sourcePool), out_pb_value_(nullptr), out_pb_item_(outPbItem) { + } void Visit(Reference* ref) override { SerializeReferenceToPb(*ref, pb_item()->mutable_ref()); } void Visit(String* str) override { - pb_item()->mutable_str()->set_idx(str->value.index()); + pb_item()->mutable_str()->set_value(*str->value); + } + + void Visit(RawString* str) override { + pb_item()->mutable_raw_str()->set_value(*str->value); } void Visit(StyledString* str) override { - pb_item()->mutable_str()->set_idx(str->value.index()); + pb::StyledString* pb_str = pb_item()->mutable_styled_str(); + pb_str->set_value(str->value->value); + + for (const StringPool::Span& span : str->value->spans) { + pb::StyledString::Span* pb_span = pb_str->add_span(); + pb_span->set_tag(*span.name); + pb_span->set_first_char(span.first_char); + pb_span->set_last_char(span.last_char); + } } void Visit(FileReference* file) override { - pb_item()->mutable_file()->set_path_idx(file->path.index()); + pb_item()->mutable_file()->set_path(*file->path); } - void Visit(Id* id) override { pb_item()->mutable_id(); } - - void Visit(RawString* raw_str) override { - pb_item()->mutable_raw_str()->set_idx(raw_str->value.index()); + void Visit(Id* /*id*/) override { + pb_item()->mutable_id(); } void Visit(BinaryPrimitive* prim) override { @@ -98,7 +98,7 @@ class PbSerializerVisitor : public RawValueVisitor { pb_attr->set_max_int(attr->max_int); for (auto& symbol : attr->symbols) { - pb::Attribute_Symbol* pb_symbol = pb_attr->add_symbols(); + pb::Attribute_Symbol* pb_symbol = pb_attr->add_symbol(); SerializeItemCommonToPb(symbol.symbol, pb_symbol); SerializeReferenceToPb(symbol.symbol, pb_symbol->mutable_name()); pb_symbol->set_value(symbol.value); @@ -114,12 +114,12 @@ class PbSerializerVisitor : public RawValueVisitor { } for (Style::Entry& entry : style->entries) { - pb::Style_Entry* pb_entry = pb_style->add_entries(); + pb::Style_Entry* pb_entry = pb_style->add_entry(); SerializeReferenceToPb(entry.key, pb_entry->mutable_key()); pb::Item* pb_item = pb_entry->mutable_item(); SerializeItemCommonToPb(entry.key, pb_entry); - PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_, pb_item); + PbSerializerVisitor sub_visitor(source_pool_, pb_item); entry.value->Accept(&sub_visitor); } } @@ -127,7 +127,7 @@ class PbSerializerVisitor : public RawValueVisitor { void Visit(Styleable* styleable) override { pb::Styleable* pb_styleable = pb_compound_value()->mutable_styleable(); for (Reference& entry : styleable->entries) { - pb::Styleable_Entry* pb_entry = pb_styleable->add_entries(); + pb::Styleable_Entry* pb_entry = pb_styleable->add_entry(); SerializeItemCommonToPb(entry, pb_entry); SerializeReferenceToPb(entry, pb_entry->mutable_attr()); } @@ -135,11 +135,10 @@ class PbSerializerVisitor : public RawValueVisitor { void Visit(Array* array) override { pb::Array* pb_array = pb_compound_value()->mutable_array(); - for (auto& value : array->items) { - pb::Array_Entry* pb_entry = pb_array->add_entries(); - SerializeItemCommonToPb(*value, pb_entry); - PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_, - pb_entry->mutable_item()); + for (auto& value : array->elements) { + pb::Array_Element* pb_element = pb_array->add_element(); + SerializeItemCommonToPb(*value, pb_element); + PbSerializerVisitor sub_visitor(source_pool_, pb_element->mutable_item()); value->Accept(&sub_visitor); } } @@ -153,11 +152,11 @@ class PbSerializerVisitor : public RawValueVisitor { continue; } - pb::Plural_Entry* pb_entry = pb_plural->add_entries(); + pb::Plural_Entry* pb_entry = pb_plural->add_entry(); pb_entry->set_arity(SerializePluralEnumToPb(i)); pb::Item* pb_element = pb_entry->mutable_item(); SerializeItemCommonToPb(*plural->values[i], pb_entry); - PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_, pb_element); + PbSerializerVisitor sub_visitor(source_pool_, pb_element); plural->values[i]->Accept(&sub_visitor); } } @@ -179,8 +178,7 @@ class PbSerializerVisitor : public RawValueVisitor { template <typename T> void SerializeItemCommonToPb(const Item& item, T* pb_item) { - SerializeSourceToPb(item.GetSource(), source_pool_, - pb_item->mutable_source()); + SerializeSourceToPb(item.GetSource(), source_pool_, pb_item->mutable_source()); if (!item.GetComment().empty()) { pb_item->set_comment(item.GetComment()); } @@ -192,8 +190,7 @@ class PbSerializerVisitor : public RawValueVisitor { } if (ref.name) { - StringPool::Ref symbol_ref = symbol_pool_->MakeRef(ref.name.value().ToString()); - pb_ref->set_symbol_idx(static_cast<uint32_t>(symbol_ref.index())); + pb_ref->set_name(ref.name.value().ToString()); } pb_ref->set_private_(ref.private_reference); @@ -201,7 +198,6 @@ class PbSerializerVisitor : public RawValueVisitor { } StringPool* source_pool_; - StringPool* symbol_pool_; pb::Value* out_pb_value_; pb::Item* out_pb_item_; }; @@ -220,26 +216,24 @@ std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) { }); auto pb_table = util::make_unique<pb::ResourceTable>(); - SerializeStringPoolToPb(table->string_pool, pb_table->mutable_string_pool()); - - StringPool source_pool, symbol_pool; + StringPool source_pool; for (auto& package : table->packages) { - pb::Package* pb_package = pb_table->add_packages(); + pb::Package* pb_package = pb_table->add_package(); if (package->id) { pb_package->set_package_id(package->id.value()); } pb_package->set_package_name(package->name); for (auto& type : package->types) { - pb::Type* pb_type = pb_package->add_types(); + pb::Type* pb_type = pb_package->add_type(); if (type->id) { pb_type->set_id(type->id.value()); } pb_type->set_name(ToString(type->type).to_string()); for (auto& entry : type->entries) { - pb::Entry* pb_entry = pb_type->add_entries(); + pb::Entry* pb_entry = pb_type->add_entry(); if (entry->id) { pb_entry->set_id(entry->id.value()); } @@ -253,7 +247,7 @@ std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) { pb_status->set_allow_new(entry->symbol_status.allow_new); for (auto& config_value : entry->values) { - pb::ConfigValue* pb_config_value = pb_entry->add_config_values(); + pb::ConfigValue* pb_config_value = pb_entry->add_config_value(); SerializeConfig(config_value->config, pb_config_value->mutable_config()); if (!config_value->product.empty()) { pb_config_value->mutable_config()->set_product(config_value->product); @@ -270,7 +264,7 @@ std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) { pb_value->set_weak(true); } - PbSerializerVisitor visitor(&source_pool, &symbol_pool, pb_value); + PbSerializerVisitor visitor(&source_pool, pb_value); config_value->value->Accept(&visitor); } } @@ -278,27 +272,25 @@ std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) { } SerializeStringPoolToPb(source_pool, pb_table->mutable_source_pool()); - SerializeStringPoolToPb(symbol_pool, pb_table->mutable_symbol_pool()); return pb_table; } -std::unique_ptr<pb::CompiledFile> SerializeCompiledFileToPb( - const ResourceFile& file) { - auto pb_file = util::make_unique<pb::CompiledFile>(); +std::unique_ptr<pb::internal::CompiledFile> SerializeCompiledFileToPb(const ResourceFile& file) { + auto pb_file = util::make_unique<pb::internal::CompiledFile>(); pb_file->set_resource_name(file.name.ToString()); pb_file->set_source_path(file.source.path); SerializeConfig(file.config, pb_file->mutable_config()); for (const SourcedResourceName& exported : file.exported_symbols) { - pb::CompiledFile_Symbol* pb_symbol = pb_file->add_exported_symbols(); + pb::internal::CompiledFile_Symbol* pb_symbol = pb_file->add_exported_symbol(); pb_symbol->set_resource_name(exported.name.ToString()); - pb_symbol->set_line_no(exported.line); + pb_symbol->mutable_source()->set_line_number(exported.line); } return pb_file; } -CompiledFileOutputStream::CompiledFileOutputStream(ZeroCopyOutputStream* out) - : out_(out) {} +CompiledFileOutputStream::CompiledFileOutputStream(ZeroCopyOutputStream* out) : out_(out) { +} void CompiledFileOutputStream::EnsureAlignedWrite() { const int padding = out_.ByteCount() % 4; @@ -313,8 +305,7 @@ void CompiledFileOutputStream::WriteLittleEndian32(uint32_t val) { out_.WriteLittleEndian32(val); } -void CompiledFileOutputStream::WriteCompiledFile( - const pb::CompiledFile* compiled_file) { +void CompiledFileOutputStream::WriteCompiledFile(const pb::internal::CompiledFile* compiled_file) { EnsureAlignedWrite(); out_.WriteLittleEndian64(static_cast<uint64_t>(compiled_file->ByteSize())); compiled_file->SerializeWithCachedSizes(&out_); @@ -334,7 +325,9 @@ void CompiledFileOutputStream::WriteData(const void* data, size_t len) { out_.WriteRaw(data, len); } -bool CompiledFileOutputStream::HadError() { return out_.HadError(); } +bool CompiledFileOutputStream::HadError() { + return out_.HadError(); +} CompiledFileInputStream::CompiledFileInputStream(const void* data, size_t size) : in_(static_cast<const uint8_t*>(data), size) {} @@ -352,7 +345,7 @@ bool CompiledFileInputStream::ReadLittleEndian32(uint32_t* out_val) { return in_.ReadLittleEndian32(out_val); } -bool CompiledFileInputStream::ReadCompiledFile(pb::CompiledFile* out_val) { +bool CompiledFileInputStream::ReadCompiledFile(pb::internal::CompiledFile* out_val) { EnsureAlignedRead(); google::protobuf::uint64 pb_size = 0u; @@ -379,8 +372,7 @@ bool CompiledFileInputStream::ReadCompiledFile(pb::CompiledFile* out_val) { return true; } -bool CompiledFileInputStream::ReadDataMetaData(uint64_t* out_offset, - uint64_t* out_len) { +bool CompiledFileInputStream::ReadDataMetaData(uint64_t* out_offset, uint64_t* out_len) { EnsureAlignedRead(); google::protobuf::uint64 pb_size = 0u; diff --git a/tools/aapt2/proto/TableProtoSerializer_test.cpp b/tools/aapt2/proto/TableProtoSerializer_test.cpp index 3ebb08eb791e..80608b3d9c05 100644 --- a/tools/aapt2/proto/TableProtoSerializer_test.cpp +++ b/tools/aapt2/proto/TableProtoSerializer_test.cpp @@ -20,7 +20,9 @@ #include "test/Test.h" using ::google::protobuf::io::StringOutputStream; +using ::testing::Eq; using ::testing::NotNull; +using ::testing::SizeIs; namespace aapt { @@ -38,12 +40,12 @@ TEST(TableProtoSerializer, SerializeSinglePackage) { Symbol public_symbol; public_symbol.state = SymbolState::kPublic; - ASSERT_TRUE(table->SetSymbolState( - test::ParseNameOrDie("com.app.a:layout/main"), ResourceId(0x7f020000), - public_symbol, context->GetDiagnostics())); + ASSERT_TRUE(table->SetSymbolState(test::ParseNameOrDie("com.app.a:layout/main"), + ResourceId(0x7f020000), public_symbol, + context->GetDiagnostics())); Id* id = test::GetValue<Id>(table.get(), "com.app.a:id/foo"); - ASSERT_NE(nullptr, id); + ASSERT_THAT(id, NotNull()); // Make a plural. std::unique_ptr<Plural> plural = util::make_unique<Plural>(); @@ -52,6 +54,15 @@ TEST(TableProtoSerializer, SerializeSinglePackage) { ConfigDescription{}, {}, std::move(plural), context->GetDiagnostics())); + // Make a styled string. + StyleString style_string; + style_string.str = "hello"; + style_string.spans.push_back(Span{"b", 0u, 4u}); + ASSERT_TRUE( + table->AddResource(test::ParseNameOrDie("com.app.a:string/styled"), ConfigDescription{}, {}, + util::make_unique<StyledString>(table->string_pool.MakeRef(style_string)), + context->GetDiagnostics())); + // Make a resource with different products. ASSERT_TRUE(table->AddResource( test::ParseNameOrDie("com.app.a:integer/one"), @@ -65,9 +76,8 @@ TEST(TableProtoSerializer, SerializeSinglePackage) { context->GetDiagnostics())); // Make a reference with both resource name and resource ID. - // The reference should point to a resource outside of this table to test that - // both - // name and id get serialized. + // The reference should point to a resource outside of this table to test that both name and id + // get serialized. Reference expected_ref; expected_ref.name = test::ParseNameOrDie("android:layout/main"); expected_ref.id = ResourceId(0x01020000); @@ -85,36 +95,45 @@ TEST(TableProtoSerializer, SerializeSinglePackage) { Id* new_id = test::GetValue<Id>(new_table.get(), "com.app.a:id/foo"); ASSERT_THAT(new_id, NotNull()); - EXPECT_EQ(id->IsWeak(), new_id->IsWeak()); + EXPECT_THAT(new_id->IsWeak(), Eq(id->IsWeak())); Maybe<ResourceTable::SearchResult> result = new_table->FindResource(test::ParseNameOrDie("com.app.a:layout/main")); ASSERT_TRUE(result); - EXPECT_EQ(SymbolState::kPublic, result.value().type->symbol_status.state); - EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbol_status.state); + + EXPECT_THAT(result.value().type->symbol_status.state, Eq(SymbolState::kPublic)); + EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kPublic)); result = new_table->FindResource(test::ParseNameOrDie("com.app.a:bool/foo")); ASSERT_TRUE(result); - EXPECT_EQ(SymbolState::kUndefined, result.value().entry->symbol_status.state); + EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kUndefined)); EXPECT_TRUE(result.value().entry->symbol_status.allow_new); // Find the product-dependent values BinaryPrimitive* prim = test::GetValueForConfigAndProduct<BinaryPrimitive>( new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"), ""); ASSERT_THAT(prim, NotNull()); - EXPECT_EQ(123u, prim->value.data); + EXPECT_THAT(prim->value.data, Eq(123u)); prim = test::GetValueForConfigAndProduct<BinaryPrimitive>( new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"), "tablet"); ASSERT_THAT(prim, NotNull()); - EXPECT_EQ(321u, prim->value.data); + EXPECT_THAT(prim->value.data, Eq(321u)); Reference* actual_ref = test::GetValue<Reference>(new_table.get(), "com.app.a:layout/abc"); ASSERT_THAT(actual_ref, NotNull()); ASSERT_TRUE(actual_ref->name); ASSERT_TRUE(actual_ref->id); - EXPECT_EQ(expected_ref.name.value(), actual_ref->name.value()); - EXPECT_EQ(expected_ref.id.value(), actual_ref->id.value()); + EXPECT_THAT(*actual_ref, Eq(expected_ref)); + + StyledString* actual_styled_str = + test::GetValue<StyledString>(new_table.get(), "com.app.a:string/styled"); + ASSERT_THAT(actual_styled_str, NotNull()); + EXPECT_THAT(actual_styled_str->value->value, Eq("hello")); + ASSERT_THAT(actual_styled_str->value->spans, SizeIs(1u)); + EXPECT_THAT(*actual_styled_str->value->spans[0].name, Eq("b")); + EXPECT_THAT(actual_styled_str->value->spans[0].first_char, Eq(0u)); + EXPECT_THAT(actual_styled_str->value->spans[0].last_char, Eq(4u)); } TEST(TableProtoSerializer, SerializeFileHeader) { @@ -132,10 +151,10 @@ TEST(TableProtoSerializer, SerializeFileHeader) { std::string output_str; { - std::unique_ptr<pb::CompiledFile> pb_file1 = SerializeCompiledFileToPb(f); + std::unique_ptr<pb::internal::CompiledFile> pb_file1 = SerializeCompiledFileToPb(f); f.name.entry = "__" + f.name.entry + "$0"; - std::unique_ptr<pb::CompiledFile> pb_file2 = SerializeCompiledFileToPb(f); + std::unique_ptr<pb::internal::CompiledFile> pb_file2 = SerializeCompiledFileToPb(f); StringOutputStream out_stream(&output_str); CompiledFileOutputStream out_file_stream(&out_stream); @@ -154,7 +173,7 @@ TEST(TableProtoSerializer, SerializeFileHeader) { // Read the first compiled file. - pb::CompiledFile new_pb_file; + pb::internal::CompiledFile new_pb_file; ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_file)); std::unique_ptr<ResourceFile> file = DeserializeCompiledFileFromPb( @@ -191,7 +210,7 @@ TEST(TableProtoSerializer, SerializeFileHeader) { TEST(TableProtoSerializer, DeserializeCorruptHeaderSafely) { ResourceFile f; - std::unique_ptr<pb::CompiledFile> pb_file = SerializeCompiledFileToPb(f); + std::unique_ptr<pb::internal::CompiledFile> pb_file = SerializeCompiledFileToPb(f); const std::string expected_data = "1234"; @@ -213,7 +232,7 @@ TEST(TableProtoSerializer, DeserializeCorruptHeaderSafely) { EXPECT_TRUE(in_file_stream.ReadLittleEndian32(&num_files)); EXPECT_EQ(1u, num_files); - pb::CompiledFile new_pb_file; + pb::internal::CompiledFile new_pb_file; EXPECT_FALSE(in_file_stream.ReadCompiledFile(&new_pb_file)); uint64_t offset, len; diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md index c8d36177beb4..8368f9d16af8 100644 --- a/tools/aapt2/readme.md +++ b/tools/aapt2/readme.md @@ -1,5 +1,18 @@ # Android Asset Packaging Tool 2.0 (AAPT2) release notes +## Version 2.19 +- Added navigation resource type. +- Fixed issue with resource deduplication. (bug 64397629) +- Added a daemon mode for issuing commands. This is invoked with `aapt2 daemon`. + Command line arguments are separated by newlines, with an empty line signalling the + end of a command. Sending `EOF (Ctrl+D)` to the daemon will exit. +- Fixed an issue where multiple permissions defined in AndroidManifest.xml would generate + conflicting definitions for the same Java constant in Manifest.java. Changed the implementation + to match that of `aapt`, which will take the last definition as the sole definition. + A warning is logged if such a scenario occurs. (bug 64472942) +- Made improvements to handling of paths on Windows. This should resolve a lot of issues with + Unicode paths. (bug 62336414, 63830502) + ## Version 2.18 ### `aapt2 ...` - Fixed issue where enum values were interpreted as integers and range checked. (bug 62358540) diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp index 27e13d81ff49..9d49ca6c0aa9 100644 --- a/tools/aapt2/split/TableSplitter.cpp +++ b/tools/aapt2/split/TableSplitter.cpp @@ -32,8 +32,7 @@ namespace aapt { using ConfigClaimedMap = std::unordered_map<ResourceConfigValue*, bool>; -using ConfigDensityGroups = - std::map<ConfigDescription, std::vector<ResourceConfigValue*>>; +using ConfigDensityGroups = std::map<ConfigDescription, std::vector<ResourceConfigValue*>>; static ConfigDescription CopyWithoutDensity(const ConfigDescription& config) { ConfigDescription without_density = config; @@ -51,8 +50,7 @@ class SplitValueSelector { if (config.density == 0) { density_independent_configs_.insert(config); } else { - density_dependent_config_to_density_map_[CopyWithoutDensity(config)] = - config.density; + density_dependent_config_to_density_map_[CopyWithoutDensity(config)] = config.density; } } } @@ -94,9 +92,7 @@ class SplitValueSelector { ResourceConfigValue* best_value = nullptr; for (ResourceConfigValue* this_value : related_values) { - if (!best_value || - this_value->config.isBetterThan(best_value->config, - &target_density)) { + if (!best_value || this_value->config.isBetterThan(best_value->config, &target_density)) { best_value = this_value; } } @@ -120,9 +116,8 @@ class SplitValueSelector { }; /** - * Marking non-preferred densities as claimed will make sure the base doesn't - * include them, - * leaving only the preferred density behind. + * Marking non-preferred densities as claimed will make sure the base doesn't include them, leaving + * only the preferred density behind. */ static void MarkNonPreferredDensitiesAsClaimed( const std::vector<uint16_t>& preferred_densities, const ConfigDensityGroups& density_groups, @@ -161,8 +156,7 @@ bool TableSplitter::VerifySplitConstraints(IAaptContext* context) { for (size_t i = 0; i < split_constraints_.size(); i++) { for (size_t j = i + 1; j < split_constraints_.size(); j++) { for (const ConfigDescription& config : split_constraints_[i].configs) { - if (split_constraints_[j].configs.find(config) != - split_constraints_[j].configs.end()) { + if (split_constraints_[j].configs.find(config) != split_constraints_[j].configs.end()) { context->GetDiagnostics()->Error(DiagMessage() << "config '" << config << "' appears in multiple splits, " @@ -193,28 +187,22 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { for (auto& entry : type->entries) { if (options_.config_filter) { // First eliminate any resource that we definitely don't want. - for (std::unique_ptr<ResourceConfigValue>& config_value : - entry->values) { + for (std::unique_ptr<ResourceConfigValue>& config_value : entry->values) { if (!options_.config_filter->Match(config_value->config)) { - // null out the entry. We will clean up and remove nulls at the - // end for performance reasons. + // null out the entry. We will clean up and remove nulls at the end for performance + // reasons. config_value.reset(); } } } - // Organize the values into two separate buckets. Those that are - // density-dependent - // and those that are density-independent. - // One density technically matches all density, it's just that some - // densities - // match better. So we need to be aware of the full set of densities to - // make this - // decision. + // Organize the values into two separate buckets. Those that are density-dependent and those + // that are density-independent. One density technically matches all density, it's just that + // some densities match better. So we need to be aware of the full set of densities to make + // this decision. ConfigDensityGroups density_groups; ConfigClaimedMap config_claimed_map; - for (const std::unique_ptr<ResourceConfigValue>& config_value : - entry->values) { + for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) { if (config_value) { config_claimed_map[config_value.get()] = false; @@ -226,9 +214,8 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { } } - // First we check all the splits. If it doesn't match one of the splits, - // we - // leave it in the base. + // First we check all the splits. If it doesn't match one of the splits, we leave it in the + // base. for (size_t idx = 0; idx < split_count; idx++) { const SplitConstraints& split_constraint = split_constraints_[idx]; ResourceTable* split_table = splits_[idx].get(); @@ -240,20 +227,16 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { // No need to do any work if we selected nothing. if (!selected_values.empty()) { - // Create the same resource structure in the split. We do this - // lazily because we might not have actual values for each - // type/entry. - ResourceTablePackage* split_pkg = - split_table->FindPackage(pkg->name); - ResourceTableType* split_type = - split_pkg->FindOrCreateType(type->type); + // Create the same resource structure in the split. We do this lazily because we might + // not have actual values for each type/entry. + ResourceTablePackage* split_pkg = split_table->FindPackage(pkg->name); + ResourceTableType* split_type = split_pkg->FindOrCreateType(type->type); if (!split_type->id) { split_type->id = type->id; split_type->symbol_status = type->symbol_status; } - ResourceEntry* split_entry = - split_type->FindOrCreateEntry(entry->name); + ResourceEntry* split_entry = split_type->FindOrCreateEntry(entry->name); if (!split_entry->id) { split_entry->id = entry->id; split_entry->symbol_status = entry->symbol_status; @@ -262,8 +245,7 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { // Copy the selected values into the new Split Entry. for (ResourceConfigValue* config_value : selected_values) { ResourceConfigValue* new_config_value = - split_entry->FindOrCreateValue(config_value->config, - config_value->product); + split_entry->FindOrCreateValue(config_value->config, config_value->product); new_config_value->value = std::unique_ptr<Value>( config_value->value->Clone(&split_table->string_pool)); } @@ -276,11 +258,9 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { &config_claimed_map); } - // All splits are handled, now check to see what wasn't claimed and - // remove - // whatever exists in other splits. - for (std::unique_ptr<ResourceConfigValue>& config_value : - entry->values) { + // All splits are handled, now check to see what wasn't claimed and remove whatever exists + // in other splits. + for (std::unique_ptr<ResourceConfigValue>& config_value : entry->values) { if (config_value && config_claimed_map[config_value.get()]) { // Claimed, remove from base. config_value.reset(); diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp new file mode 100644 index 000000000000..e658664d8653 --- /dev/null +++ b/tools/aapt2/test/Builders.cpp @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "test/Builders.h" + +#include "android-base/logging.h" +#include "androidfw/StringPiece.h" + +#include "io/StringInputStream.h" +#include "test/Common.h" +#include "util/Util.h" + +using ::aapt::configuration::Abi; +using ::aapt::configuration::AndroidSdk; +using ::aapt::configuration::Artifact; +using ::aapt::io::StringInputStream; +using ::android::StringPiece; + +namespace aapt { +namespace test { + +ResourceTableBuilder& ResourceTableBuilder::SetPackageId(const StringPiece& package_name, + uint8_t id) { + ResourceTablePackage* package = table_->CreatePackage(package_name, id); + CHECK(package != nullptr); + return *this; +} + +ResourceTableBuilder& ResourceTableBuilder::AddSimple(const StringPiece& name, + const ResourceId& id) { + return AddValue(name, id, util::make_unique<Id>()); +} + +ResourceTableBuilder& ResourceTableBuilder::AddSimple(const StringPiece& name, + const ConfigDescription& config, + const ResourceId& id) { + return AddValue(name, config, id, util::make_unique<Id>()); +} + +ResourceTableBuilder& ResourceTableBuilder::AddReference(const StringPiece& name, + const StringPiece& ref) { + return AddReference(name, {}, ref); +} + +ResourceTableBuilder& ResourceTableBuilder::AddReference(const StringPiece& name, + const ResourceId& id, + const StringPiece& ref) { + return AddValue(name, id, util::make_unique<Reference>(ParseNameOrDie(ref))); +} + +ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, + const StringPiece& str) { + return AddString(name, {}, str); +} + +ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, const ResourceId& id, + const StringPiece& str) { + return AddValue(name, id, util::make_unique<String>(table_->string_pool.MakeRef(str))); +} + +ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, const ResourceId& id, + const ConfigDescription& config, + const StringPiece& str) { + return AddValue(name, config, id, util::make_unique<String>(table_->string_pool.MakeRef(str))); +} + +ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name, + const StringPiece& path) { + return AddFileReference(name, {}, path); +} + +ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name, + const ResourceId& id, + const StringPiece& path) { + return AddValue(name, id, util::make_unique<FileReference>(table_->string_pool.MakeRef(path))); +} + +ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name, + const StringPiece& path, + const ConfigDescription& config) { + return AddValue(name, config, {}, + util::make_unique<FileReference>(table_->string_pool.MakeRef(path))); +} + +ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, + std::unique_ptr<Value> value) { + return AddValue(name, {}, std::move(value)); +} + +ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, const ResourceId& id, + std::unique_ptr<Value> value) { + return AddValue(name, {}, id, std::move(value)); +} + +ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, + const ConfigDescription& config, + const ResourceId& id, + std::unique_ptr<Value> value) { + ResourceName res_name = ParseNameOrDie(name); + CHECK(table_->AddResourceAllowMangled(res_name, id, config, {}, std::move(value), + GetDiagnostics())); + return *this; +} + +ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& name, + const ResourceId& id, SymbolState state, + bool allow_new) { + ResourceName res_name = ParseNameOrDie(name); + Symbol symbol; + symbol.state = state; + symbol.allow_new = allow_new; + CHECK(table_->SetSymbolStateAllowMangled(res_name, id, symbol, GetDiagnostics())); + return *this; +} + +StringPool* ResourceTableBuilder::string_pool() { + return &table_->string_pool; +} + +std::unique_ptr<ResourceTable> ResourceTableBuilder::Build() { + return std::move(table_); +} + +std::unique_ptr<Reference> BuildReference(const StringPiece& ref, const Maybe<ResourceId>& id) { + std::unique_ptr<Reference> reference = util::make_unique<Reference>(ParseNameOrDie(ref)); + reference->id = id; + return reference; +} + +std::unique_ptr<BinaryPrimitive> BuildPrimitive(uint8_t type, uint32_t data) { + android::Res_value value = {}; + value.size = sizeof(value); + value.dataType = type; + value.data = data; + return util::make_unique<BinaryPrimitive>(value); +} + +AttributeBuilder::AttributeBuilder(bool weak) : attr_(util::make_unique<Attribute>(weak)) { + attr_->type_mask = android::ResTable_map::TYPE_ANY; +} + +AttributeBuilder& AttributeBuilder::SetTypeMask(uint32_t typeMask) { + attr_->type_mask = typeMask; + return *this; +} + +AttributeBuilder& AttributeBuilder::AddItem(const StringPiece& name, uint32_t value) { + attr_->symbols.push_back( + Attribute::Symbol{Reference(ResourceName({}, ResourceType::kId, name)), value}); + return *this; +} + +std::unique_ptr<Attribute> AttributeBuilder::Build() { + return std::move(attr_); +} + +StyleBuilder& StyleBuilder::SetParent(const StringPiece& str) { + style_->parent = Reference(ParseNameOrDie(str)); + return *this; +} + +StyleBuilder& StyleBuilder::AddItem(const StringPiece& str, std::unique_ptr<Item> value) { + style_->entries.push_back(Style::Entry{Reference(ParseNameOrDie(str)), std::move(value)}); + return *this; +} + +StyleBuilder& StyleBuilder::AddItem(const StringPiece& str, const ResourceId& id, + std::unique_ptr<Item> value) { + AddItem(str, std::move(value)); + style_->entries.back().key.id = id; + return *this; +} + +std::unique_ptr<Style> StyleBuilder::Build() { + return std::move(style_); +} + +StyleableBuilder& StyleableBuilder::AddItem(const StringPiece& str, const Maybe<ResourceId>& id) { + styleable_->entries.push_back(Reference(ParseNameOrDie(str))); + styleable_->entries.back().id = id; + return *this; +} + +std::unique_ptr<Styleable> StyleableBuilder::Build() { + return std::move(styleable_); +} + +std::unique_ptr<xml::XmlResource> BuildXmlDom(const StringPiece& str) { + std::string input = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; + input.append(str.data(), str.size()); + StringInputStream in(input); + StdErrDiagnostics diag; + std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, &diag, Source("test.xml")); + CHECK(doc != nullptr && doc->root != nullptr) << "failed to parse inline XML string"; + return doc; +} + +std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* context, + const StringPiece& str) { + std::unique_ptr<xml::XmlResource> doc = BuildXmlDom(str); + doc->file.name.package = context->GetCompilationPackage(); + return doc; +} + +PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::SetAbiGroup( + const std::string& name, const std::vector<Abi>& abis) { + config_.abi_groups[name] = abis; + return *this; +} + +PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::SetLocaleGroup( + const std::string& name, const std::vector<std::string>& locales) { + auto& group = config_.locale_groups[name]; + for (const auto& locale : locales) { + group.push_back(ParseConfigOrDie(locale)); + } + return *this; +} + +PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::SetDensityGroup( + const std::string& name, const std::vector<std::string>& densities) { + auto& group = config_.screen_density_groups[name]; + for (const auto& density : densities) { + group.push_back(ParseConfigOrDie(density)); + } + return *this; +} + +PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::SetAndroidSdk( + const std::string& name, const AndroidSdk& sdk) { + config_.android_sdk_groups[name] = sdk; + return *this; +} + +PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddArtifact( + const Artifact& artifact) { + config_.artifacts.push_back(artifact); + return *this; +} + +configuration::PostProcessingConfiguration PostProcessingConfigurationBuilder::Build() { + return config_; +} + +ArtifactBuilder& ArtifactBuilder::SetName(const std::string& name) { + artifact_.name = {name}; + return *this; +} + +ArtifactBuilder& ArtifactBuilder::SetAbiGroup(const std::string& name) { + artifact_.abi_group = {name}; + return *this; +} + +ArtifactBuilder& ArtifactBuilder::SetDensityGroup(const std::string& name) { + artifact_.screen_density_group = {name}; + return *this; +} + +ArtifactBuilder& ArtifactBuilder::SetLocaleGroup(const std::string& name) { + artifact_.locale_group = {name}; + return *this; +} + +ArtifactBuilder& ArtifactBuilder::SetAndroidSdk(const std::string& name) { + artifact_.android_sdk_group = {name}; + return *this; +} + +configuration::Artifact ArtifactBuilder::Build() { + return artifact_; +} + +} // namespace test +} // namespace aapt diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index 6b8207647471..263fb5562b25 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -19,13 +19,15 @@ #include <memory> -#include "android-base/logging.h" #include "android-base/macros.h" +#include "Resource.h" #include "ResourceTable.h" #include "ResourceValues.h" +#include "configuration/ConfigurationParser.h" +#include "process/IResourceTableConsumer.h" #include "test/Common.h" -#include "util/Util.h" +#include "util/Maybe.h" #include "xml/XmlDom.h" namespace aapt { @@ -35,97 +37,37 @@ class ResourceTableBuilder { public: ResourceTableBuilder() = default; - StringPool* string_pool() { return &table_->string_pool; } - - ResourceTableBuilder& SetPackageId(const android::StringPiece& package_name, uint8_t id) { - ResourceTablePackage* package = table_->CreatePackage(package_name, id); - CHECK(package != nullptr); - return *this; - } - - ResourceTableBuilder& AddSimple(const android::StringPiece& name, const ResourceId& id = {}) { - return AddValue(name, id, util::make_unique<Id>()); - } - + ResourceTableBuilder& SetPackageId(const android::StringPiece& package_name, uint8_t id); + ResourceTableBuilder& AddSimple(const android::StringPiece& name, const ResourceId& id = {}); ResourceTableBuilder& AddSimple(const android::StringPiece& name, const ConfigDescription& config, - const ResourceId& id = {}) { - return AddValue(name, config, id, util::make_unique<Id>()); - } - + const ResourceId& id = {}); ResourceTableBuilder& AddReference(const android::StringPiece& name, - const android::StringPiece& ref) { - return AddReference(name, {}, ref); - } - + const android::StringPiece& ref); ResourceTableBuilder& AddReference(const android::StringPiece& name, const ResourceId& id, - const android::StringPiece& ref) { - return AddValue(name, id, util::make_unique<Reference>(ParseNameOrDie(ref))); - } - + const android::StringPiece& ref); ResourceTableBuilder& AddString(const android::StringPiece& name, - const android::StringPiece& str) { - return AddString(name, {}, str); - } - + const android::StringPiece& str); ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id, - const android::StringPiece& str) { - return AddValue( - name, id, util::make_unique<String>(table_->string_pool.MakeRef(str))); - } - + const android::StringPiece& str); ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id, - const ConfigDescription& config, - const android::StringPiece& str) { - return AddValue(name, config, id, util::make_unique<String>( - table_->string_pool.MakeRef(str))); - } - + const ConfigDescription& config, const android::StringPiece& str); ResourceTableBuilder& AddFileReference(const android::StringPiece& name, - const android::StringPiece& path) { - return AddFileReference(name, {}, path); - } - + const android::StringPiece& path); ResourceTableBuilder& AddFileReference(const android::StringPiece& name, const ResourceId& id, - const android::StringPiece& path) { - return AddValue(name, id, util::make_unique<FileReference>( - table_->string_pool.MakeRef(path))); - } - + const android::StringPiece& path); ResourceTableBuilder& AddFileReference(const android::StringPiece& name, const android::StringPiece& path, - const ConfigDescription& config) { - return AddValue(name, config, {}, util::make_unique<FileReference>( - table_->string_pool.MakeRef(path))); - } - - ResourceTableBuilder& AddValue(const android::StringPiece& name, std::unique_ptr<Value> value) { - return AddValue(name, {}, std::move(value)); - } - + const ConfigDescription& config); + ResourceTableBuilder& AddValue(const android::StringPiece& name, std::unique_ptr<Value> value); ResourceTableBuilder& AddValue(const android::StringPiece& name, const ResourceId& id, - std::unique_ptr<Value> value) { - return AddValue(name, {}, id, std::move(value)); - } - + std::unique_ptr<Value> value); ResourceTableBuilder& AddValue(const android::StringPiece& name, const ConfigDescription& config, - const ResourceId& id, std::unique_ptr<Value> value) { - ResourceName res_name = ParseNameOrDie(name); - CHECK(table_->AddResourceAllowMangled(res_name, id, config, {}, std::move(value), - GetDiagnostics())); - return *this; - } - + const ResourceId& id, std::unique_ptr<Value> value); ResourceTableBuilder& SetSymbolState(const android::StringPiece& name, const ResourceId& id, - SymbolState state, bool allow_new = false) { - ResourceName res_name = ParseNameOrDie(name); - Symbol symbol; - symbol.state = state; - symbol.allow_new = allow_new; - CHECK(table_->SetSymbolStateAllowMangled(res_name, id, symbol, GetDiagnostics())); - return *this; - } + SymbolState state, bool allow_new = false); - std::unique_ptr<ResourceTable> Build() { return std::move(table_); } + StringPool* string_pool(); + std::unique_ptr<ResourceTable> Build(); private: DISALLOW_COPY_AND_ASSIGN(ResourceTableBuilder); @@ -133,29 +75,16 @@ class ResourceTableBuilder { std::unique_ptr<ResourceTable> table_ = util::make_unique<ResourceTable>(); }; -inline std::unique_ptr<Reference> BuildReference(const android::StringPiece& ref, - const Maybe<ResourceId>& id = {}) { - std::unique_ptr<Reference> reference = - util::make_unique<Reference>(ParseNameOrDie(ref)); - reference->id = id; - return reference; -} - -inline std::unique_ptr<BinaryPrimitive> BuildPrimitive(uint8_t type, - uint32_t data) { - android::Res_value value = {}; - value.size = sizeof(value); - value.dataType = type; - value.data = data; - return util::make_unique<BinaryPrimitive>(value); -} +std::unique_ptr<Reference> BuildReference(const android::StringPiece& ref, + const Maybe<ResourceId>& id = {}); +std::unique_ptr<BinaryPrimitive> BuildPrimitive(uint8_t type, uint32_t data); template <typename T> class ValueBuilder { public: template <typename... Args> - explicit ValueBuilder(Args&&... args) - : value_(new T{std::forward<Args>(args)...}) {} + explicit ValueBuilder(Args&&... args) : value_(new T{std::forward<Args>(args)...}) { + } template <typename... Args> ValueBuilder& SetSource(Args&&... args) { @@ -168,7 +97,9 @@ class ValueBuilder { return *this; } - std::unique_ptr<Value> Build() { return std::move(value_); } + std::unique_ptr<Value> Build() { + return std::move(value_); + } private: DISALLOW_COPY_AND_ASSIGN(ValueBuilder); @@ -178,23 +109,10 @@ class ValueBuilder { class AttributeBuilder { public: - explicit AttributeBuilder(bool weak = false) - : attr_(util::make_unique<Attribute>(weak)) { - attr_->type_mask = android::ResTable_map::TYPE_ANY; - } - - AttributeBuilder& SetTypeMask(uint32_t typeMask) { - attr_->type_mask = typeMask; - return *this; - } - - AttributeBuilder& AddItem(const android::StringPiece& name, uint32_t value) { - attr_->symbols.push_back(Attribute::Symbol{ - Reference(ResourceName({}, ResourceType::kId, name)), value}); - return *this; - } - - std::unique_ptr<Attribute> Build() { return std::move(attr_); } + explicit AttributeBuilder(bool weak = false); + AttributeBuilder& SetTypeMask(uint32_t typeMask); + AttributeBuilder& AddItem(const android::StringPiece& name, uint32_t value); + std::unique_ptr<Attribute> Build(); private: DISALLOW_COPY_AND_ASSIGN(AttributeBuilder); @@ -205,27 +123,11 @@ class AttributeBuilder { class StyleBuilder { public: StyleBuilder() = default; - - StyleBuilder& SetParent(const android::StringPiece& str) { - style_->parent = Reference(ParseNameOrDie(str)); - return *this; - } - - StyleBuilder& AddItem(const android::StringPiece& str, std::unique_ptr<Item> value) { - style_->entries.push_back(Style::Entry{Reference(ParseNameOrDie(str)), std::move(value)}); - return *this; - } - + StyleBuilder& SetParent(const android::StringPiece& str); + StyleBuilder& AddItem(const android::StringPiece& str, std::unique_ptr<Item> value); StyleBuilder& AddItem(const android::StringPiece& str, const ResourceId& id, - std::unique_ptr<Item> value) { - AddItem(str, std::move(value)); - style_->entries.back().key.id = id; - return *this; - } - - std::unique_ptr<Style> Build() { - return std::move(style_); - } + std::unique_ptr<Item> value); + std::unique_ptr<Style> Build(); private: DISALLOW_COPY_AND_ASSIGN(StyleBuilder); @@ -236,14 +138,8 @@ class StyleBuilder { class StyleableBuilder { public: StyleableBuilder() = default; - - StyleableBuilder& AddItem(const android::StringPiece& str, const Maybe<ResourceId>& id = {}) { - styleable_->entries.push_back(Reference(ParseNameOrDie(str))); - styleable_->entries.back().id = id; - return *this; - } - - std::unique_ptr<Styleable> Build() { return std::move(styleable_); } + StyleableBuilder& AddItem(const android::StringPiece& str, const Maybe<ResourceId>& id = {}); + std::unique_ptr<Styleable> Build(); private: DISALLOW_COPY_AND_ASSIGN(StyleableBuilder); @@ -251,22 +147,43 @@ class StyleableBuilder { std::unique_ptr<Styleable> styleable_ = util::make_unique<Styleable>(); }; -inline std::unique_ptr<xml::XmlResource> BuildXmlDom(const android::StringPiece& str) { - std::stringstream in; - in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str; - StdErrDiagnostics diag; - std::unique_ptr<xml::XmlResource> doc = - xml::Inflate(&in, &diag, Source("test.xml")); - CHECK(doc != nullptr) << "failed to parse inline XML string"; - return doc; -} - -inline std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName( - IAaptContext* context, const android::StringPiece& str) { - std::unique_ptr<xml::XmlResource> doc = BuildXmlDom(str); - doc->file.name.package = context->GetCompilationPackage(); - return doc; -} +std::unique_ptr<xml::XmlResource> BuildXmlDom(const android::StringPiece& str); +std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* context, + const android::StringPiece& str); + +class PostProcessingConfigurationBuilder { + public: + PostProcessingConfigurationBuilder() = default; + + PostProcessingConfigurationBuilder& SetAbiGroup(const std::string& name, + const std::vector<configuration::Abi>& abis); + PostProcessingConfigurationBuilder& SetLocaleGroup(const std::string& name, + const std::vector<std::string>& locales); + PostProcessingConfigurationBuilder& SetDensityGroup(const std::string& name, + const std::vector<std::string>& densities); + PostProcessingConfigurationBuilder& SetAndroidSdk(const std::string& name, + const configuration::AndroidSdk& sdk); + PostProcessingConfigurationBuilder& AddArtifact(const configuration::Artifact& artifact); + configuration::PostProcessingConfiguration Build(); + + private: + configuration::PostProcessingConfiguration config_; +}; + +class ArtifactBuilder { + public: + ArtifactBuilder() = default; + + ArtifactBuilder& SetName(const std::string& name); + ArtifactBuilder& SetAbiGroup(const std::string& name); + ArtifactBuilder& SetDensityGroup(const std::string& name); + ArtifactBuilder& SetLocaleGroup(const std::string& name); + ArtifactBuilder& SetAndroidSdk(const std::string& name); + configuration::Artifact Build(); + + private: + configuration::Artifact artifact_; +}; } // namespace test } // namespace aapt diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h index d7b46caf8c94..61d056309a69 100644 --- a/tools/aapt2/test/Common.h +++ b/tools/aapt2/test/Common.h @@ -41,13 +41,13 @@ IDiagnostics* GetDiagnostics(); inline ResourceName ParseNameOrDie(const android::StringPiece& str) { ResourceNameRef ref; - CHECK(ResourceUtils::ParseResourceName(str, &ref)) << "invalid resource name"; + CHECK(ResourceUtils::ParseResourceName(str, &ref)) << "invalid resource name: " << str; return ref.ToResourceName(); } inline ConfigDescription ParseConfigOrDie(const android::StringPiece& str) { ConfigDescription config; - CHECK(ConfigDescription::Parse(str, &config)) << "invalid configuration"; + CHECK(ConfigDescription::Parse(str, &config)) << "invalid configuration: " << str; return config; } @@ -142,10 +142,97 @@ MATCHER_P(StrEq, a, return android::StringPiece16(arg) == a; } -MATCHER_P(ValueEq, a, - std::string(negation ? "isn't" : "is") + " equal to " + ::testing::PrintToString(a)) { - return arg.Equals(&a); -} +class ValueEq { + public: + template <typename arg_type> + class BaseImpl : public ::testing::MatcherInterface<arg_type> { + BaseImpl(const BaseImpl&) = default; + + void DescribeTo(::std::ostream* os) const override { + *os << "is equal to " << *expected_; + } + + void DescribeNegationTo(::std::ostream* os) const override { + *os << "is not equal to " << *expected_; + } + + protected: + BaseImpl(const Value* expected) : expected_(expected) { + } + + const Value* expected_; + }; + + template <typename T, bool> + class Impl {}; + + template <typename T> + class Impl<T, false> : public ::testing::MatcherInterface<T> { + public: + explicit Impl(const Value* expected) : expected_(expected) { + } + + bool MatchAndExplain(T x, ::testing::MatchResultListener* listener) const override { + return expected_->Equals(&x); + } + + void DescribeTo(::std::ostream* os) const override { + *os << "is equal to " << *expected_; + } + + void DescribeNegationTo(::std::ostream* os) const override { + *os << "is not equal to " << *expected_; + } + + private: + DISALLOW_COPY_AND_ASSIGN(Impl); + + const Value* expected_; + }; + + template <typename T> + class Impl<T, true> : public ::testing::MatcherInterface<T> { + public: + explicit Impl(const Value* expected) : expected_(expected) { + } + + bool MatchAndExplain(T x, ::testing::MatchResultListener* listener) const override { + return expected_->Equals(x); + } + + void DescribeTo(::std::ostream* os) const override { + *os << "is equal to " << *expected_; + } + + void DescribeNegationTo(::std::ostream* os) const override { + *os << "is not equal to " << *expected_; + } + + private: + DISALLOW_COPY_AND_ASSIGN(Impl); + + const Value* expected_; + }; + + ValueEq(const Value& expected) : expected_(&expected) { + } + ValueEq(const Value* expected) : expected_(expected) { + } + ValueEq(const ValueEq&) = default; + + template <typename T> + operator ::testing::Matcher<T>() const { + return ::testing::Matcher<T>(new Impl<T, std::is_pointer<T>::value>(expected_)); + } + + private: + const Value* expected_; +}; + +// MATCHER_P(ValueEq, a, +// std::string(negation ? "isn't" : "is") + " equal to " + ::testing::PrintToString(a)) { +// return arg.Equals(&a); +//} MATCHER_P(StrValueEq, a, std::string(negation ? "isn't" : "is") + " equal to " + ::testing::PrintToString(a)) { diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp index 728d1f4207c4..892aee6fadcb 100644 --- a/tools/aapt2/unflatten/BinaryResourceParser.cpp +++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp @@ -544,7 +544,7 @@ std::unique_ptr<Array> BinaryResourceParser::ParseArray( const ResTable_map_entry* map) { std::unique_ptr<Array> array = util::make_unique<Array>(); for (const ResTable_map& map_entry : map) { - array->items.push_back(ParseValue(name, config, map_entry.value)); + array->elements.push_back(ParseValue(name, config, map_entry.value)); } return array; } diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp index 1bf25947ea93..bf8dc4d727f7 100644 --- a/tools/aapt2/util/Files.cpp +++ b/tools/aapt2/util/Files.cpp @@ -27,6 +27,8 @@ #include "android-base/errors.h" #include "android-base/file.h" #include "android-base/logging.h" +#include "android-base/unique_fd.h" +#include "android-base/utf8.h" #include "util/Util.h" @@ -35,14 +37,32 @@ #include <direct.h> #endif -using android::StringPiece; +using ::android::FileMap; +using ::android::StringPiece; +using ::android::base::ReadFileToString; +using ::android::base::SystemErrorCodeToString; +using ::android::base::unique_fd; namespace aapt { namespace file { -FileType GetFileType(const StringPiece& path) { +FileType GetFileType(const std::string& path) { +// TODO(adamlesinski): I'd like to move this to ::android::base::utf8 but Windows does some macro +// trickery with 'stat' and things don't override very well. +#ifdef _WIN32 + std::wstring path_utf16; + if (!::android::base::UTF8PathToWindowsLongPath(path.c_str(), &path_utf16)) { + return FileType::kNonexistant; + } + + struct _stat64 sb; + int result = _wstat64(path_utf16.c_str(), &sb); +#else struct stat sb; - if (stat(path.data(), &sb) < 0) { + int result = stat(path.c_str(), &sb); +#endif + + if (result == -1) { if (errno == ENOENT || errno == ENOTDIR) { return FileType::kNonexistant; } @@ -72,27 +92,20 @@ FileType GetFileType(const StringPiece& path) { } } -inline static int MkdirImpl(const StringPiece& path) { -#ifdef _WIN32 - return _mkdir(path.to_string().c_str()); -#else - return mkdir(path.to_string().c_str(), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP); -#endif -} - -bool mkdirs(const StringPiece& path) { - const char* start = path.begin(); - const char* end = path.end(); - for (const char* current = start; current != end; ++current) { - if (*current == sDirSep && current != start) { - StringPiece parent_path(start, current - start); - int result = MkdirImpl(parent_path); - if (result < 0 && errno != EEXIST) { - return false; - } +bool mkdirs(const std::string& path) { + constexpr const mode_t mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP; + // Start after the first character so that we don't consume the root '/'. + // This is safe to do with unicode because '/' will never match with a continuation character. + size_t current_pos = 1u; + while ((current_pos = path.find(sDirSep, current_pos)) != std::string::npos) { + std::string parent_path = path.substr(0, current_pos); + int result = ::android::base::utf8::mkdir(parent_path.c_str(), mode); + if (result < 0 && errno != EEXIST) { + return false; } + current_pos += 1; } - return MkdirImpl(path) == 0 || errno == EEXIST; + return ::android::base::utf8::mkdir(path.c_str(), mode) == 0 || errno == EEXIST; } StringPiece GetStem(const StringPiece& path) { @@ -129,10 +142,8 @@ StringPiece GetExtension(const StringPiece& path) { void AppendPath(std::string* base, StringPiece part) { CHECK(base != nullptr); - const bool base_has_trailing_sep = - (!base->empty() && *(base->end() - 1) == sDirSep); - const bool part_has_leading_sep = - (!part.empty() && *(part.begin()) == sDirSep); + const bool base_has_trailing_sep = (!base->empty() && *(base->end() - 1) == sDirSep); + const bool part_has_leading_sep = (!part.empty() && *(part.begin()) == sDirSep); if (base_has_trailing_sep && part_has_leading_sep) { // Remove the part's leading sep part = part.substr(1, part.size() - 1); @@ -151,31 +162,34 @@ std::string PackageToPath(const StringPiece& package) { return out_path; } -Maybe<android::FileMap> MmapPath(const StringPiece& path, - std::string* out_error) { - std::unique_ptr<FILE, decltype(fclose)*> f = {fopen(path.data(), "rb"), - fclose}; - if (!f) { - if (out_error) *out_error = android::base::SystemErrorCodeToString(errno); +Maybe<FileMap> MmapPath(const std::string& path, std::string* out_error) { + int flags = O_RDONLY | O_CLOEXEC | O_BINARY; + unique_fd fd(TEMP_FAILURE_RETRY(::android::base::utf8::open(path.c_str(), flags))); + if (fd == -1) { + if (out_error) { + *out_error = SystemErrorCodeToString(errno); + } return {}; } - int fd = fileno(f.get()); - struct stat filestats = {}; if (fstat(fd, &filestats) != 0) { - if (out_error) *out_error = android::base::SystemErrorCodeToString(errno); + if (out_error) { + *out_error = SystemErrorCodeToString(errno); + } return {}; } - android::FileMap filemap; + FileMap filemap; if (filestats.st_size == 0) { // mmap doesn't like a length of 0. Instead we return an empty FileMap. return std::move(filemap); } - if (!filemap.create(path.data(), fd, 0, filestats.st_size, true)) { - if (out_error) *out_error = android::base::SystemErrorCodeToString(errno); + if (!filemap.create(path.c_str(), fd, 0, filestats.st_size, true)) { + if (out_error) { + *out_error = SystemErrorCodeToString(errno); + } return {}; } return std::move(filemap); @@ -184,7 +198,7 @@ Maybe<android::FileMap> MmapPath(const StringPiece& path, bool AppendArgsFromFile(const StringPiece& path, std::vector<std::string>* out_arglist, std::string* out_error) { std::string contents; - if (!android::base::ReadFileToString(path.to_string(), &contents, true /*follow_symlinks*/)) { + if (!ReadFileToString(path.to_string(), &contents, true /*follow_symlinks*/)) { if (out_error) { *out_error = "failed to read argument-list file"; } @@ -270,7 +284,7 @@ Maybe<std::vector<std::string>> FindFiles(const android::StringPiece& path, IDia const std::string root_dir = path.to_string(); std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir); if (!d) { - diag->Error(DiagMessage() << android::base::SystemErrorCodeToString(errno)); + diag->Error(DiagMessage() << SystemErrorCodeToString(errno)); return {}; } diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h index b3b1e484d27b..b26e4fa26de6 100644 --- a/tools/aapt2/util/Files.h +++ b/tools/aapt2/util/Files.h @@ -34,8 +34,10 @@ namespace file { #ifdef _WIN32 constexpr const char sDirSep = '\\'; +constexpr const char sPathSep = ';'; #else constexpr const char sDirSep = '/'; +constexpr const char sPathSep = ':'; #endif enum class FileType { @@ -50,79 +52,56 @@ enum class FileType { kSocket, }; -FileType GetFileType(const android::StringPiece& path); +FileType GetFileType(const std::string& path); -/* - * Appends a path to `base`, separated by the directory separator. - */ +// Appends a path to `base`, separated by the directory separator. void AppendPath(std::string* base, android::StringPiece part); -/* - * Makes all the directories in `path`. The last element in the path - * is interpreted as a directory. - */ -bool mkdirs(const android::StringPiece& path); +// Makes all the directories in `path`. The last element in the path is interpreted as a directory. +bool mkdirs(const std::string& path); -/** - * Returns all but the last part of the path. - */ +// Returns all but the last part of the path. android::StringPiece GetStem(const android::StringPiece& path); -/** - * Returns the last part of the path with extension. - */ +// Returns the last part of the path with extension. android::StringPiece GetFilename(const android::StringPiece& path); -/** - * Returns the extension of the path. This is the entire string after - * the first '.' of the last part of the path. - */ +// Returns the extension of the path. This is the entire string after the first '.' of the last part +// of the path. android::StringPiece GetExtension(const android::StringPiece& path); -/** - * Converts a package name (com.android.app) to a path: com/android/app - */ +// Converts a package name (com.android.app) to a path: com/android/app std::string PackageToPath(const android::StringPiece& package); -/** - * Creates a FileMap for the file at path. - */ -Maybe<android::FileMap> MmapPath(const android::StringPiece& path, std::string* out_error); +// Creates a FileMap for the file at path. +Maybe<android::FileMap> MmapPath(const std::string& path, std::string* out_error); -/** - * Reads the file at path and appends each line to the outArgList vector. - */ +// Reads the file at path and appends each line to the outArgList vector. bool AppendArgsFromFile(const android::StringPiece& path, std::vector<std::string>* out_arglist, std::string* out_error); -/* - * Filter that determines which resource files/directories are - * processed by AAPT. Takes a pattern string supplied by the user. - * Pattern format is specified in the FileFilter::SetPattern() method. - */ +// Filter that determines which resource files/directories are +// processed by AAPT. Takes a pattern string supplied by the user. +// Pattern format is specified in the FileFilter::SetPattern() method. class FileFilter { public: explicit FileFilter(IDiagnostics* diag) : diag_(diag) {} - /* - * Patterns syntax: - * - Delimiter is : - * - Entry can start with the flag ! to avoid printing a warning - * about the file being ignored. - * - Entry can have the flag "<dir>" to match only directories - * or <file> to match only files. Default is to match both. - * - Entry can be a simplified glob "<prefix>*" or "*<suffix>" - * where prefix/suffix must have at least 1 character (so that - * we don't match a '*' catch-all pattern.) - * - The special filenames "." and ".." are always ignored. - * - Otherwise the full string is matched. - * - match is not case-sensitive. - */ + // Patterns syntax: + // - Delimiter is : + // - Entry can start with the flag ! to avoid printing a warning + // about the file being ignored. + // - Entry can have the flag "<dir>" to match only directories + // or <file> to match only files. Default is to match both. + // - Entry can be a simplified glob "<prefix>*" or "*<suffix>" + // where prefix/suffix must have at least 1 character (so that + // we don't match a '*' catch-all pattern.) + // - The special filenames "." and ".." are always ignored. + // - Otherwise the full string is matched. + // - match is not case-sensitive. bool SetPattern(const android::StringPiece& pattern); - /** - * Applies the filter, returning true for pass, false for fail. - */ + // Applies the filter, returning true for pass, false for fail. bool operator()(const std::string& filename, FileType type) const; private: diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp index 51a75d7556ad..a9b49d9d9904 100644 --- a/tools/aapt2/util/Util.cpp +++ b/tools/aapt2/util/Util.cpp @@ -520,11 +520,10 @@ bool Tokenizer::iterator::operator!=(const iterator& rhs) const { return !(*this == rhs); } -Tokenizer::iterator::iterator(StringPiece s, char sep, StringPiece tok, - bool end) +Tokenizer::iterator::iterator(const StringPiece& s, char sep, const StringPiece& tok, bool end) : str_(s), separator_(sep), token_(tok), end_(end) {} -Tokenizer::Tokenizer(StringPiece str, char sep) +Tokenizer::Tokenizer(const StringPiece& str, char sep) : begin_(++iterator(str, sep, StringPiece(str.begin() - 1, 0), false)), end_(str, sep, StringPiece(str.end(), 0), true) {} diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h index ad3989ea430f..c928458786e4 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -228,6 +228,12 @@ class Tokenizer { public: class iterator { public: + using reference = android::StringPiece&; + using value_type = android::StringPiece; + using difference_type = size_t; + using pointer = android::StringPiece*; + using iterator_category = std::forward_iterator_tag; + iterator(const iterator&) = default; iterator& operator=(const iterator&) = default; @@ -240,7 +246,7 @@ class Tokenizer { private: friend class Tokenizer; - iterator(android::StringPiece s, char sep, android::StringPiece tok, bool end); + iterator(const android::StringPiece& s, char sep, const android::StringPiece& tok, bool end); android::StringPiece str_; char separator_; @@ -248,11 +254,15 @@ class Tokenizer { bool end_; }; - Tokenizer(android::StringPiece str, char sep); + Tokenizer(const android::StringPiece& str, char sep); - iterator begin() { return begin_; } + iterator begin() const { + return begin_; + } - iterator end() { return end_; } + iterator end() const { + return end_; + } private: const iterator begin_; diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp index 352a93361633..cc664a5de722 100644 --- a/tools/aapt2/xml/XmlActionExecutor.cpp +++ b/tools/aapt2/xml/XmlActionExecutor.cpp @@ -78,7 +78,7 @@ bool XmlActionExecutor::Execute(XmlActionExecutorPolicy policy, IDiagnostics* di XmlResource* doc) const { SourcePathDiagnostics source_diag(doc->file.source, diag); - Element* el = FindRootElement(doc); + Element* el = doc->root.get(); if (!el) { if (policy == XmlActionExecutorPolicy::kWhitelist) { source_diag.Error(DiagMessage() << "no root XML tag found"); diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp index 885ab3e33fed..32ec7bc60c78 100644 --- a/tools/aapt2/xml/XmlDom.cpp +++ b/tools/aapt2/xml/XmlDom.cpp @@ -29,8 +29,9 @@ #include "XmlPullParser.h" #include "util/Util.h" -using android::StringPiece; -using android::StringPiece16; +using ::aapt::io::InputStream; +using ::android::StringPiece; +using ::android::StringPiece16; namespace aapt { namespace xml { @@ -38,17 +39,15 @@ namespace xml { constexpr char kXmlNamespaceSep = 1; struct Stack { - std::unique_ptr<xml::Node> root; - std::stack<xml::Node*> node_stack; + std::unique_ptr<xml::Element> root; + std::stack<xml::Element*> node_stack; + std::unique_ptr<xml::Element> pending_element; std::string pending_comment; std::unique_ptr<xml::Text> last_text_node; }; -/** - * Extracts the namespace and name of an expanded element or attribute name. - */ -static void SplitName(const char* name, std::string* out_ns, - std::string* out_name) { +// Extracts the namespace and name of an expanded element or attribute name. +static void SplitName(const char* name, std::string* out_ns, std::string* out_name) { const char* p = name; while (*p != 0 && *p != kXmlNamespaceSep) { p++; @@ -66,6 +65,7 @@ static void SplitName(const char* name, std::string* out_ns, static void FinishPendingText(Stack* stack) { if (stack->last_text_node != nullptr) { if (!stack->last_text_node->text.empty()) { + CHECK(!stack->node_stack.empty()); stack->node_stack.top()->AppendChild(std::move(stack->last_text_node)); } else { // Drop an empty text node. @@ -74,48 +74,27 @@ static void FinishPendingText(Stack* stack) { } } -static void AddToStack(Stack* stack, XML_Parser parser, - std::unique_ptr<Node> node) { - node->line_number = XML_GetCurrentLineNumber(parser); - node->column_number = XML_GetCurrentColumnNumber(parser); - - Node* this_node = node.get(); - if (!stack->node_stack.empty()) { - stack->node_stack.top()->AppendChild(std::move(node)); - } else { - stack->root = std::move(node); - } - - if (!NodeCast<Text>(this_node)) { - stack->node_stack.push(this_node); - } -} - -static void XMLCALL StartNamespaceHandler(void* user_data, const char* prefix, - const char* uri) { +static void XMLCALL StartNamespaceHandler(void* user_data, const char* prefix, const char* uri) { XML_Parser parser = reinterpret_cast<XML_Parser>(user_data); Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); FinishPendingText(stack); - std::unique_ptr<Namespace> ns = util::make_unique<Namespace>(); - if (prefix) { - ns->namespace_prefix = prefix; - } + NamespaceDecl decl; + decl.line_number = XML_GetCurrentLineNumber(parser); + decl.column_number = XML_GetCurrentColumnNumber(parser); + decl.prefix = prefix ? prefix : ""; + decl.uri = uri ? uri : ""; - if (uri) { - ns->namespace_uri = uri; + if (stack->pending_element == nullptr) { + stack->pending_element = util::make_unique<Element>(); } - - AddToStack(stack, parser, std::move(ns)); + stack->pending_element->namespace_decls.push_back(std::move(decl)); } -static void XMLCALL EndNamespaceHandler(void* user_data, const char* prefix) { +static void XMLCALL EndNamespaceHandler(void* user_data, const char* /*prefix*/) { XML_Parser parser = reinterpret_cast<XML_Parser>(user_data); Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); FinishPendingText(stack); - - CHECK(!stack->node_stack.empty()); - stack->node_stack.pop(); } static bool less_attribute(const Attribute& lhs, const Attribute& rhs) { @@ -123,28 +102,42 @@ static bool less_attribute(const Attribute& lhs, const Attribute& rhs) { std::tie(rhs.namespace_uri, rhs.name, rhs.value); } -static void XMLCALL StartElementHandler(void* user_data, const char* name, - const char** attrs) { +static void XMLCALL StartElementHandler(void* user_data, const char* name, const char** attrs) { XML_Parser parser = reinterpret_cast<XML_Parser>(user_data); Stack* stack = reinterpret_cast<Stack*>(XML_GetUserData(parser)); FinishPendingText(stack); - std::unique_ptr<Element> el = util::make_unique<Element>(); + std::unique_ptr<Element> el; + if (stack->pending_element != nullptr) { + el = std::move(stack->pending_element); + } else { + el = util::make_unique<Element>(); + } + + el->line_number = XML_GetCurrentLineNumber(parser); + el->column_number = XML_GetCurrentColumnNumber(parser); + el->comment = std::move(stack->pending_comment); + SplitName(name, &el->namespace_uri, &el->name); while (*attrs) { Attribute attribute; SplitName(*attrs++, &attribute.namespace_uri, &attribute.name); attribute.value = *attrs++; - - // Insert in sorted order. - auto iter = std::lower_bound(el->attributes.begin(), el->attributes.end(), attribute, - less_attribute); - el->attributes.insert(iter, std::move(attribute)); + el->attributes.push_back(std::move(attribute)); } - el->comment = std::move(stack->pending_comment); - AddToStack(stack, parser, std::move(el)); + // Sort the attributes. + std::sort(el->attributes.begin(), el->attributes.end(), less_attribute); + + // Add to the stack. + Element* this_el = el.get(); + if (!stack->node_stack.empty()) { + stack->node_stack.top()->AppendChild(std::move(el)); + } else { + stack->root = std::move(el); + } + stack->node_stack.push(this_el); } static void XMLCALL EndElementHandler(void* user_data, const char* name) { @@ -189,40 +182,41 @@ static void XMLCALL CommentDataHandler(void* user_data, const char* comment) { stack->pending_comment += comment; } -std::unique_ptr<XmlResource> Inflate(std::istream* in, IDiagnostics* diag, const Source& source) { +std::unique_ptr<XmlResource> Inflate(InputStream* in, IDiagnostics* diag, const Source& source) { Stack stack; - XML_Parser parser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep); - XML_SetUserData(parser, &stack); - XML_UseParserAsHandlerArg(parser); - XML_SetElementHandler(parser, StartElementHandler, EndElementHandler); - XML_SetNamespaceDeclHandler(parser, StartNamespaceHandler, EndNamespaceHandler); - XML_SetCharacterDataHandler(parser, CharacterDataHandler); - XML_SetCommentHandler(parser, CommentDataHandler); - - char buffer[1024]; - while (!in->eof()) { - in->read(buffer, sizeof(buffer) / sizeof(buffer[0])); - if (in->bad() && !in->eof()) { - stack.root = {}; - diag->Error(DiagMessage(source) << strerror(errno)); - break; - } - - if (XML_Parse(parser, buffer, in->gcount(), in->eof()) == XML_STATUS_ERROR) { - stack.root = {}; - diag->Error(DiagMessage(source.WithLine(XML_GetCurrentLineNumber(parser))) - << XML_ErrorString(XML_GetErrorCode(parser))); - break; + std::unique_ptr<std::remove_pointer<XML_Parser>::type, decltype(XML_ParserFree)*> parser = { + XML_ParserCreateNS(nullptr, kXmlNamespaceSep), XML_ParserFree}; + XML_SetUserData(parser.get(), &stack); + XML_UseParserAsHandlerArg(parser.get()); + XML_SetElementHandler(parser.get(), StartElementHandler, EndElementHandler); + XML_SetNamespaceDeclHandler(parser.get(), StartNamespaceHandler, EndNamespaceHandler); + XML_SetCharacterDataHandler(parser.get(), CharacterDataHandler); + XML_SetCommentHandler(parser.get(), CommentDataHandler); + + const char* buffer = nullptr; + size_t buffer_size = 0; + while (in->Next(reinterpret_cast<const void**>(&buffer), &buffer_size)) { + if (XML_Parse(parser.get(), buffer, buffer_size, false) == XML_STATUS_ERROR) { + diag->Error(DiagMessage(source.WithLine(XML_GetCurrentLineNumber(parser.get()))) + << XML_ErrorString(XML_GetErrorCode(parser.get()))); + return {}; } } - XML_ParserFree(parser); - if (stack.root) { - return util::make_unique<XmlResource>(ResourceFile{{}, {}, source}, StringPool{}, - std::move(stack.root)); + if (in->HadError()) { + diag->Error(DiagMessage(source) << in->GetError()); + return {}; + } else { + // Finish off the parsing. + if (XML_Parse(parser.get(), nullptr, 0u, true) == XML_STATUS_ERROR) { + diag->Error(DiagMessage(source.WithLine(XML_GetCurrentLineNumber(parser.get()))) + << XML_ErrorString(XML_GetErrorCode(parser.get()))); + return {}; + } } - return {}; + return util::make_unique<XmlResource>(ResourceFile{{}, {}, source}, StringPool{}, + std::move(stack.root)); } static void CopyAttributes(Element* el, android::ResXMLParser* parser, StringPool* out_pool) { @@ -242,16 +236,22 @@ static void CopyAttributes(Element* el, android::ResXMLParser* parser, StringPoo attr.name = util::Utf16ToUtf8(StringPiece16(str16, len)); } + uint32_t res_id = parser->getAttributeNameResID(i); + if (res_id > 0) { + attr.compiled_attribute = AaptAttribute(::aapt::Attribute(), {res_id}); + } + str16 = parser->getAttributeStringValue(i, &len); if (str16) { attr.value = util::Utf16ToUtf8(StringPiece16(str16, len)); + } else { + android::Res_value res_value; + if (parser->getAttributeValue(i, &res_value) > 0) { + attr.compiled_value = ResourceUtils::ParseBinaryResValue( + ResourceType::kAnim, {}, parser->getStrings(), res_value, out_pool); + } } - android::Res_value res_value; - if (parser->getAttributeValue(i, &res_value) > 0) { - attr.compiled_value = ResourceUtils::ParseBinaryResValue( - ResourceType::kAnim, {}, parser->getStrings(), res_value, out_pool); - } el->attributes.push_back(std::move(attr)); } @@ -261,13 +261,13 @@ static void CopyAttributes(Element* el, android::ResXMLParser* parser, StringPoo std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnostics* diag, const Source& source) { // We import the android namespace because on Windows NO_ERROR is a macro, not - // an enum, which - // causes errors when qualifying it with android:: + // an enum, which causes errors when qualifying it with android:: using namespace android; StringPool string_pool; - std::unique_ptr<Node> root; - std::stack<Node*> node_stack; + std::unique_ptr<Element> root; + std::stack<Element*> node_stack; + std::unique_ptr<Element> pending_element; ResXMLTree tree; if (tree.setTo(data, data_len) != NO_ERROR) { @@ -275,57 +275,79 @@ std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnos } ResXMLParser::event_code_t code; - while ((code = tree.next()) != ResXMLParser::BAD_DOCUMENT && - code != ResXMLParser::END_DOCUMENT) { + while ((code = tree.next()) != ResXMLParser::BAD_DOCUMENT && code != ResXMLParser::END_DOCUMENT) { std::unique_ptr<Node> new_node; switch (code) { case ResXMLParser::START_NAMESPACE: { - std::unique_ptr<Namespace> node = util::make_unique<Namespace>(); + NamespaceDecl decl; + decl.line_number = tree.getLineNumber(); + size_t len; const char16_t* str16 = tree.getNamespacePrefix(&len); if (str16) { - node->namespace_prefix = util::Utf16ToUtf8(StringPiece16(str16, len)); + decl.prefix = util::Utf16ToUtf8(StringPiece16(str16, len)); } str16 = tree.getNamespaceUri(&len); if (str16) { - node->namespace_uri = util::Utf16ToUtf8(StringPiece16(str16, len)); + decl.uri = util::Utf16ToUtf8(StringPiece16(str16, len)); } - new_node = std::move(node); + + if (pending_element == nullptr) { + pending_element = util::make_unique<Element>(); + } + pending_element->namespace_decls.push_back(std::move(decl)); break; } case ResXMLParser::START_TAG: { - std::unique_ptr<Element> node = util::make_unique<Element>(); + std::unique_ptr<Element> el; + if (pending_element != nullptr) { + el = std::move(pending_element); + } else { + el = util::make_unique<Element>(); + } + el->line_number = tree.getLineNumber(); + size_t len; const char16_t* str16 = tree.getElementNamespace(&len); if (str16) { - node->namespace_uri = util::Utf16ToUtf8(StringPiece16(str16, len)); + el->namespace_uri = util::Utf16ToUtf8(StringPiece16(str16, len)); } str16 = tree.getElementName(&len); if (str16) { - node->name = util::Utf16ToUtf8(StringPiece16(str16, len)); + el->name = util::Utf16ToUtf8(StringPiece16(str16, len)); } - CopyAttributes(node.get(), &tree, &string_pool); + Element* this_el = el.get(); + CopyAttributes(el.get(), &tree, &string_pool); - new_node = std::move(node); + if (!node_stack.empty()) { + node_stack.top()->AppendChild(std::move(el)); + } else { + root = std::move(el); + } + node_stack.push(this_el); break; } case ResXMLParser::TEXT: { - std::unique_ptr<Text> node = util::make_unique<Text>(); + std::unique_ptr<Text> text = util::make_unique<Text>(); + text->line_number = tree.getLineNumber(); size_t len; const char16_t* str16 = tree.getText(&len); if (str16) { - node->text = util::Utf16ToUtf8(StringPiece16(str16, len)); + text->text = util::Utf16ToUtf8(StringPiece16(str16, len)); } - new_node = std::move(node); + CHECK(!node_stack.empty()); + node_stack.top()->AppendChild(std::move(text)); break; } case ResXMLParser::END_NAMESPACE: + break; + case ResXMLParser::END_TAG: CHECK(!node_stack.empty()); node_stack.pop(); @@ -335,74 +357,32 @@ std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnos LOG(FATAL) << "unhandled XML chunk type"; break; } - - if (new_node) { - new_node->line_number = tree.getLineNumber(); - - Node* this_node = new_node.get(); - if (!root) { - CHECK(node_stack.empty()) << "node stack should be empty"; - root = std::move(new_node); - } else { - CHECK(!node_stack.empty()) << "node stack should not be empty"; - node_stack.top()->AppendChild(std::move(new_node)); - } - - if (!NodeCast<Text>(this_node)) { - node_stack.push(this_node); - } - } } return util::make_unique<XmlResource>(ResourceFile{}, std::move(string_pool), std::move(root)); } -std::unique_ptr<Node> Namespace::Clone(const ElementCloneFunc& el_cloner) { - auto ns = util::make_unique<Namespace>(); - ns->comment = comment; - ns->line_number = line_number; - ns->column_number = column_number; - ns->namespace_prefix = namespace_prefix; - ns->namespace_uri = namespace_uri; - ns->children.reserve(children.size()); - for (const std::unique_ptr<xml::Node>& child : children) { - ns->AppendChild(child->Clone(el_cloner)); - } - return std::move(ns); -} - -Element* FindRootElement(XmlResource* doc) { - return FindRootElement(doc->root.get()); -} - Element* FindRootElement(Node* node) { - if (!node) { + if (node == nullptr) { return nullptr; } - Element* el = nullptr; - while ((el = NodeCast<Element>(node)) == nullptr) { - if (node->children.empty()) { - return nullptr; - } - // We are looking for the first element, and namespaces can only have one - // child. - node = node->children.front().get(); + while (node->parent != nullptr) { + node = node->parent; } - return el; + return NodeCast<Element>(node); } -void Node::AppendChild(std::unique_ptr<Node> child) { +void Element::AppendChild(std::unique_ptr<Node> child) { child->parent = this; children.push_back(std::move(child)); } -void Node::InsertChild(size_t index, std::unique_ptr<Node> child) { +void Element::InsertChild(size_t index, std::unique_ptr<Node> child) { child->parent = this; children.insert(children.begin() + index, std::move(child)); } -Attribute* Element::FindAttribute(const StringPiece& ns, - const StringPiece& name) { +Attribute* Element::FindAttribute(const StringPiece& ns, const StringPiece& name) { for (auto& attr : attributes) { if (ns == attr.namespace_uri && name == attr.name) { return &attr; @@ -424,21 +404,11 @@ Element* Element::FindChild(const StringPiece& ns, const StringPiece& name) { return FindChildWithAttribute(ns, name, {}, {}, {}); } -Element* Element::FindChildWithAttribute(const StringPiece& ns, - const StringPiece& name, - const StringPiece& attr_ns, - const StringPiece& attr_name, +Element* Element::FindChildWithAttribute(const StringPiece& ns, const StringPiece& name, + const StringPiece& attr_ns, const StringPiece& attr_name, const StringPiece& attr_value) { - for (auto& child_node : children) { - Node* child = child_node.get(); - while (NodeCast<Namespace>(child)) { - if (child->children.empty()) { - break; - } - child = child->children[0].get(); - } - - if (Element* el = NodeCast<Element>(child)) { + for (auto& child : children) { + if (Element* el = NodeCast<Element>(child.get())) { if (ns == el->namespace_uri && name == el->name) { if (attr_ns.empty() && attr_name.empty()) { return el; @@ -457,23 +427,16 @@ Element* Element::FindChildWithAttribute(const StringPiece& ns, std::vector<Element*> Element::GetChildElements() { std::vector<Element*> elements; for (auto& child_node : children) { - Node* child = child_node.get(); - while (NodeCast<Namespace>(child)) { - if (child->children.empty()) { - break; - } - child = child->children[0].get(); - } - - if (Element* el = NodeCast<Element>(child)) { - elements.push_back(el); + if (Element* child = NodeCast<Element>(child_node.get())) { + elements.push_back(child); } } return elements; } -std::unique_ptr<Node> Element::Clone(const ElementCloneFunc& el_cloner) { +std::unique_ptr<Node> Element::Clone(const ElementCloneFunc& el_cloner) const { auto el = util::make_unique<Element>(); + el->namespace_decls = namespace_decls; el->comment = comment; el->line_number = line_number; el->column_number = column_number; @@ -488,7 +451,17 @@ std::unique_ptr<Node> Element::Clone(const ElementCloneFunc& el_cloner) { return std::move(el); } -std::unique_ptr<Node> Text::Clone(const ElementCloneFunc&) { +std::unique_ptr<Element> Element::CloneElement(const ElementCloneFunc& el_cloner) const { + return std::unique_ptr<Element>(static_cast<Element*>(Clone(el_cloner).release())); +} + +void Element::Accept(Visitor* visitor) { + visitor->BeforeVisitElement(this); + visitor->Visit(this); + visitor->AfterVisitElement(this); +} + +std::unique_ptr<Node> Text::Clone(const ElementCloneFunc&) const { auto t = util::make_unique<Text>(); t->comment = comment; t->line_number = line_number; @@ -497,36 +470,41 @@ std::unique_ptr<Node> Text::Clone(const ElementCloneFunc&) { return std::move(t); } -void PackageAwareVisitor::Visit(Namespace* ns) { - bool added = false; - if (Maybe<ExtractedPackage> maybe_package = - ExtractPackageFromNamespace(ns->namespace_uri)) { - ExtractedPackage& package = maybe_package.value(); - package_decls_.push_back( - PackageDecl{ns->namespace_prefix, std::move(package)}); - added = true; - } - - Visitor::Visit(ns); +void Text::Accept(Visitor* visitor) { + visitor->Visit(this); +} - if (added) { - package_decls_.pop_back(); +void PackageAwareVisitor::BeforeVisitElement(Element* el) { + std::vector<PackageDecl> decls; + for (const NamespaceDecl& decl : el->namespace_decls) { + if (Maybe<ExtractedPackage> maybe_package = ExtractPackageFromNamespace(decl.uri)) { + decls.push_back(PackageDecl{decl.prefix, std::move(maybe_package.value())}); + } } + package_decls_.push_back(std::move(decls)); +} + +void PackageAwareVisitor::AfterVisitElement(Element* el) { + package_decls_.pop_back(); } -Maybe<ExtractedPackage> PackageAwareVisitor::TransformPackageAlias( - const StringPiece& alias, const StringPiece& local_package) const { +Maybe<ExtractedPackage> PackageAwareVisitor::TransformPackageAlias(const StringPiece& alias) const { if (alias.empty()) { - return ExtractedPackage{local_package.to_string(), false /* private */}; + return ExtractedPackage{{}, false /*private*/}; } const auto rend = package_decls_.rend(); for (auto iter = package_decls_.rbegin(); iter != rend; ++iter) { - if (alias == iter->prefix) { - if (iter->package.package.empty()) { - return ExtractedPackage{local_package.to_string(), iter->package.private_namespace}; + const std::vector<PackageDecl>& decls = *iter; + const auto rend2 = decls.rend(); + for (auto iter2 = decls.rbegin(); iter2 != rend2; ++iter2) { + const PackageDecl& decl = *iter2; + if (alias == decl.prefix) { + if (decl.package.package.empty()) { + return ExtractedPackage{{}, decl.package.private_namespace}; + } + return decl.package; } - return iter->package; } } return {}; diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h index 2dc99d693148..9a9151da4ab7 100644 --- a/tools/aapt2/xml/XmlDom.h +++ b/tools/aapt2/xml/XmlDom.h @@ -17,7 +17,6 @@ #ifndef AAPT_XML_DOM_H #define AAPT_XML_DOM_H -#include <istream> #include <memory> #include <string> #include <vector> @@ -27,58 +26,40 @@ #include "Diagnostics.h" #include "Resource.h" #include "ResourceValues.h" +#include "io/Io.h" #include "util/Util.h" #include "xml/XmlUtil.h" namespace aapt { namespace xml { -class RawVisitor; - class Element; +class Visitor; -/** - * Base class for all XML nodes. - */ +// Base class for all XML nodes. class Node { public: - Node* parent = nullptr; - size_t line_number = 0; - size_t column_number = 0; - std::string comment; - std::vector<std::unique_ptr<Node>> children; - virtual ~Node() = default; - void AppendChild(std::unique_ptr<Node> child); - void InsertChild(size_t index, std::unique_ptr<Node> child); - virtual void Accept(RawVisitor* visitor) = 0; + Element* parent = nullptr; + size_t line_number = 0u; + size_t column_number = 0u; + std::string comment; + + virtual void Accept(Visitor* visitor) = 0; using ElementCloneFunc = std::function<void(const Element&, Element*)>; // Clones the Node subtree, using the given function to decide how to clone an Element. - virtual std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) = 0; + virtual std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) const = 0; }; -/** - * Base class that implements the visitor methods for a - * subclass of Node. - */ -template <typename Derived> -class BaseNode : public Node { - public: - virtual void Accept(RawVisitor* visitor) override; -}; - -/** - * A Namespace XML node. Can only have one child. - */ -class Namespace : public BaseNode<Namespace> { - public: - std::string namespace_prefix; - std::string namespace_uri; - - std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) override; +// A namespace declaration (xmlns:prefix="uri"). +struct NamespaceDecl { + std::string prefix; + std::string uri; + size_t line_number = 0u; + size_t column_number = 0u; }; struct AaptAttribute { @@ -90,9 +71,7 @@ struct AaptAttribute { Maybe<ResourceId> id; }; -/** - * An XML attribute. - */ +// An XML attribute. struct Attribute { std::string namespace_uri; std::string name; @@ -102,41 +81,50 @@ struct Attribute { std::unique_ptr<Item> compiled_value; }; -/** - * An Element XML node. - */ -class Element : public BaseNode<Element> { +// An Element XML node. +class Element : public Node { public: + // Ordered namespace prefix declarations. + std::vector<NamespaceDecl> namespace_decls; + std::string namespace_uri; std::string name; std::vector<Attribute> attributes; + std::vector<std::unique_ptr<Node>> children; + + void AppendChild(std::unique_ptr<Node> child); + void InsertChild(size_t index, std::unique_ptr<Node> child); Attribute* FindAttribute(const android::StringPiece& ns, const android::StringPiece& name); const Attribute* FindAttribute(const android::StringPiece& ns, const android::StringPiece& name) const; - xml::Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name); - xml::Element* FindChildWithAttribute(const android::StringPiece& ns, - const android::StringPiece& name, - const android::StringPiece& attr_ns, - const android::StringPiece& attr_name, - const android::StringPiece& attr_value); - std::vector<xml::Element*> GetChildElements(); - std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) override; + Element* FindChild(const android::StringPiece& ns, const android::StringPiece& name); + Element* FindChildWithAttribute(const android::StringPiece& ns, const android::StringPiece& name, + const android::StringPiece& attr_ns, + const android::StringPiece& attr_name, + const android::StringPiece& attr_value); + std::vector<Element*> GetChildElements(); + + // Due to overriding of subtypes not working with unique_ptr, define a convenience Clone method + // that knows cloning an element returns an element. + std::unique_ptr<Element> CloneElement(const ElementCloneFunc& el_cloner) const; + + std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) const override; + + void Accept(Visitor* visitor) override; }; -/** - * A Text (CDATA) XML node. Can not have any children. - */ -class Text : public BaseNode<Text> { +// A Text (CDATA) XML node. Can not have any children. +class Text : public Node { public: std::string text; - std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) override; + std::unique_ptr<Node> Clone(const ElementCloneFunc& el_cloner) const override; + + void Accept(Visitor* visitor) override; }; -/** - * An XML resource with a source, name, and XML tree. - */ +// An XML resource with a source, name, and XML tree. class XmlResource { public: ResourceFile file; @@ -146,99 +134,120 @@ class XmlResource { // is destroyed. StringPool string_pool; - std::unique_ptr<xml::Node> root; + std::unique_ptr<xml::Element> root; }; -/** - * Inflates an XML DOM from a text stream, logging errors to the logger. - * Returns the root node on success, or nullptr on failure. - */ -std::unique_ptr<XmlResource> Inflate(std::istream* in, IDiagnostics* diag, const Source& source); +// Inflates an XML DOM from an InputStream, logging errors to the logger. +// Returns the root node on success, or nullptr on failure. +std::unique_ptr<XmlResource> Inflate(io::InputStream* in, IDiagnostics* diag, const Source& source); -/** - * Inflates an XML DOM from a binary ResXMLTree, logging errors to the logger. - * Returns the root node on success, or nullptr on failure. - */ +// Inflates an XML DOM from a binary ResXMLTree, logging errors to the logger. +// Returns the root node on success, or nullptr on failure. std::unique_ptr<XmlResource> Inflate(const void* data, size_t data_len, IDiagnostics* diag, const Source& source); -Element* FindRootElement(XmlResource* doc); Element* FindRootElement(Node* node); -/** - * A visitor interface for the different XML Node subtypes. This will not - * traverse into - * children. Use Visitor for that. - */ -class RawVisitor { - public: - virtual ~RawVisitor() = default; - - virtual void Visit(Namespace* node) {} - virtual void Visit(Element* node) {} - virtual void Visit(Text* text) {} -}; - -/** - * Visitor whose default implementation visits the children nodes of any node. - */ -class Visitor : public RawVisitor { +// Visitor whose default implementation visits the children nodes of any node. +class Visitor { public: - using RawVisitor::Visit; + virtual ~Visitor() = default; - void Visit(Namespace* node) override { VisitChildren(node); } + virtual void Visit(Element* el) { + VisitChildren(el); + } - void Visit(Element* node) override { VisitChildren(node); } + virtual void Visit(Text* text) { + } - void Visit(Text* text) override { VisitChildren(text); } + protected: + Visitor() = default; - void VisitChildren(Node* node) { - for (auto& child : node->children) { + void VisitChildren(Element* el) { + for (auto& child : el->children) { child->Accept(this); } } + + virtual void BeforeVisitElement(Element* el) { + } + virtual void AfterVisitElement(Element* el) { + } + + private: + DISALLOW_COPY_AND_ASSIGN(Visitor); + + friend class Element; }; -/** - * An XML DOM visitor that will record the package name for a namespace prefix. - */ +// An XML DOM visitor that will record the package name for a namespace prefix. class PackageAwareVisitor : public Visitor, public IPackageDeclStack { public: using Visitor::Visit; - void Visit(Namespace* ns) override; - Maybe<ExtractedPackage> TransformPackageAlias( - const android::StringPiece& alias, const android::StringPiece& local_package) const override; + Maybe<ExtractedPackage> TransformPackageAlias(const android::StringPiece& alias) const override; + + protected: + PackageAwareVisitor() = default; + + void BeforeVisitElement(Element* el) override; + void AfterVisitElement(Element* el) override; private: + DISALLOW_COPY_AND_ASSIGN(PackageAwareVisitor); + struct PackageDecl { std::string prefix; ExtractedPackage package; }; - std::vector<PackageDecl> package_decls_; + std::vector<std::vector<PackageDecl>> package_decls_; }; -// Implementations +namespace internal { -template <typename Derived> -void BaseNode<Derived>::Accept(RawVisitor* visitor) { - visitor->Visit(static_cast<Derived*>(this)); -} +// Base class that overrides the default behaviour and does not descend into child nodes. +class NodeCastBase : public Visitor { + public: + void Visit(Element* el) override { + } + void Visit(Text* el) override { + } + + protected: + NodeCastBase() = default; + + void BeforeVisitElement(Element* el) override { + } + void AfterVisitElement(Element* el) override { + } + + private: + DISALLOW_COPY_AND_ASSIGN(NodeCastBase); +}; template <typename T> -class NodeCastImpl : public RawVisitor { +class NodeCastImpl : public NodeCastBase { public: - using RawVisitor::Visit; + using NodeCastBase::Visit; + + NodeCastImpl() = default; T* value = nullptr; - void Visit(T* v) override { value = v; } + void Visit(T* v) override { + value = v; + } + + private: + DISALLOW_COPY_AND_ASSIGN(NodeCastImpl); }; +} // namespace internal + template <typename T> T* NodeCast(Node* node) { - NodeCastImpl<T> visitor; + internal::NodeCastImpl<T> visitor; node->Accept(&visitor); return visitor.value; } diff --git a/tools/aapt2/xml/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp index f0122e8c617a..10a45870e556 100644 --- a/tools/aapt2/xml/XmlDom_test.cpp +++ b/tools/aapt2/xml/XmlDom_test.cpp @@ -16,23 +16,22 @@ #include "xml/XmlDom.h" -#include <sstream> #include <string> +#include "io/StringInputStream.h" #include "test/Test.h" +using ::aapt::io::StringInputStream; using ::testing::Eq; using ::testing::NotNull; using ::testing::SizeIs; +using ::testing::StrEq; namespace aapt { - -constexpr const char* kXmlPreamble = - "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; +namespace xml { TEST(XmlDomTest, Inflate) { - std::stringstream in(kXmlPreamble); - in << R"( + std::string input = R"(<?xml version="1.0" encoding="utf-8"?> <Layout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content"> @@ -41,26 +40,25 @@ TEST(XmlDomTest, Inflate) { android:layout_height="wrap_content" /> </Layout>)"; - const Source source("test.xml"); StdErrDiagnostics diag; - std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, &diag, source); + StringInputStream in(input); + std::unique_ptr<XmlResource> doc = Inflate(&in, &diag, Source("test.xml")); ASSERT_THAT(doc, NotNull()); - xml::Namespace* ns = xml::NodeCast<xml::Namespace>(doc->root.get()); - ASSERT_THAT(ns, NotNull()); - EXPECT_THAT(ns->namespace_uri, Eq(xml::kSchemaAndroid)); - EXPECT_THAT(ns->namespace_prefix, Eq("android")); + Element* el = doc->root.get(); + EXPECT_THAT(el->namespace_decls, SizeIs(1u)); + EXPECT_THAT(el->namespace_decls[0].uri, StrEq(xml::kSchemaAndroid)); + EXPECT_THAT(el->namespace_decls[0].prefix, StrEq("android")); } // Escaping is handled after parsing of the values for resource-specific values. TEST(XmlDomTest, ForwardEscapes) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"( + std::unique_ptr<XmlResource> doc = test::BuildXmlDom(R"( <element value="\?hello" pattern="\\d{5}">\\d{5}</element>)"); - xml::Element* el = xml::FindRootElement(doc.get()); - ASSERT_THAT(el, NotNull()); + Element* el = doc->root.get(); - xml::Attribute* attr = el->FindAttribute({}, "pattern"); + Attribute* attr = el->FindAttribute({}, "pattern"); ASSERT_THAT(attr, NotNull()); EXPECT_THAT(attr->value, Eq("\\\\d{5}")); @@ -70,21 +68,48 @@ TEST(XmlDomTest, ForwardEscapes) { ASSERT_THAT(el->children, SizeIs(1u)); - xml::Text* text = xml::NodeCast<xml::Text>(el->children[0].get()); + Text* text = xml::NodeCast<xml::Text>(el->children[0].get()); ASSERT_THAT(text, NotNull()); EXPECT_THAT(text->text, Eq("\\\\d{5}")); } TEST(XmlDomTest, XmlEscapeSequencesAreParsed) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<element value=""" />)"); - - xml::Element* el = xml::FindRootElement(doc.get()); - ASSERT_THAT(el, NotNull()); - - xml::Attribute* attr = el->FindAttribute({}, "value"); + std::unique_ptr<XmlResource> doc = test::BuildXmlDom(R"(<element value=""" />)"); + Attribute* attr = doc->root->FindAttribute({}, "value"); ASSERT_THAT(attr, NotNull()); - EXPECT_THAT(attr->value, Eq("\"")); } +class TestVisitor : public PackageAwareVisitor { + public: + using PackageAwareVisitor::Visit; + + void Visit(Element* el) override { + if (el->name == "View1") { + EXPECT_THAT(TransformPackageAlias("one"), Eq(make_value(ExtractedPackage{"com.one", false}))); + } else if (el->name == "View2") { + EXPECT_THAT(TransformPackageAlias("one"), Eq(make_value(ExtractedPackage{"com.one", false}))); + EXPECT_THAT(TransformPackageAlias("two"), Eq(make_value(ExtractedPackage{"com.two", false}))); + } else if (el->name == "View3") { + EXPECT_THAT(TransformPackageAlias("one"), Eq(make_value(ExtractedPackage{"com.one", false}))); + EXPECT_THAT(TransformPackageAlias("two"), Eq(make_value(ExtractedPackage{"com.two", false}))); + EXPECT_THAT(TransformPackageAlias("three"), + Eq(make_value(ExtractedPackage{"com.three", false}))); + } + } +}; + +TEST(XmlDomTest, PackageAwareXmlVisitor) { + std::unique_ptr<XmlResource> doc = test::BuildXmlDom(R"( + <View1 xmlns:one="http://schemas.android.com/apk/res/com.one"> + <View2 xmlns:two="http://schemas.android.com/apk/res/com.two"> + <View3 xmlns:three="http://schemas.android.com/apk/res/com.three" /> + </View2> + </View1>)"); + + TestVisitor visitor; + doc->root->Accept(&visitor); +} + +} // namespace xml } // namespace aapt diff --git a/tools/aapt2/xml/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp index c2a9c8283a6d..402e5a459f4e 100644 --- a/tools/aapt2/xml/XmlPullParser.cpp +++ b/tools/aapt2/xml/XmlPullParser.cpp @@ -22,14 +22,15 @@ #include "xml/XmlPullParser.h" #include "xml/XmlUtil.h" -using android::StringPiece; +using ::aapt::io::InputStream; +using ::android::StringPiece; namespace aapt { namespace xml { constexpr char kXmlNamespaceSep = 1; -XmlPullParser::XmlPullParser(std::istream& in) : in_(in), empty_(), depth_(0) { +XmlPullParser::XmlPullParser(InputStream* in) : in_(in), empty_(), depth_(0) { parser_ = XML_ParserCreateNS(nullptr, kXmlNamespaceSep); XML_SetUserData(parser_, this); XML_SetElementHandler(parser_, StartElementHandler, EndElementHandler); @@ -40,30 +41,35 @@ XmlPullParser::XmlPullParser(std::istream& in) : in_(in), empty_(), depth_(0) { event_queue_.push(EventData{Event::kStartDocument, 0, depth_++}); } -XmlPullParser::~XmlPullParser() { XML_ParserFree(parser_); } +XmlPullParser::~XmlPullParser() { + XML_ParserFree(parser_); +} XmlPullParser::Event XmlPullParser::Next() { const Event currentEvent = event(); - if (currentEvent == Event::kBadDocument || - currentEvent == Event::kEndDocument) { + if (currentEvent == Event::kBadDocument || currentEvent == Event::kEndDocument) { return currentEvent; } event_queue_.pop(); while (event_queue_.empty()) { - in_.read(buffer_, sizeof(buffer_) / sizeof(*buffer_)); + const char* buffer = nullptr; + size_t buffer_size = 0; + bool done = false; + if (!in_->Next(reinterpret_cast<const void**>(&buffer), &buffer_size)) { + if (in_->HadError()) { + error_ = in_->GetError(); + event_queue_.push(EventData{Event::kBadDocument}); + break; + } - const bool done = in_.eof(); - if (in_.bad() && !done) { - error_ = strerror(errno); - event_queue_.push(EventData{Event::kBadDocument}); - continue; + done = true; } - if (XML_Parse(parser_, buffer_, in_.gcount(), done) == XML_STATUS_ERROR) { + if (XML_Parse(parser_, buffer, buffer_size, done) == XML_STATUS_ERROR) { error_ = XML_ErrorString(XML_GetErrorCode(parser_)); event_queue_.push(EventData{Event::kBadDocument}); - continue; + break; } if (done) { @@ -135,17 +141,16 @@ const std::string& XmlPullParser::namespace_uri() const { return event_queue_.front().data2; } -Maybe<ExtractedPackage> XmlPullParser::TransformPackageAlias( - const StringPiece& alias, const StringPiece& local_package) const { +Maybe<ExtractedPackage> XmlPullParser::TransformPackageAlias(const StringPiece& alias) const { if (alias.empty()) { - return ExtractedPackage{local_package.to_string(), false /* private */}; + return ExtractedPackage{{}, false /*private*/}; } const auto end_iter = package_aliases_.rend(); for (auto iter = package_aliases_.rbegin(); iter != end_iter; ++iter) { if (alias == iter->prefix) { if (iter->package.package.empty()) { - return ExtractedPackage{local_package.to_string(), iter->package.private_namespace}; + return ExtractedPackage{{}, iter->package.private_namespace}; } return iter->package; } diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h index cdeeefd13976..63db66f0b2b7 100644 --- a/tools/aapt2/xml/XmlPullParser.h +++ b/tools/aapt2/xml/XmlPullParser.h @@ -31,6 +31,7 @@ #include "androidfw/StringPiece.h" #include "Resource.h" +#include "io/Io.h" #include "process/IResourceTableConsumer.h" #include "util/Maybe.h" #include "xml/XmlUtil.h" @@ -64,7 +65,7 @@ class XmlPullParser : public IPackageDeclStack { static bool SkipCurrentElement(XmlPullParser* parser); static bool IsGoodEvent(Event event); - explicit XmlPullParser(std::istream& in); + explicit XmlPullParser(io::InputStream* in); ~XmlPullParser(); /** @@ -118,8 +119,7 @@ class XmlPullParser : public IPackageDeclStack { * If xmlns:app="http://schemas.android.com/apk/res-auto", then * 'package' will be set to 'defaultPackage'. */ - Maybe<ExtractedPackage> TransformPackageAlias( - const android::StringPiece& alias, const android::StringPiece& local_package) const override; + Maybe<ExtractedPackage> TransformPackageAlias(const android::StringPiece& alias) const override; // // Remaining methods are for retrieving information about attributes @@ -169,9 +169,8 @@ class XmlPullParser : public IPackageDeclStack { std::vector<Attribute> attributes; }; - std::istream& in_; + io::InputStream* in_; XML_Parser parser_; - char buffer_[16384]; std::queue<EventData> event_queue_; std::string error_; const std::string empty_; @@ -228,18 +227,15 @@ inline ::std::ostream& operator<<(::std::ostream& out, return out; } -inline bool XmlPullParser::NextChildNode(XmlPullParser* parser, - size_t start_depth) { +inline bool XmlPullParser::NextChildNode(XmlPullParser* parser, size_t start_depth) { Event event; // First get back to the start depth. - while (IsGoodEvent(event = parser->Next()) && - parser->depth() > start_depth + 1) { + while (IsGoodEvent(event = parser->Next()) && parser->depth() > start_depth + 1) { } // Now look for the first good node. - while ((event != Event::kEndElement || parser->depth() > start_depth) && - IsGoodEvent(event)) { + while ((event != Event::kEndElement || parser->depth() > start_depth) && IsGoodEvent(event)) { switch (event) { case Event::kText: case Event::kComment: diff --git a/tools/aapt2/xml/XmlPullParser_test.cpp b/tools/aapt2/xml/XmlPullParser_test.cpp index 1cce4850cac5..681d9d48173f 100644 --- a/tools/aapt2/xml/XmlPullParser_test.cpp +++ b/tools/aapt2/xml/XmlPullParser_test.cpp @@ -16,21 +16,22 @@ #include "xml/XmlPullParser.h" -#include <sstream> - #include "androidfw/StringPiece.h" +#include "io/StringInputStream.h" #include "test/Test.h" -using android::StringPiece; +using ::aapt::io::StringInputStream; +using ::android::StringPiece; namespace aapt { TEST(XmlPullParserTest, NextChildNodeTraversesCorrectly) { - std::stringstream str; - str << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" - "<a><b><c xmlns:a=\"http://schema.org\"><d/></c><e/></b></a>"; - xml::XmlPullParser parser(str); + std::string str = + R"(<?xml version="1.0" encoding="utf-8"?> + <a><b><c xmlns:a="http://schema.org"><d/></c><e/></b></a>)"; + StringInputStream input(str); + xml::XmlPullParser parser(&input); const size_t depth_outer = parser.depth(); ASSERT_TRUE(xml::XmlPullParser::NextChildNode(&parser, depth_outer)); diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp index fb8cee8b5634..c1186e83369c 100644 --- a/tools/aapt2/xml/XmlUtil.cpp +++ b/tools/aapt2/xml/XmlUtil.cpp @@ -62,19 +62,15 @@ Maybe<ExtractedPackage> ExtractPackageFromNamespace( return {}; } -void TransformReferenceFromNamespace(IPackageDeclStack* decl_stack, - const StringPiece& local_package, - Reference* in_ref) { +void ResolvePackage(const IPackageDeclStack* decl_stack, Reference* in_ref) { if (in_ref->name) { if (Maybe<ExtractedPackage> transformed_package = - decl_stack->TransformPackageAlias(in_ref->name.value().package, - local_package)) { + decl_stack->TransformPackageAlias(in_ref->name.value().package)) { ExtractedPackage& extracted_package = transformed_package.value(); in_ref->name.value().package = std::move(extracted_package.package); // If the reference was already private (with a * prefix) and the - // namespace is public, - // we keep the reference private. + // namespace is public, we keep the reference private. in_ref->private_reference |= extracted_package.private_namespace; } } diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h index 1650ac2124ac..4eb359a9eed4 100644 --- a/tools/aapt2/xml/XmlUtil.h +++ b/tools/aapt2/xml/XmlUtil.h @@ -26,81 +26,57 @@ namespace aapt { namespace xml { constexpr const char* kSchemaAuto = "http://schemas.android.com/apk/res-auto"; -constexpr const char* kSchemaPublicPrefix = - "http://schemas.android.com/apk/res/"; -constexpr const char* kSchemaPrivatePrefix = - "http://schemas.android.com/apk/prv/res/"; -constexpr const char* kSchemaAndroid = - "http://schemas.android.com/apk/res/android"; +constexpr const char* kSchemaPublicPrefix = "http://schemas.android.com/apk/res/"; +constexpr const char* kSchemaPrivatePrefix = "http://schemas.android.com/apk/prv/res/"; +constexpr const char* kSchemaAndroid = "http://schemas.android.com/apk/res/android"; constexpr const char* kSchemaTools = "http://schemas.android.com/tools"; constexpr const char* kSchemaAapt = "http://schemas.android.com/aapt"; -/** - * Result of extracting a package name from a namespace URI declaration. - */ +// Result of extracting a package name from a namespace URI declaration. struct ExtractedPackage { - /** - * The name of the package. This can be the empty string, which means that the - * package - * should be assumed to be the package being compiled. - */ + // The name of the package. This can be the empty string, which means that the package + // should be assumed to be the same as the CallSite it was defined in. std::string package; - /** - * True if the package's private namespace was declared. This means that - * private resources - * are made visible. - */ + // True if the package's private namespace was declared. This means that private resources + // are made visible. bool private_namespace; + + friend inline bool operator==(const ExtractedPackage& a, const ExtractedPackage& b) { + return a.package == b.package && a.private_namespace == b.private_namespace; + } }; -/** - * Returns an ExtractedPackage struct if the namespace URI is of the form: - * http://schemas.android.com/apk/res/<package> or - * http://schemas.android.com/apk/prv/res/<package> - * - * Special case: if namespaceUri is http://schemas.android.com/apk/res-auto, - * returns an empty package name. - */ -Maybe<ExtractedPackage> ExtractPackageFromNamespace( - const std::string& namespace_uri); +// Returns an ExtractedPackage struct if the namespace URI is of the form: +// http://schemas.android.com/apk/res/<package> or +// http://schemas.android.com/apk/prv/res/<package> +// +// Special case: if namespaceUri is http://schemas.android.com/apk/res-auto, returns an empty +// package name. +Maybe<ExtractedPackage> ExtractPackageFromNamespace(const std::string& namespace_uri); -/** - * Returns an XML Android namespace for the given package of the form: - * - * http://schemas.android.com/apk/res/<package> - * - * If privateReference == true, the package will be of the form: - * - * http://schemas.android.com/apk/prv/res/<package> - */ +// Returns an XML Android namespace for the given package of the form: +// http://schemas.android.com/apk/res/<package> +// +// If privateReference == true, the package will be of the form: +// http://schemas.android.com/apk/prv/res/<package> std::string BuildPackageNamespace(const android::StringPiece& package, bool private_reference = false); -/** - * Interface representing a stack of XML namespace declarations. When looking up - * the package - * for a namespace prefix, the stack is checked from top to bottom. - */ +// Interface representing a stack of XML namespace declarations. When looking up the package for a +// namespace prefix, the stack is checked from top to bottom. struct IPackageDeclStack { virtual ~IPackageDeclStack() = default; - /** - * Returns an ExtractedPackage struct if the alias given corresponds with a - * package declaration. - */ + // Returns an ExtractedPackage struct if the alias given corresponds with a package declaration. virtual Maybe<ExtractedPackage> TransformPackageAlias( - const android::StringPiece& alias, const android::StringPiece& local_package) const = 0; + const android::StringPiece& alias) const = 0; }; -/** - * Helper function for transforming the original Reference inRef to a fully - * qualified reference - * via the IPackageDeclStack. This will also mark the Reference as private if - * the namespace of the package declaration was private. - */ -void TransformReferenceFromNamespace(IPackageDeclStack* decl_stack, - const android::StringPiece& local_package, Reference* in_ref); +// Helper function for transforming the original Reference inRef to a fully qualified reference +// via the IPackageDeclStack. This will also mark the Reference as private if the namespace of the +// package declaration was private. +void ResolvePackage(const IPackageDeclStack* decl_stack, Reference* in_ref); } // namespace xml } // namespace aapt diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py index 77c1c24b17eb..2eab22e8ec14 100644 --- a/tools/apilint/apilint.py +++ b/tools/apilint/apilint.py @@ -1127,6 +1127,80 @@ def verify_closable(clazz): return +def verify_member_name_not_kotlin_keyword(clazz): + """Prevent method names which are keywords in Kotlin.""" + + # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords + # This list does not include Java keywords as those are already impossible to use. + keywords = [ + 'as', + 'fun', + 'in', + 'is', + 'object', + 'typealias', + 'val', + 'var', + 'when', + ] + + for m in clazz.methods: + if m.name in keywords: + error(clazz, m, None, "Method name must not be a Kotlin keyword") + for f in clazz.fields: + if f.name in keywords: + error(clazz, f, None, "Field name must not be a Kotlin keyword") + + +def verify_method_name_not_kotlin_operator(clazz): + """Warn about method names which become operators in Kotlin.""" + + binary = set() + + def unique_binary_op(m, op): + if op in binary: + error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op)) + binary.add(op) + + for m in clazz.methods: + if 'static' in m.split: + continue + + # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators + if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0: + warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin") + + # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements + if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void': + # This only applies if the return type is the same or a subtype of the enclosing class, but we have no + # practical way of checking that relationship here. + warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin") + + # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic + if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1: + warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin") + unique_binary_op(m, m.name) + + # https://kotlinlang.org/docs/reference/operator-overloading.html#in + if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean': + warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin") + + # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed + if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1): + warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin") + + # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke + if m.name == 'invoke': + warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin") + + # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments + if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \ + and len(m.args) == 1 \ + and m.typ == 'void': + warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin") + unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix + + def examine_clazz(clazz): """Find all style issues in the given class.""" if clazz.pkg.name.startswith("java"): return @@ -1178,6 +1252,8 @@ def examine_clazz(clazz): verify_error(clazz) verify_units(clazz) verify_closable(clazz) + verify_member_name_not_kotlin_keyword(clazz) + verify_method_name_not_kotlin_operator(clazz) def examine_stream(stream): diff --git a/tools/incident_report/main.cpp b/tools/incident_report/main.cpp index 1d8809f6f603..250d1186c672 100644 --- a/tools/incident_report/main.cpp +++ b/tools/incident_report/main.cpp @@ -97,6 +97,8 @@ read_message(CodedInputStream* in, Descriptor const* descriptor, GenericMessage* message->addInt64(fieldId, value64); break; } else { + fprintf(stderr, "bad VARINT: 0x%x (%d) at index %d\n", tag, tag, + in->CurrentPosition()); return false; } case WireFormatLite::WIRETYPE_FIXED64: @@ -104,10 +106,14 @@ read_message(CodedInputStream* in, Descriptor const* descriptor, GenericMessage* message->addInt64(fieldId, value64); break; } else { + fprintf(stderr, "bad VARINT: 0x%x (%d) at index %d\n", tag, tag, + in->CurrentPosition()); return false; } case WireFormatLite::WIRETYPE_LENGTH_DELIMITED: if (!read_length_delimited(in, fieldId, descriptor, message)) { + fprintf(stderr, "bad LENGTH_DELIMITED: 0x%x (%d) at index %d\n", + tag, tag, in->CurrentPosition()); return false; } break; @@ -116,6 +122,8 @@ read_message(CodedInputStream* in, Descriptor const* descriptor, GenericMessage* message->addInt32(fieldId, value32); break; } else { + fprintf(stderr, "bad FIXED32: 0x%x (%d) at index %d\n", tag, tag, + in->CurrentPosition()); return false; } default: @@ -146,7 +154,7 @@ print_value(Out* out, FieldDescriptor const* field, GenericMessage::Node const& out->printf("%f", *(float*)&node.value32); break; default: - out->printf("(unexpected value %d (0x%x)", node.value32, node.value32); + out->printf("(unexpected value32 %d (0x%x)", node.value32, node.value32); break; } break; @@ -177,8 +185,11 @@ print_value(Out* out, FieldDescriptor const* field, GenericMessage::Node const& } break; case FieldDescriptor::TYPE_ENUM: + out->printf("%s", field->enum_type()->FindValueByNumber((int)node.value64) + ->name().c_str()); + break; default: - out->printf("(unexpected value %ld (0x%x))", node.value64, node.value64); + out->printf("(unexpected value64 %ld (0x%x))", node.value64, node.value64); break; } break; @@ -297,7 +308,7 @@ static int adb_incident_workaround(const char* adbSerial, const vector<string>& sections) { const int maxAllowedSize = 20 * 1024 * 1024; // 20MB - uint8_t* buffer = (uint8_t*)malloc(maxAllowedSize); + unique_ptr<uint8_t[]> buffer(new uint8_t[maxAllowedSize]); for (vector<string>::const_iterator it=sections.begin(); it!=sections.end(); it++) { Descriptor const* descriptor = IncidentProto::descriptor(); @@ -363,7 +374,7 @@ adb_incident_workaround(const char* adbSerial, const vector<string>& sections) size_t size = 0; while (size < maxAllowedSize) { - ssize_t amt = read(pfd[0], buffer + size, maxAllowedSize - size); + ssize_t amt = read(pfd[0], buffer.get() + size, maxAllowedSize - size); if (amt == 0) { break; } else if (amt == -1) { @@ -390,7 +401,7 @@ adb_incident_workaround(const char* adbSerial, const vector<string>& sections) fprintf(stderr, "write error: %s\n", strerror(err)); return 1; } - err = write_all(STDOUT_FILENO, buffer, size); + err = write_all(STDOUT_FILENO, buffer.get(), size); if (err != 0) { fprintf(stderr, "write error: %s\n", strerror(err)); return 1; @@ -401,7 +412,6 @@ adb_incident_workaround(const char* adbSerial, const vector<string>& sections) } } - free(buffer); return 0; } diff --git a/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp index 15f622cf9461..900690cf36d6 100644 --- a/tools/incident_section_gen/main.cpp +++ b/tools/incident_section_gen/main.cpp @@ -17,27 +17,34 @@ #include <frameworks/base/core/proto/android/os/incident.pb.h> - #include <map> +#include <string> +using namespace android; using namespace android::os; using namespace google::protobuf; using namespace google::protobuf::io; using namespace google::protobuf::internal; using namespace std; -int -main(int, const char**) -{ - map<string,FieldDescriptor const*> sections; - int N; +static inline void emptyline() { + printf("\n"); +} +static void generateHead(const char* header) { printf("// Auto generated file. Do not modify\n"); - printf("\n"); - printf("#include \"incident_sections.h\"\n"); - printf("\n"); + emptyline(); + printf("#include \"%s.h\"\n", header); + emptyline(); +} - Descriptor const* descriptor = IncidentProto::descriptor(); +// ================================================================================ +static bool generateIncidentSectionsCpp(Descriptor const* descriptor) +{ + generateHead("incident_sections"); + + map<string,FieldDescriptor const*> sections; + int N; N = descriptor->field_count(); for (int i=0; i<N; i++) { const FieldDescriptor* field = descriptor->field(i); @@ -63,5 +70,182 @@ main(int, const char**) printf("const int INCIDENT_SECTION_COUNT = %d;\n", N); - return 0; + return true; +} + +// ================================================================================ +static void splitAndPrint(const string& args) { + size_t base = 0; + size_t found; + while (true) { + found = args.find_first_of(" ", base); + if (found != base) { + string arg = args.substr(base, found - base); + printf(" \"%s\",", arg.c_str()); + } + if (found == args.npos) break; + base = found + 1; + } +} + +static const std::string replaceAll(const string& field_name, const char oldC, const string& newS) { + if (field_name.find_first_of(oldC) == field_name.npos) return field_name.c_str(); + size_t pos = 0, idx = 0; + char* res = new char[field_name.size() * newS.size() + 1]; // assign a larger buffer + while (pos != field_name.size()) { + char cur = field_name[pos++]; + if (cur != oldC) { + res[idx++] = cur; + continue; + } + + for (size_t i=0; i<newS.size(); i++) { + res[idx++] = newS[i]; + } + } + res[idx] = '\0'; + std::string result(res); + delete [] res; + return result; +} + +static inline bool isDefaultDest(const FieldDescriptor* field) { + return field->options().GetExtension(privacy).dest() == PrivacyFlags::default_instance().dest(); +} + +// Returns true if the descriptor doesn't have any non default privacy flags set, including its submessages +static bool generatePrivacyFlags(const Descriptor* descriptor, const char* alias, map<string, bool> &msgNames) { + bool hasDefaultFlags[descriptor->field_count()]; + // iterate though its field and generate sub flags first + for (int i=0; i<descriptor->field_count(); i++) { + hasDefaultFlags[i] = true; // set default to true + const FieldDescriptor* field = descriptor->field(i); + const std::string field_name_str = replaceAll(field->full_name(), '.', "__"); + const char* field_name = field_name_str.c_str(); + // check if the same name is already defined + if (msgNames.find(field_name) != msgNames.end()) { + hasDefaultFlags[i] = msgNames[field_name]; + continue; + }; + + PrivacyFlags p = field->options().GetExtension(privacy); + + switch (field->type()) { + case FieldDescriptor::TYPE_MESSAGE: + if (generatePrivacyFlags(field->message_type(), field_name, msgNames) && + isDefaultDest(field)) break; + + printf("Privacy %s { %d, %d, %s_LIST, %d, NULL };\n", field_name, field->number(), field->type(), field_name, p.dest()); + hasDefaultFlags[i] = false; + break; + case FieldDescriptor::TYPE_STRING: + if (isDefaultDest(field) && p.patterns_size() == 0) break; + + printf("const char* %s_patterns[] = {\n", field_name); + for (int i=0; i<p.patterns_size(); i++) { + // the generated string need to escape backslash as well, need to dup it here + printf(" \"%s\",\n", replaceAll(p.patterns(i), '\\', "\\\\").c_str()); + } + printf(" NULL };\n"); + printf("Privacy %s { %d, %d, NULL, %d, %s_patterns };\n", field_name, field->number(), field->type(), p.dest(), field_name); + hasDefaultFlags[i] = false; + break; + default: + if (isDefaultDest(field)) break; + printf("Privacy %s { %d, %d, NULL, %d, NULL };\n", field_name, field->number(), field->type(), p.dest()); + hasDefaultFlags[i] = false; + } + // add the field name to message map, true means it has default flags + msgNames[field_name] = hasDefaultFlags[i]; + } + + bool allDefaults = true; + for (int i=0; i<descriptor->field_count(); i++) { + allDefaults &= hasDefaultFlags[i]; + } + if (allDefaults) return true; + + emptyline(); + + bool needConst = strcmp(alias, "PRIVACY_POLICY") == 0; + int policyCount = 0; + + printf("%s Privacy* %s_LIST[] = {\n", needConst ? "const" : "", alias); + for (int i=0; i<descriptor->field_count(); i++) { + const FieldDescriptor* field = descriptor->field(i); + if (hasDefaultFlags[i]) continue; + printf(" &%s,\n", replaceAll(field->full_name(), '.', "__").c_str()); + policyCount++; + } + if (needConst) { + printf("};\n\n"); + printf("const int PRIVACY_POLICY_COUNT = %d;\n", policyCount); + } else { + printf(" NULL };\n"); + } + emptyline(); + return false; +} + +static bool generateSectionListCpp(Descriptor const* descriptor) { + generateHead("section_list"); + + // generates SECTION_LIST + printf("const Section* SECTION_LIST[] = {\n"); + for (int i=0; i<descriptor->field_count(); i++) { + const FieldDescriptor* field = descriptor->field(i); + + if (field->type() != FieldDescriptor::TYPE_MESSAGE) { + continue; + } + const SectionFlags s = field->options().GetExtension(section); + switch (s.type()) { + case SECTION_NONE: + continue; + case SECTION_FILE: + printf(" new FileSection(%d, \"%s\"),\n", field->number(), s.args().c_str()); + break; + case SECTION_COMMAND: + printf(" new CommandSection(%d,", field->number()); + splitAndPrint(s.args()); + printf(" NULL),\n"); + break; + case SECTION_DUMPSYS: + printf(" new DumpsysSection(%d,", field->number()); + splitAndPrint(s.args()); + printf(" NULL),\n"); + break; + } + } + printf(" NULL };\n"); + emptyline(); + + // generates PRIVACY_POLICY + map<string, bool> messageNames; + if (generatePrivacyFlags(descriptor, "PRIVACY_POLICY", messageNames)) { + // if no privacy options set at all, define an empty list + printf("const Privacy* PRIVACY_POLICY_LIST[] = {};\n"); + printf("const int PRIVACY_POLICY_COUNT = 0;\n"); + } + + return true; +} + +// ================================================================================ +int main(int argc, char const *argv[]) +{ + if (argc != 2) return 1; + const char* module = argv[1]; + + Descriptor const* descriptor = IncidentProto::descriptor(); + + if (strcmp(module, "incident") == 0) { + return !generateIncidentSectionsCpp(descriptor); + } + if (strcmp(module, "incidentd") == 0 ) { + return !generateSectionListCpp(descriptor); + } + + // return failure if not called by the whitelisted modules + return 1; } diff --git a/tools/validatekeymaps/Main.cpp b/tools/validatekeymaps/Main.cpp index 5b45c551c930..bbfcba6272b2 100644 --- a/tools/validatekeymaps/Main.cpp +++ b/tools/validatekeymaps/Main.cpp @@ -20,13 +20,15 @@ #include <utils/PropertyMap.h> #include <utils/String8.h> +#include <stdarg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> using namespace android; -static const char* gProgName = "validatekeymaps"; +static const char* kProgName = "validatekeymaps"; +static bool gQuiet = false; enum FileType { FILETYPE_UNKNOWN, @@ -36,15 +38,32 @@ enum FileType { FILETYPE_INPUTDEVICECONFIGURATION, }; +static void log(const char* fmt, ...) { + if (gQuiet) { + return; + } + va_list args; + va_start(args, fmt); + vfprintf(stdout, fmt, args); + va_end(args); +} + +static void error(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} static void usage() { - fprintf(stderr, "Keymap Validation Tool\n\n"); - fprintf(stderr, "Usage:\n"); - fprintf(stderr, - " %s [*.kl] [*.kcm] [*.idc] [virtualkeys.*] [...]\n" + error("Keymap Validation Tool\n\n"); + error("Usage:\n"); + error( + " %s [-q] [*.kl] [*.kcm] [*.idc] [virtualkeys.*] [...]\n" " Validates the specified key layouts, key character maps, \n" - " input device configurations, or virtual key definitions.\n\n", - gProgName); + " input device configurations, or virtual key definitions.\n\n" + " -q Quiet; do not write anything to standard out.\n", + kProgName); } static FileType getFileType(const char* filename) { @@ -69,19 +88,19 @@ static FileType getFileType(const char* filename) { } static bool validateFile(const char* filename) { - fprintf(stdout, "Validating file '%s'...\n", filename); + log("Validating file '%s'...\n", filename); FileType fileType = getFileType(filename); switch (fileType) { case FILETYPE_UNKNOWN: - fprintf(stderr, "Supported file types: *.kl, *.kcm, virtualkeys.*\n\n"); + error("Supported file types: *.kl, *.kcm, virtualkeys.*\n\n"); return false; case FILETYPE_KEYLAYOUT: { sp<KeyLayoutMap> map; status_t status = KeyLayoutMap::load(String8(filename), &map); if (status) { - fprintf(stderr, "Error %d parsing key layout file.\n\n", status); + error("Error %d parsing key layout file.\n\n", status); return false; } break; @@ -92,7 +111,7 @@ static bool validateFile(const char* filename) { status_t status = KeyCharacterMap::load(String8(filename), KeyCharacterMap::FORMAT_ANY, &map); if (status) { - fprintf(stderr, "Error %d parsing key character map file.\n\n", status); + error("Error %d parsing key character map file.\n\n", status); return false; } break; @@ -102,7 +121,7 @@ static bool validateFile(const char* filename) { PropertyMap* map; status_t status = PropertyMap::load(String8(filename), &map); if (status) { - fprintf(stderr, "Error %d parsing input device configuration file.\n\n", status); + error("Error %d parsing input device configuration file.\n\n", status); return false; } delete map; @@ -113,7 +132,7 @@ static bool validateFile(const char* filename) { VirtualKeyMap* map; status_t status = VirtualKeyMap::load(String8(filename), &map); if (status) { - fprintf(stderr, "Error %d parsing virtual key definition file.\n\n", status); + error("Error %d parsing virtual key definition file.\n\n", status); return false; } delete map; @@ -121,7 +140,7 @@ static bool validateFile(const char* filename) { } } - fputs("No errors.\n\n", stdout); + log("No errors.\n\n"); return true; } @@ -133,15 +152,19 @@ int main(int argc, const char** argv) { int result = 0; for (int i = 1; i < argc; i++) { + if (i == 1 && !strcmp(argv[1], "-q")) { + gQuiet = true; + continue; + } if (!validateFile(argv[i])) { result = 1; } } if (result) { - fputs("Failed!\n", stderr); + error("Failed!\n"); } else { - fputs("Success.\n", stdout); + log("Success.\n"); } return result; } |