diff options
Diffstat (limited to 'tools')
127 files changed, 6506 insertions, 3532 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 d92de062bcf0..bf56ec024699 100644 --- a/tools/aapt/SdkConstants.h +++ b/tools/aapt/SdkConstants.h @@ -41,7 +41,8 @@ enum { SDK_MNC = 23, SDK_NOUGAT = 24, SDK_NOUGAT_MR1 = 25, - SDK_O = 26, // STOPSHIP replace with real version + SDK_O = 26, + SDK_O_MR1 = 27, }; #endif // H_AAPT_SDK_CONSTANTS diff --git a/tools/aapt/pseudolocalize.cpp b/tools/aapt/pseudolocalize.cpp index c7fee2c19342..5c47e0fa8a16 100644 --- a/tools/aapt/pseudolocalize.cpp +++ b/tools/aapt/pseudolocalize.cpp @@ -360,9 +360,15 @@ String16 PseudoMethodBidi::text(const String16& source) String16 result; bool lastspace = true; bool space = true; + bool escape = false; + const char16_t ESCAPE_CHAR = '\\'; for (size_t i=0; i<source.size(); i++) { char16_t c = s[i]; - space = is_space(c); + if (!escape && c == ESCAPE_CHAR) { + escape = true; + continue; + } + space = (!escape && is_space(c)) || (escape && (c == 'n' || c == 't')); if (lastspace && !space) { // Word start result += k_rlm + k_rlo; @@ -371,6 +377,10 @@ String16 PseudoMethodBidi::text(const String16& source) result += k_pdf + k_rlm; } lastspace = space; + if (escape) { + result.append(&ESCAPE_CHAR, 1); + escape=false; + } result.append(&c, 1); } if (!lastspace) { diff --git a/tools/aapt/tests/Pseudolocales_test.cpp b/tools/aapt/tests/Pseudolocales_test.cpp index 4670e9fd53ea..a6aed3abd7cf 100644 --- a/tools/aapt/tests/Pseudolocales_test.cpp +++ b/tools/aapt/tests/Pseudolocales_test.cpp @@ -87,6 +87,10 @@ TEST(Pseudolocales, PlaintextBidi) { "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n" \ " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n", PSEUDO_BIDI); + simple_helper("hello\\nworld\\n", + "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\\n" + "\xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\\n", + PSEUDO_BIDI); } TEST(Pseudolocales, SimpleICU) { diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index 046de469c7cc..c6cfd3032762 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -81,13 +81,16 @@ cc_library_host_static { "compile/Pseudolocalizer.cpp", "compile/XmlIdCollector.cpp", "configuration/ConfigurationParser.cpp", + "filter/AbiFilter.cpp", "filter/ConfigFilter.cpp", "flatten/Archive.cpp", "flatten/TableFlattener.cpp", "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", @@ -106,6 +109,8 @@ cc_library_host_static { "proto/TableProtoDeserializer.cpp", "proto/TableProtoSerializer.cpp", "split/TableSplitter.cpp", + "text/Unicode.cpp", + "text/Utf8Iterator.cpp", "unflatten/BinaryResourceParser.cpp", "unflatten/ResChunkPullParser.cpp", "util/BigBuffer.cpp", @@ -133,7 +138,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, @@ -157,6 +163,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..49ed7780f950 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 << ":"; } @@ -283,26 +294,13 @@ class XmlPrinter : public xml::Visitor { std::cerr << "=" << attr.value << "\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 8a8f8be205e7..7e5efa15f61b 100644 --- a/tools/aapt2/LoadedApk.cpp +++ b/tools/aapt2/LoadedApk.cpp @@ -57,6 +57,12 @@ 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); +} + +bool LoadedApk::WriteToArchive(IAaptContext* context, 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) { @@ -89,6 +95,13 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOption continue; } + if (!filters->Keep(path)) { + if (context->IsVerbose()) { + context->GetDiagnostics()->Note(DiagMessage() << "Filtered '" << path << "' from APK."); + } + continue; + } + // The resource table needs to be re-serialized since it might have changed. if (path == "resources.arsc") { BigBuffer buffer(4096); diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h index 59eb8161a868..8aa9674aa2ed 100644 --- a/tools/aapt2/LoadedApk.h +++ b/tools/aapt2/LoadedApk.h @@ -20,6 +20,7 @@ #include "androidfw/StringPiece.h" #include "ResourceTable.h" +#include "filter/Filter.h" #include "flatten/Archive.h" #include "flatten/TableFlattener.h" #include "io/ZipArchive.h" @@ -49,6 +50,14 @@ class LoadedApk { 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); + 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 1d2e3a4f2df0..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 = "16"; +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/OWNERS b/tools/aapt2/OWNERS new file mode 100644 index 000000000000..d76233ec78ba --- /dev/null +++ b/tools/aapt2/OWNERS @@ -0,0 +1,2 @@ +set noparent +adamlesinski@google.com 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 9a37913f0edc..1c3ac2ad4f17 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -268,8 +268,7 @@ bool ResourceParser::Parse(xml::XmlPullParser* parser) { continue; } - if (!parser->element_namespace().empty() || - parser->element_name() != "resources") { + if (!parser->element_namespace().empty() || parser->element_name() != "resources") { diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) << "root element must be <resources>"); return false; @@ -328,8 +327,7 @@ bool ResourceParser::ParseResources(xml::XmlPullParser* parser) { parsed_resource.comment = std::move(comment); // Extract the product name if it exists. - if (Maybe<StringPiece> maybe_product = - xml::FindNonEmptyAttribute(parser, "product")) { + if (Maybe<StringPiece> maybe_product = xml::FindNonEmptyAttribute(parser, "product")) { parsed_resource.product = maybe_product.value().to_string(); } @@ -348,10 +346,8 @@ bool ResourceParser::ParseResources(xml::XmlPullParser* parser) { for (const ResourceName& stripped_resource : stripped_resources) { if (!table_->FindResource(stripped_resource)) { // Failed to find the resource. - diag_->Error(DiagMessage(source_) - << "resource '" << stripped_resource - << "' " - "was filtered out but no product variant remains"); + diag_->Error(DiagMessage(source_) << "resource '" << stripped_resource + << "' was filtered out but no product variant remains"); error = true; } } @@ -589,7 +585,7 @@ std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser, // This can only be a StyledString. std::unique_ptr<StyledString> styled_string = util::make_unique<StyledString>(table_->string_pool.MakeRef( - style_string, StringPool::Context(StringPool::Context::kStylePriority, config_))); + style_string, StringPool::Context(StringPool::Context::kNormalPriority, config_))); styled_string->untranslatable_sections = std::move(untranslatable_sections); return std::move(styled_string); } @@ -1223,7 +1219,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())) diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index c6382b177f42..144ebd22e105 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -22,14 +22,22 @@ #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; +using ::android::Res_value; using ::android::StringPiece; using ::testing::Eq; +using ::testing::IsEmpty; +using ::testing::IsNull; using ::testing::NotNull; using ::testing::Pointee; +using ::testing::SizeIs; namespace aapt { @@ -37,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)); } @@ -56,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(); } @@ -74,31 +88,31 @@ class ResourceParserTest : public ::testing::Test { }; TEST_F(ResourceParserTest, ParseQuotedString) { - std::string input = "<string name=\"foo\"> \" hey there \" </string>"; - ASSERT_TRUE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<string name="foo"> " hey there " </string>)")); String* str = test::GetValue<String>(&table_, "string/foo"); - ASSERT_NE(nullptr, str); - EXPECT_EQ(std::string(" hey there "), *str->value); - EXPECT_TRUE(str->untranslatable_sections.empty()); + ASSERT_THAT(str, NotNull()); + EXPECT_THAT(*str, StrValueEq(" hey there ")); + EXPECT_THAT(str->untranslatable_sections, IsEmpty()); } TEST_F(ResourceParserTest, ParseEscapedString) { - std::string input = "<string name=\"foo\">\\?123</string>"; - ASSERT_TRUE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<string name="foo">\?123</string>)")); String* str = test::GetValue<String>(&table_, "string/foo"); - ASSERT_NE(nullptr, str); - EXPECT_EQ(std::string("?123"), *str->value); - EXPECT_TRUE(str->untranslatable_sections.empty()); + ASSERT_THAT(str, NotNull()); + EXPECT_THAT(*str, StrValueEq("?123")); + EXPECT_THAT(str->untranslatable_sections, IsEmpty()); + + ASSERT_TRUE(TestParse(R"(<string name="bar">This isn\’t a bad string</string>)")); + str = test::GetValue<String>(&table_, "string/bar"); + ASSERT_THAT(str, NotNull()); + EXPECT_THAT(*str, StrValueEq("This isn’t a bad string")); } TEST_F(ResourceParserTest, ParseFormattedString) { - std::string input = "<string name=\"foo\">%d %s</string>"; - ASSERT_FALSE(TestParse(input)); - - input = "<string name=\"foo\">%1$d %2$s</string>"; - ASSERT_TRUE(TestParse(input)); + ASSERT_FALSE(TestParse(R"(<string name="foo">%d %s</string>)")); + ASSERT_TRUE(TestParse(R"(<string name="foo">%1$d %2$s</string>)")); } TEST_F(ResourceParserTest, ParseStyledString) { @@ -109,98 +123,93 @@ TEST_F(ResourceParserTest, ParseStyledString) { ASSERT_TRUE(TestParse(input)); StyledString* str = test::GetValue<StyledString>(&table_, "string/foo"); - ASSERT_NE(nullptr, str); + ASSERT_THAT(str, NotNull()); - const std::string expected_str = "This is my aunt\u2019s fickle string"; - EXPECT_EQ(expected_str, *str->value->str); - EXPECT_EQ(2u, str->value->spans.size()); - EXPECT_TRUE(str->untranslatable_sections.empty()); + EXPECT_THAT(str->value->value, Eq("This is my aunt\u2019s fickle string")); + EXPECT_THAT(str->value->spans, SizeIs(2)); + EXPECT_THAT(str->untranslatable_sections, IsEmpty()); - EXPECT_EQ(std::string("b"), *str->value->spans[0].name); - EXPECT_EQ(17u, str->value->spans[0].first_char); - EXPECT_EQ(30u, str->value->spans[0].last_char); + EXPECT_THAT(*str->value->spans[0].name, Eq("b")); + EXPECT_THAT(str->value->spans[0].first_char, Eq(17u)); + EXPECT_THAT(str->value->spans[0].last_char, Eq(30u)); - EXPECT_EQ(std::string("small"), *str->value->spans[1].name); - EXPECT_EQ(24u, str->value->spans[1].first_char); - EXPECT_EQ(30u, str->value->spans[1].last_char); + EXPECT_THAT(*str->value->spans[1].name, Eq("small")); + EXPECT_THAT(str->value->spans[1].first_char, Eq(24u)); + EXPECT_THAT(str->value->spans[1].last_char, Eq(30u)); } TEST_F(ResourceParserTest, ParseStringWithWhitespace) { - std::string input = "<string name=\"foo\"> This is what I think </string>"; - ASSERT_TRUE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<string name="foo"> This is what I think </string>)")); String* str = test::GetValue<String>(&table_, "string/foo"); - ASSERT_NE(nullptr, str); - EXPECT_EQ(std::string("This is what I think"), *str->value); - EXPECT_TRUE(str->untranslatable_sections.empty()); + ASSERT_THAT(str, NotNull()); + EXPECT_THAT(*str->value, Eq("This is what I think")); + EXPECT_THAT(str->untranslatable_sections, IsEmpty()); - input = "<string name=\"foo2\">\" This is what I think \"</string>"; - ASSERT_TRUE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<string name="foo2">" This is what I think "</string>)")); str = test::GetValue<String>(&table_, "string/foo2"); - ASSERT_NE(nullptr, str); - EXPECT_EQ(std::string(" This is what I think "), *str->value); + ASSERT_THAT(str, NotNull()); + EXPECT_THAT(*str, StrValueEq(" This is what I think ")); } TEST_F(ResourceParserTest, IgnoreXliffTagsOtherThanG) { - std::string input = R"EOF( + std::string input = R"( <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - There are <xliff:source>no</xliff:source> apples</string>)EOF"; + There are <xliff:source>no</xliff:source> apples</string>)"; ASSERT_TRUE(TestParse(input)); String* str = test::GetValue<String>(&table_, "string/foo"); - ASSERT_NE(nullptr, str); - EXPECT_EQ(StringPiece("There are no apples"), StringPiece(*str->value)); - EXPECT_TRUE(str->untranslatable_sections.empty()); + ASSERT_THAT(str, NotNull()); + EXPECT_THAT(*str, StrValueEq("There are no apples")); + EXPECT_THAT(str->untranslatable_sections, IsEmpty()); } TEST_F(ResourceParserTest, NestedXliffGTagsAreIllegal) { - std::string input = R"EOF( + std::string input = R"( <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - Do not <xliff:g>translate <xliff:g>this</xliff:g></xliff:g></string>)EOF"; + Do not <xliff:g>translate <xliff:g>this</xliff:g></xliff:g></string>)"; EXPECT_FALSE(TestParse(input)); } TEST_F(ResourceParserTest, RecordUntranslateableXliffSectionsInString) { - std::string input = R"EOF( + std::string input = R"( <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - There are <xliff:g id="count">%1$d</xliff:g> apples</string>)EOF"; + There are <xliff:g id="count">%1$d</xliff:g> apples</string>)"; ASSERT_TRUE(TestParse(input)); String* str = test::GetValue<String>(&table_, "string/foo"); - ASSERT_NE(nullptr, str); - EXPECT_EQ(StringPiece("There are %1$d apples"), StringPiece(*str->value)); - - ASSERT_EQ(1u, str->untranslatable_sections.size()); + ASSERT_THAT(str, NotNull()); + EXPECT_THAT(*str, StrValueEq("There are %1$d apples")); + ASSERT_THAT(str->untranslatable_sections, SizeIs(1)); // We expect indices and lengths that span to include the whitespace // before %1$d. This is due to how the StringBuilder withholds whitespace unless // needed (to deal with line breaks, etc.). - EXPECT_EQ(9u, str->untranslatable_sections[0].start); - EXPECT_EQ(14u, str->untranslatable_sections[0].end); + EXPECT_THAT(str->untranslatable_sections[0].start, Eq(9u)); + EXPECT_THAT(str->untranslatable_sections[0].end, Eq(14u)); } TEST_F(ResourceParserTest, RecordUntranslateableXliffSectionsInStyledString) { - std::string input = R"EOF( + std::string input = R"( <string name="foo" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - There are <b><xliff:g id="count">%1$d</xliff:g></b> apples</string>)EOF"; + There are <b><xliff:g id="count">%1$d</xliff:g></b> apples</string>)"; ASSERT_TRUE(TestParse(input)); StyledString* str = test::GetValue<StyledString>(&table_, "string/foo"); - ASSERT_NE(nullptr, str); - EXPECT_EQ(StringPiece("There are %1$d apples"), StringPiece(*str->value->str)); - - ASSERT_EQ(1u, str->untranslatable_sections.size()); + ASSERT_THAT(str, NotNull()); + EXPECT_THAT(str->value->value, Eq("There are %1$d apples")); + ASSERT_THAT(str->untranslatable_sections, SizeIs(1)); // We expect indices and lengths that span to include the whitespace // before %1$d. This is due to how the StringBuilder withholds whitespace unless // needed (to deal with line breaks, etc.). - EXPECT_EQ(9u, str->untranslatable_sections[0].start); - EXPECT_EQ(14u, str->untranslatable_sections[0].end); + EXPECT_THAT(str->untranslatable_sections[0].start, Eq(9u)); + EXPECT_THAT(str->untranslatable_sections[0].end, Eq(14u)); } TEST_F(ResourceParserTest, ParseNull) { - std::string input = "<integer name=\"foo\">@null</integer>"; + std::string input = R"(<integer name="foo">@null</integer>)"; ASSERT_TRUE(TestParse(input)); // The Android runtime treats a value of android::Res_value::TYPE_NULL as @@ -211,38 +220,36 @@ TEST_F(ResourceParserTest, ParseNull) { ASSERT_THAT(null_ref, NotNull()); EXPECT_FALSE(null_ref->name); EXPECT_FALSE(null_ref->id); - EXPECT_EQ(Reference::Type::kResource, null_ref->reference_type); + EXPECT_THAT(null_ref->reference_type, Eq(Reference::Type::kResource)); } TEST_F(ResourceParserTest, ParseEmpty) { - std::string input = "<integer name=\"foo\">@empty</integer>"; + std::string input = R"(<integer name="foo">@empty</integer>)"; ASSERT_TRUE(TestParse(input)); BinaryPrimitive* integer = test::GetValue<BinaryPrimitive>(&table_, "integer/foo"); - ASSERT_NE(nullptr, integer); - EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType); - EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data); + ASSERT_THAT(integer, NotNull()); + EXPECT_THAT(integer->value.dataType, Eq(Res_value::TYPE_NULL)); + EXPECT_THAT(integer->value.data, Eq(Res_value::DATA_NULL_EMPTY)); } TEST_F(ResourceParserTest, ParseAttr) { - std::string input = - "<attr name=\"foo\" format=\"string\"/>\n" - "<attr name=\"bar\"/>"; + std::string input = R"( + <attr name="foo" format="string"/> + <attr name="bar"/>)"; ASSERT_TRUE(TestParse(input)); Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->type_mask); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_STRING)); attr = test::GetValue<Attribute>(&table_, "attr/bar"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->type_mask); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_ANY)); } -// Old AAPT allowed attributes to be defined under different configurations, but -// ultimately -// stored them with the default configuration. Check that we have the same -// behavior. +// Old AAPT allowed attributes to be defined under different configurations, but ultimately +// stored them with the default configuration. Check that we have the same behavior. TEST_F(ResourceParserTest, ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoConfig) { const ConfigDescription watch_config = test::ParseConfigOrDie("watch"); std::string input = R"( @@ -252,583 +259,519 @@ TEST_F(ResourceParserTest, ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoC </declare-styleable>)"; ASSERT_TRUE(TestParse(input, watch_config)); - EXPECT_EQ(nullptr, test::GetValueForConfig<Attribute>(&table_, "attr/foo", watch_config)); - EXPECT_EQ(nullptr, test::GetValueForConfig<Attribute>(&table_, "attr/baz", watch_config)); - EXPECT_EQ(nullptr, test::GetValueForConfig<Styleable>(&table_, "styleable/bar", watch_config)); + EXPECT_THAT(test::GetValueForConfig<Attribute>(&table_, "attr/foo", watch_config), IsNull()); + EXPECT_THAT(test::GetValueForConfig<Attribute>(&table_, "attr/baz", watch_config), IsNull()); + EXPECT_THAT(test::GetValueForConfig<Styleable>(&table_, "styleable/bar", watch_config), IsNull()); - EXPECT_NE(nullptr, test::GetValue<Attribute>(&table_, "attr/foo")); - EXPECT_NE(nullptr, test::GetValue<Attribute>(&table_, "attr/baz")); - EXPECT_NE(nullptr, test::GetValue<Styleable>(&table_, "styleable/bar")); + EXPECT_THAT(test::GetValue<Attribute>(&table_, "attr/foo"), NotNull()); + EXPECT_THAT(test::GetValue<Attribute>(&table_, "attr/baz"), NotNull()); + EXPECT_THAT(test::GetValue<Styleable>(&table_, "styleable/bar"), NotNull()); } TEST_F(ResourceParserTest, ParseAttrWithMinMax) { - std::string input = - "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"integer\"/>"; + std::string input = R"(<attr name="foo" min="10" max="23" format="integer"/>)"; ASSERT_TRUE(TestParse(input)); Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_INTEGER), attr->type_mask); - EXPECT_EQ(10, attr->min_int); - EXPECT_EQ(23, attr->max_int); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_INTEGER)); + EXPECT_THAT(attr->min_int, Eq(10)); + EXPECT_THAT(attr->max_int, Eq(23)); } TEST_F(ResourceParserTest, FailParseAttrWithMinMaxButNotInteger) { - std::string input = - "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"string\"/>"; - ASSERT_FALSE(TestParse(input)); + ASSERT_FALSE(TestParse(R"(<attr name="foo" min="10" max="23" format="string"/>)")); } TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) { - std::string input = - "<declare-styleable name=\"Styleable\">\n" - " <attr name=\"foo\" />\n" - "</declare-styleable>\n" - "<attr name=\"foo\" format=\"string\"/>"; + std::string input = R"( + <declare-styleable name="Styleable"> + <attr name="foo" /> + </declare-styleable> + <attr name="foo" format="string"/>)"; ASSERT_TRUE(TestParse(input)); Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->type_mask); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_STRING)); } TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) { - std::string input = - "<declare-styleable name=\"Theme\">" - " <attr name=\"foo\" />\n" - "</declare-styleable>\n" - "<declare-styleable name=\"Window\">\n" - " <attr name=\"foo\" format=\"boolean\"/>\n" - "</declare-styleable>"; + std::string input = R"( + <declare-styleable name="Theme"> + <attr name="foo" /> + </declare-styleable> + <declare-styleable name="Window"> + <attr name="foo" format="boolean"/> + </declare-styleable>)"; ASSERT_TRUE(TestParse(input)); Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->type_mask); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->type_mask, Eq(ResTable_map::TYPE_BOOLEAN)); } TEST_F(ResourceParserTest, ParseEnumAttr) { - std::string input = - "<attr name=\"foo\">\n" - " <enum name=\"bar\" value=\"0\"/>\n" - " <enum name=\"bat\" value=\"1\"/>\n" - " <enum name=\"baz\" value=\"2\"/>\n" - "</attr>"; + std::string input = R"( + <attr name="foo"> + <enum name="bar" value="0"/> + <enum name="bat" value="1"/> + <enum name="baz" value="2"/> + </attr>)"; ASSERT_TRUE(TestParse(input)); Attribute* enum_attr = test::GetValue<Attribute>(&table_, "attr/foo"); - ASSERT_NE(enum_attr, nullptr); - EXPECT_EQ(enum_attr->type_mask, android::ResTable_map::TYPE_ENUM); - ASSERT_EQ(enum_attr->symbols.size(), 3u); + ASSERT_THAT(enum_attr, NotNull()); + EXPECT_THAT(enum_attr->type_mask, Eq(ResTable_map::TYPE_ENUM)); + ASSERT_THAT(enum_attr->symbols, SizeIs(3)); - AAPT_ASSERT_TRUE(enum_attr->symbols[0].symbol.name); - EXPECT_EQ(enum_attr->symbols[0].symbol.name.value().entry, "bar"); - EXPECT_EQ(enum_attr->symbols[0].value, 0u); + ASSERT_TRUE(enum_attr->symbols[0].symbol.name); + EXPECT_THAT(enum_attr->symbols[0].symbol.name.value().entry, Eq("bar")); + EXPECT_THAT(enum_attr->symbols[0].value, Eq(0u)); - AAPT_ASSERT_TRUE(enum_attr->symbols[1].symbol.name); - EXPECT_EQ(enum_attr->symbols[1].symbol.name.value().entry, "bat"); - EXPECT_EQ(enum_attr->symbols[1].value, 1u); + ASSERT_TRUE(enum_attr->symbols[1].symbol.name); + EXPECT_THAT(enum_attr->symbols[1].symbol.name.value().entry, Eq("bat")); + EXPECT_THAT(enum_attr->symbols[1].value, Eq(1u)); - AAPT_ASSERT_TRUE(enum_attr->symbols[2].symbol.name); - EXPECT_EQ(enum_attr->symbols[2].symbol.name.value().entry, "baz"); - EXPECT_EQ(enum_attr->symbols[2].value, 2u); + ASSERT_TRUE(enum_attr->symbols[2].symbol.name); + EXPECT_THAT(enum_attr->symbols[2].symbol.name.value().entry, Eq("baz")); + EXPECT_THAT(enum_attr->symbols[2].value, Eq(2u)); } TEST_F(ResourceParserTest, ParseFlagAttr) { - std::string input = - "<attr name=\"foo\">\n" - " <flag name=\"bar\" value=\"0\"/>\n" - " <flag name=\"bat\" value=\"1\"/>\n" - " <flag name=\"baz\" value=\"2\"/>\n" - "</attr>"; + std::string input = R"( + <attr name="foo"> + <flag name="bar" value="0"/> + <flag name="bat" value="1"/> + <flag name="baz" value="2"/> + </attr>)"; ASSERT_TRUE(TestParse(input)); Attribute* flag_attr = test::GetValue<Attribute>(&table_, "attr/foo"); - ASSERT_NE(nullptr, flag_attr); - EXPECT_EQ(flag_attr->type_mask, android::ResTable_map::TYPE_FLAGS); - ASSERT_EQ(flag_attr->symbols.size(), 3u); + ASSERT_THAT(flag_attr, NotNull()); + EXPECT_THAT(flag_attr->type_mask, Eq(ResTable_map::TYPE_FLAGS)); + ASSERT_THAT(flag_attr->symbols, SizeIs(3)); - AAPT_ASSERT_TRUE(flag_attr->symbols[0].symbol.name); - EXPECT_EQ(flag_attr->symbols[0].symbol.name.value().entry, "bar"); - EXPECT_EQ(flag_attr->symbols[0].value, 0u); + ASSERT_TRUE(flag_attr->symbols[0].symbol.name); + EXPECT_THAT(flag_attr->symbols[0].symbol.name.value().entry, Eq("bar")); + EXPECT_THAT(flag_attr->symbols[0].value, Eq(0u)); - AAPT_ASSERT_TRUE(flag_attr->symbols[1].symbol.name); - EXPECT_EQ(flag_attr->symbols[1].symbol.name.value().entry, "bat"); - EXPECT_EQ(flag_attr->symbols[1].value, 1u); + ASSERT_TRUE(flag_attr->symbols[1].symbol.name); + EXPECT_THAT(flag_attr->symbols[1].symbol.name.value().entry, Eq("bat")); + EXPECT_THAT(flag_attr->symbols[1].value, Eq(1u)); - AAPT_ASSERT_TRUE(flag_attr->symbols[2].symbol.name); - EXPECT_EQ(flag_attr->symbols[2].symbol.name.value().entry, "baz"); - EXPECT_EQ(flag_attr->symbols[2].value, 2u); + ASSERT_TRUE(flag_attr->symbols[2].symbol.name); + EXPECT_THAT(flag_attr->symbols[2].symbol.name.value().entry, Eq("baz")); + EXPECT_THAT(flag_attr->symbols[2].value, Eq(2u)); std::unique_ptr<BinaryPrimitive> flag_value = ResourceUtils::TryParseFlagSymbol(flag_attr, "baz|bat"); - ASSERT_NE(nullptr, flag_value); - EXPECT_EQ(flag_value->value.data, 1u | 2u); + ASSERT_THAT(flag_value, NotNull()); + EXPECT_THAT(flag_value->value.data, Eq(1u | 2u)); } TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) { - std::string input = - "<attr name=\"foo\">\n" - " <enum name=\"bar\" value=\"0\"/>\n" - " <enum name=\"bat\" value=\"1\"/>\n" - " <enum name=\"bat\" value=\"2\"/>\n" - "</attr>"; + std::string input = R"( + <attr name="foo"> + <enum name="bar" value="0"/> + <enum name="bat" value="1"/> + <enum name="bat" value="2"/> + </attr>)"; ASSERT_FALSE(TestParse(input)); } TEST_F(ResourceParserTest, ParseStyle) { - std::string input = - "<style name=\"foo\" parent=\"@style/fu\">\n" - " <item name=\"bar\">#ffffffff</item>\n" - " <item name=\"bat\">@string/hey</item>\n" - " <item name=\"baz\"><b>hey</b></item>\n" - "</style>"; + std::string input = R"( + <style name="foo" parent="@style/fu"> + <item name="bar">#ffffffff</item> + <item name="bat">@string/hey</item> + <item name="baz"><b>hey</b></item> + </style>)"; ASSERT_TRUE(TestParse(input)); Style* style = test::GetValue<Style>(&table_, "style/foo"); - ASSERT_NE(nullptr, style); - AAPT_ASSERT_TRUE(style->parent); - AAPT_ASSERT_TRUE(style->parent.value().name); - EXPECT_EQ(test::ParseNameOrDie("style/fu"), - style->parent.value().name.value()); - ASSERT_EQ(3u, style->entries.size()); - - AAPT_ASSERT_TRUE(style->entries[0].key.name); - EXPECT_EQ(test::ParseNameOrDie("attr/bar"), - style->entries[0].key.name.value()); - - AAPT_ASSERT_TRUE(style->entries[1].key.name); - EXPECT_EQ(test::ParseNameOrDie("attr/bat"), - style->entries[1].key.name.value()); + ASSERT_THAT(style, NotNull()); + ASSERT_TRUE(style->parent); + EXPECT_THAT(style->parent.value().name, Eq(make_value(test::ParseNameOrDie("style/fu")))); + ASSERT_THAT(style->entries, SizeIs(3)); - AAPT_ASSERT_TRUE(style->entries[2].key.name); - EXPECT_EQ(test::ParseNameOrDie("attr/baz"), - style->entries[2].key.name.value()); + EXPECT_THAT(style->entries[0].key.name, Eq(make_value(test::ParseNameOrDie("attr/bar")))); + EXPECT_THAT(style->entries[1].key.name, Eq(make_value(test::ParseNameOrDie("attr/bat")))); + EXPECT_THAT(style->entries[2].key.name, Eq(make_value(test::ParseNameOrDie("attr/baz")))); } TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) { - std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>"; - ASSERT_TRUE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<style name="foo" parent="com.app:Theme"/>)")); Style* style = test::GetValue<Style>(&table_, "style/foo"); - ASSERT_NE(nullptr, style); - AAPT_ASSERT_TRUE(style->parent); - AAPT_ASSERT_TRUE(style->parent.value().name); - EXPECT_EQ(test::ParseNameOrDie("com.app:style/Theme"), - style->parent.value().name.value()); + ASSERT_THAT(style, NotNull()); + ASSERT_TRUE(style->parent); + EXPECT_THAT(style->parent.value().name, Eq(make_value(test::ParseNameOrDie("com.app:style/Theme")))); } TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) { - std::string input = - "<style xmlns:app=\"http://schemas.android.com/apk/res/android\"\n" - " name=\"foo\" parent=\"app:Theme\"/>"; + std::string input = R"( + <style xmlns:app="http://schemas.android.com/apk/res/android" + name="foo" parent="app:Theme"/>)"; ASSERT_TRUE(TestParse(input)); Style* style = test::GetValue<Style>(&table_, "style/foo"); - ASSERT_NE(nullptr, style); - AAPT_ASSERT_TRUE(style->parent); - AAPT_ASSERT_TRUE(style->parent.value().name); - EXPECT_EQ(test::ParseNameOrDie("android:style/Theme"), - style->parent.value().name.value()); + ASSERT_THAT(style, NotNull()); + ASSERT_TRUE(style->parent); + ASSERT_TRUE(style->parent.value().name); + EXPECT_THAT(style->parent.value().name, Eq(make_value(test::ParseNameOrDie("android:style/Theme")))); } TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) { - std::string input = - "<style xmlns:app=\"http://schemas.android.com/apk/res/android\" " - "name=\"foo\">\n" - " <item name=\"app:bar\">0</item>\n" - "</style>"; + std::string input = R"( + <style xmlns:app="http://schemas.android.com/apk/res/android" name="foo"> + <item name="app:bar">0</item> + </style>)"; ASSERT_TRUE(TestParse(input)); Style* style = test::GetValue<Style>(&table_, "style/foo"); - ASSERT_NE(nullptr, style); - ASSERT_EQ(1u, style->entries.size()); - EXPECT_EQ(test::ParseNameOrDie("android:attr/bar"), - style->entries[0].key.name.value()); + ASSERT_THAT(style, NotNull()); + ASSERT_THAT(style->entries, SizeIs(1)); + EXPECT_THAT(style->entries[0].key.name, Eq(make_value(test::ParseNameOrDie("android:attr/bar")))); } TEST_F(ResourceParserTest, ParseStyleWithInferredParent) { - std::string input = "<style name=\"foo.bar\"/>"; - ASSERT_TRUE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<style name="foo.bar"/>)")); Style* style = test::GetValue<Style>(&table_, "style/foo.bar"); - ASSERT_NE(nullptr, style); - AAPT_ASSERT_TRUE(style->parent); - AAPT_ASSERT_TRUE(style->parent.value().name); - EXPECT_EQ(style->parent.value().name.value(), - test::ParseNameOrDie("style/foo")); + ASSERT_THAT(style, NotNull()); + ASSERT_TRUE(style->parent); + EXPECT_THAT(style->parent.value().name, Eq(make_value(test::ParseNameOrDie("style/foo")))); EXPECT_TRUE(style->parent_inferred); } -TEST_F(ResourceParserTest, - ParseStyleWithInferredParentOverridenByEmptyParentAttribute) { - std::string input = "<style name=\"foo.bar\" parent=\"\"/>"; - ASSERT_TRUE(TestParse(input)); +TEST_F(ResourceParserTest, ParseStyleWithInferredParentOverridenByEmptyParentAttribute) { + ASSERT_TRUE(TestParse(R"(<style name="foo.bar" parent=""/>)")); Style* style = test::GetValue<Style>(&table_, "style/foo.bar"); - ASSERT_NE(nullptr, style); - AAPT_EXPECT_FALSE(style->parent); + ASSERT_THAT(style, NotNull()); + EXPECT_FALSE(style->parent); EXPECT_FALSE(style->parent_inferred); } TEST_F(ResourceParserTest, ParseStyleWithPrivateParentReference) { - std::string input = - R"EOF(<style name="foo" parent="*android:style/bar" />)EOF"; - ASSERT_TRUE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<style name="foo" parent="*android:style/bar" />)")); Style* style = test::GetValue<Style>(&table_, "style/foo"); - ASSERT_NE(nullptr, style); - AAPT_ASSERT_TRUE(style->parent); + ASSERT_THAT(style, NotNull()); + ASSERT_TRUE(style->parent); EXPECT_TRUE(style->parent.value().private_reference); } TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) { - std::string input = "<string name=\"foo\">@+id/bar</string>"; - ASSERT_TRUE(TestParse(input)); - - Id* id = test::GetValue<Id>(&table_, "id/bar"); - ASSERT_NE(id, nullptr); + ASSERT_TRUE(TestParse(R"(<string name="foo">@+id/bar</string>)")); + ASSERT_THAT(test::GetValue<Id>(&table_, "id/bar"), NotNull()); } TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) { - std::string input = - "<declare-styleable name=\"foo\">\n" - " <attr name=\"bar\" />\n" - " <attr name=\"bat\" format=\"string|reference\"/>\n" - " <attr name=\"baz\">\n" - " <enum name=\"foo\" value=\"1\"/>\n" - " </attr>\n" - "</declare-styleable>"; + std::string input = R"( + <declare-styleable name="foo"> + <attr name="bar" /> + <attr name="bat" format="string|reference"/> + <attr name="baz"> + <enum name="foo" value="1"/> + </attr> + </declare-styleable>)"; ASSERT_TRUE(TestParse(input)); Maybe<ResourceTable::SearchResult> result = table_.FindResource(test::ParseNameOrDie("styleable/foo")); - AAPT_ASSERT_TRUE(result); - EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbol_status.state); + ASSERT_TRUE(result); + EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kPublic)); Attribute* attr = test::GetValue<Attribute>(&table_, "attr/bar"); - ASSERT_NE(attr, nullptr); + ASSERT_THAT(attr, NotNull()); EXPECT_TRUE(attr->IsWeak()); attr = test::GetValue<Attribute>(&table_, "attr/bat"); - ASSERT_NE(attr, nullptr); + ASSERT_THAT(attr, NotNull()); EXPECT_TRUE(attr->IsWeak()); attr = test::GetValue<Attribute>(&table_, "attr/baz"); - ASSERT_NE(attr, nullptr); + ASSERT_THAT(attr, NotNull()); EXPECT_TRUE(attr->IsWeak()); - EXPECT_EQ(1u, attr->symbols.size()); + EXPECT_THAT(attr->symbols, SizeIs(1)); - EXPECT_NE(nullptr, test::GetValue<Id>(&table_, "id/foo")); + EXPECT_THAT(test::GetValue<Id>(&table_, "id/foo"), NotNull()); Styleable* styleable = test::GetValue<Styleable>(&table_, "styleable/foo"); - ASSERT_NE(styleable, nullptr); - ASSERT_EQ(3u, styleable->entries.size()); + ASSERT_THAT(styleable, NotNull()); + ASSERT_THAT(styleable->entries, SizeIs(3)); - EXPECT_EQ(test::ParseNameOrDie("attr/bar"), - styleable->entries[0].name.value()); - EXPECT_EQ(test::ParseNameOrDie("attr/bat"), - styleable->entries[1].name.value()); + EXPECT_THAT(styleable->entries[0].name, Eq(make_value(test::ParseNameOrDie("attr/bar")))); + EXPECT_THAT(styleable->entries[1].name, Eq(make_value(test::ParseNameOrDie("attr/bat")))); + EXPECT_THAT(styleable->entries[2].name, Eq(make_value(test::ParseNameOrDie("attr/baz")))); } TEST_F(ResourceParserTest, ParsePrivateAttributesDeclareStyleable) { - std::string input = - "<declare-styleable name=\"foo\" " - "xmlns:privAndroid=\"http://schemas.android.com/apk/prv/res/android\">\n" - " <attr name=\"*android:bar\" />\n" - " <attr name=\"privAndroid:bat\" />\n" - "</declare-styleable>"; + std::string input = R"( + <declare-styleable xmlns:privAndroid="http://schemas.android.com/apk/prv/res/android" + name="foo"> + <attr name="*android:bar" /> + <attr name="privAndroid:bat" /> + </declare-styleable>)"; ASSERT_TRUE(TestParse(input)); Styleable* styleable = test::GetValue<Styleable>(&table_, "styleable/foo"); - ASSERT_NE(nullptr, styleable); - ASSERT_EQ(2u, styleable->entries.size()); + ASSERT_THAT(styleable, NotNull()); + ASSERT_THAT(styleable->entries, SizeIs(2)); EXPECT_TRUE(styleable->entries[0].private_reference); - AAPT_ASSERT_TRUE(styleable->entries[0].name); - EXPECT_EQ(std::string("android"), styleable->entries[0].name.value().package); + ASSERT_TRUE(styleable->entries[0].name); + EXPECT_THAT(styleable->entries[0].name.value().package, Eq("android")); EXPECT_TRUE(styleable->entries[1].private_reference); - AAPT_ASSERT_TRUE(styleable->entries[1].name); - EXPECT_EQ(std::string("android"), styleable->entries[1].name.value().package); + ASSERT_TRUE(styleable->entries[1].name); + EXPECT_THAT(styleable->entries[1].name.value().package, Eq("android")); } TEST_F(ResourceParserTest, ParseArray) { - std::string input = - "<array name=\"foo\">\n" - " <item>@string/ref</item>\n" - " <item>hey</item>\n" - " <item>23</item>\n" - "</array>"; + std::string input = R"( + <array name="foo"> + <item>@string/ref</item> + <item>hey</item> + <item>23</item> + </array>)"; ASSERT_TRUE(TestParse(input)); Array* array = test::GetValue<Array>(&table_, "array/foo"); - ASSERT_NE(array, nullptr); - ASSERT_EQ(3u, array->items.size()); + ASSERT_THAT(array, NotNull()); + ASSERT_THAT(array->elements, SizeIs(3)); - EXPECT_NE(nullptr, ValueCast<Reference>(array->items[0].get())); - EXPECT_NE(nullptr, ValueCast<String>(array->items[1].get())); - EXPECT_NE(nullptr, ValueCast<BinaryPrimitive>(array->items[2].get())); + 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) { - std::string input = R"EOF( + std::string input = R"( <string-array name="foo"> <item>"Werk"</item>" - </string-array>)EOF"; + </string-array>)"; ASSERT_TRUE(TestParse(input)); - EXPECT_NE(nullptr, test::GetValue<Array>(&table_, "array/foo")); + EXPECT_THAT(test::GetValue<Array>(&table_, "array/foo"), NotNull()); } TEST_F(ResourceParserTest, ParseArrayWithFormat) { - std::string input = R"EOF( + std::string input = R"( <array name="foo" format="string"> <item>100</item> - </array>)EOF"; + </array>)"; ASSERT_TRUE(TestParse(input)); Array* array = test::GetValue<Array>(&table_, "array/foo"); - ASSERT_NE(nullptr, array); + ASSERT_THAT(array, NotNull()); + ASSERT_THAT(array->elements, SizeIs(1)); - ASSERT_EQ(1u, array->items.size()); - - String* str = ValueCast<String>(array->items[0].get()); - ASSERT_NE(nullptr, str); - EXPECT_EQ(std::string("100"), *str->value); + String* str = ValueCast<String>(array->elements[0].get()); + ASSERT_THAT(str, NotNull()); + EXPECT_THAT(*str, StrValueEq("100")); } TEST_F(ResourceParserTest, ParseArrayWithBadFormat) { - std::string input = R"EOF( + std::string input = R"( <array name="foo" format="integer"> <item>Hi</item> - </array>)EOF"; + </array>)"; ASSERT_FALSE(TestParse(input)); } TEST_F(ResourceParserTest, ParsePlural) { - std::string input = - "<plurals name=\"foo\">\n" - " <item quantity=\"other\">apples</item>\n" - " <item quantity=\"one\">apple</item>\n" - "</plurals>"; + std::string input = R"( + <plurals name="foo"> + <item quantity="other">apples</item> + <item quantity="one">apple</item> + </plurals>)"; ASSERT_TRUE(TestParse(input)); Plural* plural = test::GetValue<Plural>(&table_, "plurals/foo"); - ASSERT_NE(nullptr, plural); - EXPECT_EQ(nullptr, plural->values[Plural::Zero]); - EXPECT_EQ(nullptr, plural->values[Plural::Two]); - EXPECT_EQ(nullptr, plural->values[Plural::Few]); - EXPECT_EQ(nullptr, plural->values[Plural::Many]); + ASSERT_THAT(plural, NotNull()); + EXPECT_THAT(plural->values[Plural::Zero], IsNull()); + EXPECT_THAT(plural->values[Plural::Two], IsNull()); + EXPECT_THAT(plural->values[Plural::Few], IsNull()); + EXPECT_THAT(plural->values[Plural::Many], IsNull()); - EXPECT_NE(nullptr, plural->values[Plural::One]); - EXPECT_NE(nullptr, plural->values[Plural::Other]); + EXPECT_THAT(plural->values[Plural::One], NotNull()); + EXPECT_THAT(plural->values[Plural::Other], NotNull()); } TEST_F(ResourceParserTest, ParseCommentsWithResource) { - std::string input = - "<!--This is a comment-->\n" - "<string name=\"foo\">Hi</string>"; + std::string input = R"( + <!--This is a comment--> + <string name="foo">Hi</string>)"; ASSERT_TRUE(TestParse(input)); String* value = test::GetValue<String>(&table_, "string/foo"); - ASSERT_NE(nullptr, value); - EXPECT_EQ(value->GetComment(), "This is a comment"); + ASSERT_THAT(value, NotNull()); + EXPECT_THAT(value->GetComment(), Eq("This is a comment")); } TEST_F(ResourceParserTest, DoNotCombineMultipleComments) { - std::string input = - "<!--One-->\n" - "<!--Two-->\n" - "<string name=\"foo\">Hi</string>"; + std::string input = R"( + <!--One--> + <!--Two--> + <string name="foo">Hi</string>)"; ASSERT_TRUE(TestParse(input)); String* value = test::GetValue<String>(&table_, "string/foo"); - ASSERT_NE(nullptr, value); - EXPECT_EQ(value->GetComment(), "Two"); + ASSERT_THAT(value, NotNull()); + EXPECT_THAT(value->GetComment(), Eq("Two")); } TEST_F(ResourceParserTest, IgnoreCommentBeforeEndTag) { - std::string input = - "<!--One-->\n" - "<string name=\"foo\">\n" - " Hi\n" - "<!--Two-->\n" - "</string>"; - + std::string input = R"( + <!--One--> + <string name="foo"> + Hi + <!--Two--> + </string>)"; ASSERT_TRUE(TestParse(input)); String* value = test::GetValue<String>(&table_, "string/foo"); - ASSERT_NE(nullptr, value); - EXPECT_EQ(value->GetComment(), "One"); + ASSERT_THAT(value, NotNull()); + EXPECT_THAT(value->GetComment(), Eq("One")); } TEST_F(ResourceParserTest, ParseNestedComments) { // We only care about declare-styleable and enum/flag attributes because - // comments - // from those end up in R.java - std::string input = R"EOF( - <declare-styleable name="foo"> - <!-- The name of the bar --> - <attr name="barName" format="string|reference" /> - </declare-styleable> - - <attr name="foo"> - <!-- The very first --> - <enum name="one" value="1" /> - </attr>)EOF"; + // comments from those end up in R.java + std::string input = R"( + <declare-styleable name="foo"> + <!-- The name of the bar --> + <attr name="barName" format="string|reference" /> + </declare-styleable> + + <attr name="foo"> + <!-- The very first --> + <enum name="one" value="1" /> + </attr>)"; ASSERT_TRUE(TestParse(input)); Styleable* styleable = test::GetValue<Styleable>(&table_, "styleable/foo"); - ASSERT_NE(nullptr, styleable); - ASSERT_EQ(1u, styleable->entries.size()); - - EXPECT_EQ(StringPiece("The name of the bar"), - styleable->entries.front().GetComment()); + ASSERT_THAT(styleable, NotNull()); + ASSERT_THAT(styleable->entries, SizeIs(1)); + EXPECT_THAT(styleable->entries[0].GetComment(), Eq("The name of the bar")); Attribute* attr = test::GetValue<Attribute>(&table_, "attr/foo"); - ASSERT_NE(nullptr, attr); - ASSERT_EQ(1u, attr->symbols.size()); - - EXPECT_EQ(StringPiece("The very first"), - attr->symbols.front().symbol.GetComment()); + ASSERT_THAT(attr, NotNull()); + ASSERT_THAT(attr->symbols, SizeIs(1)); + EXPECT_THAT(attr->symbols[0].symbol.GetComment(), Eq("The very first")); } -/* - * Declaring an ID as public should not require a separate definition - * (as an ID has no value). - */ +// Declaring an ID as public should not require a separate definition (as an ID has no value). TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) { - std::string input = "<public type=\"id\" name=\"foo\"/>"; - ASSERT_TRUE(TestParse(input)); - - Id* id = test::GetValue<Id>(&table_, "id/foo"); - ASSERT_NE(nullptr, id); + ASSERT_TRUE(TestParse(R"(<public type="id" name="foo"/>)")); + ASSERT_THAT(test::GetValue<Id>(&table_, "id/foo"), NotNull()); } TEST_F(ResourceParserTest, KeepAllProducts) { - std::string input = R"EOF( - <string name="foo" product="phone">hi</string> - <string name="foo" product="no-sdcard">ho</string> - <string name="bar" product="">wee</string> - <string name="baz">woo</string> - <string name="bit" product="phablet">hoot</string> - <string name="bot" product="default">yes</string> - )EOF"; + std::string input = R"( + <string name="foo" product="phone">hi</string> + <string name="foo" product="no-sdcard">ho</string> + <string name="bar" product="">wee</string> + <string name="baz">woo</string> + <string name="bit" product="phablet">hoot</string> + <string name="bot" product="default">yes</string>)"; ASSERT_TRUE(TestParse(input)); - EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>( - &table_, "string/foo", - ConfigDescription::DefaultConfig(), "phone")); - EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>( - &table_, "string/foo", - ConfigDescription::DefaultConfig(), "no-sdcard")); - EXPECT_NE(nullptr, - test::GetValueForConfigAndProduct<String>( - &table_, "string/bar", ConfigDescription::DefaultConfig(), "")); - EXPECT_NE(nullptr, - test::GetValueForConfigAndProduct<String>( - &table_, "string/baz", ConfigDescription::DefaultConfig(), "")); - EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>( - &table_, "string/bit", - ConfigDescription::DefaultConfig(), "phablet")); - EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<String>( - &table_, "string/bot", - ConfigDescription::DefaultConfig(), "default")); + ASSERT_THAT(test::GetValueForConfigAndProduct<String>(&table_, "string/foo", ConfigDescription::DefaultConfig(), "phone"), NotNull()); + ASSERT_THAT(test::GetValueForConfigAndProduct<String>(&table_, "string/foo",ConfigDescription::DefaultConfig(), "no-sdcard"), NotNull()); + ASSERT_THAT(test::GetValueForConfigAndProduct<String>(&table_, "string/bar", ConfigDescription::DefaultConfig(), ""), NotNull()); + ASSERT_THAT(test::GetValueForConfigAndProduct<String>(&table_, "string/baz", ConfigDescription::DefaultConfig(), ""), NotNull()); + ASSERT_THAT(test::GetValueForConfigAndProduct<String>(&table_, "string/bit", ConfigDescription::DefaultConfig(), "phablet"), NotNull()); + ASSERT_THAT(test::GetValueForConfigAndProduct<String>(&table_, "string/bot", ConfigDescription::DefaultConfig(), "default"), NotNull()); } TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) { - std::string input = R"EOF( - <public-group type="attr" first-id="0x01010040"> - <public name="foo" /> - <public name="bar" /> - </public-group>)EOF"; + std::string input = R"( + <public-group type="attr" first-id="0x01010040"> + <public name="foo" /> + <public name="bar" /> + </public-group>)"; ASSERT_TRUE(TestParse(input)); - Maybe<ResourceTable::SearchResult> result = - table_.FindResource(test::ParseNameOrDie("attr/foo")); - AAPT_ASSERT_TRUE(result); + Maybe<ResourceTable::SearchResult> result = table_.FindResource(test::ParseNameOrDie("attr/foo")); + ASSERT_TRUE(result); - AAPT_ASSERT_TRUE(result.value().package->id); - AAPT_ASSERT_TRUE(result.value().type->id); - AAPT_ASSERT_TRUE(result.value().entry->id); + ASSERT_TRUE(result.value().package->id); + ASSERT_TRUE(result.value().type->id); + ASSERT_TRUE(result.value().entry->id); ResourceId actual_id(result.value().package->id.value(), result.value().type->id.value(), result.value().entry->id.value()); - EXPECT_EQ(ResourceId(0x01010040), actual_id); + EXPECT_THAT(actual_id, Eq(ResourceId(0x01010040))); result = table_.FindResource(test::ParseNameOrDie("attr/bar")); - AAPT_ASSERT_TRUE(result); + ASSERT_TRUE(result); - AAPT_ASSERT_TRUE(result.value().package->id); - AAPT_ASSERT_TRUE(result.value().type->id); - AAPT_ASSERT_TRUE(result.value().entry->id); + ASSERT_TRUE(result.value().package->id); + ASSERT_TRUE(result.value().type->id); + ASSERT_TRUE(result.value().entry->id); actual_id = ResourceId(result.value().package->id.value(), result.value().type->id.value(), result.value().entry->id.value()); - EXPECT_EQ(ResourceId(0x01010041), actual_id); + EXPECT_THAT(actual_id, Eq(ResourceId(0x01010041))); } TEST_F(ResourceParserTest, ExternalTypesShouldOnlyBeReferences) { - std::string input = - R"EOF(<item type="layout" name="foo">@layout/bar</item>)EOF"; - ASSERT_TRUE(TestParse(input)); - - input = R"EOF(<item type="layout" name="bar">"this is a string"</item>)EOF"; - ASSERT_FALSE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<item type="layout" name="foo">@layout/bar</item>)")); + ASSERT_FALSE(TestParse(R"(<item type="layout" name="bar">"this is a string"</item>)")); } TEST_F(ResourceParserTest, AddResourcesElementShouldAddEntryWithUndefinedSymbol) { - std::string input = R"EOF(<add-resource name="bar" type="string" />)EOF"; - ASSERT_TRUE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<add-resource name="bar" type="string" />)")); Maybe<ResourceTable::SearchResult> result = table_.FindResource(test::ParseNameOrDie("string/bar")); - AAPT_ASSERT_TRUE(result); + ASSERT_TRUE(result); const ResourceEntry* entry = result.value().entry; - ASSERT_NE(nullptr, entry); - EXPECT_EQ(SymbolState::kUndefined, entry->symbol_status.state); + ASSERT_THAT(entry, NotNull()); + EXPECT_THAT(entry->symbol_status.state, Eq(SymbolState::kUndefined)); EXPECT_TRUE(entry->symbol_status.allow_new); } TEST_F(ResourceParserTest, ParseItemElementWithFormat) { - std::string input = R"(<item name="foo" type="integer" format="float">0.3</item>)"; - ASSERT_TRUE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<item name="foo" type="integer" format="float">0.3</item>)")); BinaryPrimitive* val = test::GetValue<BinaryPrimitive>(&table_, "integer/foo"); ASSERT_THAT(val, NotNull()); - EXPECT_THAT(val->value.dataType, Eq(android::Res_value::TYPE_FLOAT)); + EXPECT_THAT(val->value.dataType, Eq(Res_value::TYPE_FLOAT)); - input = R"(<item name="bar" type="integer" format="fraction">100</item>)"; - ASSERT_FALSE(TestParse(input)); + ASSERT_FALSE(TestParse(R"(<item name="bar" type="integer" format="fraction">100</item>)")); } // An <item> without a format specifier accepts all types of values. TEST_F(ResourceParserTest, ParseItemElementWithoutFormat) { - std::string input = R"(<item name="foo" type="integer">100%p</item>)"; - ASSERT_TRUE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<item name="foo" type="integer">100%p</item>)")); BinaryPrimitive* val = test::GetValue<BinaryPrimitive>(&table_, "integer/foo"); ASSERT_THAT(val, NotNull()); - EXPECT_THAT(val->value.dataType, Eq(android::Res_value::TYPE_FRACTION)); + EXPECT_THAT(val->value.dataType, Eq(Res_value::TYPE_FRACTION)); } TEST_F(ResourceParserTest, ParseConfigVaryingItem) { - std::string input = R"EOF(<item name="foo" type="configVarying">Hey</item>)EOF"; - ASSERT_TRUE(TestParse(input)); - ASSERT_NE(nullptr, test::GetValue<String>(&table_, "configVarying/foo")); + ASSERT_TRUE(TestParse(R"(<item name="foo" type="configVarying">Hey</item>)")); + ASSERT_THAT(test::GetValue<String>(&table_, "configVarying/foo"), NotNull()); } TEST_F(ResourceParserTest, ParseBagElement) { - std::string input = - R"EOF(<bag name="bag" type="configVarying"><item name="test">Hello!</item></bag>)EOF"; + std::string input = R"( + <bag name="bag" type="configVarying"> + <item name="test">Hello!</item> + </bag>)"; ASSERT_TRUE(TestParse(input)); Style* val = test::GetValue<Style>(&table_, "configVarying/bag"); - ASSERT_NE(nullptr, val); + ASSERT_THAT(val, NotNull()); + ASSERT_THAT(val->entries, SizeIs(1)); - ASSERT_EQ(1u, val->entries.size()); - EXPECT_EQ(Reference(test::ParseNameOrDie("attr/test")), val->entries[0].key); - EXPECT_NE(nullptr, ValueCast<RawString>(val->entries[0].value.get())); + EXPECT_THAT(val->entries[0].key, Eq(Reference(test::ParseNameOrDie("attr/test")))); + EXPECT_THAT(ValueCast<RawString>(val->entries[0].value.get()), NotNull()); } TEST_F(ResourceParserTest, ParseElementWithNoValue) { @@ -840,12 +783,11 @@ TEST_F(ResourceParserTest, ParseElementWithNoValue) { String* str = test::GetValue<String>(&table_, "string/foo"); ASSERT_THAT(str, NotNull()); - EXPECT_THAT(*str->value, Eq("")); + EXPECT_THAT(*str, StrValueEq("")); } TEST_F(ResourceParserTest, ParsePlatformIndependentNewline) { - std::string input = R"(<string name="foo">%1$s %n %2$s</string>)"; - ASSERT_TRUE(TestParse(input)); + ASSERT_TRUE(TestParse(R"(<string name="foo">%1$s %n %2$s</string>)")); } } // namespace aapt diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index 168004f0b721..ab59560d33a3 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -15,20 +15,24 @@ */ #include "ResourceTable.h" -#include "ConfigDescription.h" -#include "NameMangler.h" -#include "ResourceValues.h" -#include "ValueVisitor.h" -#include "util/Util.h" -#include <android-base/logging.h> -#include <androidfw/ResourceTypes.h> #include <algorithm> #include <memory> #include <string> #include <tuple> -using android::StringPiece; +#include "android-base/logging.h" +#include "androidfw/ResourceTypes.h" + +#include "ConfigDescription.h" +#include "NameMangler.h" +#include "ResourceValues.h" +#include "ValueVisitor.h" +#include "text/Unicode.h" +#include "util/Util.h" + +using ::aapt::text::IsValidResourceEntryName; +using ::android::StringPiece; namespace aapt { @@ -283,12 +287,9 @@ ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* exist return CollisionResult::kConflict; } -static constexpr const char* kValidNameChars = "._-"; - static StringPiece ValidateName(const StringPiece& name) { - auto iter = util::FindNonAlphaNumericAndNotInSet(name, kValidNameChars); - if (iter != name.end()) { - return StringPiece(iter, 1); + if (!IsValidResourceEntryName(name)) { + return name; } return {}; } diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp index e2b37be994ff..2a3c131f7b4b 100644 --- a/tools/aapt2/ResourceTable_test.cpp +++ b/tools/aapt2/ResourceTable_test.cpp @@ -24,6 +24,8 @@ #include <ostream> #include <string> +using ::testing::NotNull; + namespace aapt { TEST(ResourceTableTest, FailToAddResourceWithBadName) { @@ -56,7 +58,7 @@ TEST(ResourceTableTest, AddOneResource) { test::ValueBuilder<Id>().SetSource("test/path/file.xml", 23u).Build(), test::GetDiagnostics())); - ASSERT_NE(nullptr, test::GetValue<Id>(&table, "android:attr/id")); + EXPECT_THAT(test::GetValue<Id>(&table, "android:attr/id"), NotNull()); } TEST(ResourceTableTest, AddMultipleResources) { @@ -88,11 +90,10 @@ TEST(ResourceTableTest, AddMultipleResources) { .Build(), test::GetDiagnostics())); - ASSERT_NE(nullptr, test::GetValue<Id>(&table, "android:attr/layout_width")); - ASSERT_NE(nullptr, test::GetValue<Id>(&table, "android:attr/id")); - ASSERT_NE(nullptr, test::GetValue<Id>(&table, "android:string/ok")); - ASSERT_NE(nullptr, test::GetValueForConfig<BinaryPrimitive>( - &table, "android:string/ok", language_config)); + EXPECT_THAT(test::GetValue<Id>(&table, "android:attr/layout_width"), NotNull()); + EXPECT_THAT(test::GetValue<Id>(&table, "android:attr/id"), NotNull()); + EXPECT_THAT(test::GetValue<Id>(&table, "android:string/ok"), NotNull()); + EXPECT_THAT(test::GetValueForConfig<BinaryPrimitive>(&table, "android:string/ok", language_config), NotNull()); } TEST(ResourceTableTest, OverrideWeakResourceValue) { @@ -103,7 +104,7 @@ TEST(ResourceTableTest, OverrideWeakResourceValue) { util::make_unique<Attribute>(true), test::GetDiagnostics())); Attribute* attr = test::GetValue<Attribute>(&table, "android:attr/foo"); - ASSERT_NE(nullptr, attr); + ASSERT_THAT(attr, NotNull()); EXPECT_TRUE(attr->IsWeak()); ASSERT_TRUE(table.AddResource( @@ -111,7 +112,7 @@ TEST(ResourceTableTest, OverrideWeakResourceValue) { util::make_unique<Attribute>(false), test::GetDiagnostics())); attr = test::GetValue<Attribute>(&table, "android:attr/foo"); - ASSERT_NE(nullptr, attr); + ASSERT_THAT(attr, NotNull()); EXPECT_FALSE(attr->IsWeak()); } @@ -127,16 +128,12 @@ TEST(ResourceTableTest, ProductVaryingValues) { util::make_unique<Id>(), test::GetDiagnostics())); - EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>( - &table, "android:string/foo", - test::ParseConfigOrDie("land"), "tablet")); - EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>( - &table, "android:string/foo", - test::ParseConfigOrDie("land"), "phone")); + EXPECT_THAT(test::GetValueForConfigAndProduct<Id>(&table, "android:string/foo",test::ParseConfigOrDie("land"), "tablet"), NotNull()); + EXPECT_THAT(test::GetValueForConfigAndProduct<Id>(&table, "android:string/foo",test::ParseConfigOrDie("land"), "phone"), NotNull()); Maybe<ResourceTable::SearchResult> sr = table.FindResource(test::ParseNameOrDie("android:string/foo")); - AAPT_ASSERT_TRUE(sr); + ASSERT_TRUE(sr); std::vector<ResourceConfigValue*> values = sr.value().entry->FindAllValues(test::ParseConfigOrDie("land")); ASSERT_EQ(2u, values.size()); diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index deeef6ebbcb7..f193fe0c6593 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -512,7 +512,7 @@ std::unique_ptr<BinaryPrimitive> MakeBool(bool val) { } std::unique_ptr<BinaryPrimitive> TryParseInt(const StringPiece& str) { - std::u16string str16 = util::Utf8ToUtf16(str); + std::u16string str16 = util::Utf8ToUtf16(util::TrimWhitespace(str)); android::Res_value value; if (!android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { return {}; @@ -521,7 +521,7 @@ std::unique_ptr<BinaryPrimitive> TryParseInt(const StringPiece& str) { } std::unique_ptr<BinaryPrimitive> TryParseFloat(const StringPiece& str) { - std::u16string str16 = util::Utf8ToUtf16(str); + std::u16string str16 = util::Utf8ToUtf16(util::TrimWhitespace(str)); android::Res_value value; if (!android::ResTable::stringToFloat(str16.data(), str16.size(), &value)) { return {}; @@ -700,7 +700,7 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const Config spans++; } return util::make_unique<StyledString>(dst_pool->MakeRef( - style_str, StringPool::Context(StringPool::Context::kStylePriority, config))); + style_str, StringPool::Context(StringPool::Context::kNormalPriority, config))); } else { if (type != ResourceType::kString && util::StartsWith(str, "res/")) { // This must be a FileReference. diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp index cdc34f1ec752..e637c3ee2f3c 100644 --- a/tools/aapt2/ResourceUtils_test.cpp +++ b/tools/aapt2/ResourceUtils_test.cpp @@ -20,96 +20,90 @@ #include "test/Test.h" using ::aapt::test::ValueEq; +using ::android::Res_value; +using ::android::ResTable_map; +using ::testing::Eq; +using ::testing::NotNull; using ::testing::Pointee; namespace aapt { TEST(ResourceUtilsTest, ParseBool) { - EXPECT_EQ(Maybe<bool>(true), ResourceUtils::ParseBool("true")); - EXPECT_EQ(Maybe<bool>(true), ResourceUtils::ParseBool("TRUE")); - EXPECT_EQ(Maybe<bool>(true), ResourceUtils::ParseBool("True")); - EXPECT_EQ(Maybe<bool>(false), ResourceUtils::ParseBool("false")); - EXPECT_EQ(Maybe<bool>(false), ResourceUtils::ParseBool("FALSE")); - EXPECT_EQ(Maybe<bool>(false), ResourceUtils::ParseBool("False")); + EXPECT_THAT(ResourceUtils::ParseBool("true"), Eq(Maybe<bool>(true))); + EXPECT_THAT(ResourceUtils::ParseBool("TRUE"), Eq(Maybe<bool>(true))); + EXPECT_THAT(ResourceUtils::ParseBool("True"), Eq(Maybe<bool>(true))); + + EXPECT_THAT(ResourceUtils::ParseBool("false"), Eq(Maybe<bool>(false))); + EXPECT_THAT(ResourceUtils::ParseBool("FALSE"), Eq(Maybe<bool>(false))); + EXPECT_THAT(ResourceUtils::ParseBool("False"), Eq(Maybe<bool>(false))); + + EXPECT_THAT(ResourceUtils::ParseBool(" False\n "), Eq(Maybe<bool>(false))); } TEST(ResourceUtilsTest, ParseResourceName) { ResourceNameRef actual; bool actual_priv = false; - EXPECT_TRUE(ResourceUtils::ParseResourceName("android:color/foo", &actual, - &actual_priv)); - EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual); + EXPECT_TRUE(ResourceUtils::ParseResourceName("android:color/foo", &actual, &actual_priv)); + EXPECT_THAT(actual, Eq(ResourceNameRef("android", ResourceType::kColor, "foo"))); EXPECT_FALSE(actual_priv); - EXPECT_TRUE( - ResourceUtils::ParseResourceName("color/foo", &actual, &actual_priv)); - EXPECT_EQ(ResourceNameRef({}, ResourceType::kColor, "foo"), actual); + EXPECT_TRUE(ResourceUtils::ParseResourceName("color/foo", &actual, &actual_priv)); + EXPECT_THAT(actual, Eq(ResourceNameRef({}, ResourceType::kColor, "foo"))); EXPECT_FALSE(actual_priv); - EXPECT_TRUE(ResourceUtils::ParseResourceName("*android:color/foo", &actual, - &actual_priv)); - EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual); + EXPECT_TRUE(ResourceUtils::ParseResourceName("*android:color/foo", &actual, &actual_priv)); + EXPECT_THAT(actual, Eq(ResourceNameRef("android", ResourceType::kColor, "foo"))); EXPECT_TRUE(actual_priv); EXPECT_FALSE(ResourceUtils::ParseResourceName(android::StringPiece(), &actual, &actual_priv)); } TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) { - ResourceNameRef expected({}, ResourceType::kColor, "foo"); ResourceNameRef actual; bool create = false; bool private_ref = false; - EXPECT_TRUE(ResourceUtils::ParseReference("@color/foo", &actual, &create, - &private_ref)); - EXPECT_EQ(expected, actual); + EXPECT_TRUE(ResourceUtils::ParseReference("@color/foo", &actual, &create, &private_ref)); + EXPECT_THAT(actual, Eq(ResourceNameRef({}, ResourceType::kColor, "foo"))); EXPECT_FALSE(create); EXPECT_FALSE(private_ref); } TEST(ResourceUtilsTest, ParseReferenceWithPackage) { - ResourceNameRef expected("android", ResourceType::kColor, "foo"); ResourceNameRef actual; bool create = false; bool private_ref = false; - EXPECT_TRUE(ResourceUtils::ParseReference("@android:color/foo", &actual, - &create, &private_ref)); - EXPECT_EQ(expected, actual); + EXPECT_TRUE(ResourceUtils::ParseReference("@android:color/foo", &actual, &create, &private_ref)); + EXPECT_THAT(actual, Eq(ResourceNameRef("android", ResourceType::kColor, "foo"))); EXPECT_FALSE(create); EXPECT_FALSE(private_ref); } TEST(ResourceUtilsTest, ParseReferenceWithSurroundingWhitespace) { - ResourceNameRef expected("android", ResourceType::kColor, "foo"); ResourceNameRef actual; bool create = false; bool private_ref = false; - EXPECT_TRUE(ResourceUtils::ParseReference("\t @android:color/foo\n \n\t", - &actual, &create, &private_ref)); - EXPECT_EQ(expected, actual); + EXPECT_TRUE(ResourceUtils::ParseReference("\t @android:color/foo\n \n\t", &actual, &create, &private_ref)); + EXPECT_THAT(actual, Eq(ResourceNameRef("android", ResourceType::kColor, "foo"))); EXPECT_FALSE(create); EXPECT_FALSE(private_ref); } TEST(ResourceUtilsTest, ParseAutoCreateIdReference) { - ResourceNameRef expected("android", ResourceType::kId, "foo"); ResourceNameRef actual; bool create = false; bool private_ref = false; - EXPECT_TRUE(ResourceUtils::ParseReference("@+android:id/foo", &actual, - &create, &private_ref)); - EXPECT_EQ(expected, actual); + EXPECT_TRUE(ResourceUtils::ParseReference("@+android:id/foo", &actual, &create, &private_ref)); + EXPECT_THAT(actual, Eq(ResourceNameRef("android", ResourceType::kId, "foo"))); EXPECT_TRUE(create); EXPECT_FALSE(private_ref); } TEST(ResourceUtilsTest, ParsePrivateReference) { - ResourceNameRef expected("android", ResourceType::kId, "foo"); ResourceNameRef actual; bool create = false; bool private_ref = false; - EXPECT_TRUE(ResourceUtils::ParseReference("@*android:id/foo", &actual, - &create, &private_ref)); - EXPECT_EQ(expected, actual); + EXPECT_TRUE(ResourceUtils::ParseReference("@*android:id/foo", &actual, &create, &private_ref)); + EXPECT_THAT(actual, Eq(ResourceNameRef("android", ResourceType::kId, "foo"))); EXPECT_FALSE(create); EXPECT_TRUE(private_ref); } @@ -118,8 +112,7 @@ TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) { bool create = false; bool private_ref = false; ResourceNameRef actual; - EXPECT_FALSE(ResourceUtils::ParseReference("@+android:color/foo", &actual, - &create, &private_ref)); + EXPECT_FALSE(ResourceUtils::ParseReference("@+android:color/foo", &actual, &create, &private_ref)); } TEST(ResourceUtilsTest, ParseAttributeReferences) { @@ -143,82 +136,81 @@ TEST(ResourceUtilsTest, FailParseIncompleteReference) { } TEST(ResourceUtilsTest, ParseStyleParentReference) { - const ResourceName kAndroidStyleFooName("android", ResourceType::kStyle, - "foo"); + const ResourceName kAndroidStyleFooName("android", ResourceType::kStyle, "foo"); const ResourceName kStyleFooName({}, ResourceType::kStyle, "foo"); std::string err_str; - Maybe<Reference> ref = - ResourceUtils::ParseStyleParentReference("@android:style/foo", &err_str); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + Maybe<Reference> ref = ResourceUtils::ParseStyleParentReference("@android:style/foo", &err_str); + ASSERT_TRUE(ref); + EXPECT_THAT(ref.value().name, Eq(make_value(kAndroidStyleFooName))); ref = ResourceUtils::ParseStyleParentReference("@style/foo", &err_str); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kStyleFooName); + ASSERT_TRUE(ref); + EXPECT_THAT(ref.value().name, Eq(make_value(kStyleFooName))); - ref = - ResourceUtils::ParseStyleParentReference("?android:style/foo", &err_str); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + ref = ResourceUtils::ParseStyleParentReference("?android:style/foo", &err_str); + ASSERT_TRUE(ref); + EXPECT_THAT(ref.value().name, Eq(make_value(kAndroidStyleFooName))); ref = ResourceUtils::ParseStyleParentReference("?style/foo", &err_str); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kStyleFooName); + ASSERT_TRUE(ref); + EXPECT_THAT(ref.value().name, Eq(make_value(kStyleFooName))); ref = ResourceUtils::ParseStyleParentReference("android:style/foo", &err_str); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + ASSERT_TRUE(ref); + EXPECT_THAT(ref.value().name, Eq(make_value(kAndroidStyleFooName))); ref = ResourceUtils::ParseStyleParentReference("android:foo", &err_str); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + ASSERT_TRUE(ref); + EXPECT_THAT(ref.value().name, Eq(make_value(kAndroidStyleFooName))); ref = ResourceUtils::ParseStyleParentReference("@android:foo", &err_str); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + ASSERT_TRUE(ref); + EXPECT_THAT(ref.value().name, Eq(make_value(kAndroidStyleFooName))); ref = ResourceUtils::ParseStyleParentReference("foo", &err_str); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kStyleFooName); + ASSERT_TRUE(ref); + EXPECT_THAT(ref.value().name, Eq(make_value(kStyleFooName))); - ref = - ResourceUtils::ParseStyleParentReference("*android:style/foo", &err_str); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + ref = ResourceUtils::ParseStyleParentReference("*android:style/foo", &err_str); + ASSERT_TRUE(ref); + EXPECT_THAT(ref.value().name, Eq(make_value(kAndroidStyleFooName))); EXPECT_TRUE(ref.value().private_reference); } TEST(ResourceUtilsTest, ParseEmptyFlag) { std::unique_ptr<Attribute> attr = test::AttributeBuilder(false) - .SetTypeMask(android::ResTable_map::TYPE_FLAGS) + .SetTypeMask(ResTable_map::TYPE_FLAGS) .AddItem("one", 0x01) .AddItem("two", 0x02) .Build(); - std::unique_ptr<BinaryPrimitive> result = - ResourceUtils::TryParseFlagSymbol(attr.get(), ""); - ASSERT_NE(nullptr, result); - EXPECT_EQ(0u, result->value.data); + std::unique_ptr<BinaryPrimitive> result = ResourceUtils::TryParseFlagSymbol(attr.get(), ""); + ASSERT_THAT(result, NotNull()); + EXPECT_THAT(result->value.data, Eq(0u)); } TEST(ResourceUtilsTest, NullIsEmptyReference) { - auto null_value = ResourceUtils::MakeNull(); - ASSERT_THAT(null_value, Pointee(ValueEq(Reference()))); - - auto value = ResourceUtils::TryParseNullOrEmpty("@null"); - ASSERT_THAT(value, Pointee(ValueEq(Reference()))); + ASSERT_THAT(ResourceUtils::MakeNull(), Pointee(ValueEq(Reference()))); + ASSERT_THAT(ResourceUtils::TryParseNullOrEmpty("@null"), Pointee(ValueEq(Reference()))); } TEST(ResourceUtilsTest, EmptyIsBinaryPrimitive) { - auto empty_value = ResourceUtils::MakeEmpty(); - ASSERT_THAT(empty_value, Pointee(ValueEq(BinaryPrimitive(android::Res_value::TYPE_NULL, - android::Res_value::DATA_NULL_EMPTY)))); + ASSERT_THAT(ResourceUtils::MakeEmpty(), Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_NULL, Res_value::DATA_NULL_EMPTY)))); + ASSERT_THAT(ResourceUtils::TryParseNullOrEmpty("@empty"), Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_NULL, Res_value::DATA_NULL_EMPTY)))); +} + +TEST(ResourceUtilsTest, ItemsWithWhitespaceAreParsedCorrectly) { + EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(" 12\n ", ResTable_map::TYPE_INTEGER), + Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_INT_DEC, 12u)))); + EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(" true\n ", ResTable_map::TYPE_BOOLEAN), + Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_INT_BOOLEAN, 0xffffffffu)))); - auto value = ResourceUtils::TryParseNullOrEmpty("@empty"); - ASSERT_THAT(value, Pointee(ValueEq(BinaryPrimitive(android::Res_value::TYPE_NULL, - android::Res_value::DATA_NULL_EMPTY)))); + const float expected_float = 12.0f; + const uint32_t expected_float_flattened = *(uint32_t*)&expected_float; + EXPECT_THAT(ResourceUtils::TryParseItemForAttribute(" 12.0\n ", ResTable_map::TYPE_FLOAT), + Pointee(ValueEq(BinaryPrimitive(Res_value::TYPE_FLOAT, expected_float_flattened)))); } } // namespace aapt diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index e808984c75f5..1cba19462839 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -253,10 +253,9 @@ StyledString* StyledString::Clone(StringPool* new_pool) const { } void StyledString::Print(std::ostream* out) const { - *out << "(styled string) \"" << *value->str << "\""; + *out << "(styled string) \"" << value->value << "\""; for (const StringPool::Span& span : value->spans) { - *out << " " << *span.name << ":" << span.first_char << "," - << span.last_char; + *out << " " << *span.name << ":" << span.first_char << "," << span.last_char; } } @@ -533,75 +532,119 @@ void Attribute::Print(std::ostream* out) const { } } -static void BuildAttributeMismatchMessage(DiagMessage* msg, - const Attribute* attr, - const Item* value) { - *msg << "expected"; - if (attr->type_mask & android::ResTable_map::TYPE_BOOLEAN) { - *msg << " boolean"; +static void BuildAttributeMismatchMessage(const Attribute& attr, const Item& value, + DiagMessage* out_msg) { + *out_msg << "expected"; + if (attr.type_mask & android::ResTable_map::TYPE_BOOLEAN) { + *out_msg << " boolean"; } - if (attr->type_mask & android::ResTable_map::TYPE_COLOR) { - *msg << " color"; + if (attr.type_mask & android::ResTable_map::TYPE_COLOR) { + *out_msg << " color"; } - if (attr->type_mask & android::ResTable_map::TYPE_DIMENSION) { - *msg << " dimension"; + if (attr.type_mask & android::ResTable_map::TYPE_DIMENSION) { + *out_msg << " dimension"; } - if (attr->type_mask & android::ResTable_map::TYPE_ENUM) { - *msg << " enum"; + if (attr.type_mask & android::ResTable_map::TYPE_ENUM) { + *out_msg << " enum"; } - if (attr->type_mask & android::ResTable_map::TYPE_FLAGS) { - *msg << " flags"; + if (attr.type_mask & android::ResTable_map::TYPE_FLAGS) { + *out_msg << " flags"; } - if (attr->type_mask & android::ResTable_map::TYPE_FLOAT) { - *msg << " float"; + if (attr.type_mask & android::ResTable_map::TYPE_FLOAT) { + *out_msg << " float"; } - if (attr->type_mask & android::ResTable_map::TYPE_FRACTION) { - *msg << " fraction"; + if (attr.type_mask & android::ResTable_map::TYPE_FRACTION) { + *out_msg << " fraction"; } - if (attr->type_mask & android::ResTable_map::TYPE_INTEGER) { - *msg << " integer"; + if (attr.type_mask & android::ResTable_map::TYPE_INTEGER) { + *out_msg << " integer"; } - if (attr->type_mask & android::ResTable_map::TYPE_REFERENCE) { - *msg << " reference"; + if (attr.type_mask & android::ResTable_map::TYPE_REFERENCE) { + *out_msg << " reference"; } - if (attr->type_mask & android::ResTable_map::TYPE_STRING) { - *msg << " string"; + if (attr.type_mask & android::ResTable_map::TYPE_STRING) { + *out_msg << " string"; } - *msg << " but got " << *value; + *out_msg << " but got " << value; } -bool Attribute::Matches(const Item* item, DiagMessage* out_msg) const { +bool Attribute::Matches(const Item& item, DiagMessage* out_msg) const { + constexpr const uint32_t TYPE_ENUM = android::ResTable_map::TYPE_ENUM; + constexpr const uint32_t TYPE_FLAGS = android::ResTable_map::TYPE_FLAGS; + constexpr const uint32_t TYPE_INTEGER = android::ResTable_map::TYPE_INTEGER; + constexpr const uint32_t TYPE_REFERENCE = android::ResTable_map::TYPE_REFERENCE; + android::Res_value val = {}; - item->Flatten(&val); + item.Flatten(&val); + + const uint32_t flattened_data = util::DeviceToHost32(val.data); // Always allow references. - const uint32_t mask = type_mask | android::ResTable_map::TYPE_REFERENCE; - if (!(mask & ResourceUtils::AndroidTypeToAttributeTypeMask(val.dataType))) { + const uint32_t actual_type = ResourceUtils::AndroidTypeToAttributeTypeMask(val.dataType); + + // Only one type must match between the actual and expected. + if ((actual_type & (type_mask | TYPE_REFERENCE)) == 0) { if (out_msg) { - BuildAttributeMismatchMessage(out_msg, this, item); + BuildAttributeMismatchMessage(*this, item, out_msg); } return false; + } + + // Enums and flags are encoded as integers, so check them first before doing any range checks. + if ((type_mask & TYPE_ENUM) != 0 && (actual_type & TYPE_ENUM) != 0) { + for (const Symbol& s : symbols) { + if (flattened_data == s.value) { + return true; + } + } + + // If the attribute accepts integers, we can't fail here. + if ((type_mask & TYPE_INTEGER) == 0) { + if (out_msg) { + *out_msg << item << " is not a valid enum"; + } + return false; + } + } + + if ((type_mask & TYPE_FLAGS) != 0 && (actual_type & TYPE_FLAGS) != 0) { + uint32_t mask = 0u; + for (const Symbol& s : symbols) { + mask |= s.value; + } + + // Check if the flattened data is covered by the flag bit mask. + // If the attribute accepts integers, we can't fail here. + if ((mask & flattened_data) == flattened_data) { + return true; + } else if ((type_mask & TYPE_INTEGER) == 0) { + if (out_msg) { + *out_msg << item << " is not a valid flag"; + } + return false; + } + } - } else if (ResourceUtils::AndroidTypeToAttributeTypeMask(val.dataType) & - android::ResTable_map::TYPE_INTEGER) { - if (static_cast<int32_t>(util::DeviceToHost32(val.data)) < min_int) { + // Finally check the integer range of the value. + if ((type_mask & TYPE_INTEGER) != 0 && (actual_type & TYPE_INTEGER) != 0) { + if (static_cast<int32_t>(flattened_data) < min_int) { if (out_msg) { - *out_msg << *item << " is less than minimum integer " << min_int; + *out_msg << item << " is less than minimum integer " << min_int; } return false; - } else if (static_cast<int32_t>(util::DeviceToHost32(val.data)) > max_int) { + } else if (static_cast<int32_t>(flattened_data) > max_int) { if (out_msg) { - *out_msg << *item << " is greater than maximum integer " << max_int; + *out_msg << item << " is greater than maximum integer " << max_int; } return false; } @@ -762,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()); }); } @@ -777,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 ac5795fb9774..275864bbcd3e 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -264,7 +264,7 @@ struct Attribute : public BaseValue<Attribute> { Attribute* Clone(StringPool* new_pool) const override; void PrintMask(std::ostream* out) const; void Print(std::ostream* out) const override; - bool Matches(const Item* item, DiagMessage* out_msg) const; + bool Matches(const Item& item, DiagMessage* out_msg = nullptr) const; }; struct Style : public BaseValue<Style> { @@ -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 69f8e67530f6..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())); @@ -190,4 +190,52 @@ TEST(ResourcesValuesTest, EmptyReferenceFlattens) { EXPECT_EQ(0x0u, value.data); } +TEST(ResourcesValuesTest, AttributeMatches) { + constexpr const uint32_t TYPE_DIMENSION = android::ResTable_map::TYPE_DIMENSION; + constexpr const uint32_t TYPE_ENUM = android::ResTable_map::TYPE_ENUM; + constexpr const uint32_t TYPE_FLAGS = android::ResTable_map::TYPE_FLAGS; + constexpr const uint32_t TYPE_INTEGER = android::ResTable_map::TYPE_INTEGER; + constexpr const uint8_t TYPE_INT_DEC = android::Res_value::TYPE_INT_DEC; + + Attribute attr1(false /*weak*/, TYPE_DIMENSION); + EXPECT_FALSE(attr1.Matches(*ResourceUtils::TryParseColor("#7fff00"))); + EXPECT_TRUE(attr1.Matches(*ResourceUtils::TryParseFloat("23dp"))); + EXPECT_TRUE(attr1.Matches(*ResourceUtils::TryParseReference("@android:string/foo"))); + + Attribute attr2(false /*weak*/, TYPE_INTEGER | TYPE_ENUM); + attr2.min_int = 0; + attr2.symbols.push_back(Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), + static_cast<uint32_t>(-1)}); + EXPECT_FALSE(attr2.Matches(*ResourceUtils::TryParseColor("#7fff00"))); + EXPECT_TRUE(attr2.Matches(BinaryPrimitive(TYPE_INT_DEC, static_cast<uint32_t>(-1)))); + EXPECT_TRUE(attr2.Matches(BinaryPrimitive(TYPE_INT_DEC, 1u))); + EXPECT_FALSE(attr2.Matches(BinaryPrimitive(TYPE_INT_DEC, static_cast<uint32_t>(-2)))); + + Attribute attr3(false /*weak*/, TYPE_INTEGER | TYPE_FLAGS); + attr3.max_int = 100; + attr3.symbols.push_back( + Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u}); + attr3.symbols.push_back( + Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/bar")), 0x02u}); + attr3.symbols.push_back( + Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/baz")), 0x04u}); + attr3.symbols.push_back( + Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/bat")), 0x80u}); + EXPECT_FALSE(attr3.Matches(*ResourceUtils::TryParseColor("#7fff00"))); + EXPECT_TRUE(attr3.Matches(BinaryPrimitive(TYPE_INT_DEC, 0x01u | 0x02u))); + EXPECT_TRUE(attr3.Matches(BinaryPrimitive(TYPE_INT_DEC, 0x01u | 0x02u | 0x80u))); + + // Not a flag, but a value less than max_int. + EXPECT_TRUE(attr3.Matches(BinaryPrimitive(TYPE_INT_DEC, 0x08u))); + + // Not a flag and greater than max_int. + EXPECT_FALSE(attr3.Matches(BinaryPrimitive(TYPE_INT_DEC, 127u))); + + Attribute attr4(false /*weak*/, TYPE_ENUM); + attr4.symbols.push_back( + Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u}); + EXPECT_TRUE(attr4.Matches(BinaryPrimitive(TYPE_INT_DEC, 0x01u))); + EXPECT_FALSE(attr4.Matches(BinaryPrimitive(TYPE_INT_DEC, 0x02u))); +} + } // namespace aapt 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 e3745e8ce0da..5c32ed4fd849 100644 --- a/tools/aapt2/SdkConstants.h +++ b/tools/aapt2/SdkConstants.h @@ -52,6 +52,7 @@ enum : ApiVersion { SDK_NOUGAT = 24, SDK_NOUGAT_MR1 = 25, SDK_O = 26, + SDK_O_MR1 = 27, }; ApiVersion FindAttributeSdkLevel(const ResourceId& id); diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp index 57da5f01dcd1..705b1ab052af 100644 --- a/tools/aapt2/StringPool.cpp +++ b/tools/aapt2/StringPool.cpp @@ -27,7 +27,7 @@ #include "util/BigBuffer.h" #include "util/Util.h" -using android::StringPiece; +using ::android::StringPiece; namespace aapt { @@ -75,9 +75,14 @@ const std::string* StringPool::Ref::operator->() const { return &entry_->value; } -const std::string& StringPool::Ref::operator*() const { return entry_->value; } +const std::string& StringPool::Ref::operator*() const { + return entry_->value; +} -size_t StringPool::Ref::index() const { return entry_->index; } +size_t StringPool::Ref::index() const { + // Account for the styles, which *always* come first. + return entry_->pool_->styles_.size() + entry_->index_; +} const StringPool::Context& StringPool::Ref::GetContext() const { return entry_->context; @@ -104,8 +109,7 @@ StringPool::StyleRef::~StyleRef() { } } -StringPool::StyleRef& StringPool::StyleRef::operator=( - const StringPool::StyleRef& rhs) { +StringPool::StyleRef& StringPool::StyleRef::operator=(const StringPool::StyleRef& rhs) { if (rhs.entry_ != nullptr) { rhs.entry_->ref_++; } @@ -118,7 +122,7 @@ StringPool::StyleRef& StringPool::StyleRef::operator=( } bool StringPool::StyleRef::operator==(const StyleRef& rhs) const { - if (entry_->str != rhs.entry_->str) { + if (entry_->value != rhs.entry_->value) { return false; } @@ -137,7 +141,9 @@ bool StringPool::StyleRef::operator==(const StyleRef& rhs) const { return true; } -bool StringPool::StyleRef::operator!=(const StyleRef& rhs) const { return !operator==(rhs); } +bool StringPool::StyleRef::operator!=(const StyleRef& rhs) const { + return !operator==(rhs); +} const StringPool::StyleEntry* StringPool::StyleRef::operator->() const { return entry_; @@ -147,23 +153,24 @@ const StringPool::StyleEntry& StringPool::StyleRef::operator*() const { return *entry_; } -size_t StringPool::StyleRef::index() const { return entry_->str.index(); } +size_t StringPool::StyleRef::index() const { + return entry_->index_; +} const StringPool::Context& StringPool::StyleRef::GetContext() const { - return entry_->str.GetContext(); + return entry_->context; } StringPool::Ref StringPool::MakeRef(const StringPiece& str) { return MakeRefImpl(str, Context{}, true); } -StringPool::Ref StringPool::MakeRef(const StringPiece& str, - const Context& context) { +StringPool::Ref StringPool::MakeRef(const StringPiece& str, const Context& context) { return MakeRefImpl(str, context, true); } -StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str, - const Context& context, bool unique) { +StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str, const Context& context, + bool unique) { if (unique) { auto iter = indexed_strings_.find(str); if (iter != std::end(indexed_strings_)) { @@ -171,82 +178,87 @@ StringPool::Ref StringPool::MakeRefImpl(const StringPiece& str, } } - Entry* entry = new Entry(); + std::unique_ptr<Entry> entry(new Entry()); entry->value = str.to_string(); entry->context = context; - entry->index = strings_.size(); + entry->index_ = strings_.size(); entry->ref_ = 0; - strings_.emplace_back(entry); - indexed_strings_.insert(std::make_pair(StringPiece(entry->value), entry)); - return Ref(entry); + entry->pool_ = this; + + Entry* borrow = entry.get(); + strings_.emplace_back(std::move(entry)); + indexed_strings_.insert(std::make_pair(StringPiece(borrow->value), borrow)); + return Ref(borrow); } StringPool::StyleRef StringPool::MakeRef(const StyleString& str) { return MakeRef(str, Context{}); } -StringPool::StyleRef StringPool::MakeRef(const StyleString& str, - const Context& context) { - Entry* entry = new Entry(); +StringPool::StyleRef StringPool::MakeRef(const StyleString& str, const Context& context) { + std::unique_ptr<StyleEntry> entry(new StyleEntry()); entry->value = str.str; entry->context = context; - entry->index = strings_.size(); + entry->index_ = styles_.size(); entry->ref_ = 0; - strings_.emplace_back(entry); - indexed_strings_.insert(std::make_pair(StringPiece(entry->value), entry)); - - StyleEntry* style_entry = new StyleEntry(); - style_entry->str = Ref(entry); for (const aapt::Span& span : str.spans) { - style_entry->spans.emplace_back( - Span{MakeRef(span.name), span.first_char, span.last_char}); + entry->spans.emplace_back(Span{MakeRef(span.name), span.first_char, span.last_char}); } - style_entry->ref_ = 0; - styles_.emplace_back(style_entry); - return StyleRef(style_entry); + + StyleEntry* borrow = entry.get(); + styles_.emplace_back(std::move(entry)); + return StyleRef(borrow); } StringPool::StyleRef StringPool::MakeRef(const StyleRef& ref) { - Entry* entry = new Entry(); - entry->value = *ref.entry_->str; - entry->context = ref.entry_->str.entry_->context; - entry->index = strings_.size(); + std::unique_ptr<StyleEntry> entry(new StyleEntry()); + entry->value = ref.entry_->value; + entry->context = ref.entry_->context; + entry->index_ = styles_.size(); entry->ref_ = 0; - strings_.emplace_back(entry); - indexed_strings_.insert(std::make_pair(StringPiece(entry->value), entry)); - - StyleEntry* style_entry = new StyleEntry(); - style_entry->str = Ref(entry); for (const Span& span : ref.entry_->spans) { - style_entry->spans.emplace_back( - Span{MakeRef(*span.name), span.first_char, span.last_char}); + entry->spans.emplace_back(Span{MakeRef(*span.name), span.first_char, span.last_char}); + } + + StyleEntry* borrow = entry.get(); + styles_.emplace_back(std::move(entry)); + return StyleRef(borrow); +} + +void StringPool::ReAssignIndices() { + // Assign the style indices. + const size_t style_len = styles_.size(); + for (size_t index = 0; index < style_len; index++) { + styles_[index]->index_ = index; + } + + // Assign the string indices. + const size_t string_len = strings_.size(); + for (size_t index = 0; index < string_len; index++) { + strings_[index]->index_ = index; } - style_entry->ref_ = 0; - styles_.emplace_back(style_entry); - return StyleRef(style_entry); } void StringPool::Merge(StringPool&& pool) { - indexed_strings_.insert(pool.indexed_strings_.begin(), - pool.indexed_strings_.end()); - pool.indexed_strings_.clear(); - std::move(pool.strings_.begin(), pool.strings_.end(), - std::back_inserter(strings_)); - pool.strings_.clear(); - std::move(pool.styles_.begin(), pool.styles_.end(), - std::back_inserter(styles_)); + // First, change the owning pool for the incoming strings. + for (std::unique_ptr<Entry>& entry : pool.strings_) { + entry->pool_ = this; + } + + // Now move the styles, strings, and indices over. + std::move(pool.styles_.begin(), pool.styles_.end(), std::back_inserter(styles_)); pool.styles_.clear(); + std::move(pool.strings_.begin(), pool.strings_.end(), std::back_inserter(strings_)); + pool.strings_.clear(); + indexed_strings_.insert(pool.indexed_strings_.begin(), pool.indexed_strings_.end()); + pool.indexed_strings_.clear(); - // Assign the indices. - const size_t len = strings_.size(); - for (size_t index = 0; index < len; index++) { - strings_[index]->index = index; - } + ReAssignIndices(); } -void StringPool::HintWillAdd(size_t stringCount, size_t styleCount) { - strings_.reserve(strings_.size() + stringCount); - styles_.reserve(styles_.size() + styleCount); +void StringPool::HintWillAdd(size_t string_count, size_t style_count) { + strings_.reserve(strings_.size() + string_count); + styles_.reserve(styles_.size() + style_count); } void StringPool::Prune() { @@ -262,47 +274,42 @@ void StringPool::Prune() { auto end_iter2 = std::remove_if(strings_.begin(), strings_.end(), - [](const std::unique_ptr<Entry>& entry) -> bool { - return entry->ref_ <= 0; - }); - - auto end_iter3 = - std::remove_if(styles_.begin(), styles_.end(), - [](const std::unique_ptr<StyleEntry>& entry) -> bool { - return entry->ref_ <= 0; - }); - - // Remove the entries at the end or else we'll be accessing - // a deleted string from the StyleEntry. + [](const std::unique_ptr<Entry>& entry) -> bool { return entry->ref_ <= 0; }); + auto end_iter3 = std::remove_if( + styles_.begin(), styles_.end(), + [](const std::unique_ptr<StyleEntry>& entry) -> bool { return entry->ref_ <= 0; }); + + // Remove the entries at the end or else we'll be accessing a deleted string from the StyleEntry. strings_.erase(end_iter2, strings_.end()); styles_.erase(end_iter3, styles_.end()); - // Reassign the indices. - const size_t len = strings_.size(); - for (size_t index = 0; index < len; index++) { - strings_[index]->index = index; - } + ReAssignIndices(); } -void StringPool::Sort( - const std::function<bool(const Entry&, const Entry&)>& cmp) { - std::sort( - strings_.begin(), strings_.end(), - [&cmp](const std::unique_ptr<Entry>& a, - const std::unique_ptr<Entry>& b) -> bool { return cmp(*a, *b); }); - - // Assign the indices. - const size_t len = strings_.size(); - for (size_t index = 0; index < len; index++) { - strings_[index]->index = index; +template <typename E> +static void SortEntries( + std::vector<std::unique_ptr<E>>& entries, + const std::function<int(const StringPool::Context&, const StringPool::Context&)>& cmp) { + using UEntry = std::unique_ptr<E>; + + if (cmp != nullptr) { + std::sort(entries.begin(), entries.end(), [&cmp](const UEntry& a, const UEntry& b) -> bool { + int r = cmp(a->context, b->context); + if (r == 0) { + r = a->value.compare(b->value); + } + return r < 0; + }); + } else { + std::sort(entries.begin(), entries.end(), + [](const UEntry& a, const UEntry& b) -> bool { return a->value < b->value; }); } +} - // Reorder the styles. - std::sort(styles_.begin(), styles_.end(), - [](const std::unique_ptr<StyleEntry>& lhs, - const std::unique_ptr<StyleEntry>& rhs) -> bool { - return lhs->str.index() < rhs->str.index(); - }); +void StringPool::Sort(const std::function<int(const Context&, const Context&)>& cmp) { + SortEntries(styles_, cmp); + SortEntries(strings_, cmp); + ReAssignIndices(); } template <typename T> @@ -327,60 +334,31 @@ static size_t EncodedLengthUnits(size_t length) { return length > kMaxSize ? 2 : 1; } -bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8) { - const size_t start_index = out->size(); - android::ResStringPool_header* header = - out->NextBlock<android::ResStringPool_header>(); - header->header.type = android::RES_STRING_POOL_TYPE; - header->header.headerSize = sizeof(*header); - header->stringCount = pool.size(); +static void EncodeString(const std::string& str, const bool utf8, BigBuffer* out) { if (utf8) { - header->flags |= android::ResStringPool_header::UTF8_FLAG; - } - - uint32_t* indices = - pool.size() != 0 ? out->NextBlock<uint32_t>(pool.size()) : nullptr; - - uint32_t* style_indices = nullptr; - if (!pool.styles_.empty()) { - header->styleCount = pool.styles_.back()->str.index() + 1; - style_indices = out->NextBlock<uint32_t>(header->styleCount); - } - - const size_t before_strings_index = out->size(); - header->stringsStart = before_strings_index - start_index; - - for (const auto& entry : pool) { - *indices = out->size() - before_strings_index; - indices++; + const std::string& encoded = str; + const ssize_t utf16_length = + utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str.data()), str.size()); + CHECK(utf16_length >= 0); - if (utf8) { - const std::string& encoded = entry->value; - const ssize_t utf16_length = utf8_to_utf16_length( - reinterpret_cast<const uint8_t*>(entry->value.data()), - entry->value.size()); - CHECK(utf16_length >= 0); + const size_t total_size = EncodedLengthUnits<char>(utf16_length) + + EncodedLengthUnits<char>(encoded.length()) + encoded.size() + 1; - const size_t total_size = EncodedLengthUnits<char>(utf16_length) + - EncodedLengthUnits<char>(encoded.length()) + - encoded.size() + 1; + char* data = out->NextBlock<char>(total_size); - char* data = out->NextBlock<char>(total_size); - - // First encode the UTF16 string length. - data = EncodeLength(data, utf16_length); + // First encode the UTF16 string length. + data = EncodeLength(data, utf16_length); - // Now encode the size of the real UTF8 string. - data = EncodeLength(data, encoded.length()); - strncpy(data, encoded.data(), encoded.size()); + // Now encode the size of the real UTF8 string. + data = EncodeLength(data, encoded.length()); + strncpy(data, encoded.data(), encoded.size()); } else { - const std::u16string encoded = util::Utf8ToUtf16(entry->value); + const std::u16string encoded = util::Utf8ToUtf16(str); const ssize_t utf16_length = encoded.size(); // Total number of 16-bit words to write. - const size_t total_size = - EncodedLengthUnits<char16_t>(utf16_length) + encoded.size() + 1; + const size_t total_size = EncodedLengthUnits<char16_t>(utf16_length) + encoded.size() + 1; char16_t* data = out->NextBlock<char16_t>(total_size); @@ -395,31 +373,55 @@ bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8) { // The null-terminating character is already here due to the block of data // being set to 0s on allocation. } +} + +bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8) { + const size_t start_index = out->size(); + android::ResStringPool_header* header = out->NextBlock<android::ResStringPool_header>(); + header->header.type = util::HostToDevice16(android::RES_STRING_POOL_TYPE); + header->header.headerSize = util::HostToDevice16(sizeof(*header)); + header->stringCount = util::HostToDevice32(pool.size()); + header->styleCount = util::HostToDevice32(pool.styles_.size()); + if (utf8) { + header->flags |= android::ResStringPool_header::UTF8_FLAG; } - out->Align4(); + uint32_t* indices = pool.size() != 0 ? out->NextBlock<uint32_t>(pool.size()) : nullptr; + uint32_t* style_indices = + pool.styles_.size() != 0 ? out->NextBlock<uint32_t>(pool.styles_.size()) : nullptr; - if (!pool.styles_.empty()) { - const size_t before_styles_index = out->size(); - header->stylesStart = before_styles_index - start_index; + const size_t before_strings_index = out->size(); + header->stringsStart = before_strings_index - start_index; - size_t current_index = 0; - for (const auto& entry : pool.styles_) { - while (entry->str.index() > current_index) { - style_indices[current_index++] = out->size() - before_styles_index; + // Styles always come first. + for (const std::unique_ptr<StyleEntry>& entry : pool.styles_) { + *indices++ = out->size() - before_strings_index; + EncodeString(entry->value, utf8, out); + } - uint32_t* span_offset = out->NextBlock<uint32_t>(); - *span_offset = android::ResStringPool_span::END; - } - style_indices[current_index++] = out->size() - before_styles_index; - - android::ResStringPool_span* span = - out->NextBlock<android::ResStringPool_span>(entry->spans.size()); - for (const auto& s : entry->spans) { - span->name.index = s.name.index(); - span->firstChar = s.first_char; - span->lastChar = s.last_char; - span++; + for (const std::unique_ptr<Entry>& entry : pool.strings_) { + *indices++ = out->size() - before_strings_index; + EncodeString(entry->value, utf8, out); + } + + out->Align4(); + + if (style_indices != nullptr) { + const size_t before_styles_index = out->size(); + header->stylesStart = util::HostToDevice32(before_styles_index - start_index); + + for (const std::unique_ptr<StyleEntry>& entry : pool.styles_) { + *style_indices++ = out->size() - before_styles_index; + + if (!entry->spans.empty()) { + android::ResStringPool_span* span = + out->NextBlock<android::ResStringPool_span>(entry->spans.size()); + for (const Span& s : entry->spans) { + span->name.index = util::HostToDevice32(s.name.index()); + span->firstChar = util::HostToDevice32(s.first_char); + span->lastChar = util::HostToDevice32(s.last_char); + span++; + } } uint32_t* spanEnd = out->NextBlock<uint32_t>(); @@ -436,7 +438,7 @@ bool StringPool::Flatten(BigBuffer* out, const StringPool& pool, bool utf8) { memset(padding, 0xff, padding_length); out->Align4(); } - header->header.size = out->size() - start_index; + header->header.size = util::HostToDevice32(out->size() - start_index); return true; } diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h index d1232a29b5aa..8350d0d09108 100644 --- a/tools/aapt2/StringPool.h +++ b/tools/aapt2/StringPool.h @@ -42,12 +42,16 @@ struct StyleString { std::vector<Span> spans; }; +// A StringPool for storing the value of String and StyledString resources. +// Styles and Strings are stored separately, since the runtime variant of this +// class -- ResStringPool -- requires that styled strings *always* appear first, since their +// style data is stored as an array indexed by the same indices as the main string pool array. +// Otherwise, the style data array would have to be sparse and take up more space. class StringPool { public: class Context { public: enum : uint32_t { - kStylePriority = 0u, kHighPriority = 1u, kNormalPriority = 0x7fffffffu, kLowPriority = 0xffffffffu, @@ -58,8 +62,8 @@ class StringPool { Context() = default; Context(uint32_t p, const ConfigDescription& c) : priority(p), config(c) {} explicit Context(uint32_t p) : priority(p) {} - explicit Context(const ConfigDescription& c) - : priority(kNormalPriority), config(c) {} + explicit Context(const ConfigDescription& c) : priority(kNormalPriority), config(c) { + } }; class Entry; @@ -116,13 +120,14 @@ class StringPool { public: std::string value; Context context; - size_t index; private: friend class StringPool; friend class Ref; + size_t index_; int ref_; + const StringPool* pool_; }; struct Span { @@ -133,18 +138,18 @@ class StringPool { class StyleEntry { public: - Ref str; + std::string value; + Context context; std::vector<Span> spans; private: friend class StringPool; friend class StyleRef; + size_t index_; int ref_; }; - using const_iterator = std::vector<std::unique_ptr<Entry>>::const_iterator; - static bool FlattenUtf8(BigBuffer* out, const StringPool& pool); static bool FlattenUtf16(BigBuffer* out, const StringPool& pool); @@ -152,92 +157,61 @@ class StringPool { StringPool(StringPool&&) = default; StringPool& operator=(StringPool&&) = default; - /** - * Adds a string to the pool, unless it already exists. Returns - * a reference to the string in the pool. - */ + // Adds a string to the pool, unless it already exists. Returns a reference to the string in the + // pool. Ref MakeRef(const android::StringPiece& str); - /** - * Adds a string to the pool, unless it already exists, with a context - * object that can be used when sorting the string pool. Returns - * a reference to the string in the pool. - */ + // Adds a string to the pool, unless it already exists, with a context object that can be used + // when sorting the string pool. Returns a reference to the string in the pool. Ref MakeRef(const android::StringPiece& str, const Context& context); - /** - * Adds a style to the string pool and returns a reference to it. - */ + // Adds a style to the string pool and returns a reference to it. StyleRef MakeRef(const StyleString& str); - /** - * Adds a style to the string pool with a context object that - * can be used when sorting the string pool. Returns a reference - * to the style in the string pool. - */ + // Adds a style to the string pool with a context object that can be used when sorting the string + // pool. Returns a reference to the style in the string pool. StyleRef MakeRef(const StyleString& str, const Context& context); - /** - * Adds a style from another string pool. Returns a reference to the - * style in the string pool. - */ + // Adds a style from another string pool. Returns a reference to the style in the string pool. StyleRef MakeRef(const StyleRef& ref); - /** - * Moves pool into this one without coalescing strings. When this - * function returns, pool will be empty. - */ + // Moves pool into this one without coalescing strings. When this function returns, pool will be + // empty. void Merge(StringPool&& pool); - /** - * Returns the number of strings in the table. - */ - inline size_t size() const; + inline const std::vector<std::unique_ptr<Entry>>& strings() const { + return strings_; + } - /** - * Reserves space for strings and styles as an optimization. - */ + // Returns the number of strings in the table. + inline size_t size() const { + return styles_.size() + strings_.size(); + } + + // Reserves space for strings and styles as an optimization. void HintWillAdd(size_t string_count, size_t style_count); - /** - * Sorts the strings according to some comparison function. - */ - void Sort(const std::function<bool(const Entry&, const Entry&)>& cmp); + // Sorts the strings according to their Context using some comparison function. + // Equal Contexts are further sorted by string value, lexicographically. + // If no comparison function is provided, values are only sorted lexicographically. + void Sort(const std::function<int(const Context&, const Context&)>& cmp = nullptr); - /** - * Removes any strings that have no references. - */ + // Removes any strings that have no references. void Prune(); private: DISALLOW_COPY_AND_ASSIGN(StringPool); - friend const_iterator begin(const StringPool& pool); - friend const_iterator end(const StringPool& pool); - static bool Flatten(BigBuffer* out, const StringPool& pool, bool utf8); Ref MakeRefImpl(const android::StringPiece& str, const Context& context, bool unique); + void ReAssignIndices(); std::vector<std::unique_ptr<Entry>> strings_; std::vector<std::unique_ptr<StyleEntry>> styles_; std::unordered_multimap<android::StringPiece, Entry*> indexed_strings_; }; -// -// Inline implementation -// - -inline size_t StringPool::size() const { return strings_.size(); } - -inline StringPool::const_iterator begin(const StringPool& pool) { - return pool.strings_.begin(); -} - -inline StringPool::const_iterator end(const StringPool& pool) { - return pool.strings_.end(); -} - } // namespace aapt #endif // AAPT_STRING_POOL_H diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp index f64a8cf20928..b1e5ce2e28a8 100644 --- a/tools/aapt2/StringPool_test.cpp +++ b/tools/aapt2/StringPool_test.cpp @@ -23,8 +23,12 @@ #include "test/Test.h" #include "util/Util.h" -using android::StringPiece; -using android::StringPiece16; +using ::android::StringPiece; +using ::android::StringPiece16; +using ::testing::Eq; +using ::testing::Ne; +using ::testing::NotNull; +using ::testing::Pointee; namespace aapt { @@ -32,129 +36,127 @@ TEST(StringPoolTest, InsertOneString) { StringPool pool; StringPool::Ref ref = pool.MakeRef("wut"); - EXPECT_EQ(*ref, "wut"); + EXPECT_THAT(*ref, Eq("wut")); } TEST(StringPoolTest, InsertTwoUniqueStrings) { StringPool pool; - StringPool::Ref ref = pool.MakeRef("wut"); - StringPool::Ref ref2 = pool.MakeRef("hey"); + StringPool::Ref ref_a = pool.MakeRef("wut"); + StringPool::Ref ref_b = pool.MakeRef("hey"); - EXPECT_EQ(*ref, "wut"); - EXPECT_EQ(*ref2, "hey"); + EXPECT_THAT(*ref_a, Eq("wut")); + EXPECT_THAT(*ref_b, Eq("hey")); } TEST(StringPoolTest, DoNotInsertNewDuplicateString) { StringPool pool; - StringPool::Ref ref = pool.MakeRef("wut"); - StringPool::Ref ref2 = pool.MakeRef("wut"); + StringPool::Ref ref_a = pool.MakeRef("wut"); + StringPool::Ref ref_b = pool.MakeRef("wut"); - EXPECT_EQ(*ref, "wut"); - EXPECT_EQ(*ref2, "wut"); - EXPECT_EQ(1u, pool.size()); + EXPECT_THAT(*ref_a, Eq("wut")); + EXPECT_THAT(*ref_b, Eq("wut")); + EXPECT_THAT(pool.size(), Eq(1u)); } TEST(StringPoolTest, MaintainInsertionOrderIndex) { StringPool pool; - StringPool::Ref ref = pool.MakeRef("z"); - StringPool::Ref ref2 = pool.MakeRef("a"); - StringPool::Ref ref3 = pool.MakeRef("m"); + StringPool::Ref ref_a = pool.MakeRef("z"); + StringPool::Ref ref_b = pool.MakeRef("a"); + StringPool::Ref ref_c = pool.MakeRef("m"); - EXPECT_EQ(0u, ref.index()); - EXPECT_EQ(1u, ref2.index()); - EXPECT_EQ(2u, ref3.index()); + EXPECT_THAT(ref_a.index(), Eq(0u)); + EXPECT_THAT(ref_b.index(), Eq(1u)); + EXPECT_THAT(ref_c.index(), Eq(2u)); } TEST(StringPoolTest, PruneStringsWithNoReferences) { StringPool pool; - StringPool::Ref refA = pool.MakeRef("foo"); + StringPool::Ref ref_a = pool.MakeRef("foo"); + + { + StringPool::Ref ref_b = pool.MakeRef("wut"); + EXPECT_THAT(*ref_b, Eq("wut")); + EXPECT_THAT(pool.size(), Eq(2u)); + pool.Prune(); + EXPECT_THAT(pool.size(), Eq(2u)); + } + EXPECT_THAT(pool.size(), Eq(2u)); + { - StringPool::Ref ref = pool.MakeRef("wut"); - EXPECT_EQ(*ref, "wut"); - EXPECT_EQ(2u, pool.size()); + StringPool::Ref ref_c = pool.MakeRef("bar"); + EXPECT_THAT(pool.size(), Eq(3u)); + + pool.Prune(); + EXPECT_THAT(pool.size(), Eq(2u)); } - StringPool::Ref refB = pool.MakeRef("bar"); + EXPECT_THAT(pool.size(), Eq(2u)); - EXPECT_EQ(3u, pool.size()); pool.Prune(); - EXPECT_EQ(2u, pool.size()); - StringPool::const_iterator iter = begin(pool); - EXPECT_EQ((*iter)->value, "foo"); - EXPECT_LT((*iter)->index, 2u); - ++iter; - EXPECT_EQ((*iter)->value, "bar"); - EXPECT_LT((*iter)->index, 2u); + EXPECT_THAT(pool.size(), Eq(1u)); } -TEST(StringPoolTest, SortAndMaintainIndexesInReferences) { +TEST(StringPoolTest, SortAndMaintainIndexesInStringReferences) { StringPool pool; - StringPool::Ref ref = pool.MakeRef("z"); - StringPool::StyleRef ref2 = pool.MakeRef(StyleString{{"a"}}); - StringPool::Ref ref3 = pool.MakeRef("m"); + StringPool::Ref ref_a = pool.MakeRef("z"); + StringPool::Ref ref_b = pool.MakeRef("a"); + StringPool::Ref ref_c = pool.MakeRef("m"); - EXPECT_EQ(*ref, "z"); - EXPECT_EQ(0u, ref.index()); + EXPECT_THAT(*ref_a, Eq("z")); + EXPECT_THAT(ref_a.index(), Eq(0u)); - EXPECT_EQ(*(ref2->str), "a"); - EXPECT_EQ(1u, ref2.index()); + EXPECT_THAT(*ref_b, Eq("a")); + EXPECT_THAT(ref_b.index(), Eq(1u)); - EXPECT_EQ(*ref3, "m"); - EXPECT_EQ(2u, ref3.index()); + EXPECT_THAT(*ref_c, Eq("m")); + EXPECT_THAT(ref_c.index(), Eq(2u)); - pool.Sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { - return a.value < b.value; - }); + pool.Sort(); - EXPECT_EQ(*ref, "z"); - EXPECT_EQ(2u, ref.index()); + EXPECT_THAT(*ref_a, Eq("z")); + EXPECT_THAT(ref_a.index(), Eq(2u)); - EXPECT_EQ(*(ref2->str), "a"); - EXPECT_EQ(0u, ref2.index()); + EXPECT_THAT(*ref_b, Eq("a")); + EXPECT_THAT(ref_b.index(), Eq(0u)); - EXPECT_EQ(*ref3, "m"); - EXPECT_EQ(1u, ref3.index()); + EXPECT_THAT(*ref_c, Eq("m")); + EXPECT_THAT(ref_c.index(), Eq(1u)); } TEST(StringPoolTest, SortAndStillDedupe) { StringPool pool; - StringPool::Ref ref = pool.MakeRef("z"); - StringPool::Ref ref2 = pool.MakeRef("a"); - StringPool::Ref ref3 = pool.MakeRef("m"); + StringPool::Ref ref_a = pool.MakeRef("z"); + StringPool::Ref ref_b = pool.MakeRef("a"); + StringPool::Ref ref_c = pool.MakeRef("m"); - pool.Sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { - return a.value < b.value; - }); + pool.Sort(); - StringPool::Ref ref4 = pool.MakeRef("z"); - StringPool::Ref ref5 = pool.MakeRef("a"); - StringPool::Ref ref6 = pool.MakeRef("m"); + StringPool::Ref ref_d = pool.MakeRef("z"); + StringPool::Ref ref_e = pool.MakeRef("a"); + StringPool::Ref ref_f = pool.MakeRef("m"); - EXPECT_EQ(ref4.index(), ref.index()); - EXPECT_EQ(ref5.index(), ref2.index()); - EXPECT_EQ(ref6.index(), ref3.index()); + EXPECT_THAT(ref_d.index(), Eq(ref_a.index())); + EXPECT_THAT(ref_e.index(), Eq(ref_b.index())); + EXPECT_THAT(ref_f.index(), Eq(ref_c.index())); } TEST(StringPoolTest, AddStyles) { StringPool pool; - StyleString str{{"android"}, {Span{{"b"}, 2, 6}}}; - - StringPool::StyleRef ref = pool.MakeRef(str); - - EXPECT_EQ(0u, ref.index()); - EXPECT_EQ(std::string("android"), *(ref->str)); - ASSERT_EQ(1u, ref->spans.size()); + StringPool::StyleRef ref = pool.MakeRef(StyleString{{"android"}, {Span{{"b"}, 2, 6}}}); + EXPECT_THAT(ref.index(), Eq(0u)); + EXPECT_THAT(ref->value, Eq("android")); + ASSERT_THAT(ref->spans.size(), Eq(1u)); const StringPool::Span& span = ref->spans.front(); - EXPECT_EQ(*(span.name), "b"); - EXPECT_EQ(2u, span.first_char); - EXPECT_EQ(6u, span.last_char); + EXPECT_THAT(*span.name, Eq("b")); + EXPECT_THAT(span.first_char, Eq(2u)); + EXPECT_THAT(span.last_char, Eq(6u)); } TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) { @@ -163,9 +165,25 @@ TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) { StringPool::Ref ref = pool.MakeRef("android"); StyleString str{{"android"}}; - StringPool::StyleRef styleRef = pool.MakeRef(str); + StringPool::StyleRef style_ref = pool.MakeRef(StyleString{{"android"}}); + + EXPECT_THAT(ref.index(), Ne(style_ref.index())); +} + +TEST(StringPoolTest, StylesAndStringsAreSeparateAfterSorting) { + StringPool pool; + + StringPool::StyleRef ref_a = pool.MakeRef(StyleString{{"beta"}}); + StringPool::Ref ref_b = pool.MakeRef("alpha"); + StringPool::StyleRef ref_c = pool.MakeRef(StyleString{{"alpha"}}); + + EXPECT_THAT(ref_b.index(), Ne(ref_c.index())); - EXPECT_NE(ref.index(), styleRef.index()); + pool.Sort(); + + EXPECT_THAT(ref_c.index(), Eq(0u)); + EXPECT_THAT(ref_a.index(), Eq(1u)); + EXPECT_THAT(ref_b.index(), Eq(2u)); } TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) { @@ -177,7 +195,7 @@ TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) { std::unique_ptr<uint8_t[]> data = util::Copy(buffer); ResStringPool test; - ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); + ASSERT_THAT(test.setTo(data.get(), buffer.size()), Eq(NO_ERROR)); } TEST(StringPoolTest, FlattenOddCharactersUtf16) { @@ -193,9 +211,9 @@ TEST(StringPoolTest, FlattenOddCharactersUtf16) { ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); size_t len = 0; const char16_t* str = test.stringAt(0, &len); - EXPECT_EQ(1u, len); - EXPECT_EQ(u'\u093f', *str); - EXPECT_EQ(0u, str[1]); + EXPECT_THAT(len, Eq(1u)); + EXPECT_THAT(str, Pointee(Eq(u'\u093f'))); + EXPECT_THAT(str[1], Eq(0u)); } constexpr const char* sLongString = @@ -210,18 +228,20 @@ TEST(StringPoolTest, Flatten) { StringPool pool; - StringPool::Ref ref1 = pool.MakeRef("hello"); - StringPool::Ref ref2 = pool.MakeRef("goodbye"); - StringPool::Ref ref3 = pool.MakeRef(sLongString); - StringPool::Ref ref4 = pool.MakeRef(""); - StringPool::StyleRef ref5 = pool.MakeRef( - StyleString{{"style"}, {Span{{"b"}, 0, 1}, Span{{"i"}, 2, 3}}}); + StringPool::Ref ref_a = pool.MakeRef("hello"); + StringPool::Ref ref_b = pool.MakeRef("goodbye"); + StringPool::Ref ref_c = pool.MakeRef(sLongString); + StringPool::Ref ref_d = pool.MakeRef(""); + StringPool::StyleRef ref_e = + pool.MakeRef(StyleString{{"style"}, {Span{{"b"}, 0, 1}, Span{{"i"}, 2, 3}}}); + + // Styles are always first. + EXPECT_THAT(ref_e.index(), Eq(0u)); - EXPECT_EQ(0u, ref1.index()); - EXPECT_EQ(1u, ref2.index()); - EXPECT_EQ(2u, ref3.index()); - EXPECT_EQ(3u, ref4.index()); - EXPECT_EQ(4u, ref5.index()); + EXPECT_THAT(ref_a.index(), Eq(1u)); + EXPECT_THAT(ref_b.index(), Eq(2u)); + EXPECT_THAT(ref_c.index(), Eq(3u)); + EXPECT_THAT(ref_d.index(), Eq(4u)); BigBuffer buffers[2] = {BigBuffer(1024), BigBuffer(1024)}; StringPool::FlattenUtf8(&buffers[0], pool); @@ -234,38 +254,37 @@ TEST(StringPoolTest, Flatten) { ResStringPool test; ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); - EXPECT_EQ(std::string("hello"), util::GetString(test, 0)); - EXPECT_EQ(StringPiece16(u"hello"), util::GetString16(test, 0)); + EXPECT_THAT(util::GetString(test, 1), Eq("hello")); + EXPECT_THAT(util::GetString16(test, 1), Eq(u"hello")); - EXPECT_EQ(std::string("goodbye"), util::GetString(test, 1)); - EXPECT_EQ(StringPiece16(u"goodbye"), util::GetString16(test, 1)); + EXPECT_THAT(util::GetString(test, 2), Eq("goodbye")); + EXPECT_THAT(util::GetString16(test, 2), Eq(u"goodbye")); - EXPECT_EQ(StringPiece(sLongString), util::GetString(test, 2)); - EXPECT_EQ(util::Utf8ToUtf16(sLongString), util::GetString16(test, 2).to_string()); + EXPECT_THAT(util::GetString(test, 3), Eq(sLongString)); + EXPECT_THAT(util::GetString16(test, 3), Eq(util::Utf8ToUtf16(sLongString))); size_t len; - EXPECT_TRUE(test.stringAt(3, &len) != nullptr || - test.string8At(3, &len) != nullptr); - - EXPECT_EQ(std::string("style"), util::GetString(test, 4)); - EXPECT_EQ(StringPiece16(u"style"), util::GetString16(test, 4)); - - const ResStringPool_span* span = test.styleAt(4); - ASSERT_NE(nullptr, span); - EXPECT_EQ(std::string("b"), util::GetString(test, span->name.index)); - EXPECT_EQ(StringPiece16(u"b"), util::GetString16(test, span->name.index)); - EXPECT_EQ(0u, span->firstChar); - EXPECT_EQ(1u, span->lastChar); + EXPECT_TRUE(test.stringAt(4, &len) != nullptr || test.string8At(4, &len) != nullptr); + + EXPECT_THAT(util::GetString(test, 0), Eq("style")); + EXPECT_THAT(util::GetString16(test, 0), Eq(u"style")); + + const ResStringPool_span* span = test.styleAt(0); + ASSERT_THAT(span, NotNull()); + EXPECT_THAT(util::GetString(test, span->name.index), Eq("b")); + EXPECT_THAT(util::GetString16(test, span->name.index), Eq(u"b")); + EXPECT_THAT(span->firstChar, Eq(0u)); + EXPECT_THAT(span->lastChar, Eq(1u)); span++; - ASSERT_NE(ResStringPool_span::END, span->name.index); - EXPECT_EQ(std::string("i"), util::GetString(test, span->name.index)); - EXPECT_EQ(StringPiece16(u"i"), util::GetString16(test, span->name.index)); - EXPECT_EQ(2u, span->firstChar); - EXPECT_EQ(3u, span->lastChar); + ASSERT_THAT(span->name.index, Ne(ResStringPool_span::END)); + EXPECT_THAT(util::GetString(test, span->name.index), Eq("i")); + EXPECT_THAT(util::GetString16(test, span->name.index), Eq(u"i")); + EXPECT_THAT(span->firstChar, Eq(2u)); + EXPECT_THAT(span->lastChar, Eq(3u)); span++; - EXPECT_EQ(ResStringPool_span::END, span->name.index); + EXPECT_THAT(span->name.index, Eq(ResStringPool_span::END)); } } 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 740a401f9b57..d9e7ac6c1e08 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)); @@ -482,7 +482,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))) { @@ -574,6 +574,11 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv if (file_op.xml_to_flatten) { std::vector<std::unique_ptr<xml::XmlResource>> versioned_docs = LinkAndVersionXmlFile(table, &file_op); + if (versioned_docs.empty()) { + error = true; + continue; + } + for (std::unique_ptr<xml::XmlResource>& doc : versioned_docs) { std::string dst_path = file_op.dst_path; if (doc->file.config != file_op.config) { @@ -989,7 +994,8 @@ class LinkCommand { manifest_class->GetCommentBuilder()->AppendComment(proper_annotation); } - const std::string& package_utf8 = context_->GetCompilationPackage(); + const std::string package_utf8 = + options_.custom_java_package.value_or_default(context_->GetCompilationPackage()); std::string out_path = options_.generate_java_class_path.value(); file::AppendPath(&out_path, file::PackageToPath(package_utf8)); @@ -1289,7 +1295,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"); diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp index 194c0c80c2b2..9d71775889d4 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -17,6 +17,7 @@ #include <memory> #include <vector> +#include "android-base/stringprintf.h" #include "androidfw/StringPiece.h" #include "Diagnostics.h" @@ -26,6 +27,8 @@ #include "SdkConstants.h" #include "ValueVisitor.h" #include "cmd/Util.h" +#include "configuration/ConfigurationParser.h" +#include "filter/AbiFilter.h" #include "flatten/TableFlattener.h" #include "flatten/XmlFlattener.h" #include "io/BigBufferInputStream.h" @@ -33,14 +36,21 @@ #include "optimize/ResourceDeduper.h" #include "optimize/VersionCollapser.h" #include "split/TableSplitter.h" +#include "util/Files.h" -using android::StringPiece; +using ::aapt::configuration::Abi; +using ::aapt::configuration::Artifact; +using ::aapt::configuration::PostProcessingConfiguration; +using ::android::StringPiece; +using ::android::base::StringPrintf; namespace aapt { struct OptimizeOptions { // Path to the output APK. - std::string output_path; + Maybe<std::string> output_path; + // Path to the output APK directory for splits. + Maybe<std::string> output_dir; // Details of the app extracted from the AndroidManifest.xml AppInfo app_info; @@ -55,6 +65,8 @@ struct OptimizeOptions { std::vector<SplitConstraints> split_constraints; TableFlattenerOptions table_flattener_options; + + Maybe<PostProcessingConfiguration> configuration; }; class OptimizeContext : public IAaptContext { @@ -175,10 +187,52 @@ class OptimizeCommand { ++split_constraints_iter; } - std::unique_ptr<IArchiveWriter> writer = - CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path); - if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) { - return 1; + 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; + } + } + } + } + + if (options_.output_path) { + std::unique_ptr<IArchiveWriter> writer = + CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path.value()); + if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) { + return 1; + } } return 0; @@ -214,8 +268,8 @@ class OptimizeCommand { if (file_ref->file == nullptr) { ResourceNameRef name(pkg->name, type->type, entry->name); context_->GetDiagnostics()->Warn(DiagMessage(file_ref->GetSource()) - << "file for resource " << name << " with config '" - << config_value->config << "' not found"); + << "file for resource " << name << " with config '" + << config_value->config << "' not found"); continue; } @@ -293,13 +347,16 @@ bool ExtractAppDataFromManifest(OptimizeContext* context, LoadedApk* apk, int Optimize(const std::vector<StringPiece>& args) { OptimizeContext context; OptimizeOptions options; + Maybe<std::string> config_path; Maybe<std::string> target_densities; std::vector<std::string> configs; std::vector<std::string> split_args; bool verbose = false; Flags flags = Flags() - .RequiredFlag("-o", "Path to the output APK.", &options.output_path) + .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) .OptionalFlag( "--target-densities", "Comma separated list of the screen densities that the APK will be optimized for.\n" @@ -369,6 +426,22 @@ int Optimize(const std::vector<StringPiece>& args) { } } + 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(); + } else { + context.GetDiagnostics()->Error(DiagMessage() << "Could not parse config file " << path); + return 1; + } + } + if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) { return 1; } diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp index 8741b7b678ec..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,6 +181,13 @@ 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 (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, ","); + manifest_el->attributes.push_back(xml::Attribute{"", "targetConfig", target_config_str.str()}); + std::unique_ptr<xml::Element> application_el = util::make_unique<xml::Element>(); application_el->name = "application"; application_el->attributes.push_back( @@ -186,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; } @@ -277,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/IdAssigner_test.cpp b/tools/aapt2/compile/IdAssigner_test.cpp index d465091d224e..5cff0048c062 100644 --- a/tools/aapt2/compile/IdAssigner_test.cpp +++ b/tools/aapt2/compile/IdAssigner_test.cpp @@ -64,12 +64,12 @@ TEST(IdAssignerTest, AssignIdsWithReservedIds) { // Expect to fill in the gaps between 0x0101XXXX and 0x0104XXXX. maybe_result = table->FindResource(test::ParseNameOrDie("android:dimen/two")); - AAPT_ASSERT_TRUE(maybe_result); + ASSERT_TRUE(maybe_result); EXPECT_EQ(make_value<uint8_t>(2), maybe_result.value().type->id); maybe_result = table->FindResource(test::ParseNameOrDie("android:integer/three")); - AAPT_ASSERT_TRUE(maybe_result); + ASSERT_TRUE(maybe_result); EXPECT_EQ(make_value<uint8_t>(3), maybe_result.value().type->id); // Expect to bypass the reserved 0x0104XXXX IDs and use the next 0x0105XXXX @@ -77,17 +77,17 @@ TEST(IdAssignerTest, AssignIdsWithReservedIds) { maybe_result = table->FindResource(test::ParseNameOrDie("android:string/five")); - AAPT_ASSERT_TRUE(maybe_result); + ASSERT_TRUE(maybe_result); EXPECT_EQ(make_value<uint8_t>(5), maybe_result.value().type->id); // Expect to fill in the gaps between 0x01040000 and 0x01040006. maybe_result = table->FindResource(test::ParseNameOrDie("android:attr/bar")); - AAPT_ASSERT_TRUE(maybe_result); + ASSERT_TRUE(maybe_result); EXPECT_EQ(make_value<uint16_t>(1), maybe_result.value().entry->id); maybe_result = table->FindResource(test::ParseNameOrDie("android:attr/baz")); - AAPT_ASSERT_TRUE(maybe_result); + ASSERT_TRUE(maybe_result); EXPECT_EQ(make_value<uint16_t>(2), maybe_result.value().entry->id); } @@ -121,7 +121,7 @@ TEST(IdAssignerTest, AssignIdsWithIdMap) { ASSERT_TRUE(VerifyIds(table.get())); Maybe<ResourceTable::SearchResult> result = table->FindResource(test::ParseNameOrDie("android:attr/foo")); - AAPT_ASSERT_TRUE(result); + ASSERT_TRUE(result); const ResourceTable::SearchResult& search_result = result.value(); EXPECT_EQ(make_value<uint8_t>(0x01), search_result.package->id); diff --git a/tools/aapt2/compile/InlineXmlFormatParser.cpp b/tools/aapt2/compile/InlineXmlFormatParser.cpp index 786494b6ad1c..857cdd5365a0 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 << "'"); + context_->GetDiagnostics()->Error(DiagMessage(src) << "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/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp index a031ea4c31ec..871ed4f01e77 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator.cpp +++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp @@ -120,7 +120,7 @@ std::unique_ptr<StyledString> PseudolocalizeStyledString(StyledString* string, // All Span indices are UTF-16 based, according to the resources.arsc format expected by the // runtime. So we will do all our processing in UTF-16, then convert back. - const std::u16string text16 = util::Utf8ToUtf16(*string->value->str); + const std::u16string text16 = util::Utf8ToUtf16(string->value->value); // Convenient wrapper around the text that allows us to work with StringPieces. const StringPiece16 text(text16); diff --git a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp index b08e1dab35a9..711558aa51c1 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp +++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp @@ -31,7 +31,7 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeStyledString) { util::make_unique<StyledString>(pool.MakeRef(original_style)).get(), Pseudolocalizer::Method::kNone, &pool); - EXPECT_EQ(original_style.str, *new_string->value->str); + EXPECT_EQ(original_style.str, new_string->value->value); ASSERT_EQ(original_style.spans.size(), new_string->value->spans.size()); EXPECT_EQ(std::string("i"), *new_string->value->spans[0].name); @@ -52,7 +52,7 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeStyledString) { util::make_unique<StyledString>(pool.MakeRef(original_style)).get(), Pseudolocalizer::Method::kAccent, &pool); - EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð¡ one two]"), *new_string->value->str); + EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð¡ one two]"), new_string->value->value); ASSERT_EQ(original_style.spans.size(), new_string->value->spans.size()); EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[0].first_char); @@ -79,7 +79,7 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeAdjacentNestedTags) { Pseudolocalizer::Method::kAccent, &pool); ASSERT_NE(nullptr, new_string); ASSERT_EQ(2u, new_string->value->spans.size()); - EXPECT_EQ(std::string("[ɓöļð one]"), *new_string->value->str); + EXPECT_EQ(std::string("[ɓöļð one]"), new_string->value->value); EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name); EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[0].first_char); @@ -101,7 +101,7 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeAdjacentTagsUnsorted) { Pseudolocalizer::Method::kAccent, &pool); ASSERT_NE(nullptr, new_string); ASSERT_EQ(2u, new_string->value->spans.size()); - EXPECT_EQ(std::string("[ɓöļð one]"), *new_string->value->str); + EXPECT_EQ(std::string("[ɓöļð one]"), new_string->value->value); EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name); EXPECT_EQ(std::u16string(u"[").size(), new_string->value->spans[0].first_char); @@ -126,7 +126,7 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizeNestedAndAdjacentTags) { ASSERT_EQ(4u, new_string->value->spans.size()); EXPECT_EQ(std::string( "[Ţĥîš šéñţéñçé îš ñöţ ŵĥåţ ýöû ţĥîñķ îţ îš åţ åļļ. one two three four five six]"), - *new_string->value->str); + new_string->value->value); EXPECT_EQ(std::string("b"), *new_string->value->spans[0].name); EXPECT_EQ(std::u16string(u"[Ţĥîš šéñţéñçé îš").size(), new_string->value->spans[0].first_char); @@ -165,7 +165,7 @@ TEST(PseudolocaleGeneratorTest, PseudolocalizePartsOfString) { ASSERT_NE(nullptr, new_string); ASSERT_EQ(2u, new_string->value->spans.size()); EXPECT_EQ(std::string("[Ţĥîš šĥöûļð NOT ɓé þšéûðöļöçåļîžéð. one two three four]"), - *new_string->value->str); + new_string->value->value); EXPECT_EQ(std::string("em"), *new_string->value->spans[0].name); EXPECT_EQ(std::u16string(u"[Ţĥîš").size(), new_string->value->spans[0].first_char); @@ -265,7 +265,7 @@ TEST(PseudolocaleGeneratorTest, RespectUntranslateableSections) { ASSERT_NE(nullptr, new_styled_string); // "world" should be untranslated. - EXPECT_NE(std::string::npos, new_styled_string->value->str->find("world")); + EXPECT_NE(std::string::npos, new_styled_string->value->value.find("world")); String* new_string = test::GetValueForConfig<String>(table.get(), "android:string/bar", test::ParseConfigOrDie("en-rXA")); diff --git a/tools/aapt2/compile/Pseudolocalizer.cpp b/tools/aapt2/compile/Pseudolocalizer.cpp index 15a3d8c289e2..3a515fad3202 100644 --- a/tools/aapt2/compile/Pseudolocalizer.cpp +++ b/tools/aapt2/compile/Pseudolocalizer.cpp @@ -445,9 +445,15 @@ std::string PseudoMethodBidi::Text(const StringPiece& source) { std::string result; bool lastspace = true; bool space = true; + bool escape = false; + const char ESCAPE_CHAR = '\\'; for (size_t i = 0; i < source.size(); i++) { char c = s[i]; - space = isspace(c); + if (!escape && c == ESCAPE_CHAR) { + escape = true; + continue; + } + space = (!escape && isspace(c)) || (escape && (c == 'n' || c == 't')); if (lastspace && !space) { // Word start result += kRlm + kRlo; @@ -456,6 +462,10 @@ std::string PseudoMethodBidi::Text(const StringPiece& source) { result += kPdf + kRlm; } lastspace = space; + if (escape) { + result.append(&ESCAPE_CHAR, 1); + escape=false; + } result.append(&c, 1); } if (!lastspace) { diff --git a/tools/aapt2/compile/Pseudolocalizer_test.cpp b/tools/aapt2/compile/Pseudolocalizer_test.cpp index d3b7b02d1abb..65d2472ef2a9 100644 --- a/tools/aapt2/compile/Pseudolocalizer_test.cpp +++ b/tools/aapt2/compile/Pseudolocalizer_test.cpp @@ -97,6 +97,11 @@ TEST(PseudolocalizerTest, PlaintextBidi) { "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n" " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n", Pseudolocalizer::Method::kBidi)); + EXPECT_TRUE( + SimpleHelper("hello\\nworld\\n", + "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\\n" + "\xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\\n", + Pseudolocalizer::Method::kBidi)); } TEST(PseudolocalizerTest, SimpleICU) { diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp index 89618d3a4676..bdccf8bcae3a 100644 --- a/tools/aapt2/configuration/ConfigurationParser.cpp +++ b/tools/aapt2/configuration/ConfigurationParser.cpp @@ -18,13 +18,19 @@ #include <algorithm> #include <functional> +#include <map> #include <memory> #include <utility> -#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/Maybe.h" #include "util/Util.h" #include "xml/XmlActionExecutor.h" #include "xml/XmlDom.h" @@ -38,27 +44,30 @@ using ::aapt::configuration::Abi; using ::aapt::configuration::AndroidManifest; using ::aapt::configuration::AndroidSdk; using ::aapt::configuration::Artifact; -using ::aapt::configuration::Configuration; +using ::aapt::configuration::PostProcessingConfiguration; using ::aapt::configuration::GlTexture; 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; -const std::unordered_map<std::string, Abi> kAbiMap = { - {"armeabi", Abi::kArmeV6}, - {"armeabi-v7a", Abi::kArmV7a}, - {"arm64-v8a", Abi::kArm64V8a}, - {"x86", Abi::kX86}, - {"x86_64", Abi::kX86_64}, - {"mips", Abi::kMips}, - {"mips64", Abi::kMips64}, - {"universal", Abi::kUniversal}, +const std::unordered_map<std::string, Abi> kStringToAbiMap = { + {"armeabi", Abi::kArmeV6}, {"armeabi-v7a", Abi::kArmV7a}, {"arm64-v8a", Abi::kArm64V8a}, + {"x86", Abi::kX86}, {"x86_64", Abi::kX86_64}, {"mips", Abi::kMips}, + {"mips64", Abi::kMips64}, {"universal", Abi::kUniversal}, +}; +const std::map<Abi, std::string> kAbiToStringMap = { + {Abi::kArmeV6, "armeabi"}, {Abi::kArmV7a, "armeabi-v7a"}, {Abi::kArm64V8a, "arm64-v8a"}, + {Abi::kX86, "x86"}, {Abi::kX86_64, "x86_64"}, {Abi::kMips, "mips"}, + {Abi::kMips64, "mips64"}, {Abi::kUniversal, "universal"}, }; constexpr const char* kAaptXmlNs = "http://schemas.android.com/tools/aapt"; @@ -96,21 +105,92 @@ class NamespaceVisitor : public xml::Visitor { } // namespace +namespace configuration { + +const std::string& AbiToString(Abi abi) { + return kAbiToStringMap.find(abi)->second; +} + +/** + * Attempts to replace the placeholder in the name string with the provided value. Returns true on + * 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, + std::string* name, IDiagnostics* diag) { + size_t offset = name->find(placeholder); + if (value) { + if (offset == std::string::npos) { + diag->Error(DiagMessage() << "Missing placeholder for artifact: " << placeholder); + return false; + } + name->replace(offset, placeholder.length(), value.value()); + return true; + } + + // Make sure the placeholder was not present if the desired value was not present. + bool result = (offset == std::string::npos); + if (!result) { + diag->Error(DiagMessage() << "Placeholder present but no value for artifact: " << placeholder); + } + return result; +} + +Maybe<std::string> Artifact::ToArtifactName(const std::string& format, IDiagnostics* diag) const { + std::string result = format; + + if (!ReplacePlaceholder("{abi}", abi_group, &result, diag)) { + return {}; + } + + if (!ReplacePlaceholder("{density}", screen_density_group, &result, diag)) { + return {}; + } + + if (!ReplacePlaceholder("{locale}", locale_group, &result, diag)) { + return {}; + } + + if (!ReplacePlaceholder("{sdk}", android_sdk_group, &result, diag)) { + return {}; + } + + if (!ReplacePlaceholder("{feature}", device_feature_group, &result, diag)) { + return {}; + } + + if (!ReplacePlaceholder("{gl}", gl_texture_group, &result, diag)) { + return {}; + } + + return result; +} + +} // namespace configuration + +/** Returns a ConfigurationParser for the file located at the provided path. */ +Maybe<ConfigurationParser> ConfigurationParser::ForPath(const std::string& path) { + std::string contents; + if (!ReadFileToString(path, &contents, true)) { + return {}; + } + return ConfigurationParser(contents); +} + ConfigurationParser::ConfigurationParser(std::string contents) : contents_(std::move(contents)), diag_(&noop_) { } -Maybe<Configuration> ConfigurationParser::Parse() { - std::istringstream in(contents_); - - auto doc = xml::Inflate(&in, diag_, Source("config.xml")); +Maybe<PostProcessingConfiguration> ConfigurationParser::Parse() { + 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 {}; @@ -133,10 +213,11 @@ Maybe<Configuration> ConfigurationParser::Parse() { XmlNodeAction& artifacts_action = root_action["artifacts"]; XmlNodeAction& groups_action = root_action["groups"]; - Configuration config; + PostProcessingConfiguration config; // Helper to bind a static method to an action handler in the DOM executor. - auto bind_handler = [&config](std::function<bool(Configuration*, Element*, IDiagnostics*)> h) + auto bind_handler = + [&config](std::function<bool(PostProcessingConfiguration*, Element*, IDiagnostics*)> h) -> XmlNodeAction::ActionFuncWithDiag { return std::bind(h, &config, std::placeholders::_1, std::placeholders::_2); }; @@ -158,275 +239,266 @@ Maybe<Configuration> ConfigurationParser::Parse() { return {}; } + // TODO: Validate all references in the configuration are valid. It should be safe to assume from + // this point on that any references from one section to another will be present. + return {config}; } ConfigurationParser::ActionHandler ConfigurationParser::artifact_handler_ = - [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool { - Artifact artifact{}; - for (const auto& attr : root_element->attributes) { - if (attr.name == "name") { - artifact.name = attr.value; - } else if (attr.name == "abi-group") { - artifact.abi_group = {attr.value}; - } else if (attr.name == "screen-density-group") { - artifact.screen_density_group = {attr.value}; - } else if (attr.name == "locale-group") { - artifact.locale_group = {attr.value}; - } else if (attr.name == "android-sdk-group") { - artifact.android_sdk_group = {attr.value}; - } else if (attr.name == "gl-texture-group") { - artifact.gl_texture_group = {attr.value}; - } else if (attr.name == "device-feature-group") { - artifact.device_feature_group = {attr.value}; - } else { - diag->Note( - DiagMessage() << "Unknown artifact attribute: " << attr.name << " = " << attr.value); - } - } - config->artifacts[artifact.name] = artifact; - return true; - }; + [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool { + Artifact artifact{}; + for (const auto& attr : root_element->attributes) { + if (attr.name == "name") { + artifact.name = attr.value; + } else if (attr.name == "abi-group") { + artifact.abi_group = {attr.value}; + } else if (attr.name == "screen-density-group") { + artifact.screen_density_group = {attr.value}; + } else if (attr.name == "locale-group") { + artifact.locale_group = {attr.value}; + } else if (attr.name == "android-sdk-group") { + artifact.android_sdk_group = {attr.value}; + } else if (attr.name == "gl-texture-group") { + artifact.gl_texture_group = {attr.value}; + } else if (attr.name == "device-feature-group") { + artifact.device_feature_group = {attr.value}; + } else { + diag->Note(DiagMessage() << "Unknown artifact attribute: " << attr.name << " = " + << attr.value); + } + } + config->artifacts.push_back(artifact); + return true; +}; ConfigurationParser::ActionHandler ConfigurationParser::artifact_format_handler_ = - [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool { - for (auto& node : root_element->children) { - xml::Text* t; - if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { - config->artifact_format = TrimWhitespace(t->text).to_string(); - break; - } - } - return true; - }; + [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool { + for (auto& node : root_element->children) { + xml::Text* t; + if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { + config->artifact_format = TrimWhitespace(t->text).to_string(); + break; + } + } + return true; +}; ConfigurationParser::ActionHandler ConfigurationParser::abi_group_handler_ = - [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool { - std::string label = GetLabel(root_element, diag); - if (label.empty()) { - return false; - } + [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool { + std::string label = GetLabel(root_element, diag); + if (label.empty()) { + return false; + } - auto& group = config->abi_groups[label]; - bool valid = true; + auto& group = config->abi_groups[label]; + bool valid = true; - for (auto* child : root_element->GetChildElements()) { - if (child->name != "abi") { - diag->Error( - DiagMessage() << "Unexpected element in ABI group: " << child->name); - valid = false; - } else { - for (auto& node : child->children) { - xml::Text* t; - if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { - group.push_back(kAbiMap.at(TrimWhitespace(t->text).to_string())); - break; - } - } + for (auto* child : root_element->GetChildElements()) { + if (child->name != "abi") { + diag->Error(DiagMessage() << "Unexpected element in ABI group: " << child->name); + valid = false; + } else { + for (auto& node : child->children) { + xml::Text* t; + if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { + group.push_back(kStringToAbiMap.at(TrimWhitespace(t->text).to_string())); + break; } } + } + } - return valid; - }; + return valid; +}; ConfigurationParser::ActionHandler ConfigurationParser::screen_density_group_handler_ = - [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool { - std::string label = GetLabel(root_element, diag); - if (label.empty()) { - return false; - } + [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool { + std::string label = GetLabel(root_element, diag); + if (label.empty()) { + return false; + } - auto& group = config->screen_density_groups[label]; - bool valid = true; + auto& group = config->screen_density_groups[label]; + bool valid = true; - for (auto* child : root_element->GetChildElements()) { - if (child->name != "screen-density") { - diag->Error( - DiagMessage() << "Unexpected root_element in screen density group: " - << child->name); - valid = false; - } else { - 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); - if (ConfigDescription::Parse(text, &config_descriptor)) { - // Copy the density 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; - } + for (auto* child : root_element->GetChildElements()) { + if (child->name != "screen-density") { + diag->Error(DiagMessage() << "Unexpected root_element in screen density group: " + << child->name); + valid = false; + } else { + 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); + if (ConfigDescription::Parse(text, &config_descriptor)) { + // Copy the density 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; } } + } + } - return valid; - }; + return valid; +}; ConfigurationParser::ActionHandler ConfigurationParser::locale_group_handler_ = - [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool { - std::string label = GetLabel(root_element, diag); - if (label.empty()) { - return false; - } + [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool { + std::string label = GetLabel(root_element, diag); + if (label.empty()) { + return false; + } - auto& group = config->locale_groups[label]; - bool valid = true; + auto& group = config->locale_groups[label]; + bool valid = true; - for (auto* child : root_element->GetChildElements()) { - if (child->name != "locale") { - diag->Error( - DiagMessage() << "Unexpected root_element in screen density group: " - << child->name); - valid = false; + for (auto* child : root_element->GetChildElements()) { + if (child->name != "locale") { + diag->Error(DiagMessage() << "Unexpected root_element in screen density group: " + << 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 { - 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); - } - } - group.push_back(entry); + diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value); } } + group.push_back(entry); + } + } - return valid; - }; + return valid; +}; ConfigurationParser::ActionHandler ConfigurationParser::android_sdk_group_handler_ = - [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool { - std::string label = GetLabel(root_element, diag); - if (label.empty()) { - return false; - } - - auto& group = config->android_sdk_groups[label]; - bool valid = true; + [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool { + std::string label = GetLabel(root_element, diag); + if (label.empty()) { + return false; + } - for (auto* child : root_element->GetChildElements()) { - if (child->name != "android-sdk") { - diag->Error( - DiagMessage() << "Unexpected root_element in ABI group: " << child->name); - valid = false; + auto& group = config->android_sdk_groups[label]; + bool valid = true; + + for (auto* child : root_element->GetChildElements()) { + if (child->name != "android-sdk") { + diag->Error(DiagMessage() << "Unexpected root_element in ABI group: " << child->name); + valid = false; + } else { + AndroidSdk entry; + for (const auto& attr : child->attributes) { + if (attr.name == "minSdkVersion") { + entry.min_sdk_version = {attr.value}; + } else if (attr.name == "targetSdkVersion") { + entry.target_sdk_version = {attr.value}; + } else if (attr.name == "maxSdkVersion") { + entry.max_sdk_version = {attr.value}; } else { - AndroidSdk entry; - for (const auto& attr : child->attributes) { - if (attr.name == "minSdkVersion") { - entry.min_sdk_version = {attr.value}; - } else if (attr.name == "targetSdkVersion") { - entry.target_sdk_version = {attr.value}; - } else if (attr.name == "maxSdkVersion") { - entry.max_sdk_version = {attr.value}; - } else { - diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name - << " = " << attr.value); - } - } + diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value); + } + } - // TODO: Fill in the manifest details when they are finalised. - for (auto node : child->GetChildElements()) { - if (node->name == "manifest") { - if (entry.manifest) { - diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates."); - continue; - } - entry.manifest = {AndroidManifest()}; - } + // TODO: Fill in the manifest details when they are finalised. + for (auto node : child->GetChildElements()) { + if (node->name == "manifest") { + if (entry.manifest) { + diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates."); + continue; } - - group.push_back(entry); + entry.manifest = {AndroidManifest()}; } } - return valid; - }; + group.push_back(entry); + } + } + + return valid; +}; ConfigurationParser::ActionHandler ConfigurationParser::gl_texture_group_handler_ = - [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool { - std::string label = GetLabel(root_element, diag); - if (label.empty()) { - return false; - } + [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool { + std::string label = GetLabel(root_element, diag); + if (label.empty()) { + return false; + } - auto& group = config->gl_texture_groups[label]; - bool valid = true; + auto& group = config->gl_texture_groups[label]; + bool valid = true; - GlTexture result; - for (auto* child : root_element->GetChildElements()) { - if (child->name != "gl-texture") { - diag->Error( - DiagMessage() << "Unexpected element in GL texture group: " - << child->name); - valid = false; - } else { - for (const auto& attr : child->attributes) { - if (attr.name == "name") { - result.name = attr.value; - break; - } - } + GlTexture result; + for (auto* child : root_element->GetChildElements()) { + if (child->name != "gl-texture") { + diag->Error(DiagMessage() << "Unexpected element in GL texture group: " << child->name); + valid = false; + } else { + for (const auto& attr : child->attributes) { + if (attr.name == "name") { + result.name = attr.value; + break; + } + } - for (auto* element : child->GetChildElements()) { - if (element->name != "texture-path") { - diag->Error( - DiagMessage() << "Unexpected element in gl-texture element: " - << child->name); - valid = false; - continue; - } - for (auto& node : element->children) { - xml::Text* t; - if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { - result.texture_paths.push_back(TrimWhitespace(t->text).to_string()); - } - } + for (auto* element : child->GetChildElements()) { + if (element->name != "texture-path") { + diag->Error(DiagMessage() << "Unexpected element in gl-texture element: " << child->name); + valid = false; + continue; + } + for (auto& node : element->children) { + xml::Text* t; + if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { + result.texture_paths.push_back(TrimWhitespace(t->text).to_string()); } } - group.push_back(result); } + } + group.push_back(result); + } - return valid; - }; + return valid; +}; ConfigurationParser::ActionHandler ConfigurationParser::device_feature_group_handler_ = - [](Configuration* config, Element* root_element, IDiagnostics* diag) -> bool { - std::string label = GetLabel(root_element, diag); - if (label.empty()) { - return false; - } + [](PostProcessingConfiguration* config, Element* root_element, IDiagnostics* diag) -> bool { + std::string label = GetLabel(root_element, diag); + if (label.empty()) { + return false; + } - auto& group = config->device_feature_groups[label]; - bool valid = true; + auto& group = config->device_feature_groups[label]; + bool valid = true; - for (auto* child : root_element->GetChildElements()) { - if (child->name != "supports-feature") { - diag->Error( - DiagMessage() << "Unexpected root_element in device feature group: " - << child->name); - valid = false; - } else { - for (auto& node : child->children) { - xml::Text* t; - if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { - group.push_back(TrimWhitespace(t->text).to_string()); - break; - } - } + for (auto* child : root_element->GetChildElements()) { + if (child->name != "supports-feature") { + diag->Error(DiagMessage() << "Unexpected root_element in device feature group: " + << child->name); + valid = false; + } else { + for (auto& node : child->children) { + xml::Text* t; + if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { + group.push_back(TrimWhitespace(t->text).to_string()); + break; } } + } + } - return valid; - }; + return valid; +}; } // namespace aapt diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h index 0fb2f714a76c..28c355e39643 100644 --- a/tools/aapt2/configuration/ConfigurationParser.h +++ b/tools/aapt2/configuration/ConfigurationParser.h @@ -20,8 +20,9 @@ #include <string> #include <unordered_map> #include <vector> -#include <ConfigDescription.h> +#include "ConfigDescription.h" +#include "Diagnostics.h" #include "util/Maybe.h" namespace aapt { @@ -48,6 +49,9 @@ struct Artifact { Maybe<std::string> device_feature_group; /** If present, uses the OpenGL texture group with this name. */ 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; }; /** Enumeration of currently supported ABIs. */ @@ -62,6 +66,9 @@ enum class Abi { kUniversal }; +/** Helper method to convert an ABI to a string representing the path within the APK. */ +const std::string& AbiToString(Abi abi); + /** * Represents an individual locale. When a locale is included, it must be * declared from least specific to most specific, as a region does not make @@ -114,11 +121,10 @@ struct GlTexture { } }; -/** - * AAPT2 XML configuration binary representation. - */ -struct Configuration { - std::unordered_map<std::string, Artifact> artifacts; +/** AAPT2 XML configuration file binary representation. */ +struct PostProcessingConfiguration { + // TODO: Support named artifacts? + std::vector<Artifact> artifacts; Maybe<std::string> artifact_format; Group<Abi> abi_groups; @@ -142,18 +148,16 @@ class Element; */ class ConfigurationParser { public: + + /** Returns a ConfigurationParser for the file located at the provided path. */ + static Maybe<ConfigurationParser> ForPath(const std::string& path); + /** Returns a ConfigurationParser for the configuration in the provided file contents. */ static ConfigurationParser ForContents(const std::string& contents) { ConfigurationParser parser{contents}; return parser; } - /** Returns a ConfigurationParser for the file located at the provided path. */ - static ConfigurationParser ForPath(const std::string& path) { - // TODO: Read XML file into memory. - return ForContents(path); - } - /** Sets the diagnostics context to use when parsing. */ ConfigurationParser& WithDiagnostics(IDiagnostics* diagnostics) { diag_ = diagnostics; @@ -164,7 +168,7 @@ class ConfigurationParser { * Parses the configuration file and returns the results. If the configuration could not be parsed * the result is empty and any errors will be displayed with the provided diagnostics context. */ - Maybe<configuration::Configuration> Parse(); + Maybe<configuration::PostProcessingConfiguration> Parse(); protected: /** @@ -183,9 +187,8 @@ class ConfigurationParser { * An ActionHandler for processing XML elements in the XmlActionExecutor. Returns true if the * element was successfully processed, otherwise returns false. */ - using ActionHandler = std::function<bool(configuration::Configuration* config, - xml::Element* element, - IDiagnostics* diag)>; + using ActionHandler = std::function<bool(configuration::PostProcessingConfiguration* config, + xml::Element* element, IDiagnostics* diag)>; /** Handler for <artifact> tags. */ static ActionHandler artifact_handler_; @@ -213,4 +216,4 @@ class ConfigurationParser { } // namespace aapt -#endif //AAPT2_CONFIGURATION_H +#endif // AAPT2_CONFIGURATION_H diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp index 72a97b273cb0..f89773720cc5 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,15 +26,16 @@ namespace aapt { namespace { -using android::ResTable_config; +using ::android::ResTable_config; using configuration::Abi; using configuration::AndroidSdk; -using configuration::Configuration; +using configuration::Artifact; +using configuration::PostProcessingConfiguration; using configuration::DeviceFeature; using configuration::GlTexture; using configuration::Locale; using configuration::AndroidManifest; -using testing::ElementsAre; +using ::testing::ElementsAre; using xml::Element; using xml::NodeCast; @@ -130,11 +128,16 @@ class ConfigurationParserTest : public ConfigurationParser, public ::testing::Te StdErrDiagnostics diag_; }; +TEST_F(ConfigurationParserTest, ForPath_NoFile) { + auto result = ConfigurationParser::ForPath("./does_not_exist.xml"); + EXPECT_FALSE(result); +} + TEST_F(ConfigurationParserTest, ValidateFile) { auto parser = ConfigurationParser::ForContents(kValidConfig).WithDiagnostics(&diag_); auto result = parser.Parse(); ASSERT_TRUE(result); - Configuration& value = result.value(); + PostProcessingConfiguration& value = result.value(); EXPECT_EQ(2ul, value.artifacts.size()); ASSERT_TRUE(value.artifact_format); EXPECT_EQ( @@ -185,13 +188,13 @@ TEST_F(ConfigurationParserTest, ArtifactAction) { auto doc = test::BuildXmlDom(xml); - Configuration config; - bool ok = artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); + PostProcessingConfiguration config; + bool ok = artifact_handler_(&config, NodeCast<Element>(doc->root.get()), &diag_); ASSERT_TRUE(ok); EXPECT_EQ(1ul, config.artifacts.size()); - auto& artifact = config.artifacts.begin()->second; + auto& artifact = config.artifacts.front(); EXPECT_EQ("", artifact.name); // TODO: make this fail. EXPECT_EQ("arm", artifact.abi_group.value()); EXPECT_EQ("large", artifact.screen_density_group.value()); @@ -199,6 +202,21 @@ TEST_F(ConfigurationParserTest, ArtifactAction) { EXPECT_EQ("19", artifact.android_sdk_group.value()); EXPECT_EQ("dxt1", artifact.gl_texture_group.value()); EXPECT_EQ("low-latency", artifact.device_feature_group.value()); + + // Perform a second action to ensure we get 2 artifacts. + static constexpr const char* second = R"xml( + <artifact + abi-group="other" + screen-density-group="large" + locale-group="europe" + android-sdk-group="19" + gl-texture-group="dxt1" + device-feature-group="low-latency"/>)xml"; + doc = test::BuildXmlDom(second); + + ok = artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); + ASSERT_TRUE(ok); + EXPECT_EQ(2ul, config.artifacts.size()); } TEST_F(ConfigurationParserTest, ArtifactFormatAction) { @@ -209,7 +227,7 @@ TEST_F(ConfigurationParserTest, ArtifactFormatAction) { auto doc = test::BuildXmlDom(xml); - Configuration config; + PostProcessingConfiguration config; bool ok = artifact_format_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); ASSERT_TRUE(config.artifact_format); @@ -232,7 +250,7 @@ TEST_F(ConfigurationParserTest, AbiGroupAction) { auto doc = test::BuildXmlDom(xml); - Configuration config; + PostProcessingConfiguration config; bool ok = abi_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); @@ -255,7 +273,7 @@ TEST_F(ConfigurationParserTest, ScreenDensityGroupAction) { auto doc = test::BuildXmlDom(xml); - Configuration config; + PostProcessingConfiguration config; bool ok = screen_density_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); @@ -285,7 +303,7 @@ TEST_F(ConfigurationParserTest, LocaleGroupAction) { auto doc = test::BuildXmlDom(xml); - Configuration config; + PostProcessingConfiguration config; bool ok = locale_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); @@ -321,7 +339,7 @@ TEST_F(ConfigurationParserTest, AndroidSdkGroupAction) { auto doc = test::BuildXmlDom(xml); - Configuration config; + PostProcessingConfiguration config; bool ok = android_sdk_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); @@ -353,7 +371,7 @@ TEST_F(ConfigurationParserTest, GlTextureGroupAction) { auto doc = test::BuildXmlDom(xml); - Configuration config; + PostProcessingConfiguration config; bool ok = gl_texture_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); @@ -382,7 +400,7 @@ TEST_F(ConfigurationParserTest, DeviceFeatureGroupAction) { auto doc = test::BuildXmlDom(xml); - Configuration config; + PostProcessingConfiguration config; bool ok = device_feature_group_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); ASSERT_TRUE(ok); @@ -397,5 +415,55 @@ TEST_F(ConfigurationParserTest, DeviceFeatureGroupAction) { ASSERT_THAT(out, ElementsAre(low_latency, pro)); } +TEST(ArtifactTest, Simple) { + StdErrDiagnostics diag; + Artifact x86; + x86.abi_group = {"x86"}; + + 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"); +} + +TEST(ArtifactTest, Complex) { + StdErrDiagnostics diag; + Artifact artifact; + artifact.abi_group = {"mips64"}; + artifact.screen_density_group = {"ldpi"}; + 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"); +} + +TEST(ArtifactTest, Missing) { + StdErrDiagnostics diag; + Artifact x86; + x86.abi_group = {"x86"}; + + EXPECT_FALSE(x86.ToArtifactName("something.{density}.apk", &diag)); + EXPECT_FALSE(x86.ToArtifactName("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)); +} + } // namespace } // namespace aapt diff --git a/tools/aapt2/configuration/example/config.xml b/tools/aapt2/configuration/example/config.xml index a8360f870b71..ce31e61b2d62 100644 --- a/tools/aapt2/configuration/example/config.xml +++ b/tools/aapt2/configuration/example/config.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8" ?> -<post-process xmlns="http://schemas.android.com/tools/aapt2"> +<post-process xmlns="http://schemas.android.com/tools/aapt"> <groups> <abi-group label="arm"> <abi>armeabi-v7a</abi> diff --git a/tools/aapt2/filter/AbiFilter.cpp b/tools/aapt2/filter/AbiFilter.cpp new file mode 100644 index 000000000000..cb96235f98f9 --- /dev/null +++ b/tools/aapt2/filter/AbiFilter.cpp @@ -0,0 +1,51 @@ +/* + * 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 "AbiFilter.h" + +#include <memory> + +#include "io/Util.h" + +namespace aapt { + +std::unique_ptr<AbiFilter> AbiFilter::FromAbiList(const std::vector<configuration::Abi>& abi_list) { + std::unordered_set<std::string> abi_set; + for (auto& abi : abi_list) { + abi_set.insert(configuration::AbiToString(abi)); + } + // Make unique by hand as the constructor is private. + return std::unique_ptr<AbiFilter>(new AbiFilter(abi_set)); +} + +bool AbiFilter::Keep(const std::string& path) { + // We only care about libraries. + if (!util::StartsWith(path, kLibPrefix)) { + return true; + } + + auto abi_end = path.find('/', kLibPrefixLen); + if (abi_end == std::string::npos) { + // Ignore any files in the top level lib directory. + return true; + } + + // Strip the lib/ prefix. + const std::string& path_abi = path.substr(kLibPrefixLen, abi_end - kLibPrefixLen); + return (abis_.find(path_abi) != abis_.end()); +} + +} // namespace aapt diff --git a/tools/aapt2/filter/AbiFilter.h b/tools/aapt2/filter/AbiFilter.h new file mode 100644 index 000000000000..d875cb2b127b --- /dev/null +++ b/tools/aapt2/filter/AbiFilter.h @@ -0,0 +1,54 @@ +/* + * 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_ABISPLITTER_H +#define AAPT2_ABISPLITTER_H + +#include <memory> +#include <string> +#include <unordered_set> +#include <vector> + +#include "configuration/ConfigurationParser.h" +#include "filter/Filter.h" + +namespace aapt { + +/** + * Filters native library paths by ABI. ABIs present in the filter list are kept and all over + * libraries are removed. The filter is only applied to native library paths (this under lib/). + */ +class AbiFilter : public IPathFilter { + public: + /** Factory method to create a filter from a list of configuration::Abi. */ + static std::unique_ptr<AbiFilter> FromAbiList(const std::vector<configuration::Abi>& abi_list); + + /** Returns true if the path is for a native library in the list of desired ABIs. */ + bool Keep(const std::string& path) override; + + private: + explicit AbiFilter(std::unordered_set<std::string> abis) : abis_(std::move(abis)) { + } + + /** The path prefix to where all native libs end up inside an APK file. */ + static constexpr const char* kLibPrefix = "lib/"; + static constexpr size_t kLibPrefixLen = 4; + const std::unordered_set<std::string> abis_; +}; + +} // namespace aapt + +#endif // AAPT2_ABISPLITTER_H diff --git a/tools/aapt2/filter/AbiFilter_test.cpp b/tools/aapt2/filter/AbiFilter_test.cpp new file mode 100644 index 000000000000..0c8ea3575a29 --- /dev/null +++ b/tools/aapt2/filter/AbiFilter_test.cpp @@ -0,0 +1,66 @@ +/* + * 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 "filter/AbiFilter.h" + +#include <string> + +#include "gtest/gtest.h" + +namespace aapt { +namespace { + +using ::aapt::configuration::Abi; + +struct TestData { + std::string path; + bool kept; +}; + +const TestData kTestData[] = { + /* Keep. */ + {"lib/mips/libnative.so", true}, + {"not/native/file.txt", true}, + // Not sure if this is a valid use case. + {"lib/listing.txt", true}, + {"lib/mips/foo/bar/baz.so", true}, + {"lib/mips/x86/foo.so", true}, + /* Discard. */ + {"lib/mips_horse/foo.so", false}, + {"lib/horse_mips/foo.so", false}, + {"lib/mips64/armeabi-v7a/foo.so", false}, + {"lib/mips64/x86_64/x86.so", false}, + {"lib/x86/libnative.so", false}, + {"lib/x86/foo/bar/baz.so", false}, + {"lib/x86/x86/foo.so", false}, + {"lib/x86_horse/foo.so", false}, + {"lib/horse_x86/foo.so", false}, + {"lib/x86/armeabi-v7a/foo.so", false}, + {"lib/x86_64/x86_64/x86.so", false}, +}; + +class AbiFilterTest : public ::testing::TestWithParam<TestData> {}; + +TEST_P(AbiFilterTest, Keep) { + auto mips = AbiFilter::FromAbiList({Abi::kMips}); + const TestData& data = GetParam(); + EXPECT_EQ(mips->Keep(data.path), data.kept); +} + +INSTANTIATE_TEST_CASE_P(NativePaths, AbiFilterTest, ::testing::ValuesIn(kTestData)); + +} // namespace +} // namespace aapt diff --git a/tools/aapt2/filter/Filter.h b/tools/aapt2/filter/Filter.h new file mode 100644 index 000000000000..d737dc92e87b --- /dev/null +++ b/tools/aapt2/filter/Filter.h @@ -0,0 +1,77 @@ +/* + * 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_FILTER_H +#define AAPT2_FILTER_H + +#include <string> +#include <vector> + +#include "util/Util.h" + +namespace aapt { + +/** A filter to be applied to a path segment. */ +class IPathFilter { + public: + ~IPathFilter() = default; + + /** Returns true if the path should be kept. */ + virtual bool Keep(const std::string& path) = 0; +}; + +/** + * Path filter that keeps anything that matches the provided prefix. + */ +class PrefixFilter : public IPathFilter { + public: + explicit PrefixFilter(std::string prefix) : prefix_(std::move(prefix)) { + } + + /** Returns true if the provided path matches the prefix. */ + bool Keep(const std::string& path) override { + return util::StartsWith(path, prefix_); + } + + private: + const std::string prefix_; +}; + +/** Applies a set of IPathFilters to a path and returns true iif all filters keep the path. */ +class FilterChain : public IPathFilter { + public: + /** Adds a filter to the list to be applied to each path. */ + void AddFilter(std::unique_ptr<IPathFilter> filter) { + filters_.push_back(std::move(filter)); + } + + /** Returns true if all filters keep the path. */ + bool Keep(const std::string& path) override { + for (auto& filter : filters_) { + if (!filter->Keep(path)) { + return false; + } + } + return true; + } + + private: + std::vector<std::unique_ptr<IPathFilter>> filters_; +}; + +} // namespace aapt + +#endif // AAPT2_FILTER_H diff --git a/tools/aapt2/filter/Filter_test.cpp b/tools/aapt2/filter/Filter_test.cpp new file mode 100644 index 000000000000..fb75a4b4d7c1 --- /dev/null +++ b/tools/aapt2/filter/Filter_test.cpp @@ -0,0 +1,54 @@ +/* + * 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 "filter/Filter.h" + +#include <string> + +#include "io/Util.h" + +#include "gtest/gtest.h" + +namespace aapt { +namespace { + +TEST(FilterChainTest, EmptyChain) { + 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/")); + + ASSERT_FALSE(chain.Keep("removed/path")); + ASSERT_FALSE(chain.Keep("/keep/really/wrong/prefix")); + ASSERT_FALSE(chain.Keep("keep/maybe/1")); + ASSERT_TRUE(chain.Keep("keep/really/1")); +} + +} // namespace +} // namespace aapt 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 f4d02262f25c..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)); @@ -557,19 +557,15 @@ class PackageFlattener { } // namespace bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) { - // We must do this before writing the resources, since the string pool IDs may - // change. - table->string_pool.Sort( - [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { - int diff = a.context.priority - b.context.priority; - if (diff < 0) return true; - if (diff > 0) return false; - diff = a.context.config.compare(b.context.config); - if (diff < 0) return true; - if (diff > 0) return false; - return a.value < b.value; - }); + // We must do this before writing the resources, since the string pool IDs may change. table->string_pool.Prune(); + table->string_pool.Sort([](const StringPool::Context& a, const StringPool::Context& b) -> int { + int diff = util::compare(a.priority, b.priority); + if (diff == 0) { + diff = a.config.compare(b.config); + } + return diff; + }); // Write the ResTable header. ChunkWriter table_writer(buffer_); diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp index 6d1350d433a4..4fdb2eced59b 100644 --- a/tools/aapt2/flatten/TableFlattener_test.cpp +++ b/tools/aapt2/flatten/TableFlattener_test.cpp @@ -26,6 +26,9 @@ using namespace android; +using ::testing::IsNull; +using ::testing::NotNull; + namespace aapt { class TableFlattenerTest : public ::testing::Test { @@ -235,13 +238,12 @@ TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) { ResourceTable result; ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &result)); - Attribute* actualAttr = - test::GetValue<Attribute>(&result, "android:attr/foo"); - ASSERT_NE(nullptr, actualAttr); - EXPECT_EQ(attr.IsWeak(), actualAttr->IsWeak()); - EXPECT_EQ(attr.type_mask, actualAttr->type_mask); - EXPECT_EQ(attr.min_int, actualAttr->min_int); - EXPECT_EQ(attr.max_int, actualAttr->max_int); + Attribute* actual_attr = test::GetValue<Attribute>(&result, "android:attr/foo"); + ASSERT_THAT(actual_attr, NotNull()); + EXPECT_EQ(attr.IsWeak(), actual_attr->IsWeak()); + EXPECT_EQ(attr.type_mask, actual_attr->type_mask); + EXPECT_EQ(attr.min_int, actual_attr->min_int); + EXPECT_EQ(attr.max_int, actual_attr->max_int); } static std::unique_ptr<ResourceTable> BuildTableWithSparseEntries( @@ -303,15 +305,13 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkO) { auto value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_0", sparse_config); - ASSERT_NE(nullptr, value); + ASSERT_THAT(value, NotNull()); EXPECT_EQ(0u, value->value.data); - ASSERT_EQ(nullptr, test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1", - sparse_config)); + ASSERT_THAT(test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_1", sparse_config), IsNull()); - value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4", - sparse_config); - ASSERT_NE(nullptr, value); + value = test::GetValueForConfig<BinaryPrimitive>(&sparse_table, "android:string/foo_4", sparse_config); + ASSERT_THAT(value, NotNull()); EXPECT_EQ(4u, value->value.data); } @@ -372,7 +372,7 @@ TEST_F(TableFlattenerTest, FlattenSharedLibrary) { Maybe<ResourceTable::SearchResult> search_result = result.FindResource(test::ParseNameOrDie("lib:id/foo")); - AAPT_ASSERT_TRUE(search_result); + ASSERT_TRUE(search_result); EXPECT_EQ(0x00u, search_result.value().package->id.value()); auto iter = result.included_packages_.find(0x00); @@ -398,7 +398,7 @@ TEST_F(TableFlattenerTest, FlattenTableReferencingSharedLibraries) { ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result)); const DynamicRefTable* dynamic_ref_table = result.getDynamicRefTableForCookie(1); - ASSERT_NE(nullptr, dynamic_ref_table); + ASSERT_THAT(dynamic_ref_table, NotNull()); const KeyedVector<String16, uint8_t>& entries = dynamic_ref_table->entries(); @@ -423,7 +423,7 @@ TEST_F(TableFlattenerTest, PackageWithNonStandardIdHasDynamicRefTable) { ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result)); const DynamicRefTable* dynamic_ref_table = result.getDynamicRefTableForCookie(1); - ASSERT_NE(nullptr, dynamic_ref_table); + ASSERT_THAT(dynamic_ref_table, NotNull()); const KeyedVector<String16, uint8_t>& entries = dynamic_ref_table->entries(); ssize_t idx = entries.indexOfKey(android::String16("app")); diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp index bfebedef2a1e..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,17 +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); + ResXMLTree_namespaceExt* flat_ns = writer.NextBlock<ResXMLTree_namespaceExt>(); + AddString(decl.prefix, kLowPriority, &flat_ns->prefix); + AddString(decl.uri, kLowPriority, &flat_ns->uri); writer.Finish(); } @@ -289,8 +283,7 @@ class XmlFlattenerVisitor : public xml::Visitor { BigBuffer* buffer_; XmlFlattenerOptions options_; - // Scratch vector to filter attributes. We avoid allocations - // making this a member. + // Scratch vector to filter attributes. We avoid allocations making this a member. std::vector<xml::Attribute*> filtered_attrs_; }; @@ -307,10 +300,9 @@ bool XmlFlattener::Flatten(IAaptContext* context, xml::Node* node) { } // Sort the string pool so that attribute resource IDs show up first. - visitor.pool.Sort( - [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { - return a.context.priority < b.context.priority; - }); + visitor.pool.Sort([](const StringPool::Context& a, const StringPool::Context& b) -> int { + return util::compare(a.priority, b.priority); + }); // Now we flatten the string pool references into the correct places. for (const auto& ref_entry : visitor.string_refs) { @@ -328,15 +320,13 @@ bool XmlFlattener::Flatten(IAaptContext* context, xml::Node* node) { // Write the array of resource IDs, indexed by StringPool order. ChunkWriter res_id_map_writer(buffer_); res_id_map_writer.StartChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE); - for (const auto& str : visitor.pool) { - ResourceId id = {str->context.priority}; - if (id.id == kLowPriority || !id.is_valid()) { - // When we see the first non-resource ID, - // we're done. + for (const auto& str : visitor.pool.strings()) { + ResourceId id(str->context.priority); + if (str->context.priority == kLowPriority || !id.is_valid()) { + // When we see the first non-resource ID, we're done. break; } - - *res_id_map_writer.NextBlock<uint32_t>() = id.id; + *res_id_map_writer.NextBlock<uint32_t>() = util::HostToDevice32(id.id); } res_id_map_writer.Finish(); } diff --git a/tools/aapt2/integration-tests/AppOne/res/navigation/home.xml b/tools/aapt2/integration-tests/AppOne/res/navigation/home.xml new file mode 100644 index 000000000000..ade271d60ab6 --- /dev/null +++ b/tools/aapt2/integration-tests/AppOne/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/values/styles.xml b/tools/aapt2/integration-tests/AppOne/res/values/styles.xml index f05845cfab28..19d96c0809db 100644 --- a/tools/aapt2/integration-tests/AppOne/res/values/styles.xml +++ b/tools/aapt2/integration-tests/AppOne/res/values/styles.xml @@ -25,6 +25,7 @@ <style name="Pop"> <item name="custom">@android:drawable/btn_default</item> <item name="android:focusable">true</item> + <item name="android:numColumns">auto_fit</item> </style> <string name="yo">@string/wow</string> 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 a0ef00b1ea1f..1f83fa098d74 100644 --- a/tools/aapt2/java/AnnotationProcessor.cpp +++ b/tools/aapt2/java/AnnotationProcessor.cpp @@ -18,12 +18,29 @@ #include <algorithm> +#include "text/Unicode.h" +#include "text/Utf8Iterator.h" #include "util/Util.h" -using android::StringPiece; +using ::aapt::text::Utf8Iterator; +using ::android::StringPiece; namespace aapt { +StringPiece AnnotationProcessor::ExtractFirstSentence(const StringPiece& comment) { + Utf8Iterator iter(comment); + while (iter.HasNext()) { + const char32_t codepoint = iter.Next(); + if (codepoint == U'.') { + const size_t current_position = iter.Position(); + if (!iter.HasNext() || text::IsWhitespace(iter.Next())) { + return comment.substr(0, current_position); + } + } + } + return comment; +} + void AnnotationProcessor::AppendCommentLine(std::string& comment) { static const std::string sDeprecated = "@deprecated"; static const std::string sSystemApi = "@SystemApi"; diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h index 99cd44fd2cc1..a06eda0f9c5c 100644 --- a/tools/aapt2/java/AnnotationProcessor.h +++ b/tools/aapt2/java/AnnotationProcessor.h @@ -53,6 +53,8 @@ namespace aapt { */ class AnnotationProcessor { public: + static android::StringPiece ExtractFirstSentence(const android::StringPiece& comment); + /** * Adds more comments. Since resources can have various values with different * configurations, diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp index 3e43c4295c07..9ccac8888426 100644 --- a/tools/aapt2/java/AnnotationProcessor_test.cpp +++ b/tools/aapt2/java/AnnotationProcessor_test.cpp @@ -18,6 +18,10 @@ #include "test/Test.h" +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::Not; + namespace aapt { TEST(AnnotationProcessorTest, EmitsDeprecated) { @@ -33,7 +37,7 @@ TEST(AnnotationProcessorTest, EmitsDeprecated) { processor.WriteToStream(&result, ""); std::string annotations = result.str(); - EXPECT_NE(std::string::npos, annotations.find("@Deprecated")); + EXPECT_THAT(annotations, HasSubstr("@Deprecated")); } TEST(AnnotationProcessorTest, EmitsSystemApiAnnotationAndRemovesFromComment) { @@ -44,10 +48,20 @@ TEST(AnnotationProcessorTest, EmitsSystemApiAnnotationAndRemovesFromComment) { processor.WriteToStream(&result, ""); std::string annotations = result.str(); - EXPECT_NE(std::string::npos, - annotations.find("@android.annotation.SystemApi")); - EXPECT_EQ(std::string::npos, annotations.find("@SystemApi")); - EXPECT_NE(std::string::npos, annotations.find("This is a system API")); + EXPECT_THAT(annotations, HasSubstr("@android.annotation.SystemApi")); + EXPECT_THAT(annotations, Not(HasSubstr("@SystemApi"))); + EXPECT_THAT(annotations, HasSubstr("This is a system API")); +} + +TEST(AnnotationProcessor, ExtractsFirstSentence) { + EXPECT_THAT(AnnotationProcessor::ExtractFirstSentence("This is the only sentence"), + Eq("This is the only sentence")); + EXPECT_THAT(AnnotationProcessor::ExtractFirstSentence( + "This is the\n first sentence. This is the rest of the paragraph."), + Eq("This is the\n first sentence.")); + EXPECT_THAT(AnnotationProcessor::ExtractFirstSentence( + "This is the first sentence with a {@link android.R.styleable.Theme}."), + Eq("This is the first sentence with a {@link android.R.styleable.Theme}.")); } } // namespace aapt diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp index 0cec9ae407f5..d7508d257db4 100644 --- a/tools/aapt2/java/ClassDefinition.cpp +++ b/tools/aapt2/java/ClassDefinition.cpp @@ -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()) { 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 2a23aa9e5372..44fa0f19a0e5 100644 --- a/tools/aapt2/java/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -299,24 +299,16 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res } const ResourceName& attr_name = entry.attr_ref->name.value(); - styleable_comment << "<tr><td>"; - styleable_comment << "<code>{@link #" << entry.field_name << " " - << (!attr_name.package.empty() - ? attr_name.package - : context_->GetCompilationPackage()) - << ":" << attr_name.entry << "}</code>"; - styleable_comment << "</td>"; - - styleable_comment << "<td>"; + styleable_comment << "<tr><td><code>{@link #" << entry.field_name << " " + << (!attr_name.package.empty() ? attr_name.package + : context_->GetCompilationPackage()) + << ":" << attr_name.entry << "}</code></td>"; // Only use the comment up until the first '.'. This is to stay compatible with // the way old AAPT did it (presumably to keep it short and to avoid including // annotations like @hide which would affect this Styleable). - auto iter = std::find(attr_comment_line.begin(), attr_comment_line.end(), '.'); - if (iter != attr_comment_line.end()) { - attr_comment_line = attr_comment_line.substr(0, (iter - attr_comment_line.begin()) + 1); - } - styleable_comment << attr_comment_line << "</td></tr>\n"; + styleable_comment << "<td>" << AnnotationProcessor::ExtractFirstSentence(attr_comment_line) + << "</td></tr>\n"; } styleable_comment << "</table>\n"; diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp index f49e4985fcf1..cad4c6c7c94f 100644 --- a/tools/aapt2/java/ManifestClassGenerator.cpp +++ b/tools/aapt2/java/ManifestClassGenerator.cpp @@ -81,7 +81,10 @@ 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; } diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp index 5ebf508807e8..44b6a1ffd5ae 100644 --- a/tools/aapt2/java/ManifestClassGenerator_test.cpp +++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp @@ -18,124 +18,130 @@ #include "test/Test.h" -namespace aapt { +using ::testing::HasSubstr; +using ::testing::Not; -static ::testing::AssertionResult GetManifestClassText(IAaptContext* context, - xml::XmlResource* res, - std::string* out_str) { - std::unique_ptr<ClassDefinition> manifest_class = - GenerateManifestClass(context->GetDiagnostics(), res); - if (!manifest_class) { - return ::testing::AssertionFailure() << "manifest_class == nullptr"; - } - - std::stringstream out; - if (!manifest_class->WriteJavaFile(manifest_class.get(), "android", true, - &out)) { - return ::testing::AssertionFailure() << "failed to write java file"; - } +namespace aapt { - *out_str = out.str(); - return ::testing::AssertionSuccess(); -} +static ::testing::AssertionResult GetManifestClassText(IAaptContext* context, xml::XmlResource* res, + std::string* out_str); TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - std::unique_ptr<xml::XmlResource> manifest = test::BuildXmlDom(R"EOF( - <manifest xmlns:android="http://schemas.android.com/apk/res/android"> - <permission android:name="android.permission.ACCESS_INTERNET" /> - <permission android:name="android.DO_DANGEROUS_THINGS" /> - <permission android:name="com.test.sample.permission.HUH" /> - <permission-group android:name="foo.bar.PERMISSION" /> - </manifest>)EOF"); + 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="android.DO_DANGEROUS_THINGS" /> + <permission android:name="com.test.sample.permission.HUH" /> + <permission-group android:name="foo.bar.PERMISSION" /> + </manifest>)"); std::string actual; ASSERT_TRUE(GetManifestClassText(context.get(), manifest.get(), &actual)); - const size_t permission_class_pos = - actual.find("public static final class permission {"); - const size_t permission_croup_class_pos = + ASSERT_THAT(actual, HasSubstr("public static final class permission {")); + ASSERT_THAT(actual, HasSubstr("public static final class permission_group {")); + + const size_t permission_start_pos = actual.find("public static final class permission {"); + const size_t permission_group_start_pos = actual.find("public static final class permission_group {"); - ASSERT_NE(std::string::npos, permission_class_pos); - ASSERT_NE(std::string::npos, permission_croup_class_pos); // // Make sure these permissions are in the permission class. // - - size_t pos = actual.find( - "public static final String ACCESS_INTERNET=" - "\"android.permission.ACCESS_INTERNET\";"); - EXPECT_GT(pos, permission_class_pos); - EXPECT_LT(pos, permission_croup_class_pos); - - pos = actual.find( - "public static final String DO_DANGEROUS_THINGS=" - "\"android.DO_DANGEROUS_THINGS\";"); - EXPECT_GT(pos, permission_class_pos); - EXPECT_LT(pos, permission_croup_class_pos); - - pos = actual.find( - "public static final String HUH=\"com.test.sample.permission.HUH\";"); - EXPECT_GT(pos, permission_class_pos); - EXPECT_LT(pos, permission_croup_class_pos); + const std::string permission_class = + actual.substr(permission_start_pos, permission_group_start_pos - permission_start_pos); + + EXPECT_THAT( + permission_class, + HasSubstr( + "public static final String ACCESS_INTERNET=\"android.permission.ACCESS_INTERNET\";")); + EXPECT_THAT( + permission_class, + HasSubstr("public static final String DO_DANGEROUS_THINGS=\"android.DO_DANGEROUS_THINGS\";")); + EXPECT_THAT(permission_class, + HasSubstr("public static final String HUH=\"com.test.sample.permission.HUH\";")); // // Make sure these permissions are in the permission_group class // + const std::string permission_group_class = actual.substr(permission_group_start_pos); - pos = actual.find( - "public static final String PERMISSION=" - "\"foo.bar.PERMISSION\";"); - EXPECT_GT(pos, permission_croup_class_pos); - EXPECT_LT(pos, std::string::npos); + EXPECT_THAT(permission_group_class, + HasSubstr("public static final String PERMISSION=\"foo.bar.PERMISSION\";")); } TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - std::unique_ptr<xml::XmlResource> manifest = test::BuildXmlDom(R"EOF( - <manifest xmlns:android="http://schemas.android.com/apk/res/android"> - <!-- Required to access the internet. - Added in API 1. --> - <permission android:name="android.permission.ACCESS_INTERNET" /> - <!-- @deprecated This permission is for playing outside. --> - <permission android:name="android.permission.PLAY_OUTSIDE" /> - <!-- This is a private permission for system only! - @hide - @SystemApi --> - <permission android:name="android.permission.SECRET" /> - </manifest>)EOF"); + std::unique_ptr<xml::XmlResource> manifest = test::BuildXmlDom(R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- Required to access the internet. + Added in API 1. --> + <permission android:name="android.permission.ACCESS_INTERNET" /> + <!-- @deprecated This permission is for playing outside. --> + <permission android:name="android.permission.PLAY_OUTSIDE" /> + <!-- This is a private permission for system only! + @hide + @SystemApi --> + <permission android:name="android.permission.SECRET" /> + </manifest>)"); std::string actual; ASSERT_TRUE(GetManifestClassText(context.get(), manifest.get(), &actual)); - const char* expected_access_internet = - R"EOF( /** + const char* expected_access_internet = R"( /** * Required to access the internet. * Added in API 1. */ - public static final String ACCESS_INTERNET="android.permission.ACCESS_INTERNET";)EOF"; + public static final String ACCESS_INTERNET="android.permission.ACCESS_INTERNET";)"; + EXPECT_THAT(actual, HasSubstr(expected_access_internet)); - EXPECT_NE(std::string::npos, actual.find(expected_access_internet)); - - const char* expected_play_outside = - R"EOF( /** + const char* expected_play_outside = R"( /** * @deprecated This permission is for playing outside. */ @Deprecated - public static final String PLAY_OUTSIDE="android.permission.PLAY_OUTSIDE";)EOF"; - - EXPECT_NE(std::string::npos, actual.find(expected_play_outside)); + public static final String PLAY_OUTSIDE="android.permission.PLAY_OUTSIDE";)"; + EXPECT_THAT(actual, HasSubstr(expected_play_outside)); - const char* expected_secret = - R"EOF( /** + const char* expected_secret = R"( /** * This is a private permission for system only! * @hide */ @android.annotation.SystemApi - public static final String SECRET="android.permission.SECRET";)EOF"; + public static final String SECRET="android.permission.SECRET";)"; + EXPECT_THAT(actual, HasSubstr(expected_secret)); +} + +// 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, + std::string* out_str) { + std::unique_ptr<ClassDefinition> manifest_class = + GenerateManifestClass(context->GetDiagnostics(), res); + if (!manifest_class) { + return ::testing::AssertionFailure() << "manifest_class == nullptr"; + } + + std::stringstream out; + if (!manifest_class->WriteJavaFile(manifest_class.get(), "android", true, &out)) { + return ::testing::AssertionFailure() << "failed to write java file"; + } - EXPECT_NE(std::string::npos, actual.find(expected_secret)); + *out_str = out.str(); + return ::testing::AssertionSuccess(); } } // namespace aapt diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp index 624a559c4dae..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,14 +72,18 @@ 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()) { - check_class = node->name == "view" || node->name == "fragment"; + if (node->name == "view") { + check_class = true; + } else if (node->name == "fragment") { + check_class = check_name = true; + } } else if (node->namespace_uri == xml::kSchemaAndroid) { check_name = node->name == "fragment"; } @@ -110,12 +108,38 @@ class LayoutVisitor : public BaseVisitor { DISALLOW_COPY_AND_ASSIGN(LayoutVisitor); }; +class MenuVisitor : public BaseVisitor { + public: + MenuVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) { + } + + 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) { + if ((attr.name == "actionViewClass" || attr.name == "actionProviderClass") && + util::IsJavaClassName(attr.value)) { + AddClass(node->line_number, attr.value); + } else if (attr.name == "onClick") { + AddMethod(node->line_number, attr.value); + } + } + } + } + + BaseVisitor::Visit(node); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MenuVisitor); +}; + 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 = @@ -139,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)) { @@ -165,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") { @@ -175,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; } @@ -196,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_; @@ -212,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()); } @@ -231,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) { @@ -242,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; } @@ -267,6 +283,12 @@ bool CollectProguardRules(const Source& source, xml::XmlResource* res, break; } + case ResourceType::kMenu: { + MenuVisitor visitor(source, keep_set); + res->root->Accept(&visitor); + break; + } + default: break; } @@ -285,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/java/ProguardRules_test.cpp b/tools/aapt2/java/ProguardRules_test.cpp new file mode 100644 index 000000000000..900b07339715 --- /dev/null +++ b/tools/aapt2/java/ProguardRules_test.cpp @@ -0,0 +1,119 @@ +/* + * 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 "java/ProguardRules.h" + +#include "test/Test.h" + +using ::testing::HasSubstr; +using ::testing::Not; + +namespace aapt { + +TEST(ProguardRulesTest, FragmentNameRuleIsEmitted) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"( + <fragment xmlns:android="http://schemas.android.com/apk/res/android" + android:name="com.foo.Bar"/>)"); + layout->file.name = test::ParseNameOrDie("layout/foo"); + + proguard::KeepSet set; + ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set)); + + std::stringstream out; + ASSERT_TRUE(proguard::WriteKeepSet(&out, set)); + + std::string actual = out.str(); + EXPECT_THAT(actual, HasSubstr("com.foo.Bar")); +} + +TEST(ProguardRulesTest, FragmentClassRuleIsEmitted) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> layout = + test::BuildXmlDom(R"(<fragment class="com.foo.Bar"/>)"); + layout->file.name = test::ParseNameOrDie("layout/foo"); + + proguard::KeepSet set; + ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set)); + + std::stringstream out; + ASSERT_TRUE(proguard::WriteKeepSet(&out, set)); + + std::string actual = out.str(); + EXPECT_THAT(actual, HasSubstr("com.foo.Bar")); +} + +TEST(ProguardRulesTest, FragmentNameAndClassRulesAreEmitted) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"( + <fragment xmlns:android="http://schemas.android.com/apk/res/android" + android:name="com.foo.Baz" + class="com.foo.Bar"/>)"); + layout->file.name = test::ParseNameOrDie("layout/foo"); + + proguard::KeepSet set; + ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set)); + + std::stringstream out; + ASSERT_TRUE(proguard::WriteKeepSet(&out, set)); + + std::string actual = out.str(); + EXPECT_THAT(actual, HasSubstr("com.foo.Bar")); + EXPECT_THAT(actual, HasSubstr("com.foo.Baz")); +} + +TEST(ProguardRulesTest, ViewOnClickRuleIsEmitted) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> layout = test::BuildXmlDom(R"( + <View xmlns:android="http://schemas.android.com/apk/res/android" + android:onClick="bar_method" />)"); + layout->file.name = test::ParseNameOrDie("layout/foo"); + + proguard::KeepSet set; + ASSERT_TRUE(proguard::CollectProguardRules({}, layout.get(), &set)); + + std::stringstream out; + ASSERT_TRUE(proguard::WriteKeepSet(&out, set)); + + std::string actual = out.str(); + EXPECT_THAT(actual, HasSubstr("bar_method")); +} + +TEST(ProguardRulesTest, MenuRulesAreEmitted) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> menu = test::BuildXmlDom(R"( + <menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:onClick="on_click" + android:actionViewClass="com.foo.Bar" + android:actionProviderClass="com.foo.Baz" + android:name="com.foo.Bat" /> + </menu>)"); + menu->file.name = test::ParseNameOrDie("menu/foo"); + + proguard::KeepSet set; + ASSERT_TRUE(proguard::CollectProguardRules({}, menu.get(), &set)); + + std::stringstream out; + ASSERT_TRUE(proguard::WriteKeepSet(&out, set)); + + std::string actual = out.str(); + EXPECT_THAT(actual, HasSubstr("on_click")); + EXPECT_THAT(actual, HasSubstr("com.foo.Bar")); + EXPECT_THAT(actual, HasSubstr("com.foo.Baz")); + EXPECT_THAT(actual, Not(HasSubstr("com.foo.Bat"))); +} + +} // namespace aapt diff --git a/tools/aapt2/link/AutoVersioner_test.cpp b/tools/aapt2/link/AutoVersioner_test.cpp index 755af0a1c6cc..49639f8ad549 100644 --- a/tools/aapt2/link/AutoVersioner_test.cpp +++ b/tools/aapt2/link/AutoVersioner_test.cpp @@ -19,43 +19,34 @@ #include "ConfigDescription.h" #include "test/Test.h" +using ::testing::NotNull; + namespace aapt { TEST(AutoVersionerTest, GenerateVersionedResources) { const ConfigDescription land_config = test::ParseConfigOrDie("land"); - const ConfigDescription sw600dp_land_config = - test::ParseConfigOrDie("sw600dp-land"); + const ConfigDescription sw600dp_land_config = test::ParseConfigOrDie("sw600dp-land"); ResourceEntry entry("foo"); - entry.values.push_back(util::make_unique<ResourceConfigValue>( - ConfigDescription::DefaultConfig(), "")); - entry.values.push_back( - util::make_unique<ResourceConfigValue>(land_config, "")); - entry.values.push_back( - util::make_unique<ResourceConfigValue>(sw600dp_land_config, "")); - - EXPECT_TRUE(ShouldGenerateVersionedResource( - &entry, ConfigDescription::DefaultConfig(), 17)); + entry.values.push_back(util::make_unique<ResourceConfigValue>(ConfigDescription::DefaultConfig(), "")); + entry.values.push_back(util::make_unique<ResourceConfigValue>(land_config, "")); + entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dp_land_config, "")); + + EXPECT_TRUE(ShouldGenerateVersionedResource(&entry, ConfigDescription::DefaultConfig(), 17)); EXPECT_TRUE(ShouldGenerateVersionedResource(&entry, land_config, 17)); } TEST(AutoVersionerTest, GenerateVersionedResourceWhenHigherVersionExists) { - const ConfigDescription sw600dp_v13_config = - test::ParseConfigOrDie("sw600dp-v13"); + const ConfigDescription sw600dp_v13_config = test::ParseConfigOrDie("sw600dp-v13"); const ConfigDescription v21_config = test::ParseConfigOrDie("v21"); ResourceEntry entry("foo"); - entry.values.push_back(util::make_unique<ResourceConfigValue>( - ConfigDescription::DefaultConfig(), "")); - entry.values.push_back( - util::make_unique<ResourceConfigValue>(sw600dp_v13_config, "")); - entry.values.push_back( - util::make_unique<ResourceConfigValue>(v21_config, "")); - - EXPECT_TRUE(ShouldGenerateVersionedResource( - &entry, ConfigDescription::DefaultConfig(), 17)); - EXPECT_FALSE(ShouldGenerateVersionedResource( - &entry, ConfigDescription::DefaultConfig(), 22)); + entry.values.push_back(util::make_unique<ResourceConfigValue>(ConfigDescription::DefaultConfig(), "")); + entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dp_v13_config, "")); + entry.values.push_back(util::make_unique<ResourceConfigValue>(v21_config, "")); + + EXPECT_TRUE(ShouldGenerateVersionedResource(&entry, ConfigDescription::DefaultConfig(), 17)); + EXPECT_FALSE(ShouldGenerateVersionedResource(&entry, ConfigDescription::DefaultConfig(), 22)); } TEST(AutoVersionerTest, VersionStylesForTable) { @@ -92,46 +83,28 @@ TEST(AutoVersionerTest, VersionStylesForTable) { AutoVersioner versioner; ASSERT_TRUE(versioner.Consume(context.get(), table.get())); - Style* style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", - test::ParseConfigOrDie("v4")); - ASSERT_NE(style, nullptr); + Style* style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", test::ParseConfigOrDie("v4")); + ASSERT_THAT(style, NotNull()); ASSERT_EQ(style->entries.size(), 1u); - AAPT_ASSERT_TRUE(style->entries.front().key.name); - EXPECT_EQ(style->entries.front().key.name.value(), - test::ParseNameOrDie("android:attr/onClick")); + EXPECT_EQ(make_value(test::ParseNameOrDie("android:attr/onClick")), style->entries.front().key.name); - style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", - test::ParseConfigOrDie("v13")); - ASSERT_NE(style, nullptr); + style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", test::ParseConfigOrDie("v13")); + ASSERT_THAT(style, NotNull()); ASSERT_EQ(style->entries.size(), 2u); - AAPT_ASSERT_TRUE(style->entries[0].key.name); - EXPECT_EQ(style->entries[0].key.name.value(), - test::ParseNameOrDie("android:attr/onClick")); - AAPT_ASSERT_TRUE(style->entries[1].key.name); - EXPECT_EQ(style->entries[1].key.name.value(), - test::ParseNameOrDie("android:attr/requiresSmallestWidthDp")); - - style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", - test::ParseConfigOrDie("v17")); - ASSERT_NE(style, nullptr); + EXPECT_EQ(make_value(test::ParseNameOrDie("android:attr/onClick")),style->entries[0].key.name); + EXPECT_EQ(make_value(test::ParseNameOrDie("android:attr/requiresSmallestWidthDp")), style->entries[1].key.name); + + style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", test::ParseConfigOrDie("v17")); + ASSERT_THAT(style, NotNull()); ASSERT_EQ(style->entries.size(), 3u); - AAPT_ASSERT_TRUE(style->entries[0].key.name); - EXPECT_EQ(style->entries[0].key.name.value(), - test::ParseNameOrDie("android:attr/onClick")); - AAPT_ASSERT_TRUE(style->entries[1].key.name); - EXPECT_EQ(style->entries[1].key.name.value(), - test::ParseNameOrDie("android:attr/requiresSmallestWidthDp")); - AAPT_ASSERT_TRUE(style->entries[2].key.name); - EXPECT_EQ(style->entries[2].key.name.value(), - test::ParseNameOrDie("android:attr/paddingStart")); - - style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", - test::ParseConfigOrDie("v21")); - ASSERT_NE(style, nullptr); - ASSERT_EQ(style->entries.size(), 1u); - AAPT_ASSERT_TRUE(style->entries.front().key.name); - EXPECT_EQ(style->entries.front().key.name.value(), - test::ParseNameOrDie("android:attr/paddingEnd")); + EXPECT_EQ(make_value(test::ParseNameOrDie("android:attr/onClick")), style->entries[0].key.name); + EXPECT_EQ(make_value(test::ParseNameOrDie("android:attr/requiresSmallestWidthDp")), style->entries[1].key.name); + EXPECT_EQ(make_value(test::ParseNameOrDie("android:attr/paddingStart")), style->entries[2].key.name); + + style = test::GetValueForConfig<Style>(table.get(), "app:style/Foo", test::ParseConfigOrDie("v21")); + ASSERT_THAT(style, NotNull()); + ASSERT_EQ(1u, style->entries.size()); + EXPECT_EQ(make_value(test::ParseNameOrDie("android:attr/paddingEnd")), style->entries.front().key.name); } } // namespace aapt diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index 012bb5ecdeca..6fb1793d90ed 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -310,6 +310,9 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, manifest_action["meta-data"] = meta_data_action; manifest_action["uses-split"].Action(RequiredNameIsJavaPackage); + manifest_action["key-sets"]["key-set"]["public-key"]; + manifest_action["key-sets"]["upgrade-key-set"]; + // Application actions. xml::XmlNodeAction& application_action = manifest_action["application"]; application_action.Action(OptionalNameIsJavaClassName); diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp index 064d3650065d..da7f410b8b08 100644 --- a/tools/aapt2/link/ManifestFixer_test.cpp +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -18,7 +18,8 @@ #include "test/Test.h" -using android::StringPiece; +using ::android::StringPiece; +using ::testing::NotNull; namespace aapt { @@ -121,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); @@ -140,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); @@ -159,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); @@ -176,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); @@ -198,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); @@ -247,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; @@ -296,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 = @@ -320,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 = @@ -343,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); @@ -420,4 +421,22 @@ TEST_F(ManifestFixerTest, DoNotIgnoreNonNamespacedElements) { EXPECT_EQ(nullptr, Verify(input)); } +TEST_F(ManifestFixerTest, SupportKeySets) { + std::string input = R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="android"> + <key-sets> + <key-set android:name="old-set"> + <public-key android:name="old-key" android:value="some+old+key" /> + </key-set> + <key-set android:name="new-set"> + <public-key android:name="new-key" android:value="some+new+key" /> + </key-set> + <upgrade-key-set android:name="old-set" /> + <upgrade-key-set android:name="new-set" /> + </key-sets> + </manifest>)"; + EXPECT_THAT(Verify(input), NotNull()); +} + } // namespace aapt diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp index a8e510cd6140..414e56eb5dcc 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -109,18 +109,15 @@ class ReferenceLinkerVisitor : public ValueVisitor { entry.value->Accept(this); // Now verify that the type of this item is compatible with the - // attribute it - // is defined for. We pass `nullptr` as the DiagMessage so that this - // check is - // fast and we avoid creating a DiagMessage when the match is - // successful. - if (!symbol->attribute->Matches(entry.value.get(), nullptr)) { + // attribute it is defined for. We pass `nullptr` as the DiagMessage so that this + // check is fast and we avoid creating a DiagMessage when the match is successful. + if (!symbol->attribute->Matches(*entry.value, nullptr)) { // 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. - symbol->attribute->Matches(entry.value.get(), &msg); + symbol->attribute->Matches(*entry.value, &msg); context_->GetDiagnostics()->Error(msg); error_ = true; } diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp index d8e33a42711a..72a91689e392 100644 --- a/tools/aapt2/link/ReferenceLinker_test.cpp +++ b/tools/aapt2/link/ReferenceLinker_test.cpp @@ -19,6 +19,7 @@ #include "test/Test.h" using android::ResTable_map; +using ::testing::NotNull; namespace aapt { @@ -54,18 +55,18 @@ TEST(ReferenceLinkerTest, LinkSimpleReferences) { ASSERT_TRUE(linker.Consume(context.get(), table.get())); Reference* ref = test::GetValue<Reference>(table.get(), "com.app.test:string/foo"); - ASSERT_NE(nullptr, ref); - AAPT_ASSERT_TRUE(ref->id); + ASSERT_THAT(ref, NotNull()); + ASSERT_TRUE(ref->id); EXPECT_EQ(ResourceId(0x7f020001), ref->id.value()); ref = test::GetValue<Reference>(table.get(), "com.app.test:string/bar"); - ASSERT_NE(nullptr, ref); - AAPT_ASSERT_TRUE(ref->id); + ASSERT_THAT(ref, NotNull()); + ASSERT_TRUE(ref->id); EXPECT_EQ(ResourceId(0x7f020002), ref->id.value()); ref = test::GetValue<Reference>(table.get(), "com.app.test:string/baz"); - ASSERT_NE(nullptr, ref); - AAPT_ASSERT_TRUE(ref->id); + ASSERT_THAT(ref, NotNull()); + ASSERT_TRUE(ref->id); EXPECT_EQ(ResourceId(0x01040034), ref->id.value()); } @@ -84,10 +85,9 @@ TEST(ReferenceLinkerTest, LinkStyleAttributes) { { // We need to fill in the value for the attribute android:attr/bar after we - // build the - // table, because we need access to the string pool. + // build the table, because we need access to the string pool. Style* style = test::GetValue<Style>(table.get(), "com.app.test:style/Theme"); - ASSERT_NE(nullptr, style); + ASSERT_THAT(style, NotNull()); style->entries.back().value = util::make_unique<RawString>(table->string_pool.MakeRef("one|two")); } @@ -118,20 +118,20 @@ TEST(ReferenceLinkerTest, LinkStyleAttributes) { ASSERT_TRUE(linker.Consume(context.get(), table.get())); Style* style = test::GetValue<Style>(table.get(), "com.app.test:style/Theme"); - ASSERT_NE(nullptr, style); - AAPT_ASSERT_TRUE(style->parent); - AAPT_ASSERT_TRUE(style->parent.value().id); + ASSERT_THAT(style, NotNull()); + ASSERT_TRUE(style->parent); + ASSERT_TRUE(style->parent.value().id); EXPECT_EQ(ResourceId(0x01060000), style->parent.value().id.value()); ASSERT_EQ(2u, style->entries.size()); - AAPT_ASSERT_TRUE(style->entries[0].key.id); + ASSERT_TRUE(style->entries[0].key.id); EXPECT_EQ(ResourceId(0x01010001), style->entries[0].key.id.value()); - ASSERT_NE(nullptr, ValueCast<BinaryPrimitive>(style->entries[0].value.get())); + ASSERT_THAT(ValueCast<BinaryPrimitive>(style->entries[0].value.get()), NotNull()); - AAPT_ASSERT_TRUE(style->entries[1].key.id); + ASSERT_TRUE(style->entries[1].key.id); EXPECT_EQ(ResourceId(0x01010002), style->entries[1].key.id.value()); - ASSERT_NE(nullptr, ValueCast<BinaryPrimitive>(style->entries[1].value.get())); + ASSERT_THAT(ValueCast<BinaryPrimitive>(style->entries[1].value.get()), NotNull()); } TEST(ReferenceLinkerTest, LinkMangledReferencesAndAttributes) { @@ -165,9 +165,9 @@ TEST(ReferenceLinkerTest, LinkMangledReferencesAndAttributes) { ASSERT_TRUE(linker.Consume(context.get(), table.get())); Style* style = test::GetValue<Style>(table.get(), "com.app.test:style/Theme"); - ASSERT_NE(nullptr, style); + ASSERT_THAT(style, NotNull()); ASSERT_EQ(1u, style->entries.size()); - AAPT_ASSERT_TRUE(style->entries.front().key.id); + ASSERT_TRUE(style->entries.front().key.id); EXPECT_EQ(ResourceId(0x7f010000), style->entries.front().key.id.value()); } @@ -266,7 +266,7 @@ TEST(ReferenceLinkerTest, AppsWithSamePackageButDifferentIdAreVisibleNonPublic) const CallSite call_site{ResourceNameRef("com.app.test", ResourceType::kString, "foo")}; const SymbolTable::Symbol* symbol = ReferenceLinker::ResolveSymbolCheckVisibility( *test::BuildReference("com.app.test:string/foo"), call_site, &table, &error); - ASSERT_NE(nullptr, symbol); + ASSERT_THAT(symbol, NotNull()); EXPECT_TRUE(error.empty()); } @@ -283,12 +283,12 @@ TEST(ReferenceLinkerTest, AppsWithDifferentPackageCanNotUseEachOthersAttribute) std::string error; const CallSite call_site{ResourceNameRef("com.app.ext", ResourceType::kLayout, "foo")}; - AAPT_EXPECT_FALSE(ReferenceLinker::CompileXmlAttribute( + EXPECT_FALSE(ReferenceLinker::CompileXmlAttribute( *test::BuildReference("com.app.test:attr/foo"), call_site, &table, &error)); EXPECT_FALSE(error.empty()); error = ""; - AAPT_ASSERT_TRUE(ReferenceLinker::CompileXmlAttribute( + ASSERT_TRUE(ReferenceLinker::CompileXmlAttribute( *test::BuildReference("com.app.test:attr/public_foo"), call_site, &table, &error)); EXPECT_TRUE(error.empty()); } 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..bcecd2003846 100644 --- a/tools/aapt2/link/XmlReferenceLinker.cpp +++ b/tools/aapt2/link/XmlReferenceLinker.cpp @@ -144,7 +144,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); diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp index de81e73f613e..ef99355e5b5f 100644 --- a/tools/aapt2/link/XmlReferenceLinker_test.cpp +++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp @@ -18,6 +18,9 @@ #include "test/Test.h" +using ::testing::IsNull; +using ::testing::NotNull; + namespace aapt { class XmlReferenceLinkerTest : public ::testing::Test { @@ -76,199 +79,179 @@ 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()); - ASSERT_NE(nullptr, view_el); + xml::Element* view_el = doc->root.get(); + ASSERT_THAT(view_el, NotNull()); xml::Attribute* xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "layout_width"); - ASSERT_NE(nullptr, xml_attr); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id); - EXPECT_EQ(ResourceId(0x01010000), xml_attr->compiled_attribute.value().id.value()); - ASSERT_NE(nullptr, xml_attr->compiled_value); - ASSERT_NE(nullptr, ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get())); + ASSERT_THAT(xml_attr, NotNull()); + ASSERT_TRUE(xml_attr->compiled_attribute); + EXPECT_EQ(make_value(ResourceId(0x01010000)), xml_attr->compiled_attribute.value().id); + EXPECT_THAT(ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get()), NotNull()); xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "background"); - ASSERT_NE(nullptr, xml_attr); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id); - EXPECT_EQ(ResourceId(0x01010001), xml_attr->compiled_attribute.value().id.value()); - ASSERT_NE(nullptr, xml_attr->compiled_value); + ASSERT_THAT(xml_attr, NotNull()); + ASSERT_TRUE(xml_attr->compiled_attribute); + EXPECT_EQ(make_value(ResourceId(0x01010001)), xml_attr->compiled_attribute.value().id); Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.get()); - ASSERT_NE(nullptr, ref); - AAPT_ASSERT_TRUE(ref->name); - EXPECT_EQ(test::ParseNameOrDie("color/green"), ref->name.value()); // Make sure the name - // didn't change. - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ResourceId(0x7f020000), ref->id.value()); + ASSERT_THAT(ref, NotNull()); + EXPECT_EQ(make_value(test::ParseNameOrDie("color/green")), ref->name); // Make sure the name + // didn't change. + EXPECT_EQ(make_value(ResourceId(0x7f020000)), ref->id); xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "text"); - ASSERT_NE(nullptr, xml_attr); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); - ASSERT_FALSE(xml_attr->compiled_value); // Strings don't get compiled for memory sake. + ASSERT_THAT(xml_attr, NotNull()); + EXPECT_TRUE(xml_attr->compiled_attribute); + EXPECT_THAT(xml_attr->compiled_value, IsNull()); // Strings don't get compiled for memory sake. xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "attr"); - ASSERT_NE(nullptr, xml_attr); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); - ASSERT_FALSE(xml_attr->compiled_value); // Should be a plain string. + ASSERT_THAT(xml_attr, NotNull()); + EXPECT_TRUE(xml_attr->compiled_attribute); + EXPECT_THAT(xml_attr->compiled_value, IsNull()); // Should be a plain string. xml_attr = view_el->FindAttribute("", "nonAaptAttr"); - ASSERT_NE(nullptr, xml_attr); - AAPT_ASSERT_FALSE(xml_attr->compiled_attribute); - ASSERT_NE(nullptr, xml_attr->compiled_value); - ASSERT_NE(nullptr, ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get())); + ASSERT_THAT(xml_attr, NotNull()); + EXPECT_FALSE(xml_attr->compiled_attribute); + EXPECT_THAT(ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get()), NotNull()); xml_attr = view_el->FindAttribute("", "nonAaptAttrRef"); - ASSERT_NE(nullptr, xml_attr); - AAPT_ASSERT_FALSE(xml_attr->compiled_attribute); - ASSERT_NE(nullptr, xml_attr->compiled_value); - ASSERT_NE(nullptr, ValueCast<Reference>(xml_attr->compiled_value.get())); + ASSERT_THAT(xml_attr, NotNull()); + EXPECT_FALSE(xml_attr->compiled_attribute); + EXPECT_THAT(ValueCast<Reference>(xml_attr->compiled_value.get()), NotNull()); xml_attr = view_el->FindAttribute("", "class"); - ASSERT_NE(nullptr, xml_attr); - AAPT_ASSERT_FALSE(xml_attr->compiled_attribute); - ASSERT_EQ(nullptr, xml_attr->compiled_value); + ASSERT_THAT(xml_attr, NotNull()); + EXPECT_FALSE(xml_attr->compiled_attribute); + EXPECT_THAT(xml_attr->compiled_value, IsNull()); } 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()); - ASSERT_NE(view_el, nullptr); + xml::Element* view_el = doc->root.get(); + ASSERT_THAT(view_el, NotNull()); xml::Attribute* xml_attr = view_el->FindAttribute(xml::BuildPackageNamespace("com.android.support"), "colorAccent"); - ASSERT_NE(xml_attr, nullptr); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id); - EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(), ResourceId(0x7f010001)); - ASSERT_NE(ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get()), nullptr); + ASSERT_THAT(xml_attr, NotNull()); + ASSERT_TRUE(xml_attr->compiled_attribute); + EXPECT_EQ(make_value(ResourceId(0x7f010001)), xml_attr->compiled_attribute.value().id); + EXPECT_THAT(ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get()), NotNull()); } 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()); - ASSERT_NE(view_el, nullptr); + xml::Element* view_el = doc->root.get(); + ASSERT_THAT(view_el, NotNull()); xml::Attribute* xml_attr = view_el->FindAttribute(xml::kSchemaAuto, "colorAccent"); - ASSERT_NE(xml_attr, nullptr); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id); - EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(), ResourceId(0x7f010000)); + ASSERT_THAT(xml_attr, NotNull()); + ASSERT_TRUE(xml_attr->compiled_attribute); + EXPECT_EQ(make_value(ResourceId(0x7f010000)), xml_attr->compiled_attribute.value().id); Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.get()); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->name); - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001)); + ASSERT_THAT(ref, NotNull()); + ASSERT_TRUE(ref->name); + EXPECT_EQ(make_value(ResourceId(0x7f020001)), ref->id); } 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()); - ASSERT_NE(view_el, nullptr); + xml::Element* view_el = doc->root.get(); + ASSERT_THAT(view_el, NotNull()); // All attributes and references in this element should be referring to // "android" (0x01). xml::Attribute* xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "attr"); - ASSERT_NE(xml_attr, nullptr); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id); - EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(), ResourceId(0x01010002)); + ASSERT_THAT(xml_attr, NotNull()); + ASSERT_TRUE(xml_attr->compiled_attribute); + EXPECT_EQ(make_value(ResourceId(0x01010002)), xml_attr->compiled_attribute.value().id); Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.get()); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x01030000)); + ASSERT_THAT(ref, NotNull()); + EXPECT_EQ(make_value(ResourceId(0x01030000)), ref->id); ASSERT_FALSE(view_el->GetChildElements().empty()); view_el = view_el->GetChildElements().front(); - ASSERT_NE(view_el, nullptr); + ASSERT_THAT(view_el, NotNull()); // All attributes and references in this element should be referring to // "com.app.test" (0x7f). xml_attr = view_el->FindAttribute(xml::BuildPackageNamespace("com.app.test"), "attr"); - ASSERT_NE(xml_attr, nullptr); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id); - EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(), ResourceId(0x7f010002)); + ASSERT_THAT(xml_attr, NotNull()); + ASSERT_TRUE(xml_attr->compiled_attribute); + EXPECT_EQ(make_value(ResourceId(0x7f010002)), xml_attr->compiled_attribute.value().id); ref = ValueCast<Reference>(xml_attr->compiled_value.get()); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000)); + ASSERT_THAT(ref, NotNull()); + EXPECT_EQ(make_value(ResourceId(0x7f030000)), ref->id); } 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()); - ASSERT_NE(view_el, nullptr); + xml::Element* view_el = doc->root.get(); + ASSERT_THAT(view_el, NotNull()); // All attributes and references in this element should be referring to // "com.app.test" (0x7f). - xml::Attribute* xml_attr = - view_el->FindAttribute(xml::BuildPackageNamespace("com.app.test"), "attr"); - ASSERT_NE(xml_attr, nullptr); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute); - AAPT_ASSERT_TRUE(xml_attr->compiled_attribute.value().id); - EXPECT_EQ(xml_attr->compiled_attribute.value().id.value(), ResourceId(0x7f010002)); + xml::Attribute* xml_attr = view_el->FindAttribute(xml::BuildPackageNamespace("com.app.test"), "attr"); + ASSERT_THAT(xml_attr, NotNull()); + ASSERT_TRUE(xml_attr->compiled_attribute); + EXPECT_EQ(make_value(ResourceId(0x7f010002)), xml_attr->compiled_attribute.value().id); Reference* ref = ValueCast<Reference>(xml_attr->compiled_value.get()); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000)); + ASSERT_THAT(ref, NotNull()); + EXPECT_EQ(make_value(ResourceId(0x7f030000)), ref->id); } } // namespace aapt diff --git a/tools/aapt2/proto/ProtoHelpers.cpp b/tools/aapt2/proto/ProtoHelpers.cpp index 38bf4e3bd8eb..aa99c982f6ae 100644 --- a/tools/aapt2/proto/ProtoHelpers.cpp +++ b/tools/aapt2/proto/ProtoHelpers.cpp @@ -18,8 +18,7 @@ namespace aapt { -void SerializeStringPoolToPb(const StringPool& pool, - pb::StringPool* out_pb_pool) { +void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool) { BigBuffer buffer(1024); StringPool::FlattenUtf8(&buffer, pool); @@ -28,51 +27,47 @@ void SerializeStringPoolToPb(const StringPool& pool, size_t offset = 0; for (const BigBuffer::Block& block : buffer) { - data->insert(data->begin() + offset, block.buffer.get(), - block.buffer.get() + block.size); + data->insert(data->begin() + offset, block.buffer.get(), block.buffer.get() + block.size); offset += block.size; } } -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) { 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())); } } -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) { if (pb_source.has_path_idx()) { 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; @@ -80,8 +75,7 @@ SymbolState DeserializeVisibilityFromPb( return SymbolState::kUndefined; } -void SerializeConfig(const ConfigDescription& config, - pb::ConfigDescription* out_pb_config) { +void SerializeConfig(const ConfigDescription& config, pb::ConfigDescription* out_pb_config) { android::ResTable_config flat_config = config; flat_config.size = sizeof(flat_config); flat_config.swapHtoD(); @@ -99,8 +93,7 @@ bool DeserializeConfigDescriptionFromPb(const pb::ConfigDescription& pb_config, return false; } - config = reinterpret_cast<const android::ResTable_config*>( - pb_config.data().data()); + config = reinterpret_cast<const android::ResTable_config*>(pb_config.data().data()); out_config->copyFromDtoH(*config); return true; } @@ -108,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; @@ -132,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 4b5619235c06..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,46 +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::kStylePriority, 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"); @@ -223,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); @@ -236,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)) { @@ -256,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)) { @@ -289,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); @@ -300,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 {}; } @@ -338,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; } @@ -351,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; } @@ -378,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 {}; } @@ -441,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; @@ -457,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 d87d64e1cb46..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 { @@ -87,7 +87,9 @@ class PbSerializerVisitor : public RawValueVisitor { pb_prim->set_data(val.data); } - void VisitItem(Item* item) override { LOG(FATAL) << "unimplemented item"; } + void VisitItem(Item* item) override { + LOG(FATAL) << "unimplemented item"; + } void Visit(Attribute* attr) override { pb::Attribute* pb_attr = pb_compound_value()->mutable_attr(); @@ -96,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); @@ -112,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); } } @@ -125,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()); } @@ -133,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); } } @@ -151,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); } } @@ -177,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()); } @@ -190,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); @@ -199,7 +198,6 @@ class PbSerializerVisitor : public RawValueVisitor { } StringPool* source_pool_; - StringPool* symbol_pool_; pb::Value* out_pb_value_; pb::Item* out_pb_item_; }; @@ -207,41 +205,35 @@ class PbSerializerVisitor : public RawValueVisitor { } // namespace std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) { - // We must do this before writing the resources, since the string pool IDs may - // change. - table->string_pool.Sort( - [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { - int diff = a.context.priority - b.context.priority; - if (diff < 0) return true; - if (diff > 0) return false; - diff = a.context.config.compare(b.context.config); - if (diff < 0) return true; - if (diff > 0) return false; - return a.value < b.value; - }); + // We must do this before writing the resources, since the string pool IDs may change. table->string_pool.Prune(); + table->string_pool.Sort([](const StringPool::Context& a, const StringPool::Context& b) -> int { + int diff = util::compare(a.priority, b.priority); + if (diff == 0) { + diff = a.config.compare(b.config); + } + return diff; + }); 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()); } @@ -255,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); @@ -272,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); } } @@ -280,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; @@ -315,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_); @@ -336,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) {} @@ -354,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; @@ -381,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 e6ce6d37b879..80608b3d9c05 100644 --- a/tools/aapt2/proto/TableProtoSerializer_test.cpp +++ b/tools/aapt2/proto/TableProtoSerializer_test.cpp @@ -20,6 +20,9 @@ #include "test/Test.h" using ::google::protobuf::io::StringOutputStream; +using ::testing::Eq; +using ::testing::NotNull; +using ::testing::SizeIs; namespace aapt { @@ -37,21 +40,29 @@ 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>(); - plural->values[Plural::One] = - util::make_unique<String>(table->string_pool.MakeRef("one")); + plural->values[Plural::One] = util::make_unique<String>(table->string_pool.MakeRef("one")); ASSERT_TRUE(table->AddResource(test::ParseNameOrDie("com.app.a:plurals/hey"), 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); @@ -77,44 +87,53 @@ TEST(TableProtoSerializer, SerializeSinglePackage) { context->GetDiagnostics())); std::unique_ptr<pb::ResourceTable> pb_table = SerializeTableToPb(table.get()); - ASSERT_NE(nullptr, pb_table); + ASSERT_THAT(pb_table, NotNull()); std::unique_ptr<ResourceTable> new_table = DeserializeTableFromPb( *pb_table, Source{"test"}, context->GetDiagnostics()); - ASSERT_NE(nullptr, new_table); + ASSERT_THAT(new_table, NotNull()); Id* new_id = test::GetValue<Id>(new_table.get(), "com.app.a:id/foo"); - ASSERT_NE(nullptr, new_id); - EXPECT_EQ(id->IsWeak(), new_id->IsWeak()); + ASSERT_THAT(new_id, NotNull()); + EXPECT_THAT(new_id->IsWeak(), Eq(id->IsWeak())); Maybe<ResourceTable::SearchResult> result = new_table->FindResource(test::ParseNameOrDie("com.app.a:layout/main")); - AAPT_ASSERT_TRUE(result); - EXPECT_EQ(SymbolState::kPublic, result.value().type->symbol_status.state); - EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbol_status.state); + ASSERT_TRUE(result); + + 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_NE(nullptr, prim); - EXPECT_EQ(123u, prim->value.data); + ASSERT_THAT(prim, NotNull()); + EXPECT_THAT(prim->value.data, Eq(123u)); prim = test::GetValueForConfigAndProduct<BinaryPrimitive>( new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"), "tablet"); - ASSERT_NE(nullptr, prim); - EXPECT_EQ(321u, prim->value.data); + ASSERT_THAT(prim, NotNull()); + EXPECT_THAT(prim->value.data, Eq(321u)); Reference* actual_ref = test::GetValue<Reference>(new_table.get(), "com.app.a:layout/abc"); - ASSERT_NE(nullptr, actual_ref); - AAPT_ASSERT_TRUE(actual_ref->name); - AAPT_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()); + ASSERT_THAT(actual_ref, NotNull()); + ASSERT_TRUE(actual_ref->name); + ASSERT_TRUE(actual_ref->id); + 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,12 +173,12 @@ 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( new_pb_file, Source("test"), context->GetDiagnostics()); - ASSERT_NE(nullptr, file); + ASSERT_THAT(file, NotNull()); uint64_t offset, len; ASSERT_TRUE(in_file_stream.ReadDataMetaData(&offset, &len)); @@ -171,16 +190,14 @@ TEST(TableProtoSerializer, SerializeFileHeader) { EXPECT_EQ(0u, offset & 0x03); ASSERT_EQ(1u, file->exported_symbols.size()); - EXPECT_EQ(test::ParseNameOrDie("id/unchecked"), - file->exported_symbols[0].name); + EXPECT_EQ(test::ParseNameOrDie("id/unchecked"), file->exported_symbols[0].name); // Read the second compiled file. ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_file)); - file = DeserializeCompiledFileFromPb(new_pb_file, Source("test"), - context->GetDiagnostics()); - ASSERT_NE(nullptr, file); + file = DeserializeCompiledFileFromPb(new_pb_file, Source("test"), context->GetDiagnostics()); + ASSERT_THAT(file, NotNull()); ASSERT_TRUE(in_file_stream.ReadDataMetaData(&offset, &len)); @@ -193,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"; @@ -215,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 0290e30dfced..8368f9d16af8 100644 --- a/tools/aapt2/readme.md +++ b/tools/aapt2/readme.md @@ -1,9 +1,45 @@ # 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) +- Fixed issue where ints and floats with trailing whitespace would not be parsed. (bug 62902869) +- Fixed issue where `--custom-package` was not honored when writing Manifest.java. (bug 62826426) +- Add `<key-sets>` and its nested tags to the allowed set of XML tags in AndroidManifest.xml. + (bug 62839863) +- Fixed issue where Java classes referenced from fragments and menus were not added to + the set of Proguard keep rules. (bug 62216174) +- Fixed issue where escaped unicode characters would generate malformed UTF-8. (bug 62839202) +- Fixed issue where apostrophes or quotes used in XML attribute values were ignored. + (bug 62840406, 62840718) + ## Version 2.17 -### `aapt2 compile ...` -- Fixed an issue where symlinks would not be followed when compiling PNGs. (bug 62144459) +### `aapt2 ...` +- Fixed issue where symlinks would not be followed when compiling PNGs. (bug 62144459) - Fixed issue where overlays that declared `<add-resource>` did not compile. (bug 38355988) +- Fixed issue where `%n` in a string resource was interpreted as a format argument. (bug 37132275) +- Allow empty resources to compile, giving them a value of `""` or `@null`, depending on the + accepted formats. (bug 38425050) +- Resources declared via `<item>` with no format attribute were changed to accept all + resource types. (bug 62260121) +- Allow `<layout>` element under `<activity>` in AndroidManifest.xml. (bug 62189611) +- Fix issue where `--no-version-vector` did not apply to `pathInterpolator` and `objectAnimator`. + (bug 62211148) +- Fix issue where overlaid `<style>` would not be merged, and would replace the original resource + instead. This fix brings behavior in-line with AAPT. (bug 38355988) ## Version 2.16 ### `aapt2 link ...` diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp new file mode 100644 index 000000000000..8f9788e8a25d --- /dev/null +++ b/tools/aapt2/test/Builders.cpp @@ -0,0 +1,216 @@ +/* + * 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::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; +} + +} // namespace test +} // namespace aapt diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index f3da780cacbd..d9f3912fb4c6 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -19,13 +19,13 @@ #include <memory> -#include "android-base/logging.h" #include "android-base/macros.h" +#include "Resource.h" #include "ResourceTable.h" #include "ResourceValues.h" -#include "test/Common.h" -#include "util/Util.h" +#include "process/IResourceTableConsumer.h" +#include "util/Maybe.h" #include "xml/XmlDom.h" namespace aapt { @@ -35,97 +35,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 +73,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 +95,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 +107,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,26 +121,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); @@ -235,14 +136,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); @@ -250,22 +145,9 @@ 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); } // namespace test } // namespace aapt diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h index 8efd56a7514b..e6b38c0007b4 100644 --- a/tools/aapt2/test/Common.h +++ b/tools/aapt2/test/Common.h @@ -34,15 +34,6 @@ #include "io/File.h" #include "process/IResourceTableConsumer.h" -// -// GTEST 1.7 doesn't explicitly cast to bool, which causes explicit operators to -// fail to compile. -// -#define AAPT_ASSERT_TRUE(v) ASSERT_TRUE(bool(v)) -#define AAPT_ASSERT_FALSE(v) ASSERT_FALSE(bool(v)) -#define AAPT_EXPECT_TRUE(v) EXPECT_TRUE(bool(v)) -#define AAPT_EXPECT_FALSE(v) EXPECT_FALSE(bool(v)) - namespace aapt { namespace test { @@ -151,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/text/Unicode.cpp b/tools/aapt2/text/Unicode.cpp new file mode 100644 index 000000000000..75eeb46c7f5e --- /dev/null +++ b/tools/aapt2/text/Unicode.cpp @@ -0,0 +1,125 @@ +/* + * 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 "text/Unicode.h" + +#include <algorithm> +#include <array> + +#include "text/Utf8Iterator.h" + +using ::android::StringPiece; + +namespace aapt { +namespace text { + +namespace { + +struct CharacterProperties { + enum : uint32_t { + kXidStart = 1 << 0, + kXidContinue = 1 << 1, + }; + + char32_t first_char; + char32_t last_char; + uint32_t properties; +}; + +// Incude the generated data table. +#include "text/Unicode_data.cpp" + +bool CompareCharacterProperties(const CharacterProperties& a, char32_t codepoint) { + return a.last_char < codepoint; +} + +uint32_t FindCharacterProperties(char32_t codepoint) { + const auto iter_end = sCharacterProperties.end(); + const auto iter = std::lower_bound(sCharacterProperties.begin(), iter_end, codepoint, + CompareCharacterProperties); + if (iter != iter_end && codepoint >= iter->first_char) { + return iter->properties; + } + return 0u; +} + +} // namespace + +bool IsXidStart(char32_t codepoint) { + return FindCharacterProperties(codepoint) & CharacterProperties::kXidStart; +} + +bool IsXidContinue(char32_t codepoint) { + return FindCharacterProperties(codepoint) & CharacterProperties::kXidContinue; +} + +// Hardcode the White_Space characters since they are few and the external/icu project doesn't +// list them as data files to parse. +// Sourced from http://www.unicode.org/Public/UCD/latest/ucd/PropList.txt +bool IsWhitespace(char32_t codepoint) { + return (codepoint >= 0x0009 && codepoint <= 0x000d) || (codepoint == 0x0020) || + (codepoint == 0x0085) || (codepoint == 0x00a0) || (codepoint == 0x1680) || + (codepoint >= 0x2000 && codepoint <= 0x200a) || (codepoint == 0x2028) || + (codepoint == 0x2029) || (codepoint == 0x202f) || (codepoint == 0x205f) || + (codepoint == 0x3000); +} + +bool IsJavaIdentifier(const StringPiece& str) { + Utf8Iterator iter(str); + + // Check the first character. + if (!iter.HasNext()) { + return false; + } + + if (!IsXidStart(iter.Next())) { + return false; + } + + while (iter.HasNext()) { + const char32_t codepoint = iter.Next(); + if (!IsXidContinue(codepoint) && codepoint != U'$') { + return false; + } + } + return true; +} + +bool IsValidResourceEntryName(const StringPiece& str) { + Utf8Iterator iter(str); + + // Check the first character. + if (!iter.HasNext()) { + return false; + } + + // Resources are allowed to start with '_' + const char32_t first_codepoint = iter.Next(); + if (!IsXidStart(first_codepoint) && first_codepoint != U'_') { + return false; + } + + while (iter.HasNext()) { + const char32_t codepoint = iter.Next(); + if (!IsXidContinue(codepoint) && codepoint != U'.' && codepoint != U'-') { + return false; + } + } + return true; +} + +} // namespace text +} // namespace aapt diff --git a/tools/aapt2/text/Unicode.h b/tools/aapt2/text/Unicode.h new file mode 100644 index 000000000000..546714e9a290 --- /dev/null +++ b/tools/aapt2/text/Unicode.h @@ -0,0 +1,58 @@ +/* + * 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_TEXT_UNICODE_H +#define AAPT_TEXT_UNICODE_H + +#include "androidfw/StringPiece.h" + +namespace aapt { +namespace text { + +// Returns true if the Unicode codepoint has the XID_Start property, meaning it can be used as the +// first character of a programming language identifier. +// http://unicode.org/reports/tr31/#Default_Identifier_Syntax +// +// XID_Start is a Unicode Derived Core Property. It is a variation of the ID_Start +// Derived Core Property, accounting for a few characters that, when normalized, yield valid +// characters in the ID_Start set. +bool IsXidStart(char32_t codepoint); + +// Returns true if the Unicode codepoint has the XID_Continue property, meaning it can be used in +// any position of a programming language identifier, except the first. +// http://unicode.org/reports/tr31/#Default_Identifier_Syntax +// +// XID_Continue is a Unicode Derived Core Property. It is a variation of the ID_Continue +// Derived Core Property, accounting for a few characters that, when normalized, yield valid +// characters in the ID_Continue set. +bool IsXidContinue(char32_t codepoint); + +// Returns true if the Unicode codepoint has the White_Space property. +// http://unicode.org/reports/tr44/#White_Space +bool IsWhitespace(char32_t codepoint); + +// Returns true if the UTF8 string can be used as a Java identifier. +// NOTE: This does not check against the set of reserved Java keywords. +bool IsJavaIdentifier(const android::StringPiece& str); + +// Returns true if the UTF8 string can be used as the entry name of a resource name. +// This is the `entry` part of package:type/entry. +bool IsValidResourceEntryName(const android::StringPiece& str); + +} // namespace text +} // namespace aapt + +#endif // AAPT_TEXT_UNICODE_H diff --git a/tools/aapt2/text/Unicode_data.cpp b/tools/aapt2/text/Unicode_data.cpp new file mode 100644 index 000000000000..96dc57b84031 --- /dev/null +++ b/tools/aapt2/text/Unicode_data.cpp @@ -0,0 +1,629 @@ +/* + * 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. + */ + +const static std::array<CharacterProperties, 611> sCharacterProperties = {{ + {0x0030, 0x0039, CharacterProperties::kXidContinue}, + {0x0041, 0x005a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x005f, 0x005f, CharacterProperties::kXidContinue}, + {0x0061, 0x007a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x00aa, 0x00aa, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x00b5, 0x00b5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x00b7, 0x00b7, CharacterProperties::kXidContinue}, + {0x00ba, 0x00ba, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x00c0, 0x00d6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x00d8, 0x00f6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x00f8, 0x02c1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x02c6, 0x02d1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x02e0, 0x02e4, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x02ec, 0x02ec, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x02ee, 0x02ee, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0300, 0x036f, CharacterProperties::kXidContinue}, + {0x0370, 0x0374, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0376, 0x0377, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x037b, 0x037d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x037f, 0x037f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0386, 0x0386, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0387, 0x0387, CharacterProperties::kXidContinue}, + {0x0388, 0x038a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x038c, 0x038c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x038e, 0x03a1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x03a3, 0x03f5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x03f7, 0x0481, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0483, 0x0487, CharacterProperties::kXidContinue}, + {0x048a, 0x052f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0531, 0x0556, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0559, 0x0559, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0561, 0x0587, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0591, 0x05bd, CharacterProperties::kXidContinue}, + {0x05bf, 0x05bf, CharacterProperties::kXidContinue}, + {0x05c1, 0x05c2, CharacterProperties::kXidContinue}, + {0x05c4, 0x05c5, CharacterProperties::kXidContinue}, + {0x05c7, 0x05c7, CharacterProperties::kXidContinue}, + {0x05d0, 0x05ea, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x05f0, 0x05f2, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0610, 0x061a, CharacterProperties::kXidContinue}, + {0x0620, 0x064a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x064b, 0x0669, CharacterProperties::kXidContinue}, + {0x066e, 0x066f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0670, 0x0670, CharacterProperties::kXidContinue}, + {0x0671, 0x06d3, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x06d5, 0x06d5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x06d6, 0x06dc, CharacterProperties::kXidContinue}, + {0x06df, 0x06e4, CharacterProperties::kXidContinue}, + {0x06e5, 0x06e6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x06e7, 0x06e8, CharacterProperties::kXidContinue}, + {0x06ea, 0x06ed, CharacterProperties::kXidContinue}, + {0x06ee, 0x06ef, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x06f0, 0x06f9, CharacterProperties::kXidContinue}, + {0x06fa, 0x06fc, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x06ff, 0x06ff, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0710, 0x0710, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0711, 0x0711, CharacterProperties::kXidContinue}, + {0x0712, 0x072f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0730, 0x074a, CharacterProperties::kXidContinue}, + {0x074d, 0x07a5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x07a6, 0x07b0, CharacterProperties::kXidContinue}, + {0x07b1, 0x07b1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x07c0, 0x07c9, CharacterProperties::kXidContinue}, + {0x07ca, 0x07ea, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x07eb, 0x07f3, CharacterProperties::kXidContinue}, + {0x07f4, 0x07f5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x07fa, 0x07fa, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0800, 0x0815, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0816, 0x0819, CharacterProperties::kXidContinue}, + {0x081a, 0x081a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x081b, 0x0823, CharacterProperties::kXidContinue}, + {0x0824, 0x0824, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0825, 0x0827, CharacterProperties::kXidContinue}, + {0x0828, 0x0828, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0829, 0x082d, CharacterProperties::kXidContinue}, + {0x0840, 0x0858, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0859, 0x085b, CharacterProperties::kXidContinue}, + {0x08a0, 0x08b4, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x08b6, 0x08bd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x08d4, 0x08e1, CharacterProperties::kXidContinue}, + {0x08e3, 0x0903, CharacterProperties::kXidContinue}, + {0x0904, 0x0939, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x093a, 0x093c, CharacterProperties::kXidContinue}, + {0x093d, 0x093d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x093e, 0x094f, CharacterProperties::kXidContinue}, + {0x0950, 0x0950, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0951, 0x0957, CharacterProperties::kXidContinue}, + {0x0958, 0x0961, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0962, 0x0963, CharacterProperties::kXidContinue}, + {0x0966, 0x096f, CharacterProperties::kXidContinue}, + {0x0971, 0x0980, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0981, 0x0983, CharacterProperties::kXidContinue}, + {0x0985, 0x098c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x098f, 0x0990, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0993, 0x09a8, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x09aa, 0x09b0, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x09b2, 0x09b2, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x09b6, 0x09b9, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x09bc, 0x09bc, CharacterProperties::kXidContinue}, + {0x09bd, 0x09bd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x09be, 0x09c4, CharacterProperties::kXidContinue}, + {0x09c7, 0x09c8, CharacterProperties::kXidContinue}, + {0x09cb, 0x09cd, CharacterProperties::kXidContinue}, + {0x09ce, 0x09ce, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x09d7, 0x09d7, CharacterProperties::kXidContinue}, + {0x09dc, 0x09dd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x09df, 0x09e1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x09e2, 0x09e3, CharacterProperties::kXidContinue}, + {0x09e6, 0x09ef, CharacterProperties::kXidContinue}, + {0x09f0, 0x09f1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a01, 0x0a03, CharacterProperties::kXidContinue}, + {0x0a05, 0x0a0a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a0f, 0x0a10, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a13, 0x0a28, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a2a, 0x0a30, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a32, 0x0a33, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a35, 0x0a36, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a38, 0x0a39, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a3c, 0x0a3c, CharacterProperties::kXidContinue}, + {0x0a3e, 0x0a42, CharacterProperties::kXidContinue}, + {0x0a47, 0x0a48, CharacterProperties::kXidContinue}, + {0x0a4b, 0x0a4d, CharacterProperties::kXidContinue}, + {0x0a51, 0x0a51, CharacterProperties::kXidContinue}, + {0x0a59, 0x0a5c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a5e, 0x0a5e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a66, 0x0a71, CharacterProperties::kXidContinue}, + {0x0a72, 0x0a74, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a75, 0x0a75, CharacterProperties::kXidContinue}, + {0x0a81, 0x0a83, CharacterProperties::kXidContinue}, + {0x0a85, 0x0a8d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a8f, 0x0a91, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0a93, 0x0aa8, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0aaa, 0x0ab0, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ab2, 0x0ab3, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ab5, 0x0ab9, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0abc, 0x0abc, CharacterProperties::kXidContinue}, + {0x0abd, 0x0abd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0abe, 0x0ac5, CharacterProperties::kXidContinue}, + {0x0ac7, 0x0ac9, CharacterProperties::kXidContinue}, + {0x0acb, 0x0acd, CharacterProperties::kXidContinue}, + {0x0ad0, 0x0ad0, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ae0, 0x0ae1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ae2, 0x0ae3, CharacterProperties::kXidContinue}, + {0x0ae6, 0x0aef, CharacterProperties::kXidContinue}, + {0x0af9, 0x0af9, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b01, 0x0b03, CharacterProperties::kXidContinue}, + {0x0b05, 0x0b0c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b0f, 0x0b10, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b13, 0x0b28, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b2a, 0x0b30, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b32, 0x0b33, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b35, 0x0b39, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b3c, 0x0b3c, CharacterProperties::kXidContinue}, + {0x0b3d, 0x0b3d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b3e, 0x0b44, CharacterProperties::kXidContinue}, + {0x0b47, 0x0b48, CharacterProperties::kXidContinue}, + {0x0b4b, 0x0b4d, CharacterProperties::kXidContinue}, + {0x0b56, 0x0b57, CharacterProperties::kXidContinue}, + {0x0b5c, 0x0b5d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b5f, 0x0b61, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b62, 0x0b63, CharacterProperties::kXidContinue}, + {0x0b66, 0x0b6f, CharacterProperties::kXidContinue}, + {0x0b71, 0x0b71, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b82, 0x0b82, CharacterProperties::kXidContinue}, + {0x0b83, 0x0b83, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b85, 0x0b8a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b8e, 0x0b90, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b92, 0x0b95, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b99, 0x0b9a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b9c, 0x0b9c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0b9e, 0x0b9f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ba3, 0x0ba4, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ba8, 0x0baa, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0bae, 0x0bb9, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0bbe, 0x0bc2, CharacterProperties::kXidContinue}, + {0x0bc6, 0x0bc8, CharacterProperties::kXidContinue}, + {0x0bca, 0x0bcd, CharacterProperties::kXidContinue}, + {0x0bd0, 0x0bd0, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0bd7, 0x0bd7, CharacterProperties::kXidContinue}, + {0x0be6, 0x0bef, CharacterProperties::kXidContinue}, + {0x0c00, 0x0c03, CharacterProperties::kXidContinue}, + {0x0c05, 0x0c0c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0c0e, 0x0c10, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0c12, 0x0c28, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0c2a, 0x0c39, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0c3d, 0x0c3d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0c3e, 0x0c44, CharacterProperties::kXidContinue}, + {0x0c46, 0x0c48, CharacterProperties::kXidContinue}, + {0x0c4a, 0x0c4d, CharacterProperties::kXidContinue}, + {0x0c55, 0x0c56, CharacterProperties::kXidContinue}, + {0x0c58, 0x0c5a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0c60, 0x0c61, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0c62, 0x0c63, CharacterProperties::kXidContinue}, + {0x0c66, 0x0c6f, CharacterProperties::kXidContinue}, + {0x0c80, 0x0c80, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0c81, 0x0c83, CharacterProperties::kXidContinue}, + {0x0c85, 0x0c8c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0c8e, 0x0c90, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0c92, 0x0ca8, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0caa, 0x0cb3, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0cb5, 0x0cb9, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0cbc, 0x0cbc, CharacterProperties::kXidContinue}, + {0x0cbd, 0x0cbd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0cbe, 0x0cc4, CharacterProperties::kXidContinue}, + {0x0cc6, 0x0cc8, CharacterProperties::kXidContinue}, + {0x0cca, 0x0ccd, CharacterProperties::kXidContinue}, + {0x0cd5, 0x0cd6, CharacterProperties::kXidContinue}, + {0x0cde, 0x0cde, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ce0, 0x0ce1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ce2, 0x0ce3, CharacterProperties::kXidContinue}, + {0x0ce6, 0x0cef, CharacterProperties::kXidContinue}, + {0x0cf1, 0x0cf2, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0d01, 0x0d03, CharacterProperties::kXidContinue}, + {0x0d05, 0x0d0c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0d0e, 0x0d10, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0d12, 0x0d3a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0d3d, 0x0d3d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0d3e, 0x0d44, CharacterProperties::kXidContinue}, + {0x0d46, 0x0d48, CharacterProperties::kXidContinue}, + {0x0d4a, 0x0d4d, CharacterProperties::kXidContinue}, + {0x0d4e, 0x0d4e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0d54, 0x0d56, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0d57, 0x0d57, CharacterProperties::kXidContinue}, + {0x0d5f, 0x0d61, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0d62, 0x0d63, CharacterProperties::kXidContinue}, + {0x0d66, 0x0d6f, CharacterProperties::kXidContinue}, + {0x0d7a, 0x0d7f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0d82, 0x0d83, CharacterProperties::kXidContinue}, + {0x0d85, 0x0d96, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0d9a, 0x0db1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0db3, 0x0dbb, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0dbd, 0x0dbd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0dc0, 0x0dc6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0dca, 0x0dca, CharacterProperties::kXidContinue}, + {0x0dcf, 0x0dd4, CharacterProperties::kXidContinue}, + {0x0dd6, 0x0dd6, CharacterProperties::kXidContinue}, + {0x0dd8, 0x0ddf, CharacterProperties::kXidContinue}, + {0x0de6, 0x0def, CharacterProperties::kXidContinue}, + {0x0df2, 0x0df3, CharacterProperties::kXidContinue}, + {0x0e01, 0x0e30, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0e31, 0x0e31, CharacterProperties::kXidContinue}, + {0x0e32, 0x0e32, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0e33, 0x0e3a, CharacterProperties::kXidContinue}, + {0x0e40, 0x0e46, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0e47, 0x0e4e, CharacterProperties::kXidContinue}, + {0x0e50, 0x0e59, CharacterProperties::kXidContinue}, + {0x0e81, 0x0e82, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0e84, 0x0e84, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0e87, 0x0e88, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0e8a, 0x0e8a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0e8d, 0x0e8d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0e94, 0x0e97, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0e99, 0x0e9f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ea1, 0x0ea3, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ea5, 0x0ea5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ea7, 0x0ea7, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0eaa, 0x0eab, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ead, 0x0eb0, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0eb1, 0x0eb1, CharacterProperties::kXidContinue}, + {0x0eb2, 0x0eb2, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0eb3, 0x0eb9, CharacterProperties::kXidContinue}, + {0x0ebb, 0x0ebc, CharacterProperties::kXidContinue}, + {0x0ebd, 0x0ebd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ec0, 0x0ec4, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ec6, 0x0ec6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0ec8, 0x0ecd, CharacterProperties::kXidContinue}, + {0x0ed0, 0x0ed9, CharacterProperties::kXidContinue}, + {0x0edc, 0x0edf, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0f00, 0x0f00, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0f18, 0x0f19, CharacterProperties::kXidContinue}, + {0x0f20, 0x0f29, CharacterProperties::kXidContinue}, + {0x0f35, 0x0f35, CharacterProperties::kXidContinue}, + {0x0f37, 0x0f37, CharacterProperties::kXidContinue}, + {0x0f39, 0x0f39, CharacterProperties::kXidContinue}, + {0x0f3e, 0x0f3f, CharacterProperties::kXidContinue}, + {0x0f40, 0x0f47, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0f49, 0x0f6c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0f71, 0x0f84, CharacterProperties::kXidContinue}, + {0x0f86, 0x0f87, CharacterProperties::kXidContinue}, + {0x0f88, 0x0f8c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x0f8d, 0x0f97, CharacterProperties::kXidContinue}, + {0x0f99, 0x0fbc, CharacterProperties::kXidContinue}, + {0x0fc6, 0x0fc6, CharacterProperties::kXidContinue}, + {0x1000, 0x102a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x102b, 0x103e, CharacterProperties::kXidContinue}, + {0x103f, 0x103f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1040, 0x1049, CharacterProperties::kXidContinue}, + {0x1050, 0x1055, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1056, 0x1059, CharacterProperties::kXidContinue}, + {0x105a, 0x105d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x105e, 0x1060, CharacterProperties::kXidContinue}, + {0x1061, 0x1061, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1062, 0x1064, CharacterProperties::kXidContinue}, + {0x1065, 0x1066, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1067, 0x106d, CharacterProperties::kXidContinue}, + {0x106e, 0x1070, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1071, 0x1074, CharacterProperties::kXidContinue}, + {0x1075, 0x1081, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1082, 0x108d, CharacterProperties::kXidContinue}, + {0x108e, 0x108e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x108f, 0x109d, CharacterProperties::kXidContinue}, + {0x10a0, 0x10c5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x10c7, 0x10c7, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x10cd, 0x10cd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x10d0, 0x10fa, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x10fc, 0x1248, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x124a, 0x124d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1250, 0x1256, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1258, 0x1258, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x125a, 0x125d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1260, 0x1288, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x128a, 0x128d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1290, 0x12b0, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x12b2, 0x12b5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x12b8, 0x12be, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x12c0, 0x12c0, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x12c2, 0x12c5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x12c8, 0x12d6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x12d8, 0x1310, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1312, 0x1315, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1318, 0x135a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x135d, 0x135f, CharacterProperties::kXidContinue}, + {0x1369, 0x1371, CharacterProperties::kXidContinue}, + {0x1380, 0x138f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x13a0, 0x13f5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x13f8, 0x13fd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1401, 0x166c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x166f, 0x167f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1681, 0x169a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x16a0, 0x16ea, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x16ee, 0x16f8, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1700, 0x170c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x170e, 0x1711, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1712, 0x1714, CharacterProperties::kXidContinue}, + {0x1720, 0x1731, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1732, 0x1734, CharacterProperties::kXidContinue}, + {0x1740, 0x1751, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1752, 0x1753, CharacterProperties::kXidContinue}, + {0x1760, 0x176c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x176e, 0x1770, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1772, 0x1773, CharacterProperties::kXidContinue}, + {0x1780, 0x17b3, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x17b4, 0x17d3, CharacterProperties::kXidContinue}, + {0x17d7, 0x17d7, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x17dc, 0x17dc, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x17dd, 0x17dd, CharacterProperties::kXidContinue}, + {0x17e0, 0x17e9, CharacterProperties::kXidContinue}, + {0x180b, 0x180d, CharacterProperties::kXidContinue}, + {0x1810, 0x1819, CharacterProperties::kXidContinue}, + {0x1820, 0x1877, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1880, 0x18a8, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x18a9, 0x18a9, CharacterProperties::kXidContinue}, + {0x18aa, 0x18aa, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x18b0, 0x18f5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1900, 0x191e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1920, 0x192b, CharacterProperties::kXidContinue}, + {0x1930, 0x193b, CharacterProperties::kXidContinue}, + {0x1946, 0x194f, CharacterProperties::kXidContinue}, + {0x1950, 0x196d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1970, 0x1974, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1980, 0x19ab, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x19b0, 0x19c9, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x19d0, 0x19da, CharacterProperties::kXidContinue}, + {0x1a00, 0x1a16, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1a17, 0x1a1b, CharacterProperties::kXidContinue}, + {0x1a20, 0x1a54, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1a55, 0x1a5e, CharacterProperties::kXidContinue}, + {0x1a60, 0x1a7c, CharacterProperties::kXidContinue}, + {0x1a7f, 0x1a89, CharacterProperties::kXidContinue}, + {0x1a90, 0x1a99, CharacterProperties::kXidContinue}, + {0x1aa7, 0x1aa7, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1ab0, 0x1abd, CharacterProperties::kXidContinue}, + {0x1b00, 0x1b04, CharacterProperties::kXidContinue}, + {0x1b05, 0x1b33, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1b34, 0x1b44, CharacterProperties::kXidContinue}, + {0x1b45, 0x1b4b, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1b50, 0x1b59, CharacterProperties::kXidContinue}, + {0x1b6b, 0x1b73, CharacterProperties::kXidContinue}, + {0x1b80, 0x1b82, CharacterProperties::kXidContinue}, + {0x1b83, 0x1ba0, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1ba1, 0x1bad, CharacterProperties::kXidContinue}, + {0x1bae, 0x1baf, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1bb0, 0x1bb9, CharacterProperties::kXidContinue}, + {0x1bba, 0x1be5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1be6, 0x1bf3, CharacterProperties::kXidContinue}, + {0x1c00, 0x1c23, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1c24, 0x1c37, CharacterProperties::kXidContinue}, + {0x1c40, 0x1c49, CharacterProperties::kXidContinue}, + {0x1c4d, 0x1c4f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1c50, 0x1c59, CharacterProperties::kXidContinue}, + {0x1c5a, 0x1c7d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1c80, 0x1c88, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1cd0, 0x1cd2, CharacterProperties::kXidContinue}, + {0x1cd4, 0x1ce8, CharacterProperties::kXidContinue}, + {0x1ce9, 0x1cec, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1ced, 0x1ced, CharacterProperties::kXidContinue}, + {0x1cee, 0x1cf1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1cf2, 0x1cf4, CharacterProperties::kXidContinue}, + {0x1cf5, 0x1cf6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1cf8, 0x1cf9, CharacterProperties::kXidContinue}, + {0x1d00, 0x1dbf, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1dc0, 0x1df5, CharacterProperties::kXidContinue}, + {0x1dfb, 0x1dff, CharacterProperties::kXidContinue}, + {0x1e00, 0x1f15, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1f18, 0x1f1d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1f20, 0x1f45, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1f48, 0x1f4d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1f50, 0x1f57, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1f59, 0x1f59, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1f5b, 0x1f5b, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1f5d, 0x1f5d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1f5f, 0x1f7d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1f80, 0x1fb4, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1fb6, 0x1fbc, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1fbe, 0x1fbe, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1fc2, 0x1fc4, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1fc6, 0x1fcc, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1fd0, 0x1fd3, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1fd6, 0x1fdb, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1fe0, 0x1fec, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1ff2, 0x1ff4, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x1ff6, 0x1ffc, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x203f, 0x2040, CharacterProperties::kXidContinue}, + {0x2054, 0x2054, CharacterProperties::kXidContinue}, + {0x2071, 0x2071, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x207f, 0x207f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2090, 0x209c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x20d0, 0x20dc, CharacterProperties::kXidContinue}, + {0x20e1, 0x20e1, CharacterProperties::kXidContinue}, + {0x20e5, 0x20f0, CharacterProperties::kXidContinue}, + {0x2102, 0x2102, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2107, 0x2107, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x210a, 0x2113, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2115, 0x2115, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2118, 0x211d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2124, 0x2124, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2126, 0x2126, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2128, 0x2128, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x212a, 0x2139, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x213c, 0x213f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2145, 0x2149, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x214e, 0x214e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2160, 0x2188, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2c00, 0x2c2e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2c30, 0x2c5e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2c60, 0x2ce4, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2ceb, 0x2cee, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2cef, 0x2cf1, CharacterProperties::kXidContinue}, + {0x2cf2, 0x2cf3, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2d00, 0x2d25, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2d27, 0x2d27, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2d2d, 0x2d2d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2d30, 0x2d67, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2d6f, 0x2d6f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2d7f, 0x2d7f, CharacterProperties::kXidContinue}, + {0x2d80, 0x2d96, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2da0, 0x2da6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2da8, 0x2dae, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2db0, 0x2db6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2db8, 0x2dbe, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2dc0, 0x2dc6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2dc8, 0x2dce, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2dd0, 0x2dd6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2dd8, 0x2dde, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x2de0, 0x2dff, CharacterProperties::kXidContinue}, + {0x3005, 0x3007, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x3021, 0x3029, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x302a, 0x302f, CharacterProperties::kXidContinue}, + {0x3031, 0x3035, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x3038, 0x303c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x3041, 0x3096, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x3099, 0x309a, CharacterProperties::kXidContinue}, + {0x309d, 0x309f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x30a1, 0x30fa, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x30fc, 0x30ff, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x3105, 0x312d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x3131, 0x318e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x31a0, 0x31ba, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x31f0, 0x31ff, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x3400, 0x4db5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0x4e00, 0x9fd5, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa000, 0xa48c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa4d0, 0xa4fd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa500, 0xa60c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa610, 0xa61f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa620, 0xa629, CharacterProperties::kXidContinue}, + {0xa62a, 0xa62b, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa640, 0xa66e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa66f, 0xa66f, CharacterProperties::kXidContinue}, + {0xa674, 0xa67d, CharacterProperties::kXidContinue}, + {0xa67f, 0xa69d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa69e, 0xa69f, CharacterProperties::kXidContinue}, + {0xa6a0, 0xa6ef, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa6f0, 0xa6f1, CharacterProperties::kXidContinue}, + {0xa717, 0xa71f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa722, 0xa788, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa78b, 0xa7ae, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa7b0, 0xa7b7, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa7f7, 0xa801, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa802, 0xa802, CharacterProperties::kXidContinue}, + {0xa803, 0xa805, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa806, 0xa806, CharacterProperties::kXidContinue}, + {0xa807, 0xa80a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa80b, 0xa80b, CharacterProperties::kXidContinue}, + {0xa80c, 0xa822, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa823, 0xa827, CharacterProperties::kXidContinue}, + {0xa840, 0xa873, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa880, 0xa881, CharacterProperties::kXidContinue}, + {0xa882, 0xa8b3, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa8b4, 0xa8c5, CharacterProperties::kXidContinue}, + {0xa8d0, 0xa8d9, CharacterProperties::kXidContinue}, + {0xa8e0, 0xa8f1, CharacterProperties::kXidContinue}, + {0xa8f2, 0xa8f7, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa8fb, 0xa8fb, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa8fd, 0xa8fd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa900, 0xa909, CharacterProperties::kXidContinue}, + {0xa90a, 0xa925, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa926, 0xa92d, CharacterProperties::kXidContinue}, + {0xa930, 0xa946, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa947, 0xa953, CharacterProperties::kXidContinue}, + {0xa960, 0xa97c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa980, 0xa983, CharacterProperties::kXidContinue}, + {0xa984, 0xa9b2, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa9b3, 0xa9c0, CharacterProperties::kXidContinue}, + {0xa9cf, 0xa9cf, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa9d0, 0xa9d9, CharacterProperties::kXidContinue}, + {0xa9e0, 0xa9e4, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa9e5, 0xa9e5, CharacterProperties::kXidContinue}, + {0xa9e6, 0xa9ef, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xa9f0, 0xa9f9, CharacterProperties::kXidContinue}, + {0xa9fa, 0xa9fe, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaa00, 0xaa28, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaa29, 0xaa36, CharacterProperties::kXidContinue}, + {0xaa40, 0xaa42, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaa43, 0xaa43, CharacterProperties::kXidContinue}, + {0xaa44, 0xaa4b, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaa4c, 0xaa4d, CharacterProperties::kXidContinue}, + {0xaa50, 0xaa59, CharacterProperties::kXidContinue}, + {0xaa60, 0xaa76, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaa7a, 0xaa7a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaa7b, 0xaa7d, CharacterProperties::kXidContinue}, + {0xaa7e, 0xaaaf, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaab0, 0xaab0, CharacterProperties::kXidContinue}, + {0xaab1, 0xaab1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaab2, 0xaab4, CharacterProperties::kXidContinue}, + {0xaab5, 0xaab6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaab7, 0xaab8, CharacterProperties::kXidContinue}, + {0xaab9, 0xaabd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaabe, 0xaabf, CharacterProperties::kXidContinue}, + {0xaac0, 0xaac0, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaac1, 0xaac1, CharacterProperties::kXidContinue}, + {0xaac2, 0xaac2, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaadb, 0xaadd, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaae0, 0xaaea, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaaeb, 0xaaef, CharacterProperties::kXidContinue}, + {0xaaf2, 0xaaf4, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xaaf5, 0xaaf6, CharacterProperties::kXidContinue}, + {0xab01, 0xab06, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xab09, 0xab0e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xab11, 0xab16, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xab20, 0xab26, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xab28, 0xab2e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xab30, 0xab5a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xab5c, 0xab65, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xab70, 0xabe2, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xabe3, 0xabea, CharacterProperties::kXidContinue}, + {0xabec, 0xabed, CharacterProperties::kXidContinue}, + {0xabf0, 0xabf9, CharacterProperties::kXidContinue}, + {0xac00, 0xd7a3, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xd7b0, 0xd7c6, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xd7cb, 0xd7fb, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xf900, 0xfa6d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfa70, 0xfad9, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfb00, 0xfb06, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfb13, 0xfb17, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfb1d, 0xfb1d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfb1e, 0xfb1e, CharacterProperties::kXidContinue}, + {0xfb1f, 0xfb28, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfb2a, 0xfb36, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfb38, 0xfb3c, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfb3e, 0xfb3e, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfb40, 0xfb41, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfb43, 0xfb44, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfb46, 0xfbb1, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfbd3, 0xfc5d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfc64, 0xfd3d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfd50, 0xfd8f, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfd92, 0xfdc7, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfdf0, 0xfdf9, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfe00, 0xfe0f, CharacterProperties::kXidContinue}, + {0xfe20, 0xfe2f, CharacterProperties::kXidContinue}, + {0xfe33, 0xfe34, CharacterProperties::kXidContinue}, + {0xfe4d, 0xfe4f, CharacterProperties::kXidContinue}, + {0xfe71, 0xfe71, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfe73, 0xfe73, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfe77, 0xfe77, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfe79, 0xfe79, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfe7b, 0xfe7b, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfe7d, 0xfe7d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xfe7f, 0xfefc, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xff10, 0xff19, CharacterProperties::kXidContinue}, + {0xff21, 0xff3a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xff3f, 0xff3f, CharacterProperties::kXidContinue}, + {0xff41, 0xff5a, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xff66, 0xff9d, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xff9e, 0xff9f, CharacterProperties::kXidContinue}, + {0xffa0, 0xffbe, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xffc2, 0xffc7, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xffca, 0xffcf, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xffd2, 0xffd7, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, + {0xffda, 0xffdc, CharacterProperties::kXidStart | CharacterProperties::kXidContinue}, +}}; diff --git a/tools/aapt2/text/Unicode_test.cpp b/tools/aapt2/text/Unicode_test.cpp new file mode 100644 index 000000000000..d47fb282bc0a --- /dev/null +++ b/tools/aapt2/text/Unicode_test.cpp @@ -0,0 +1,68 @@ +/* + * 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 "text/Unicode.h" + +#include "test/Test.h" + +using ::testing::Each; +using ::testing::Eq; +using ::testing::ResultOf; + +namespace aapt { +namespace text { + +TEST(UnicodeTest, IsXidStart) { + std::u32string valid_input = U"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZˮø"; + EXPECT_THAT(valid_input, Each(ResultOf(IsXidStart, Eq(true)))); + + std::u32string invalid_input = U"$;\'/<>+=-.{}[]()\\|?@#%^&*!~`\",1234567890_"; + EXPECT_THAT(invalid_input, Each(ResultOf(IsXidStart, Eq(false)))); +} + +TEST(UnicodeTest, IsXidContinue) { + std::u32string valid_input = U"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_ˮø"; + EXPECT_THAT(valid_input, Each(ResultOf(IsXidContinue, Eq(true)))); + + std::u32string invalid_input = U"$;\'/<>+=-.{}[]()\\|?@#%^&*!~`\","; + EXPECT_THAT(invalid_input, Each(ResultOf(IsXidContinue, Eq(false)))); +} + +TEST(UnicodeTest, IsJavaIdentifier) { + EXPECT_TRUE(IsJavaIdentifier("FøøBar_12")); + EXPECT_TRUE(IsJavaIdentifier("Føø$Bar")); + + EXPECT_FALSE(IsJavaIdentifier("12FøøBar")); + EXPECT_FALSE(IsJavaIdentifier("_FøøBar")); + EXPECT_FALSE(IsJavaIdentifier("$Føø$Bar")); +} + +TEST(UnicodeTest, IsValidResourceEntryName) { + EXPECT_TRUE(IsJavaIdentifier("FøøBar")); + EXPECT_TRUE(IsValidResourceEntryName("FøøBar_12")); + EXPECT_TRUE(IsValidResourceEntryName("Føø.Bar")); + EXPECT_TRUE(IsValidResourceEntryName("Føø-Bar")); + EXPECT_TRUE(IsValidResourceEntryName("_FøøBar")); + + EXPECT_FALSE(IsValidResourceEntryName("12FøøBar")); + EXPECT_FALSE(IsValidResourceEntryName("Føø$Bar")); + EXPECT_FALSE(IsValidResourceEntryName("Føø/Bar")); + EXPECT_FALSE(IsValidResourceEntryName("Føø:Bar")); + EXPECT_FALSE(IsValidResourceEntryName("Føø;Bar")); +} + +} // namespace text +} // namespace aapt diff --git a/tools/aapt2/text/Utf8Iterator.cpp b/tools/aapt2/text/Utf8Iterator.cpp new file mode 100644 index 000000000000..20b9073b9a26 --- /dev/null +++ b/tools/aapt2/text/Utf8Iterator.cpp @@ -0,0 +1,65 @@ +/* + * 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 "text/Utf8Iterator.h" + +#include "android-base/logging.h" +#include "utils/Unicode.h" + +using ::android::StringPiece; + +namespace aapt { +namespace text { + +Utf8Iterator::Utf8Iterator(const StringPiece& str) + : str_(str), current_pos_(0), next_pos_(0), current_codepoint_(0) { + DoNext(); +} + +void Utf8Iterator::DoNext() { + current_pos_ = next_pos_; + int32_t result = utf32_from_utf8_at(str_.data(), str_.size(), current_pos_, &next_pos_); + if (result == -1) { + current_codepoint_ = 0u; + } else { + current_codepoint_ = static_cast<char32_t>(result); + } +} + +bool Utf8Iterator::HasNext() const { + return current_codepoint_ != 0; +} + +size_t Utf8Iterator::Position() const { + return current_pos_; +} + +void Utf8Iterator::Skip(int amount) { + while (amount > 0 && HasNext()) { + Next(); + --amount; + } +} + +char32_t Utf8Iterator::Next() { + CHECK(HasNext()) << "Next() called after iterator exhausted"; + char32_t result = current_codepoint_; + DoNext(); + return result; +} + +} // namespace text +} // namespace aapt diff --git a/tools/aapt2/text/Utf8Iterator.h b/tools/aapt2/text/Utf8Iterator.h new file mode 100644 index 000000000000..9318401876d1 --- /dev/null +++ b/tools/aapt2/text/Utf8Iterator.h @@ -0,0 +1,54 @@ +/* + * 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_TEXT_UTF8ITERATOR_H +#define AAPT_TEXT_UTF8ITERATOR_H + +#include "android-base/macros.h" +#include "androidfw/StringPiece.h" + +namespace aapt { +namespace text { + +class Utf8Iterator { + public: + explicit Utf8Iterator(const android::StringPiece& str); + + bool HasNext() const; + + // Returns the current position of the iterator in bytes of the source UTF8 string. + // This position is the start of the codepoint returned by the next call to Next(). + size_t Position() const; + + void Skip(int amount); + + char32_t Next(); + + private: + DISALLOW_COPY_AND_ASSIGN(Utf8Iterator); + + void DoNext(); + + android::StringPiece str_; + size_t current_pos_; + size_t next_pos_; + char32_t current_codepoint_; +}; + +} // namespace text +} // namespace aapt + +#endif // AAPT_TEXT_UTF8ITERATOR_H diff --git a/tools/aapt2/text/Utf8Iterator_test.cpp b/tools/aapt2/text/Utf8Iterator_test.cpp new file mode 100644 index 000000000000..8c3e77446595 --- /dev/null +++ b/tools/aapt2/text/Utf8Iterator_test.cpp @@ -0,0 +1,95 @@ +/* + * 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 "text/Utf8Iterator.h" + +#include "test/Test.h" + +using ::android::StringPiece; +using ::testing::Eq; + +namespace aapt { +namespace text { + +TEST(Utf8IteratorTest, IteratesOverAscii) { + Utf8Iterator iter("hello"); + + ASSERT_TRUE(iter.HasNext()); + EXPECT_THAT(iter.Next(), Eq(U'h')); + + ASSERT_TRUE(iter.HasNext()); + EXPECT_THAT(iter.Next(), Eq(U'e')); + + ASSERT_TRUE(iter.HasNext()); + EXPECT_THAT(iter.Next(), Eq(U'l')); + + ASSERT_TRUE(iter.HasNext()); + EXPECT_THAT(iter.Next(), Eq(U'l')); + + ASSERT_TRUE(iter.HasNext()); + EXPECT_THAT(iter.Next(), Eq(U'o')); + + EXPECT_FALSE(iter.HasNext()); +} + +TEST(Utf8IteratorTest, IteratesOverUnicode) { + Utf8Iterator iter("Hi there 華勵蓮🍩"); + iter.Skip(9); + + ASSERT_TRUE(iter.HasNext()); + EXPECT_THAT(iter.Next(), Eq(U'華')); + + ASSERT_TRUE(iter.HasNext()); + EXPECT_THAT(iter.Next(), Eq(U'勵')); + + ASSERT_TRUE(iter.HasNext()); + EXPECT_THAT(iter.Next(), Eq(U'蓮')); + + ASSERT_TRUE(iter.HasNext()); + EXPECT_THAT(iter.Next(), Eq(U'🍩')); + + EXPECT_FALSE(iter.HasNext()); +} + +TEST(Utf8IteratorTest, PositionPointsToTheCorrectPlace) { + const StringPiece expected("Mm🍩"); + Utf8Iterator iter(expected); + + // Before any character, the position should be 0. + EXPECT_THAT(iter.Position(), Eq(0u)); + + // The 'M' character, one byte. + ASSERT_TRUE(iter.HasNext()); + iter.Next(); + EXPECT_THAT(iter.Position(), Eq(1u)); + + // The 'm' character, one byte. + ASSERT_TRUE(iter.HasNext()); + iter.Next(); + EXPECT_THAT(iter.Position(), Eq(2u)); + + // The doughnut character, 4 bytes. + ASSERT_TRUE(iter.HasNext()); + iter.Next(); + EXPECT_THAT(iter.Position(), Eq(6u)); + + // There should be nothing left. + EXPECT_FALSE(iter.HasNext()); + EXPECT_THAT(iter.Position(), Eq(expected.size())); +} + +} // namespace text +} // namespace aapt diff --git a/tools/aapt2/tools/extract_unicode_properties.py b/tools/aapt2/tools/extract_unicode_properties.py new file mode 100644 index 000000000000..7577ec82aa86 --- /dev/null +++ b/tools/aapt2/tools/extract_unicode_properties.py @@ -0,0 +1,102 @@ +#!/bin/env python3 + +"""Extracts the XID_Start and XID_Continue Derived core properties from the ICU data files +and emits a std::array<> for binary searching. +""" + +import re +import sys + +CharacterPropertyEnumMap = { + 1: "CharacterProperties::kXidStart", + 2: "CharacterProperties::kXidContinue" +} + +class CharacterProperty: + def __init__(self, first_char, last_char, prop_type): + self.first_char = first_char + self.last_char = last_char + self.prop_type = prop_type + + def key(self): + return self.first_char + + def merge(self, other): + if self.last_char + 1 == other.first_char and self.prop_type == other.prop_type: + self.last_char = other.last_char + else: + raise KeyError() + + def __repr__(self): + types = [] + for enum_int, enum_str in CharacterPropertyEnumMap.items(): + if enum_int & self.prop_type: + types.append(enum_str) + return "{}0x{:04x}, 0x{:04x}, {}{}".format( + "{", self.first_char, self.last_char, ' | '.join(types), "}") + +def extract_unicode_properties(f, props, chars_out): + prog = re.compile(r"^(?P<first>\w{4})(..(?P<last>\w{4}))?\W+;\W+(?P<prop>\w+)") + for line in f: + result = prog.match(line) + if result: + prop_type_str = result.group('prop') + first_char_str = result.group('first') + last_char_str = result.group('last') + if prop_type_str in props: + start_char = int(first_char_str, 16) + last_char = (int(last_char_str, 16) if last_char_str else start_char) + 1 + prop_type = props[prop_type_str] + for char in range(start_char, last_char): + if char not in chars_out: + chars_out[char] = CharacterProperty(char, char, 0) + chars_out[char].prop_type |= prop_type + return chars_out + +def flatten_unicode_properties(chars): + result = [] + for char_prop in sorted(chars.values(), key=CharacterProperty.key): + if len(result) == 0: + result.append(char_prop) + else: + try: + result[len(result) - 1].merge(char_prop) + except KeyError: + result.append(char_prop) + return result + +license = """/* + * 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. + */ +""" + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("must specify path to icu DerivedCoreProperties file (e.g:" \ + "external/icu/icu4c/source/data/unidata/DerivedCoreProperties.txt)") + sys.exit(1) + + props = {"XID_Start": 1, "XID_Continue": 2} + char_props = {} + for file_path in sys.argv[1:]: + with open(file_path) as f: + extract_unicode_properties(f, props, char_props) + result = flatten_unicode_properties(char_props) + print("{}\nconst static std::array<CharacterProperties, {}> sCharacterProperties = {}" + .format(license, len(result), "{{")) + for prop in result: + print(" {},".format(prop)) + print("}};") + diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp index f3116701056b..892aee6fadcb 100644 --- a/tools/aapt2/unflatten/BinaryResourceParser.cpp +++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp @@ -38,20 +38,17 @@ namespace aapt { using namespace android; -using android::base::StringPrintf; +using ::android::base::StringPrintf; namespace { -/* - * Visitor that converts a reference's resource ID to a resource name, - * given a mapping from resource ID to resource name. - */ +// Visitor that converts a reference's resource ID to a resource name, given a mapping from +// resource ID to resource name. class ReferenceIdToNameVisitor : public ValueVisitor { public: using ValueVisitor::Visit; - explicit ReferenceIdToNameVisitor( - const std::map<ResourceId, ResourceName>* mapping) + explicit ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceName>* mapping) : mapping_(mapping) { CHECK(mapping_ != nullptr); } @@ -99,7 +96,7 @@ bool BinaryResourceParser::Parse() { if (parser.chunk()->type != android::RES_TABLE_TYPE) { context_->GetDiagnostics()->Error(DiagMessage(source_) << StringPrintf("unknown chunk of type 0x%02x", - (int)parser.chunk()->type)); + static_cast<int>(parser.chunk()->type))); return false; } @@ -115,7 +112,7 @@ bool BinaryResourceParser::Parse() { context_->GetDiagnostics()->Warn( DiagMessage(source_) << StringPrintf( "unexpected chunk of type 0x%02x trailing RES_TABLE_TYPE", - (int)parser.chunk()->type)); + static_cast<int>(parser.chunk()->type))); } } return true; @@ -165,9 +162,8 @@ bool BinaryResourceParser::ParseTable(const ResChunk_header* chunk) { default: context_->GetDiagnostics()->Warn( - DiagMessage(source_) - << "unexpected chunk type " - << (int)util::DeviceToHost16(parser.chunk()->type)); + DiagMessage(source_) << "unexpected chunk type " + << static_cast<int>(util::DeviceToHost16(parser.chunk()->type))); break; } } @@ -245,8 +241,7 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { return false; } } else { - context_->GetDiagnostics()->Warn(DiagMessage(source_) - << "unexpected string pool"); + context_->GetDiagnostics()->Warn(DiagMessage(source_) << "unexpected string pool"); } break; @@ -270,9 +265,8 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { default: context_->GetDiagnostics()->Warn( - DiagMessage(source_) - << "unexpected chunk type " - << (int)util::DeviceToHost16(parser.chunk()->type)); + DiagMessage(source_) << "unexpected chunk type " + << static_cast<int>(util::DeviceToHost16(parser.chunk()->type))); break; } } @@ -550,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/BigBuffer_test.cpp b/tools/aapt2/util/BigBuffer_test.cpp index 12c0b3ee3214..a7776e33ae74 100644 --- a/tools/aapt2/util/BigBuffer_test.cpp +++ b/tools/aapt2/util/BigBuffer_test.cpp @@ -18,12 +18,14 @@ #include "test/Test.h" +using ::testing::NotNull; + namespace aapt { TEST(BigBufferTest, AllocateSingleBlock) { BigBuffer buffer(4); - EXPECT_NE(nullptr, buffer.NextBlock<char>(2)); + EXPECT_THAT(buffer.NextBlock<char>(2), NotNull()); EXPECT_EQ(2u, buffer.size()); } @@ -31,10 +33,10 @@ TEST(BigBufferTest, ReturnSameBlockIfNextAllocationFits) { BigBuffer buffer(16); char* b1 = buffer.NextBlock<char>(8); - EXPECT_NE(nullptr, b1); + EXPECT_THAT(b1, NotNull()); char* b2 = buffer.NextBlock<char>(4); - EXPECT_NE(nullptr, b2); + EXPECT_THAT(b2, NotNull()); EXPECT_EQ(b1 + 8, b2); } @@ -42,7 +44,7 @@ TEST(BigBufferTest, ReturnSameBlockIfNextAllocationFits) { TEST(BigBufferTest, AllocateExactSizeBlockIfLargerThanBlockSize) { BigBuffer buffer(16); - EXPECT_NE(nullptr, buffer.NextBlock<char>(32)); + EXPECT_THAT(buffer.NextBlock<char>(32), NotNull()); EXPECT_EQ(32u, buffer.size()); } @@ -50,13 +52,13 @@ TEST(BigBufferTest, AppendAndMoveBlock) { BigBuffer buffer(16); uint32_t* b1 = buffer.NextBlock<uint32_t>(); - ASSERT_NE(nullptr, b1); + ASSERT_THAT(b1, NotNull()); *b1 = 33; { BigBuffer buffer2(16); b1 = buffer2.NextBlock<uint32_t>(); - ASSERT_NE(nullptr, b1); + ASSERT_THAT(b1, NotNull()); *b1 = 44; buffer.AppendBuffer(std::move(buffer2)); @@ -83,7 +85,7 @@ TEST(BigBufferTest, AppendAndMoveBlock) { TEST(BigBufferTest, PadAndAlignProperly) { BigBuffer buffer(16); - ASSERT_NE(buffer.NextBlock<char>(2), nullptr); + ASSERT_THAT(buffer.NextBlock<char>(2), NotNull()); ASSERT_EQ(2u, buffer.size()); buffer.Pad(2); ASSERT_EQ(4u, buffer.size()); 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/Maybe_test.cpp b/tools/aapt2/util/Maybe_test.cpp index ca14793dd5c3..2057ddcc9e45 100644 --- a/tools/aapt2/util/Maybe_test.cpp +++ b/tools/aapt2/util/Maybe_test.cpp @@ -80,22 +80,22 @@ struct Dummy { TEST(MaybeTest, MakeNothing) { Maybe<int> val = make_nothing<int>(); - AAPT_EXPECT_FALSE(val); + EXPECT_FALSE(val); Maybe<std::string> val2 = make_nothing<std::string>(); - AAPT_EXPECT_FALSE(val2); + EXPECT_FALSE(val2); val2 = make_nothing<std::string>(); - AAPT_EXPECT_FALSE(val2); + EXPECT_FALSE(val2); } TEST(MaybeTest, MakeSomething) { Maybe<int> val = make_value(23); - AAPT_ASSERT_TRUE(val); + ASSERT_TRUE(val); EXPECT_EQ(23, val.value()); Maybe<std::string> val2 = make_value(std::string("hey")); - AAPT_ASSERT_TRUE(val2); + ASSERT_TRUE(val2); EXPECT_EQ(std::string("hey"), val2.value()); } diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp index 8a8be858cb4c..51a75d7556ad 100644 --- a/tools/aapt2/util/Util.cpp +++ b/tools/aapt2/util/Util.cpp @@ -16,19 +16,21 @@ #include "util/Util.h" -#include <utils/Unicode.h> #include <algorithm> #include <ostream> #include <string> #include <vector> #include "androidfw/StringPiece.h" +#include "utils/Unicode.h" +#include "text/Utf8Iterator.h" #include "util/BigBuffer.h" #include "util/Maybe.h" -using android::StringPiece; -using android::StringPiece16; +using ::aapt::text::Utf8Iterator; +using ::android::StringPiece; +using ::android::StringPiece16; namespace aapt { namespace util { @@ -283,33 +285,46 @@ bool VerifyJavaStringFormat(const StringPiece& str) { return true; } -static Maybe<std::string> ParseUnicodeCodepoint(const char** start, - const char* end) { +static bool AppendCodepointToUtf8String(char32_t codepoint, std::string* output) { + ssize_t len = utf32_to_utf8_length(&codepoint, 1); + if (len < 0) { + return false; + } + + const size_t start_append_pos = output->size(); + + // Make room for the next character. + output->resize(output->size() + len); + + char* dst = &*(output->begin() + start_append_pos); + utf32_to_utf8(&codepoint, 1, dst, len + 1); + return true; +} + +static bool AppendUnicodeCodepoint(Utf8Iterator* iter, std::string* output) { char32_t code = 0; - for (size_t i = 0; i < 4 && *start != end; i++, (*start)++) { - char c = **start; + for (size_t i = 0; i < 4 && iter->HasNext(); i++) { + char32_t codepoint = iter->Next(); char32_t a; - if (c >= '0' && c <= '9') { - a = c - '0'; - } else if (c >= 'a' && c <= 'f') { - a = c - 'a' + 10; - } else if (c >= 'A' && c <= 'F') { - a = c - 'A' + 10; + if (codepoint >= U'0' && codepoint <= U'9') { + a = codepoint - U'0'; + } else if (codepoint >= U'a' && codepoint <= U'f') { + a = codepoint - U'a' + 10; + } else if (codepoint >= U'A' && codepoint <= U'F') { + a = codepoint - U'A' + 10; } else { return {}; } code = (code << 4) | a; } + return AppendCodepointToUtf8String(code, output); +} - ssize_t len = utf32_to_utf8_length(&code, 1); - if (len < 0) { - return {}; +static bool IsCodepointSpace(char32_t codepoint) { + if (static_cast<uint32_t>(codepoint) & 0xffffff00u) { + return false; } - - std::string result_utf8; - result_utf8.resize(len); - utf32_to_utf8(&code, 1, &*result_utf8.begin(), len + 1); - return result_utf8; + return isspace(static_cast<char>(codepoint)); } StringBuilder::StringBuilder(bool preserve_spaces) : preserve_spaces_(preserve_spaces) { @@ -321,57 +336,46 @@ StringBuilder& StringBuilder::Append(const StringPiece& str) { } // Where the new data will be appended to. - size_t new_data_index = str_.size(); + const size_t new_data_index = str_.size(); + + Utf8Iterator iter(str); + while (iter.HasNext()) { + const char32_t codepoint = iter.Next(); - const char* const end = str.end(); - const char* start = str.begin(); - const char* current = start; - while (current != end) { if (last_char_was_escape_) { - switch (*current) { - case 't': + switch (codepoint) { + case U't': str_ += '\t'; break; - case 'n': + + case U'n': str_ += '\n'; break; - case '#': - str_ += '#'; - break; - case '@': - str_ += '@'; - break; - case '?': - str_ += '?'; - break; - case '"': - str_ += '"'; - break; - case '\'': - str_ += '\''; - break; - case '\\': - str_ += '\\'; + + case U'#': + case U'@': + case U'?': + case U'"': + case U'\'': + case U'\\': + str_ += static_cast<char>(codepoint); break; - case 'u': { - current++; - Maybe<std::string> c = ParseUnicodeCodepoint(¤t, end); - if (!c) { + + case U'u': + if (!AppendUnicodeCodepoint(&iter, &str_)) { error_ = "invalid unicode escape sequence"; return *this; } - str_ += c.value(); - current -= 1; break; - } default: - // Ignore. + // Ignore the escape character and just include the codepoint. + AppendCodepointToUtf8String(codepoint, &str_); break; } last_char_was_escape_ = false; - start = current + 1; - } else if (!preserve_spaces_ && *current == '"') { + + } else if (!preserve_spaces_ && codepoint == U'"') { if (!quote_ && trailing_space_) { // We found an opening quote, and we have trailing space, so we should append that // space now. @@ -384,13 +388,13 @@ StringBuilder& StringBuilder::Append(const StringPiece& str) { } } quote_ = !quote_; - str_.append(start, current - start); - start = current + 1; - } else if (!preserve_spaces_ && *current == '\'' && !quote_) { + + } else if (!preserve_spaces_ && codepoint == U'\'' && !quote_) { // This should be escaped. error_ = "unescaped apostrophe"; return *this; - } else if (*current == '\\') { + + } else if (codepoint == U'\\') { // This is an escape sequence, convert to the real value. if (!quote_ && trailing_space_) { // We had trailing whitespace, so @@ -400,40 +404,35 @@ StringBuilder& StringBuilder::Append(const StringPiece& str) { } trailing_space_ = false; } - str_.append(start, current - start); - start = current + 1; last_char_was_escape_ = true; - } else if (!preserve_spaces_ && !quote_) { - // This is not quoted text, so look for whitespace. - if (isspace(*current)) { - // We found whitespace, see if we have seen some - // before. - if (!trailing_space_) { - // We didn't see a previous adjacent space, - // so mark that we did. + } else { + if (preserve_spaces_ || quote_) { + // Quotes mean everything is taken, including whitespace. + AppendCodepointToUtf8String(codepoint, &str_); + } else { + // This is not quoted text, so we will accumulate whitespace and only emit a single + // character of whitespace if it is followed by a non-whitespace character. + if (IsCodepointSpace(codepoint)) { + // We found whitespace. trailing_space_ = true; - str_.append(start, current - start); - } - - // Keep skipping whitespace. - start = current + 1; - } else if (trailing_space_) { - // We saw trailing space before, so replace all - // that trailing space with one space. - if (!str_.empty()) { - str_ += ' '; + } else { + if (trailing_space_) { + // We saw trailing space before, so replace all + // that trailing space with one space. + if (!str_.empty()) { + str_ += ' '; + } + trailing_space_ = false; + } + AppendCodepointToUtf8String(codepoint, &str_); } - trailing_space_ = false; } } - current++; } - str_.append(start, end - start); // Accumulate the added string's UTF-16 length. - ssize_t len = utf8_to_utf16_length( - reinterpret_cast<const uint8_t*>(str_.data()) + new_data_index, - str_.size() - new_data_index); + ssize_t len = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(str_.data()) + new_data_index, + str_.size() - new_data_index); if (len < 0) { error_ = "invalid unicode code point"; return *this; diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h index b9ada7704a26..8f021ab8cb8a 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -70,12 +70,6 @@ bool EndsWith(const android::StringPiece& str, const android::StringPiece& suffi android::StringPiece TrimWhitespace(const android::StringPiece& str); /** - * UTF-16 isspace(). It basically checks for lower range characters that are - * whitespace. - */ -inline bool isspace16(char16_t c) { return c < 0x0080 && isspace(c); } - -/** * Returns an iterator to the first character that is not alpha-numeric and that * is not in the allowedChars set. */ @@ -104,6 +98,16 @@ bool IsJavaPackageName(const android::StringPiece& str); Maybe<std::string> GetFullyQualifiedClassName(const android::StringPiece& package, const android::StringPiece& class_name); +template <typename T> +typename std::enable_if<std::is_arithmetic<T>::value, int>::type compare(const T& a, const T& b) { + if (a < b) { + return -1; + } else if (a > b) { + return 1; + } + return 0; +} + /** * Makes a std::unique_ptr<> with the template parameter inferred by the compiler. * This will be present in C++14 and can be removed then. @@ -224,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; @@ -246,9 +256,13 @@ class Tokenizer { Tokenizer(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/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp index 5cced3e9acab..adb52911ab82 100644 --- a/tools/aapt2/util/Util_test.cpp +++ b/tools/aapt2/util/Util_test.cpp @@ -136,58 +136,34 @@ TEST(UtilTest, IsJavaPackageName) { } TEST(UtilTest, FullyQualifiedClassName) { - Maybe<std::string> res = util::GetFullyQualifiedClassName("android", ".asdf"); - AAPT_ASSERT_TRUE(res); - EXPECT_EQ(res.value(), "android.asdf"); - - res = util::GetFullyQualifiedClassName("android", ".a.b"); - AAPT_ASSERT_TRUE(res); - EXPECT_EQ(res.value(), "android.a.b"); - - res = util::GetFullyQualifiedClassName("android", "a.b"); - AAPT_ASSERT_TRUE(res); - EXPECT_EQ(res.value(), "a.b"); - - res = util::GetFullyQualifiedClassName("", "a.b"); - AAPT_ASSERT_TRUE(res); - EXPECT_EQ(res.value(), "a.b"); - - res = util::GetFullyQualifiedClassName("android", "Class"); - AAPT_ASSERT_TRUE(res); - EXPECT_EQ(res.value(), "android.Class"); - - res = util::GetFullyQualifiedClassName("", ""); - AAPT_ASSERT_FALSE(res); - - res = util::GetFullyQualifiedClassName("android", "./Apple"); - AAPT_ASSERT_FALSE(res); + EXPECT_THAT(util::GetFullyQualifiedClassName("android", ".asdf"), Eq("android.asdf")); + EXPECT_THAT(util::GetFullyQualifiedClassName("android", ".a.b"), Eq("android.a.b")); + EXPECT_THAT(util::GetFullyQualifiedClassName("android", "a.b"), Eq("a.b")); + EXPECT_THAT(util::GetFullyQualifiedClassName("", "a.b"), Eq("a.b")); + EXPECT_THAT(util::GetFullyQualifiedClassName("android", "Class"), Eq("android.Class")); + EXPECT_FALSE(util::GetFullyQualifiedClassName("", "")); + EXPECT_FALSE(util::GetFullyQualifiedClassName("android", "./Apple")); } TEST(UtilTest, ExtractResourcePathComponents) { StringPiece prefix, entry, suffix; - ASSERT_TRUE(util::ExtractResFilePathParts("res/xml-sw600dp/entry.xml", - &prefix, &entry, &suffix)); - EXPECT_EQ(prefix, "res/xml-sw600dp/"); - EXPECT_EQ(entry, "entry"); - EXPECT_EQ(suffix, ".xml"); - - ASSERT_TRUE(util::ExtractResFilePathParts("res/xml-sw600dp/entry.9.png", - &prefix, &entry, &suffix)); - - EXPECT_EQ(prefix, "res/xml-sw600dp/"); - EXPECT_EQ(entry, "entry"); - EXPECT_EQ(suffix, ".9.png"); - - EXPECT_FALSE(util::ExtractResFilePathParts("AndroidManifest.xml", &prefix, - &entry, &suffix)); - EXPECT_FALSE( - util::ExtractResFilePathParts("res/.xml", &prefix, &entry, &suffix)); - - ASSERT_TRUE( - util::ExtractResFilePathParts("res//.", &prefix, &entry, &suffix)); - EXPECT_EQ(prefix, "res//"); - EXPECT_EQ(entry, ""); - EXPECT_EQ(suffix, "."); + ASSERT_TRUE(util::ExtractResFilePathParts("res/xml-sw600dp/entry.xml", &prefix, &entry, &suffix)); + EXPECT_THAT(prefix, Eq("res/xml-sw600dp/")); + EXPECT_THAT(entry, Eq("entry")); + EXPECT_THAT(suffix, Eq(".xml")); + + ASSERT_TRUE(util::ExtractResFilePathParts("res/xml-sw600dp/entry.9.png", &prefix, &entry, &suffix)); + EXPECT_THAT(prefix, Eq("res/xml-sw600dp/")); + EXPECT_THAT(entry, Eq("entry")); + EXPECT_THAT(suffix, Eq(".9.png")); + + ASSERT_TRUE(util::ExtractResFilePathParts("res//.", &prefix, &entry, &suffix)); + EXPECT_THAT(prefix, Eq("res//")); + EXPECT_THAT(entry, Eq("")); + EXPECT_THAT(suffix, Eq(".")); + + EXPECT_FALSE(util::ExtractResFilePathParts("AndroidManifest.xml", &prefix, &entry, &suffix)); + EXPECT_FALSE(util::ExtractResFilePathParts("res/.xml", &prefix, &entry, &suffix)); } TEST(UtilTest, VerifyJavaStringFormat) { 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/XmlActionExecutor_test.cpp b/tools/aapt2/xml/XmlActionExecutor_test.cpp index 7110c90fa3a9..0fe7ab05ceb2 100644 --- a/tools/aapt2/xml/XmlActionExecutor_test.cpp +++ b/tools/aapt2/xml/XmlActionExecutor_test.cpp @@ -18,6 +18,8 @@ #include "test/Test.h" +using ::testing::NotNull; + namespace aapt { namespace xml { @@ -42,12 +44,11 @@ TEST(XmlActionExecutorTest, BuildsAccessibleNestedPattern) { test::BuildXmlDom("<manifest><application /></manifest>"); StdErrDiagnostics diag; - ASSERT_TRUE( - executor.Execute(XmlActionExecutorPolicy::kNone, &diag, doc.get())); - ASSERT_NE(nullptr, manifest_el); + ASSERT_TRUE(executor.Execute(XmlActionExecutorPolicy::kNone, &diag, doc.get())); + ASSERT_THAT(manifest_el, NotNull()); EXPECT_EQ(std::string("manifest"), manifest_el->name); - ASSERT_NE(nullptr, application_el); + ASSERT_THAT(application_el, NotNull()); EXPECT_EQ(std::string("application"), application_el->name); } @@ -58,8 +59,7 @@ TEST(XmlActionExecutorTest, FailsWhenUndefinedHierarchyExists) { std::unique_ptr<XmlResource> doc = test::BuildXmlDom("<manifest><application /><activity /></manifest>"); StdErrDiagnostics diag; - ASSERT_FALSE( - executor.Execute(XmlActionExecutorPolicy::kWhitelist, &diag, doc.get())); + ASSERT_FALSE(executor.Execute(XmlActionExecutorPolicy::kWhitelist, &diag, doc.get())); } } // namespace xml diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp index 885ab3e33fed..cbb652ed9a1a 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) { @@ -261,13 +255,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 +269,76 @@ 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; 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)); + } + + if (pending_element == nullptr) { + pending_element = util::make_unique<Element>(); } - new_node = std::move(node); 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>(); + ; + } + 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 +348,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 +395,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 +418,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 +442,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,21 +461,22 @@ 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( @@ -522,11 +487,16 @@ Maybe<ExtractedPackage> PackageAwareVisitor::TransformPackageAlias( 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{local_package.to_string(), 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..154224381626 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,121 @@ 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; + 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 031801e1dd2f..6ed2d616f782 100644 --- a/tools/aapt2/xml/XmlDom_test.cpp +++ b/tools/aapt2/xml/XmlDom_test.cpp @@ -16,19 +16,22 @@ #include "xml/XmlDom.h" -#include <sstream> #include <string> +#include "io/StringInputStream.h" #include "test/Test.h" -namespace aapt { +using ::aapt::io::StringInputStream; +using ::testing::Eq; +using ::testing::NotNull; +using ::testing::SizeIs; +using ::testing::StrEq; -constexpr const char* kXmlPreamble = - "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; +namespace aapt { +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"> @@ -37,47 +40,82 @@ 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); - ASSERT_NE(doc, nullptr); - - xml::Namespace* ns = xml::NodeCast<xml::Namespace>(doc->root.get()); - ASSERT_NE(ns, nullptr); - EXPECT_EQ(ns->namespace_uri, xml::kSchemaAndroid); - EXPECT_EQ(ns->namespace_prefix, "android"); + StringInputStream in(input); + std::unique_ptr<XmlResource> doc = Inflate(&in, &diag, Source("test.xml")); + ASSERT_THAT(doc, NotNull()); + + 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->root.get()); - ASSERT_NE(nullptr, el); + Element* el = doc->root.get(); - xml::Attribute* attr = el->FindAttribute({}, "pattern"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ("\\\\d{5}", attr->value); + Attribute* attr = el->FindAttribute({}, "pattern"); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, Eq("\\\\d{5}")); attr = el->FindAttribute({}, "value"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ("\\?hello", attr->value); + ASSERT_THAT(attr, NotNull()); + EXPECT_THAT(attr->value, Eq("\\?hello")); - xml::Text* text = xml::NodeCast<xml::Text>(el->children[0].get()); - ASSERT_NE(nullptr, text); - EXPECT_EQ("\\\\d{5}", text->text); + ASSERT_THAT(el->children, SizeIs(1u)); + + 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_NE(nullptr, el); + 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("\"")); +} - xml::Attribute* attr = el->FindAttribute({}, "value"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ("\"", attr->value); +class TestVisitor : public PackageAwareVisitor { + public: + using PackageAwareVisitor::Visit; + + void Visit(Element* el) override { + if (el->name == "View1") { + EXPECT_THAT(TransformPackageAlias("one", "local"), + Eq(make_value(ExtractedPackage{"com.one", false}))); + } else if (el->name == "View2") { + EXPECT_THAT(TransformPackageAlias("one", "local"), + Eq(make_value(ExtractedPackage{"com.one", false}))); + EXPECT_THAT(TransformPackageAlias("two", "local"), + Eq(make_value(ExtractedPackage{"com.two", false}))); + } else if (el->name == "View3") { + EXPECT_THAT(TransformPackageAlias("one", "local"), + Eq(make_value(ExtractedPackage{"com.one", false}))); + EXPECT_THAT(TransformPackageAlias("two", "local"), + Eq(make_value(ExtractedPackage{"com.two", false}))); + EXPECT_THAT(TransformPackageAlias("three", "local"), + 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>)"); + + Debug::DumpXml(doc.get()); + 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..30bdc507303b 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) { diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h index cdeeefd13976..a00caa139061 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(); /** @@ -169,9 +170,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 +228,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.h b/tools/aapt2/xml/XmlUtil.h index 1650ac2124ac..866b6dcd7a88 100644 --- a/tools/aapt2/xml/XmlUtil.h +++ b/tools/aapt2/xml/XmlUtil.h @@ -26,79 +26,56 @@ 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 package being compiled. 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; }; -/** - * 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. - */ +// 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); diff --git a/tools/aapt2/xml/XmlUtil_test.cpp b/tools/aapt2/xml/XmlUtil_test.cpp index 5eecc8f5fb20..cbded8ffac8e 100644 --- a/tools/aapt2/xml/XmlUtil_test.cpp +++ b/tools/aapt2/xml/XmlUtil_test.cpp @@ -21,37 +21,30 @@ namespace aapt { TEST(XmlUtilTest, ExtractPackageFromNamespace) { - AAPT_ASSERT_FALSE(xml::ExtractPackageFromNamespace("com.android")); - AAPT_ASSERT_FALSE( - xml::ExtractPackageFromNamespace("http://schemas.android.com/apk")); - AAPT_ASSERT_FALSE( - xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res")); - AAPT_ASSERT_FALSE( - xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res/")); - AAPT_ASSERT_FALSE(xml::ExtractPackageFromNamespace( - "http://schemas.android.com/apk/prv/res/")); + ASSERT_FALSE(xml::ExtractPackageFromNamespace("com.android")); + ASSERT_FALSE(xml::ExtractPackageFromNamespace("http://schemas.android.com/apk")); + ASSERT_FALSE(xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res")); + ASSERT_FALSE(xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res/")); + ASSERT_FALSE(xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/prv/res/")); Maybe<xml::ExtractedPackage> p = xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res/a"); - AAPT_ASSERT_TRUE(p); + ASSERT_TRUE(p); EXPECT_EQ(std::string("a"), p.value().package); EXPECT_FALSE(p.value().private_namespace); - p = xml::ExtractPackageFromNamespace( - "http://schemas.android.com/apk/prv/res/android"); - AAPT_ASSERT_TRUE(p); + p = xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/prv/res/android"); + ASSERT_TRUE(p); EXPECT_EQ(std::string("android"), p.value().package); EXPECT_TRUE(p.value().private_namespace); - p = xml::ExtractPackageFromNamespace( - "http://schemas.android.com/apk/prv/res/com.test"); - AAPT_ASSERT_TRUE(p); + p = xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/prv/res/com.test"); + ASSERT_TRUE(p); EXPECT_EQ(std::string("com.test"), p.value().package); EXPECT_TRUE(p.value().private_namespace); - p = xml::ExtractPackageFromNamespace( - "http://schemas.android.com/apk/res-auto"); - AAPT_ASSERT_TRUE(p); + p = xml::ExtractPackageFromNamespace("http://schemas.android.com/apk/res-auto"); + ASSERT_TRUE(p); EXPECT_EQ(std::string(), p.value().package); EXPECT_TRUE(p.value().private_namespace); } diff --git a/tools/bit/adb.cpp b/tools/bit/adb.cpp index 0c8424de566d..c8faf5c66722 100644 --- a/tools/bit/adb.cpp +++ b/tools/bit/adb.cpp @@ -283,10 +283,19 @@ run_instrumentation_test(const string& packageName, const string& runner, const cmd.AddArg("instrument"); cmd.AddArg("-w"); cmd.AddArg("-m"); - if (className.length() > 0) { - cmd.AddArg("-e"); - cmd.AddArg("class"); - cmd.AddArg(className); + const int classLen = className.length(); + if (classLen > 0) { + if (classLen > 1 && className[classLen - 1] == '.') { + cmd.AddArg("-e"); + cmd.AddArg("package"); + + // "am" actually accepts without removing the last ".", but for cleanlines... + cmd.AddArg(className.substr(0, classLen - 1)); + } else { + cmd.AddArg("-e"); + cmd.AddArg("class"); + cmd.AddArg(className); + } } cmd.AddArg(packageName + "/" + runner); diff --git a/tools/bit/main.cpp b/tools/bit/main.cpp index d056ba53ca84..91ca5143965e 100644 --- a/tools/bit/main.cpp +++ b/tools/bit/main.cpp @@ -50,6 +50,7 @@ struct Target { int testPassCount; int testFailCount; + int unknownFailureCount; // unknown failure == "Process crashed", etc. bool actionsWithNoTests; Target(bool b, bool i, bool t, const string& p); @@ -63,6 +64,7 @@ Target::Target(bool b, bool i, bool t, const string& p) testActionCount(0), testPassCount(0), testFailCount(0), + unknownFailureCount(0), actionsWithNoTests(false) { } @@ -188,8 +190,13 @@ public: */ void SetCurrentAction(TestAction* action); + bool IsSuccess(); + + string GetErrorMessage(); + private: TestAction* m_currentAction; + SessionStatus m_sessionStatus; }; void @@ -241,8 +248,10 @@ TestResults::OnTestStatus(TestStatus& status) } line << ": " << m_currentAction->target->name << ':' << className << "\\#" << testName; print_one_line("%s", line.str().c_str()); - } else if (resultCode == -2) { + } else if ((resultCode == -1) || (resultCode == -2)) { // test failed + // Note -2 means an assertion failure, and -1 means other exceptions. We just treat them + // all as "failures". m_currentAction->failCount++; m_currentAction->target->testFailCount++; printf("%s\n%sFailed: %s:%s\\#%s%s\n", g_escapeClearLine, g_escapeRedBold, @@ -257,9 +266,13 @@ TestResults::OnTestStatus(TestStatus& status) } void -TestResults::OnSessionStatus(SessionStatus& /*status*/) +TestResults::OnSessionStatus(SessionStatus& status) { //status.PrintDebugString(); + m_sessionStatus = status; + if (m_currentAction && !IsSuccess()) { + m_currentAction->target->unknownFailureCount++; + } } void @@ -268,6 +281,24 @@ TestResults::SetCurrentAction(TestAction* action) m_currentAction = action; } +bool +TestResults::IsSuccess() +{ + return m_sessionStatus.result_code() == -1; // Activity.RESULT_OK. +} + +string +TestResults::GetErrorMessage() +{ + bool found; + string shortMsg = get_bundle_string(m_sessionStatus.results(), &found, "shortMsg", NULL); + if (!found) { + return IsSuccess() ? "" : "Unknown failure"; + } + return shortMsg; +} + + /** * Prints the usage statement / help text. */ @@ -342,6 +373,10 @@ print_usage(FILE* out) { fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs the testWrite\n"); fprintf(out, " and testRepeated test methods on that class.\n"); fprintf(out, "\n"); + fprintf(out, " bit CtsProtoTestCases:android.util.proto.cts.\n"); + fprintf(out, " Builds and installs CtsProtoTestCases.apk, and runs the tests in the java package\n"); + fprintf(out, " \"android.util.proto.cts\".\n"); + fprintf(out, "\n"); fprintf(out, " Launching an Activity\n"); fprintf(out, " ---------------------\n"); fprintf(out, " To launch an activity, specify the activity class name after\n"); @@ -564,7 +599,7 @@ check_device_property(const string& property, const string& expected) /** * Run the build, install, and test actions. */ -void +bool run_phases(vector<Target*> targets, const Options& options) { int err = 0; @@ -731,7 +766,7 @@ run_phases(vector<Target*> targets, const Options& options) InstallApk& apk = installApks[i]; if (!apk.file.fileInfo.exists || apk.file.HasChanged()) { // It didn't exist before or it changed, so int needs install - err = run_adb("install", "-r", apk.file.filename.c_str(), NULL); + err = run_adb("install", "-r", "-g", apk.file.filename.c_str(), NULL); check_error(err); apk.installed = true; } else { @@ -833,6 +868,10 @@ run_phases(vector<Target*> targets, const Options& options) printf("%s%d passed%s, %d failed\n", g_escapeGreenBold, action.passCount, g_escapeEndColor, action.failCount); } + if (!testResults.IsSuccess()) { + printf("\n%sTest didn't finish successfully: %s%s\n", g_escapeRedBold, + testResults.GetErrorMessage().c_str(), g_escapeEndColor); + } } } @@ -903,6 +942,7 @@ run_phases(vector<Target*> targets, const Options& options) } // Tests + bool hasErrors = false; if (testActions.size() > 0) { printf("%sRan tests:%s\n", g_escapeBold, g_escapeEndColor); size_t maxNameLength = 0; @@ -920,12 +960,18 @@ run_phases(vector<Target*> targets, const Options& options) Target* target = targets[i]; if (target->testActionCount > 0) { printf(" %s%s", target->name.c_str(), padding.c_str() + target->name.length()); - if (target->actionsWithNoTests) { + if (target->unknownFailureCount > 0) { + printf(" %sUnknown failure, see above message.%s\n", + g_escapeRedBold, g_escapeEndColor); + hasErrors = true; + } else if (target->actionsWithNoTests) { printf(" %s%d passed, %d failed%s\n", g_escapeYellowBold, target->testPassCount, target->testFailCount, g_escapeEndColor); + hasErrors = true; } else if (target->testFailCount > 0) { printf(" %d passed, %s%d failed%s\n", target->testPassCount, g_escapeRedBold, target->testFailCount, g_escapeEndColor); + hasErrors = true; } else { printf(" %s%d passed%s, %d failed\n", g_escapeGreenBold, target->testPassCount, g_escapeEndColor, target->testFailCount); @@ -940,6 +986,7 @@ run_phases(vector<Target*> targets, const Options& options) } printf("%s--------------------------------------------%s\n", g_escapeBold, g_escapeEndColor); + return !hasErrors; } /** @@ -987,7 +1034,7 @@ main(int argc, const char** argv) exit(0); } else { // Normal run - run_phases(options.targets, options); + exit(run_phases(options.targets, options) ? 0 : 1); } return 0; diff --git a/tools/incident_report/Android.bp b/tools/incident_report/Android.bp index 6f21605a99a4..ab55dbd81821 100644 --- a/tools/incident_report/Android.bp +++ b/tools/incident_report/Android.bp @@ -32,7 +32,4 @@ cc_binary_host { ], cflags: ["-Wno-unused-parameter"], - - // b/34740546, work around clang-tidy segmentation fault. - tidy_checks: ["-modernize*"], } diff --git a/tools/incident_section_gen/Android.bp b/tools/incident_section_gen/Android.bp index 7f8151f196ae..1756e06c66fa 100644 --- a/tools/incident_section_gen/Android.bp +++ b/tools/incident_section_gen/Android.bp @@ -19,8 +19,6 @@ // ========================================================== cc_binary_host { name: "incident-section-gen", - // b/34740546, work around clang-tidy segmentation fault. - tidy_checks: ["-modernize*"], cflags: [ "-g", "-O0", |