diff options
Diffstat (limited to 'tools')
87 files changed, 3597 insertions, 2130 deletions
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index 14d05fdf6ee8..24aa6ebbcbc2 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -92,7 +92,9 @@ cc_library_host_static { "flatten/XmlFlattener.cpp", "io/BigBufferStreams.cpp", "io/File.cpp", + "io/FileInputStream.cpp", "io/FileSystem.cpp", + "io/StringInputStream.cpp", "io/Util.cpp", "io/ZipArchive.cpp", "link/AutoVersioner.cpp", @@ -104,6 +106,7 @@ cc_library_host_static { "link/XmlCompatVersioner.cpp", "link/XmlNamespaceRemover.cpp", "link/XmlReferenceLinker.cpp", + "optimize/MultiApkGenerator.cpp", "optimize/ResourceDeduper.cpp", "optimize/VersionCollapser.cpp", "process/SymbolTable.cpp", @@ -140,7 +143,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, @@ -164,6 +168,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..6a099651f2ce 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; 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 7e5efa15f61b..b80780e33dfa 100644 --- a/tools/aapt2/LoadedApk.cpp +++ b/tools/aapt2/LoadedApk.cpp @@ -58,14 +58,15 @@ std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(IAaptContext* context, bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options, IArchiveWriter* writer) { FilterChain empty; - return WriteToArchive(context, options, &empty, writer); + return WriteToArchive(context, table_.get(), options, &empty, writer); } -bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options, - FilterChain* filters, IArchiveWriter* writer) { +bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table, + const TableFlattenerOptions& options, FilterChain* filters, + IArchiveWriter* writer) { std::set<std::string> referenced_resources; // List the files being referenced in the resource table. - for (auto& pkg : table_->packages) { + for (auto& pkg : split_table->packages) { for (auto& type : pkg->types) { for (auto& entry : type->entries) { for (auto& config_value : entry->values) { @@ -84,7 +85,7 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOption std::string path = file->GetSource().path; // The name of the path has the format "<zip-file-name>@<path-to-file>". - path = path.substr(path.find("@") + 1); + path = path.substr(path.find('@') + 1); // Skip resources that are not referenced if requested. if (path.find("res/") == 0 && referenced_resources.find(path) == referenced_resources.end()) { @@ -108,7 +109,7 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOption // TODO(adamlesinski): How to determine if there were sparse entries (and if to encode // with sparse entries) b/35389232. TableFlattener flattener(options, &buffer); - if (!flattener.Consume(context, table_.get())) { + if (!flattener.Consume(context, split_table)) { return false; } diff --git a/tools/aapt2/LoadedApk.h b/tools/aapt2/LoadedApk.h index 8aa9674aa2ed..dacd0c2130a9 100644 --- a/tools/aapt2/LoadedApk.h +++ b/tools/aapt2/LoadedApk.h @@ -47,16 +47,17 @@ class LoadedApk { * Writes the APK on disk at the given path, while also removing the resource * files that are not referenced in the resource table. */ - bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options, - IArchiveWriter* writer); + virtual bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options, + IArchiveWriter* writer); /** * Writes the APK on disk at the given path, while also removing the resource * files that are not referenced in the resource table. The provided filter * chain is applied to each entry in the APK file. */ - bool WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options, - FilterChain* filters, IArchiveWriter* writer); + virtual bool WriteToArchive(IAaptContext* context, ResourceTable* split_table, + const TableFlattenerOptions& options, FilterChain* filters, + IArchiveWriter* writer); static std::unique_ptr<LoadedApk> LoadApkFromPath(IAaptContext* context, const android::StringPiece& path); diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index c5d38abcdf71..e4ca7a463df1 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 { @@ -29,52 +43,126 @@ static const char* sMajorVersion = "2"; // Update minor version whenever a feature or flag is added. static const char* sMinorVersion = "18"; -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; } -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); +static void PrintUsage() { + std::cerr << "\nusage: aapt2 [compile|link|dump|diff|optimize|version] ..." << std::endl; +} -} // namespace aapt +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); -int main(int argc, char** argv) { - if (argc >= 2) { - argv += 1; - argc -= 1; +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; +} - std::vector<android::StringPiece> args; - for (int i = 1; i < argc; i++) { - args.push_back(argv[i]); +static void RunDaemon(IDiagnostics* diagnostics) { + std::cout << "Ready" << std::endl; + + // Run in daemon mode. Each line of input from stdin is treated as a command line argument + // invocation. This means we need to split the line into a vector of args. + for (std::string line; std::getline(std::cin, line);) { + const util::Tokenizer tokenizer = util::Tokenize(line, file::sPathSep); + auto token_iter = tokenizer.begin(); + if (token_iter == tokenizer.end()) { + diagnostics->Error(DiagMessage() << "no command"); + continue; } - 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(); + const StringPiece command(*token_iter); + if (command == "quit") { + break; } - std::cerr << "unknown command '" << command << "'\n"; - } else { + + ++token_iter; + + std::vector<StringPiece> args; + args.insert(args.end(), token_iter, tokenizer.end()); + ExecuteCommand(command, args, diagnostics); + std::cout << "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/Resource.cpp b/tools/aapt2/Resource.cpp index 35971e7bd99b..a9f5f298e019 100644 --- a/tools/aapt2/Resource.cpp +++ b/tools/aapt2/Resource.cpp @@ -61,6 +61,8 @@ StringPiece ToString(ResourceType type) { return "menu"; case ResourceType::kMipmap: return "mipmap"; + case ResourceType::kNavigation: + return "navigation"; case ResourceType::kPlurals: return "plurals"; case ResourceType::kRaw: @@ -98,6 +100,7 @@ static const std::map<StringPiece, ResourceType> sResourceTypeMap{ {"layout", ResourceType::kLayout}, {"menu", ResourceType::kMenu}, {"mipmap", ResourceType::kMipmap}, + {"navigation", ResourceType::kNavigation}, {"plurals", ResourceType::kPlurals}, {"raw", ResourceType::kRaw}, {"string", ResourceType::kString}, diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index 0a74c1a0f77d..cbcc8fb805aa 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -59,6 +59,7 @@ enum class ResourceType { kLayout, kMenu, kMipmap, + kNavigation, kPlurals, kRaw, kString, diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index a5783a532e23..1c3ac2ad4f17 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -1219,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 1683c64a6a5c..144ebd22e105 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -22,9 +22,11 @@ #include "ResourceTable.h" #include "ResourceUtils.h" #include "ResourceValues.h" +#include "io/StringInputStream.h" #include "test/Test.h" #include "xml/XmlPullParser.h" +using ::aapt::io::StringInputStream; using ::aapt::test::StrValueEq; using ::aapt::test::ValueEq; using ::android::ResTable_map; @@ -43,11 +45,13 @@ constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?> TEST(ResourceParserSingleTest, FailToParseWithNoRootResourcesElement) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); - std::stringstream input(kXmlPreamble); - input << R"(<attr name="foo"/>)" << std::endl; ResourceTable table; ResourceParser parser(context->GetDiagnostics(), &table, Source{"test"}, {}); - xml::XmlPullParser xml_parser(input); + + std::string input = kXmlPreamble; + input += R"(<attr name="foo"/>)"; + StringInputStream in(input); + xml::XmlPullParser xml_parser(&in); ASSERT_FALSE(parser.Parse(&xml_parser)); } @@ -62,12 +66,16 @@ class ResourceParserTest : public ::testing::Test { } ::testing::AssertionResult TestParse(const StringPiece& str, const ConfigDescription& config) { - std::stringstream input(kXmlPreamble); - input << "<resources>\n" << str << "\n</resources>" << std::endl; ResourceParserOptions parserOptions; ResourceParser parser(context_->GetDiagnostics(), &table_, Source{"test"}, config, parserOptions); - xml::XmlPullParser xmlParser(input); + + std::string input = kXmlPreamble; + input += "<resources>\n"; + input.append(str.data(), str.size()); + input += "\n</resources>"; + StringInputStream in(input); + xml::XmlPullParser xmlParser(&in); if (parser.Parse(&xmlParser)) { return ::testing::AssertionSuccess(); } @@ -532,11 +540,11 @@ TEST_F(ResourceParserTest, ParseArray) { Array* array = test::GetValue<Array>(&table_, "array/foo"); ASSERT_THAT(array, NotNull()); - ASSERT_THAT(array->items, SizeIs(3)); + ASSERT_THAT(array->elements, SizeIs(3)); - EXPECT_THAT(ValueCast<Reference>(array->items[0].get()), NotNull()); - EXPECT_THAT(ValueCast<String>(array->items[1].get()), NotNull()); - EXPECT_THAT(ValueCast<BinaryPrimitive>(array->items[2].get()), NotNull()); + EXPECT_THAT(ValueCast<Reference>(array->elements[0].get()), NotNull()); + EXPECT_THAT(ValueCast<String>(array->elements[1].get()), NotNull()); + EXPECT_THAT(ValueCast<BinaryPrimitive>(array->elements[2].get()), NotNull()); } TEST_F(ResourceParserTest, ParseStringArray) { @@ -557,9 +565,9 @@ TEST_F(ResourceParserTest, ParseArrayWithFormat) { Array* array = test::GetValue<Array>(&table_, "array/foo"); ASSERT_THAT(array, NotNull()); - ASSERT_THAT(array->items, SizeIs(1)); + ASSERT_THAT(array->elements, SizeIs(1)); - String* str = ValueCast<String>(array->items[0].get()); + String* str = ValueCast<String>(array->elements[0].get()); ASSERT_THAT(str, NotNull()); EXPECT_THAT(*str, StrValueEq("100")); } diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index ab59560d33a3..0304e21698df 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -546,4 +546,34 @@ Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNam return SearchResult{package, type, entry}; } +std::unique_ptr<ResourceTable> ResourceTable::Clone() const { + std::unique_ptr<ResourceTable> new_table = util::make_unique<ResourceTable>(); + for (const auto& pkg : packages) { + ResourceTablePackage* new_pkg = new_table->CreatePackage(pkg->name, pkg->id); + for (const auto& type : pkg->types) { + ResourceTableType* new_type = new_pkg->FindOrCreateType(type->type); + if (!new_type->id) { + new_type->id = type->id; + new_type->symbol_status = type->symbol_status; + } + + for (const auto& entry : type->entries) { + ResourceEntry* new_entry = new_type->FindOrCreateEntry(entry->name); + if (!new_entry->id) { + new_entry->id = entry->id; + new_entry->symbol_status = entry->symbol_status; + } + + for (const auto& config_value : entry->values) { + ResourceConfigValue* new_value = + new_entry->FindOrCreateValue(config_value->config, config_value->product); + Value* value = config_value->value->Clone(&new_table->string_pool); + new_value->value = std::unique_ptr<Value>(value); + } + } + } + } + return new_table; +} + } // namespace aapt diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index 4295d0674774..d5db67e77f51 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -251,6 +251,8 @@ class ResourceTable { ResourceTablePackage* CreatePackage(const android::StringPiece& name, Maybe<uint8_t> id = {}); + std::unique_ptr<ResourceTable> Clone() const; + /** * The string pool used by this resource table. Values that reference strings * must use diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index eb59175edf3b..1cba19462839 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -805,13 +805,12 @@ bool Array::Equals(const Value* value) const { return false; } - if (items.size() != other->items.size()) { + if (elements.size() != other->elements.size()) { return false; } - return std::equal(items.begin(), items.end(), other->items.begin(), - [](const std::unique_ptr<Item>& a, - const std::unique_ptr<Item>& b) -> bool { + return std::equal(elements.begin(), elements.end(), other->elements.begin(), + [](const std::unique_ptr<Item>& a, const std::unique_ptr<Item>& b) -> bool { return a->Equals(b.get()); }); } @@ -820,14 +819,14 @@ Array* Array::Clone(StringPool* new_pool) const { Array* array = new Array(); array->comment_ = comment_; array->source_ = source_; - for (auto& item : items) { - array->items.emplace_back(std::unique_ptr<Item>(item->Clone(new_pool))); + for (auto& item : elements) { + array->elements.emplace_back(std::unique_ptr<Item>(item->Clone(new_pool))); } return array; } void Array::Print(std::ostream* out) const { - *out << "(array) [" << util::Joiner(items, ", ") << "]"; + *out << "(array) [" << util::Joiner(elements, ", ") << "]"; } bool Plural::Equals(const Value* value) const { diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index 7e7547fc1b94..275864bbcd3e 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -292,7 +292,7 @@ struct Style : public BaseValue<Style> { }; struct Array : public BaseValue<Array> { - std::vector<std::unique_ptr<Item>> items; + std::vector<std::unique_ptr<Item>> elements; bool Equals(const Value* value) const override; Array* Clone(StringPool* new_pool) const override; diff --git a/tools/aapt2/ResourceValues_test.cpp b/tools/aapt2/ResourceValues_test.cpp index 06c3404561de..10f9b55ede08 100644 --- a/tools/aapt2/ResourceValues_test.cpp +++ b/tools/aapt2/ResourceValues_test.cpp @@ -54,19 +54,19 @@ TEST(ResourceValuesTest, ArrayEquals) { StringPool pool; Array a; - a.items.push_back(util::make_unique<String>(pool.MakeRef("one"))); - a.items.push_back(util::make_unique<String>(pool.MakeRef("two"))); + a.elements.push_back(util::make_unique<String>(pool.MakeRef("one"))); + a.elements.push_back(util::make_unique<String>(pool.MakeRef("two"))); Array b; - b.items.push_back(util::make_unique<String>(pool.MakeRef("une"))); - b.items.push_back(util::make_unique<String>(pool.MakeRef("deux"))); + b.elements.push_back(util::make_unique<String>(pool.MakeRef("une"))); + b.elements.push_back(util::make_unique<String>(pool.MakeRef("deux"))); Array c; - c.items.push_back(util::make_unique<String>(pool.MakeRef("uno"))); + c.elements.push_back(util::make_unique<String>(pool.MakeRef("uno"))); Array d; - d.items.push_back(util::make_unique<String>(pool.MakeRef("one"))); - d.items.push_back(util::make_unique<String>(pool.MakeRef("two"))); + d.elements.push_back(util::make_unique<String>(pool.MakeRef("one"))); + d.elements.push_back(util::make_unique<String>(pool.MakeRef("two"))); EXPECT_FALSE(a.Equals(&b)); EXPECT_FALSE(a.Equals(&c)); @@ -78,8 +78,8 @@ TEST(ResourceValuesTest, ArrayClone) { StringPool pool; Array a; - a.items.push_back(util::make_unique<String>(pool.MakeRef("one"))); - a.items.push_back(util::make_unique<String>(pool.MakeRef("two"))); + a.elements.push_back(util::make_unique<String>(pool.MakeRef("one"))); + a.elements.push_back(util::make_unique<String>(pool.MakeRef("two"))); std::unique_ptr<Array> b(a.Clone(&pool)); EXPECT_TRUE(a.Equals(b.get())); diff --git a/tools/aapt2/Resource_test.cpp b/tools/aapt2/Resource_test.cpp index ad4e3ce02b32..c557f3c77654 100644 --- a/tools/aapt2/Resource_test.cpp +++ b/tools/aapt2/Resource_test.cpp @@ -93,6 +93,10 @@ TEST(ResourceTypeTest, ParseResourceTypes) { ASSERT_NE(type, nullptr); EXPECT_EQ(*type, ResourceType::kMipmap); + type = ParseResourceType("navigation"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kNavigation); + type = ParseResourceType("plurals"); ASSERT_NE(type, nullptr); EXPECT_EQ(*type, ResourceType::kPlurals); diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto new file mode 100644 index 000000000000..71f33b0853ad --- /dev/null +++ b/tools/aapt2/Resources.proto @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Keep proto2 syntax because we require the distinction between fields that +// are set and unset. +syntax = "proto2"; + +option java_package = "com.android.aapt"; +option optimize_for = LITE_RUNTIME; + +package aapt.pb; + +// A configuration description that wraps the binary form of the C++ class +// aapt::ConfigDescription, with an added product definition. +// TODO(adamlesinski): Flesh this out to be represented in proto. +message ConfigDescription { + optional bytes data = 1; + optional string product = 2; +} + +// A string pool that wraps the binary form of the C++ class android::ResStringPool. +message StringPool { + optional bytes data = 1; +} + +// The position of a declared entity within a file. +message SourcePosition { + optional uint32 line_number = 1; + optional uint32 column_number = 2; +} + +// Developer friendly source file information for an entity in the resource table. +message Source { + // The index of the string path within the source string pool of a ResourceTable. + optional uint32 path_idx = 1; + optional SourcePosition position = 2; +} + +// Top level message representing a resource table. +message ResourceTable { + // The string pool containing source paths referenced throughout the resource table. This does + // not end up in the final binary ARSC file. + optional StringPool source_pool = 1; + + // Resource definitions corresponding to an Android package. + repeated Package package = 2; +} + +// Defines resources for an Android package. +message Package { + // The package ID of this package, in the range [0x00, 0xff]. + // The ID 0x00 is reserved for shared libraries, or when the ID is assigned at run-time. + // The ID 0x01 is reserved for the 'android' package (framework). + // The ID range [0x02, 0x7f) is reserved for auto-assignment to shared libraries at run-time. + // The ID 0x7f is reserved for the application package. + // IDs > 0x7f are reserved for the application as well and are treated as feature splits. + optional uint32 package_id = 1; + + // The Java compatible Android package name of the app. + optional string package_name = 2; + + // The series of types defined by the package. + repeated Type type = 3; +} + +// A set of resources grouped under a common type. Such types include string, layout, xml, dimen, +// attr, etc. This maps to the second part of a resource identifier in Java (R.type.entry). +message Type { + // The ID of the type. This may be 0, which indicates no ID is set. + optional uint32 id = 1; + + // The name of the type. This corresponds to the 'type' part of a full resource name of the form + // package:type/entry. The set of legal type names is listed in Resource.cpp. + optional string name = 2; + + // The entries defined for this type. + repeated Entry entry = 3; +} + +// The status of a symbol/entry. This contains information like visibility (public/private), +// comments, and whether the entry can be overridden. +message SymbolStatus { + // The visibility of the resource outside of its package. + enum Visibility { + // No visibility was explicitly specified. This is typically treated as private. + // The distinction is important when two separate R.java files are generated: a public and + // private one. An unknown visibility, in this case, would cause the resource to be omitted + // from either R.java. + UNKNOWN = 0; + + // A resource was explicitly marked as private. This means the resource can not be accessed + // outside of its package unless the @*package:type/entry notation is used (the asterisk being + // the private accessor). If two R.java files are generated (private + public), the resource + // will only be emitted to the private R.java file. + PRIVATE = 1; + + // A resource was explicitly marked as public. This means the resource can be accessed + // from any package, and is emitted into all R.java files, public and private. + PUBLIC = 2; + } + + optional Visibility visibility = 1; + + // The path at which this entry's visibility was defined (eg. public.xml). + optional Source source = 2; + + // The comment associated with the <public> tag. + optional string comment = 3; + + // Whether the symbol can be merged into another resource table without there being an existing + // definition to override. Used for overlays and set to true when <add-resource> is specified. + optional bool allow_new = 4; +} + +// An entry declaration. An entry has a full resource ID that is the combination of package ID, +// type ID, and its own entry ID. An entry on its own has no value, but values are defined for +// various configurations/variants. +message Entry { + // The ID of this entry. Together with the package ID and type ID, this forms a full resource ID + // of the form 0xPPTTEEEE, where PP is the package ID, TT is the type ID, and EEEE is the entry + // ID. + optional uint32 id = 1; + + // The name of this entry. This corresponds to the 'entry' part of a full resource name of the + // form package:type/entry. + optional string name = 2; + + // The symbol status of this entry, which includes visibility information. + optional SymbolStatus symbol_status = 3; + + // The set of values defined for this entry, each corresponding to a different + // configuration/variant. + repeated ConfigValue config_value = 4; +} + +// A Configuration/Value pair. +message ConfigValue { + optional ConfigDescription config = 1; + optional Value value = 2; +} + +// The generic meta-data for every value in a resource table. +message Value { + // Where the value was defined. + optional Source source = 1; + + // Any comment associated with the value. + optional string comment = 2; + + // Whether the value can be overridden. + optional bool weak = 3; + + // If the value is an Item, this is set. + optional Item item = 4; + + // If the value is a CompoundValue, this is set. + optional CompoundValue compound_value = 5; +} + +// An Item is an abstract type. It represents a value that can appear inline in many places, such +// as XML attribute values or on the right hand side of style attribute definitions. The concrete +// type is one of the types below. Only one can be set. +message Item { + optional Reference ref = 1; + optional String str = 2; + optional RawString raw_str = 3; + optional StyledString styled_str = 4; + optional FileReference file = 5; + optional Id id = 6; + optional Primitive prim = 7; +} + +// A CompoundValue is an abstract type. It represents a value that is a made of other values. +// These can only usually appear as top-level resources. The concrete type is one of the types +// below. Only one can be set. +message CompoundValue { + optional Attribute attr = 1; + optional Style style = 2; + optional Styleable styleable = 3; + optional Array array = 4; + optional Plural plural = 5; +} + +// A value that is a reference to another resource. This reference can be by name or resource ID. +message Reference { + enum Type { + // A plain reference (@package:type/entry). + REFERENCE = 0; + + // A reference to a theme attribute (?package:type/entry). + ATTRIBUTE = 1; + } + + optional Type type = 1; + + // The resource ID (0xPPTTEEEE) of the resource being referred. + optional uint32 id = 2; + + // The optional resource name. + optional string name = 3; + + // Whether this reference is referencing a private resource (@*package:type/entry). + optional bool private = 4; +} + +// A value that represents an ID. This is just a placeholder, as ID values are used to occupy a +// resource ID (0xPPTTEEEE) as a unique identifier. Their value is unimportant. +message Id { +} + +// A value that is a string. +message String { + optional string value = 1; +} + +// A value that is a raw string, which is unescaped/uninterpreted. This is typically used to +// represent the value of a style attribute before the attribute is compiled and the set of +// allowed values is known. +message RawString { + optional string value = 1; +} + +// A string with styling information, like html tags that specify boldness, italics, etc. +message StyledString { + // The raw text of the string. + optional string value = 1; + + // A Span marks a region of the string text that is styled. + message Span { + // The name of the tag, and its attributes, encoded as follows: + // tag_name;attr1=value1;attr2=value2;[...] + optional string tag = 1; + + // The first character position this span applies to, in UTF-16 offset. + optional uint32 first_char = 2; + + // The last character position this span applies to, in UTF-16 offset. + optional uint32 last_char = 3; + } + + repeated Span span = 2; +} + +// A value that is a reference to an external entity, like an XML file or a PNG. +message FileReference { + // Path to a file within the APK (typically res/type-config/entry.ext). + optional string path = 1; +} + +// A value that represents a primitive data type (float, int, boolean, etc.). +// Corresponds to the fields (type/data) of the C struct android::Res_value. +message Primitive { + optional uint32 type = 1; + optional uint32 data = 2; +} + +// A value that represents an XML attribute and what values it accepts. +message Attribute { + // A Symbol used to represent an enum or a flag. + message Symbol { + // Where the enum/flag item was defined. + optional Source source = 1; + + // Any comments associated with the enum or flag. + optional string comment = 2; + + // The name of the enum/flag as a reference. Enums/flag items are generated as ID resource + // values. + optional Reference name = 3; + + // The value of the enum/flag. + optional uint32 value = 4; + } + + // Bitmask of formats allowed for an attribute. + enum FormatFlags { + ANY = 0x0000ffff; // Allows any type except ENUM and FLAGS. + REFERENCE = 0x01; // Allows Reference values. + STRING = 0x02; // Allows String/StyledString values. + INTEGER = 0x04; // Allows any integer BinaryPrimitive values. + BOOLEAN = 0x08; // Allows any boolean BinaryPrimitive values. + COLOR = 0x010; // Allows any color BinaryPrimitive values. + FLOAT = 0x020; // Allows any float BinaryPrimitive values. + DIMENSION = 0x040; // Allows any dimension BinaryPrimitive values. + FRACTION = 0x080; // Allows any fraction BinaryPrimitive values. + ENUM = 0x00010000; // Allows enums that are defined in the Attribute's symbols. + // ENUM and FLAGS cannot BOTH be set. + FLAGS = 0x00020000; // Allows flags that are defined in the Attribute's symbols. + // ENUM and FLAGS cannot BOTH be set. + } + + // A bitmask of types that this XML attribute accepts. Corresponds to the flags in the + // enum FormatFlags. + optional uint32 format_flags = 1; + + // The smallest integer allowed for this XML attribute. Only makes sense if the format includes + // FormatFlags::INTEGER. + optional int32 min_int = 2; + + // The largest integer allowed for this XML attribute. Only makes sense if the format includes + // FormatFlags::INTEGER. + optional int32 max_int = 3; + + // The set of enums/flags defined in this attribute. Only makes sense if the format includes + // either FormatFlags::ENUM or FormatFlags::FLAGS. Having both is an error. + repeated Symbol symbol = 4; +} + +// A value that represents a style. +message Style { + // An XML attribute/value pair defined in the style. + message Entry { + // Where the entry was defined. + optional Source source = 1; + + // Any comments associated with the entry. + optional string comment = 2; + + // A reference to the XML attribute. + optional Reference key = 3; + + // The Item defined for this XML attribute. + optional Item item = 4; + } + + // The optinal style from which this style inherits attributes. + optional Reference parent = 1; + + // The source file information of the parent inheritance declaration. + optional Source parent_source = 2; + + // The set of XML attribute/value pairs for this style. + repeated Entry entry = 3; +} + +// A value that represents a <declare-styleable> XML resource. These are not real resources and +// only end up as Java fields in the generated R.java. They do not end up in the binary ARSC file. +message Styleable { + // An attribute defined for this styleable. + message Entry { + // Where the attribute was defined within the <declare-styleable> block. + optional Source source = 1; + + // Any comments associated with the declaration. + optional string comment = 2; + + // The reference to the attribute. + optional Reference attr = 3; + } + + // The set of attribute declarations. + repeated Entry entry = 1; +} + +// A value that represents an array of resource values. +message Array { + // A single element of the array. + message Element { + // Where the element was defined. + optional Source source = 1; + + // Any comments associated with the element. + optional string comment = 2; + + // The value assigned to this element. + optional Item item = 3; + } + + // The list of array elements. + repeated Element element = 1; +} + +// A value that represents a string and its many variations based on plurality. +message Plural { + // The arity of the plural. + enum Arity { + ZERO = 0; + ONE = 1; + TWO = 2; + FEW = 3; + MANY = 4; + OTHER = 5; + } + + // The plural value for a given arity. + message Entry { + // Where the plural was defined. + optional Source source = 1; + + // Any comments associated with the plural. + optional string comment = 2; + + // The arity of the plural. + optional Arity arity = 3; + + // The value assigned to this plural. + optional Item item = 4; + } + + // The set of arity/plural mappings. + repeated Entry entry = 1; +} + +// Defines an abstract XmlNode that must be either an XmlElement, or +// a text node represented by a string. +message XmlNode { + // If set, this node is an element/tag. + optional XmlElement element = 1; + + // If set, this node is a chunk of text. + optional string text = 2; + + // Source line and column info. + optional SourcePosition source = 3; +} + +// An <element> in an XML document. +message XmlElement { + // Namespaces defined on this element. + repeated XmlNamespace namespace_declaration = 1; + + // The namespace URI of this element. + optional string namespace_uri = 2; + + // The name of this element. + optional string name = 3; + + // The attributes of this element. + repeated XmlAttribute attribute = 4; + + // The children of this element. + repeated XmlNode child = 5; +} + +// A namespace declaration on an XmlElement (xmlns:android="http://..."). +message XmlNamespace { + optional string prefix = 1; + optional string uri = 2; + + // Source line and column info. + optional SourcePosition source = 3; +} + +// An attribute defined on an XmlElement (android:text="..."). +message XmlAttribute { + optional string namespace_uri = 1; + optional string name = 2; + optional string value = 3; + + // Source line and column info. + optional SourcePosition source = 4; + + // The resource ID (0xPPTTEEEE) of the attribute. + optional uint32 resource_id = 5; + + // The interpreted/compiled version of the `value` string. + optional Item compiled_item = 6; +} diff --git a/tools/aapt2/ResourcesInternal.proto b/tools/aapt2/ResourcesInternal.proto new file mode 100644 index 000000000000..31179174b843 --- /dev/null +++ b/tools/aapt2/ResourcesInternal.proto @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +syntax = "proto2"; + +option java_package = "android.aapt.pb.internal"; +option optimize_for = LITE_RUNTIME; + +import "frameworks/base/tools/aapt2/Resources.proto"; + +package aapt.pb.internal; + +// The top level message representing an external resource file (layout XML, PNG, etc). +// This is used to represent a compiled file before it is linked. Only useful to aapt2. +message CompiledFile { + message Symbol { + // The name of the symbol (in the form package:type/name). + optional string resource_name = 1; + + // The position in the file at which this symbol is defined. For debug use. + optional aapt.pb.SourcePosition source = 2; + } + + // The name of the resource (in the form package:type/name). + optional string resource_name = 1; + + // The configuration for which the resource is defined. + optional aapt.pb.ConfigDescription config = 2; + + // The filesystem path to where the source file originated. + // Mainly used to display helpful error messages. + optional string source_path = 3; + + // Any symbols this file auto-generates/exports (eg. @+id/foo in an XML file). + repeated Symbol exported_symbol = 4; + + // If this is a compiled XML file, this is the root node. + optional aapt.pb.XmlNode xml_root = 5; +} diff --git a/tools/aapt2/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 e6bf3a6f9f56..d4ff6188d48d 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) { @@ -915,7 +920,7 @@ class LinkCommand { bool WriteJavaFile(ResourceTable* table, const StringPiece& package_name_to_generate, const StringPiece& out_package, const JavaClassGeneratorOptions& java_options, - const Maybe<std::string> out_text_symbols_path = {}) { + const Maybe<std::string>& out_text_symbols_path = {}) { if (!options_.generate_java_class_path) { return true; } @@ -1290,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 9d71775889d4..84b79274b9a6 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -18,6 +18,7 @@ #include <vector> #include "android-base/stringprintf.h" +#include "androidfw/ResourceTypes.h" #include "androidfw/StringPiece.h" #include "Diagnostics.h" @@ -33,15 +34,19 @@ #include "flatten/XmlFlattener.h" #include "io/BigBufferInputStream.h" #include "io/Util.h" +#include "optimize/MultiApkGenerator.h" #include "optimize/ResourceDeduper.h" #include "optimize/VersionCollapser.h" #include "split/TableSplitter.h" #include "util/Files.h" +#include "util/Util.h" using ::aapt::configuration::Abi; using ::aapt::configuration::Artifact; using ::aapt::configuration::PostProcessingConfiguration; +using ::android::ResTable_config; using ::android::StringPiece; +using ::android::base::StringAppendF; using ::android::base::StringPrintf; namespace aapt { @@ -188,42 +193,10 @@ class OptimizeCommand { } if (options_.configuration && options_.output_dir) { - PostProcessingConfiguration& config = options_.configuration.value(); - - // For now, just write out the stripped APK since ABI splitting doesn't modify anything else. - for (const Artifact& artifact : config.artifacts) { - if (artifact.abi_group) { - const std::string& group = artifact.abi_group.value(); - - auto abi_group = config.abi_groups.find(group); - // TODO: Remove validation when configuration parser ensures referential integrity. - if (abi_group == config.abi_groups.end()) { - context_->GetDiagnostics()->Note( - DiagMessage() << "could not find referenced ABI group '" << group << "'"); - return 1; - } - FilterChain filters; - filters.AddFilter(AbiFilter::FromAbiList(abi_group->second)); - - const std::string& path = apk->GetSource().path; - const StringPiece ext = file::GetExtension(path); - const std::string name = path.substr(0, path.rfind(ext.to_string())); - - // Name is hard coded for now since only one split dimension is supported. - // TODO: Incorporate name generation into the configuration objects. - const std::string file_name = - StringPrintf("%s.%s%s", name.c_str(), group.c_str(), ext.data()); - std::string out = options_.output_dir.value(); - file::AppendPath(&out, file_name); - - std::unique_ptr<IArchiveWriter> writer = - CreateZipFileArchiveWriter(context_->GetDiagnostics(), out); - - if (!apk->WriteToArchive(context_, options_.table_flattener_options, &filters, - writer.get())) { - return 1; - } - } + MultiApkGenerator generator{apk.get(), context_}; + if (!generator.FromBaseApk(options_.output_dir.value(), options_.configuration.value(), + options_.table_flattener_options)) { + return 1; } } @@ -260,7 +233,7 @@ class OptimizeCommand { for (auto& entry : type->entries) { for (auto& config_value : entry->values) { - FileReference* file_ref = ValueCast<FileReference>(config_value->value.get()); + auto* file_ref = ValueCast<FileReference>(config_value->value.get()); if (file_ref == nullptr) { continue; } @@ -297,11 +270,8 @@ class OptimizeCommand { } io::BigBufferInputStream table_buffer_in(&table_buffer); - if (!io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc", - ArchiveEntry::kAlign, writer)) { - return false; - } - return true; + return io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc", + ArchiveEntry::kAlign, writer); } OptimizeOptions options_; @@ -349,6 +319,7 @@ int Optimize(const std::vector<StringPiece>& args) { OptimizeOptions options; Maybe<std::string> config_path; Maybe<std::string> target_densities; + Maybe<std::string> target_abis; std::vector<std::string> configs; std::vector<std::string> split_args; bool verbose = false; @@ -363,6 +334,12 @@ int Optimize(const std::vector<StringPiece>& args) { "All the resources that would be unused on devices of the given densities will be \n" "removed from the APK.", &target_densities) + .OptionalFlag( + "--target-abis", + "Comma separated list of the CPU ABIs that the APK will be optimized for.\n" + "All the native libraries that would be unused on devices of the given ABIs will \n" + "be removed from the APK.", + &target_abis) .OptionalFlagList("-c", "Comma separated list of configurations to include. The default\n" "is all configurations.", @@ -388,7 +365,8 @@ int Optimize(const std::vector<StringPiece>& args) { return 1; } - std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[0]); + const std::string& apk_path = flags.GetArgs()[0]; + std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(&context, apk_path); if (!apk) { return 1; } @@ -418,8 +396,8 @@ int Optimize(const std::vector<StringPiece>& args) { // Parse the split parameters. for (const std::string& split_arg : split_args) { - options.split_paths.push_back({}); - options.split_constraints.push_back({}); + options.split_paths.emplace_back(); + options.split_constraints.emplace_back(); if (!ParseSplitParameter(split_arg, context.GetDiagnostics(), &options.split_paths.back(), &options.split_constraints.back())) { return 1; diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp index e1c45d68f611..d17858d45d08 100644 --- a/tools/aapt2/cmd/Util.cpp +++ b/tools/aapt2/cmd/Util.cpp @@ -28,7 +28,7 @@ #include "util/Maybe.h" #include "util/Util.h" -using android::StringPiece; +using ::android::StringPiece; namespace aapt { @@ -134,19 +134,21 @@ static xml::AaptAttribute CreateAttributeWithId(const ResourceId& id) { return xml::AaptAttribute(Attribute(), id); } +static xml::NamespaceDecl CreateAndroidNamespaceDecl() { + xml::NamespaceDecl decl; + decl.prefix = "android"; + decl.uri = xml::kSchemaAndroid; + return decl; +} + std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info, const SplitConstraints& constraints) { const ResourceId kVersionCode(0x0101021b); const ResourceId kRevisionCode(0x010104d5); const ResourceId kHasCode(0x0101000c); - std::unique_ptr<xml::XmlResource> doc = util::make_unique<xml::XmlResource>(); - - std::unique_ptr<xml::Namespace> namespace_android = util::make_unique<xml::Namespace>(); - namespace_android->namespace_uri = xml::kSchemaAndroid; - namespace_android->namespace_prefix = "android"; - std::unique_ptr<xml::Element> manifest_el = util::make_unique<xml::Element>(); + manifest_el->namespace_decls.push_back(CreateAndroidNamespaceDecl()); manifest_el->name = "manifest"; manifest_el->attributes.push_back(xml::Attribute{"", "package", app_info.package}); @@ -179,8 +181,8 @@ std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info, xml::Attribute{"", "configForSplit", app_info.split_name.value()}); } - // Splits may contain more configurations than originally desired (fallback densities, etc.). - // This makes programmatic discovery of split targetting difficult. Encode the original + // Splits may contain more configurations than originally desired (fall-back densities, etc.). + // This makes programmatic discovery of split targeting difficult. Encode the original // split constraints intended for this split. std::stringstream target_config_str; target_config_str << util::Joiner(constraints.configs, ","); @@ -193,8 +195,9 @@ std::unique_ptr<xml::XmlResource> GenerateSplitManifest(const AppInfo& app_info, util::make_unique<BinaryPrimitive>(android::Res_value::TYPE_INT_BOOLEAN, 0u)}); manifest_el->AppendChild(std::move(application_el)); - namespace_android->AppendChild(std::move(manifest_el)); - doc->root = std::move(namespace_android); + + std::unique_ptr<xml::XmlResource> doc = util::make_unique<xml::XmlResource>(); + doc->root = std::move(manifest_el); return doc; } @@ -284,7 +287,7 @@ static Maybe<int> ExtractSdkVersion(xml::Attribute* attr, std::string* out_error Maybe<AppInfo> ExtractAppInfoFromBinaryManifest(xml::XmlResource* xml_res, IDiagnostics* diag) { // Make sure the first element is <manifest> with package attribute. - xml::Element* manifest_el = xml::FindRootElement(xml_res->root.get()); + xml::Element* manifest_el = xml_res->root.get(); if (manifest_el == nullptr) { return {}; } diff --git a/tools/aapt2/compile/InlineXmlFormatParser.cpp b/tools/aapt2/compile/InlineXmlFormatParser.cpp index 786494b6ad1c..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/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp index d051120b9445..424e9be3ef09 100644 --- a/tools/aapt2/configuration/ConfigurationParser.cpp +++ b/tools/aapt2/configuration/ConfigurationParser.cpp @@ -22,13 +22,14 @@ #include <memory> #include <utility> -#include <android-base/file.h> -#include <android-base/logging.h> +#include "android-base/file.h" +#include "android-base/logging.h" #include "ConfigDescription.h" #include "Diagnostics.h" #include "io/File.h" #include "io/FileSystem.h" +#include "io/StringInputStream.h" #include "util/Maybe.h" #include "util/Util.h" #include "xml/XmlActionExecutor.h" @@ -49,14 +50,15 @@ using ::aapt::configuration::Group; using ::aapt::configuration::Locale; using ::aapt::io::IFile; using ::aapt::io::RegularFile; +using ::aapt::io::StringInputStream; using ::aapt::util::TrimWhitespace; using ::aapt::xml::Element; -using ::aapt::xml::FindRootElement; using ::aapt::xml::NodeCast; using ::aapt::xml::XmlActionExecutor; using ::aapt::xml::XmlActionExecutorPolicy; using ::aapt::xml::XmlNodeAction; using ::android::base::ReadFileToString; +using ::android::StringPiece; const std::unordered_map<std::string, Abi> kStringToAbiMap = { {"armeabi", Abi::kArmeV6}, {"armeabi-v7a", Abi::kArmV7a}, {"arm64-v8a", Abi::kArm64V8a}, @@ -115,53 +117,105 @@ const std::string& AbiToString(Abi abi) { * success, or false if the either the placeholder is not found in the name, or the value is not * present and the placeholder was. */ -static bool ReplacePlaceholder(const std::string& placeholder, const Maybe<std::string>& value, +static bool ReplacePlaceholder(const StringPiece& placeholder, const Maybe<StringPiece>& value, std::string* name, IDiagnostics* diag) { - size_t offset = name->find(placeholder); - if (value) { - if (offset == std::string::npos) { + size_t offset = name->find(placeholder.data()); + bool found = (offset != std::string::npos); + + // Make sure the placeholder was present if the desired value is present. + if (!found) { + if (value) { diag->Error(DiagMessage() << "Missing placeholder for artifact: " << placeholder); return false; } - name->replace(offset, placeholder.length(), value.value()); return true; } + DCHECK(found) << "Missing return path for placeholder not found"; + // Make sure the placeholder was not present if the desired value was not present. - bool result = (offset == std::string::npos); - if (!result) { + if (!value) { diag->Error(DiagMessage() << "Placeholder present but no value for artifact: " << placeholder); + return false; } - return result; + + name->replace(offset, placeholder.length(), value.value().data()); + + // Make sure there was only one instance of the placeholder. + if (name->find(placeholder.data()) != std::string::npos) { + diag->Error(DiagMessage() << "Placeholder present multiple times: " << placeholder); + return false; + } + return true; } -Maybe<std::string> Artifact::ToArtifactName(const std::string& format, IDiagnostics* diag) const { - std::string result = format; +Maybe<std::string> Artifact::ToArtifactName(const StringPiece& format, IDiagnostics* diag, + const StringPiece& base_name, + const StringPiece& ext) const { + std::string result = format.to_string(); + + Maybe<StringPiece> maybe_base_name = + base_name.empty() ? Maybe<StringPiece>{} : Maybe<StringPiece>{base_name}; + if (!ReplacePlaceholder("${basename}", maybe_base_name, &result, diag)) { + return {}; + } + + // Extension is optional. + if (result.find("${ext}") != std::string::npos) { + if (!ReplacePlaceholder("${ext}", {ext}, &result, diag)) { + return {}; + } + } + + if (!ReplacePlaceholder("${abi}", abi_group, &result, diag)) { + return {}; + } - if (!ReplacePlaceholder("{abi}", abi_group, &result, diag)) { + if (!ReplacePlaceholder("${density}", screen_density_group, &result, diag)) { return {}; } - if (!ReplacePlaceholder("{density}", screen_density_group, &result, diag)) { + if (!ReplacePlaceholder("${locale}", locale_group, &result, diag)) { return {}; } - if (!ReplacePlaceholder("{locale}", locale_group, &result, diag)) { + if (!ReplacePlaceholder("${sdk}", android_sdk_group, &result, diag)) { return {}; } - if (!ReplacePlaceholder("{sdk}", android_sdk_group, &result, diag)) { + if (!ReplacePlaceholder("${feature}", device_feature_group, &result, diag)) { return {}; } - if (!ReplacePlaceholder("{feature}", device_feature_group, &result, diag)) { + if (!ReplacePlaceholder("${gl}", gl_texture_group, &result, diag)) { return {}; } - if (!ReplacePlaceholder("{gl}", gl_texture_group, &result, diag)) { + return result; +} + +Maybe<std::string> Artifact::Name(const StringPiece& base_name, const StringPiece& ext, + IDiagnostics* diag) const { + if (!name) { return {}; } + std::string result = name.value(); + + // Base name is optional. + if (result.find("${basename}") != std::string::npos) { + if (!ReplacePlaceholder("${basename}", {base_name}, &result, diag)) { + return {}; + } + } + + // Extension is optional. + if (result.find("${ext}") != std::string::npos) { + if (!ReplacePlaceholder("${ext}", {ext}, &result, diag)) { + return {}; + } + } + return result; } @@ -182,15 +236,14 @@ ConfigurationParser::ConfigurationParser(std::string contents) } Maybe<PostProcessingConfiguration> ConfigurationParser::Parse() { - std::istringstream in(contents_); - - auto doc = xml::Inflate(&in, diag_, Source("config.xml")); + StringInputStream in(contents_); + std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, diag_, Source("config.xml")); if (!doc) { return {}; } // Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace. - auto* root = FindRootElement(doc.get()); + Element* root = doc->root.get(); if (root == nullptr) { diag_->Error(DiagMessage() << "Could not find the root element in the XML document"); return {}; @@ -333,7 +386,10 @@ ConfigurationParser::ActionHandler ConfigurationParser::screen_density_group_han if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { ConfigDescription config_descriptor; const android::StringPiece& text = TrimWhitespace(t->text); - if (ConfigDescription::Parse(text, &config_descriptor)) { + bool parsed = ConfigDescription::Parse(text, &config_descriptor); + if (parsed && + (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) == + android::ResTable_config::CONFIG_DENSITY)) { // Copy the density with the minimum SDK version stripped out. group.push_back(config_descriptor.CopyWithoutSdkVersion()); } else { @@ -366,17 +422,25 @@ ConfigurationParser::ActionHandler ConfigurationParser::locale_group_handler_ = << child->name); valid = false; } else { - Locale entry; - for (const auto& attr : child->attributes) { - if (attr.name == "lang") { - entry.lang = {attr.value}; - } else if (attr.name == "region") { - entry.region = {attr.value}; - } else { - diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value); + for (auto& node : child->children) { + xml::Text* t; + if ((t = NodeCast<xml::Text>(node.get())) != nullptr) { + ConfigDescription config_descriptor; + const android::StringPiece& text = TrimWhitespace(t->text); + bool parsed = ConfigDescription::Parse(text, &config_descriptor); + if (parsed && + (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) == + android::ResTable_config::CONFIG_LOCALE)) { + // Copy the locale with the minimum SDK version stripped out. + group.push_back(config_descriptor.CopyWithoutSdkVersion()); + } else { + diag->Error(DiagMessage() + << "Could not parse config descriptor for screen-density: " << text); + valid = false; + } + break; } } - group.push_back(entry); } } diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h index 28c355e39643..6259ce8e28ea 100644 --- a/tools/aapt2/configuration/ConfigurationParser.h +++ b/tools/aapt2/configuration/ConfigurationParser.h @@ -36,7 +36,7 @@ using Group = std::unordered_map<std::string, std::vector<T>>; /** Output artifact configuration options. */ struct Artifact { /** Name to use for output of processing foo.apk -> foo.<name>.apk. */ - std::string name; + Maybe<std::string> name; /** If present, uses the ABI group with this name. */ Maybe<std::string> abi_group; /** If present, uses the screen density group with this name. */ @@ -51,7 +51,13 @@ struct Artifact { Maybe<std::string> gl_texture_group; /** Convert an artifact name template into a name string based on configuration contents. */ - Maybe<std::string> ToArtifactName(const std::string& format, IDiagnostics* diag) const; + Maybe<std::string> ToArtifactName(const android::StringPiece& format, IDiagnostics* diag, + const android::StringPiece& base_name = "", + const android::StringPiece& ext = "apk") const; + + /** Convert an artifact name template into a name string based on configuration contents. */ + Maybe<std::string> Name(const android::StringPiece& base_name, const android::StringPiece& ext, + IDiagnostics* diag) const; }; /** Enumeration of currently supported ABIs. */ @@ -129,7 +135,7 @@ struct PostProcessingConfiguration { Group<Abi> abi_groups; Group<ConfigDescription> screen_density_groups; - Group<Locale> locale_groups; + Group<ConfigDescription> locale_groups; Group<AndroidSdk> android_sdk_groups; Group<DeviceFeature> device_feature_groups; Group<GlTexture> gl_texture_groups; diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp index fb71e98d2fb5..ece70a9e42ba 100644 --- a/tools/aapt2/configuration/ConfigurationParser_test.cpp +++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp @@ -18,9 +18,6 @@ #include <string> -#include <gmock/gmock.h> -#include <gtest/gtest.h> - #include "androidfw/ResourceTypes.h" #include "test/Test.h" @@ -29,7 +26,7 @@ namespace aapt { namespace { -using android::ResTable_config; +using ::android::ResTable_config; using configuration::Abi; using configuration::AndroidSdk; using configuration::Artifact; @@ -38,7 +35,7 @@ using configuration::DeviceFeature; using configuration::GlTexture; using configuration::Locale; using configuration::AndroidManifest; -using testing::ElementsAre; +using ::testing::ElementsAre; using xml::Element; using xml::NodeCast; @@ -67,18 +64,15 @@ constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?> <screen-density>xxxhdpi</screen-density> </screen-density-group> <locale-group label="europe"> - <locale lang="en"/> - <locale lang="es"/> - <locale lang="fr"/> - <locale lang="de"/> + <locale>en</locale> + <locale>es</locale> + <locale>fr</locale> + <locale>de</locale> </locale-group> <locale-group label="north-america"> - <locale lang="en"/> - <locale lang="es" region="MX"/> - <locale lang="fr" region="CA"/> - </locale-group> - <locale-group label="all"> - <locale/> + <locale>en</locale> + <locale>es-rMX</locale> + <locale>fr-rCA</locale> </locale-group> <android-sdk-group label="19"> <android-sdk @@ -156,10 +150,9 @@ TEST_F(ConfigurationParserTest, ValidateFile) { EXPECT_EQ(3ul, value.screen_density_groups["large"].size()); EXPECT_EQ(6ul, value.screen_density_groups["alldpi"].size()); - EXPECT_EQ(3ul, value.locale_groups.size()); + EXPECT_EQ(2ul, value.locale_groups.size()); EXPECT_EQ(4ul, value.locale_groups["europe"].size()); EXPECT_EQ(3ul, value.locale_groups["north-america"].size()); - EXPECT_EQ(1ul, value.locale_groups["all"].size()); EXPECT_EQ(1ul, value.android_sdk_groups.size()); EXPECT_EQ(1ul, value.android_sdk_groups["19"].size()); @@ -192,13 +185,13 @@ TEST_F(ConfigurationParserTest, ArtifactAction) { auto doc = test::BuildXmlDom(xml); PostProcessingConfiguration config; - bool ok = artifact_handler_(&config, NodeCast<Element>(doc.get()->root.get()), &diag_); + bool ok = artifact_handler_(&config, NodeCast<Element>(doc->root.get()), &diag_); ASSERT_TRUE(ok); EXPECT_EQ(1ul, config.artifacts.size()); auto& artifact = config.artifacts.front(); - EXPECT_EQ("", artifact.name); // TODO: make this fail. + EXPECT_FALSE(artifact.name); // TODO: make this fail. EXPECT_EQ("arm", artifact.abi_group.value()); EXPECT_EQ("large", artifact.screen_density_group.value()); EXPECT_EQ("europe", artifact.locale_group.value()); @@ -298,10 +291,10 @@ TEST_F(ConfigurationParserTest, ScreenDensityGroupAction) { TEST_F(ConfigurationParserTest, LocaleGroupAction) { static constexpr const char* xml = R"xml( <locale-group label="europe"> - <locale lang="en"/> - <locale lang="es"/> - <locale lang="fr"/> - <locale lang="de"/> + <locale>en</locale> + <locale>es</locale> + <locale>fr</locale> + <locale>de</locale> </locale-group>)xml"; auto doc = test::BuildXmlDom(xml); @@ -313,16 +306,12 @@ TEST_F(ConfigurationParserTest, LocaleGroupAction) { ASSERT_EQ(1ul, config.locale_groups.size()); ASSERT_EQ(1u, config.locale_groups.count("europe")); - auto& out = config.locale_groups["europe"]; + const auto& out = config.locale_groups["europe"]; - Locale en; - en.lang = std::string("en"); - Locale es; - es.lang = std::string("es"); - Locale fr; - fr.lang = std::string("fr"); - Locale de; - de.lang = std::string("de"); + ConfigDescription en = test::ParseConfigOrDie("en"); + ConfigDescription es = test::ParseConfigOrDie("es"); + ConfigDescription fr = test::ParseConfigOrDie("fr"); + ConfigDescription de = test::ParseConfigOrDie("de"); ASSERT_THAT(out, ElementsAre(en, es, fr, de)); } @@ -418,19 +407,21 @@ TEST_F(ConfigurationParserTest, DeviceFeatureGroupAction) { ASSERT_THAT(out, ElementsAre(low_latency, pro)); } +// Artifact name parser test cases. + TEST(ArtifactTest, Simple) { StdErrDiagnostics diag; Artifact x86; x86.abi_group = {"x86"}; - auto x86_result = x86.ToArtifactName("something.{abi}.apk", &diag); + auto x86_result = x86.ToArtifactName("something.${abi}.apk", &diag); ASSERT_TRUE(x86_result); EXPECT_EQ(x86_result.value(), "something.x86.apk"); Artifact arm; arm.abi_group = {"armeabi-v7a"}; - auto arm_result = arm.ToArtifactName("app.{abi}.apk", &diag); + auto arm_result = arm.ToArtifactName("app.${abi}.apk", &diag); ASSERT_TRUE(arm_result); EXPECT_EQ(arm_result.value(), "app.armeabi-v7a.apk"); } @@ -445,8 +436,8 @@ TEST(ArtifactTest, Complex) { artifact.locale_group = {"en-AU"}; artifact.android_sdk_group = {"26"}; - auto result = - artifact.ToArtifactName("app.{density}_{locale}_{feature}_{gl}.sdk{sdk}.{abi}.apk", &diag); + 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"); } @@ -456,7 +447,7 @@ TEST(ArtifactTest, Missing) { Artifact x86; x86.abi_group = {"x86"}; - EXPECT_FALSE(x86.ToArtifactName("something.{density}.apk", &diag)); + EXPECT_FALSE(x86.ToArtifactName("something.${density}.apk", &diag)); EXPECT_FALSE(x86.ToArtifactName("something.apk", &diag)); } @@ -464,9 +455,57 @@ TEST(ArtifactTest, Empty) { StdErrDiagnostics diag; Artifact artifact; - EXPECT_FALSE(artifact.ToArtifactName("something.{density}.apk", &diag)); + EXPECT_FALSE(artifact.ToArtifactName("something.${density}.apk", &diag)); EXPECT_TRUE(artifact.ToArtifactName("something.apk", &diag)); } +TEST(ArtifactTest, Repeated) { + StdErrDiagnostics diag; + Artifact artifact; + artifact.screen_density_group = {"mdpi"}; + + ASSERT_TRUE(artifact.ToArtifactName("something.${density}.apk", &diag)); + EXPECT_FALSE(artifact.ToArtifactName("something.${density}.${density}.apk", &diag)); +} + +TEST(ArtifactTest, Nesting) { + StdErrDiagnostics diag; + Artifact x86; + x86.abi_group = {"x86"}; + + EXPECT_FALSE(x86.ToArtifactName("something.${abi${density}}.apk", &diag)); + + const Maybe<std::string>& name = x86.ToArtifactName("something.${abi${abi}}.apk", &diag); + ASSERT_TRUE(name); + EXPECT_EQ(name.value(), "something.${abix86}.apk"); +} + +TEST(ArtifactTest, Recursive) { + StdErrDiagnostics diag; + Artifact artifact; + artifact.device_feature_group = {"${gl}"}; + artifact.gl_texture_group = {"glx1"}; + + EXPECT_FALSE(artifact.ToArtifactName("app.${feature}.${gl}.apk", &diag)); + + artifact.device_feature_group = {"df1"}; + artifact.gl_texture_group = {"${feature}"}; + { + const auto& result = artifact.ToArtifactName("app.${feature}.${gl}.apk", &diag); + ASSERT_TRUE(result); + EXPECT_EQ(result.value(), "app.df1.${feature}.apk"); + } + + // This is an invalid case, but should be the only possible case due to the ordering of + // replacement. + artifact.device_feature_group = {"${gl}"}; + artifact.gl_texture_group = {"glx1"}; + { + const auto& result = artifact.ToArtifactName("app.${feature}.apk", &diag); + ASSERT_TRUE(result); + EXPECT_EQ(result.value(), "app.glx1.apk"); + } +} + } // namespace } // namespace aapt diff --git a/tools/aapt2/filter/ConfigFilter.h b/tools/aapt2/filter/ConfigFilter.h index 3f1341684912..ebb81519dceb 100644 --- a/tools/aapt2/filter/ConfigFilter.h +++ b/tools/aapt2/filter/ConfigFilter.h @@ -38,13 +38,9 @@ class IConfigFilter { }; /** - * Implements config axis matching. An axis is one component of a configuration, - * like screen - * density or locale. If an axis is specified in the filter, and the axis is - * specified in - * the configuration to match, they must be compatible. Otherwise the - * configuration to match is - * accepted. + * Implements config axis matching. An axis is one component of a configuration, like screen density + * or locale. If an axis is specified in the filter, and the axis is specified in the configuration + * to match, they must be compatible. Otherwise the configuration to match is accepted. * * Used when handling "-c" options. */ diff --git a/tools/aapt2/filter/Filter_test.cpp b/tools/aapt2/filter/Filter_test.cpp index fb75a4b4d7c1..db2e69fc90d3 100644 --- a/tools/aapt2/filter/Filter_test.cpp +++ b/tools/aapt2/filter/Filter_test.cpp @@ -25,22 +25,16 @@ namespace aapt { namespace { -TEST(FilterChainTest, EmptyChain) { +TEST(FilterTest, FilterChain) { FilterChain chain; ASSERT_TRUE(chain.Keep("some/random/path")); -} -TEST(FilterChainTest, SingleFilter) { - FilterChain chain; chain.AddFilter(util::make_unique<PrefixFilter>("keep/")); ASSERT_FALSE(chain.Keep("removed/path")); ASSERT_TRUE(chain.Keep("keep/path/1")); ASSERT_TRUE(chain.Keep("keep/path/2")); -} -TEST(FilterChainTest, MultipleFilters) { - FilterChain chain; chain.AddFilter(util::make_unique<PrefixFilter>("keep/")); chain.AddFilter(util::make_unique<PrefixFilter>("keep/really/")); diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp index 826f91b4a2fd..5f8bd063f9b0 100644 --- a/tools/aapt2/flatten/Archive.cpp +++ b/tools/aapt2/flatten/Archive.cpp @@ -23,12 +23,14 @@ #include "android-base/errors.h" #include "android-base/macros.h" +#include "android-base/utf8.h" #include "androidfw/StringPiece.h" #include "ziparchive/zip_writer.h" #include "util/Files.h" -using android::StringPiece; +using ::android::StringPiece; +using ::android::base::SystemErrorCodeToString; namespace aapt { @@ -58,11 +60,11 @@ class DirectoryWriter : public IArchiveWriter { std::string full_path = dir_; file::AppendPath(&full_path, path); - file::mkdirs(file::GetStem(full_path)); + file::mkdirs(file::GetStem(full_path).to_string()); - file_ = {fopen(full_path.data(), "wb"), fclose}; + file_ = {::android::base::utf8::fopen(full_path.c_str(), "wb"), fclose}; if (!file_) { - error_ = android::base::SystemErrorCodeToString(errno); + error_ = SystemErrorCodeToString(errno); return false; } return true; @@ -74,7 +76,7 @@ class DirectoryWriter : public IArchiveWriter { } if (fwrite(data, 1, len, file_.get()) != static_cast<size_t>(len)) { - error_ = android::base::SystemErrorCodeToString(errno); + error_ = SystemErrorCodeToString(errno); file_.reset(nullptr); return false; } @@ -121,9 +123,9 @@ class ZipFileWriter : public IArchiveWriter { ZipFileWriter() = default; bool Open(const StringPiece& path) { - file_ = {fopen(path.data(), "w+b"), fclose}; + file_ = {::android::base::utf8::fopen(path.to_string().c_str(), "w+b"), fclose}; if (!file_) { - error_ = android::base::SystemErrorCodeToString(errno); + error_ = SystemErrorCodeToString(errno); return false; } writer_ = util::make_unique<ZipWriter>(file_.get()); diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp index e5993a65366d..14b776b1bd99 100644 --- a/tools/aapt2/flatten/TableFlattener.cpp +++ b/tools/aapt2/flatten/TableFlattener.cpp @@ -133,7 +133,7 @@ class MapFlattenVisitor : public RawValueVisitor { } void Visit(Array* array) override { - for (auto& item : array->items) { + for (auto& item : array->elements) { ResTable_map* out_entry = buffer_->NextBlock<ResTable_map>(); FlattenValue(item.get(), out_entry); out_entry->value.size = util::HostToDevice16(sizeof(out_entry->value)); diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp index 331ef784a7da..b3b308a29fc5 100644 --- a/tools/aapt2/flatten/XmlFlattener.cpp +++ b/tools/aapt2/flatten/XmlFlattener.cpp @@ -38,12 +38,10 @@ namespace { constexpr uint32_t kLowPriority = 0xffffffffu; -static bool cmp_xml_attribute_by_id(const xml::Attribute* a, - const xml::Attribute* b) { +static bool cmp_xml_attribute_by_id(const xml::Attribute* a, const xml::Attribute* b) { if (a->compiled_attribute && a->compiled_attribute.value().id) { if (b->compiled_attribute && b->compiled_attribute.value().id) { - return a->compiled_attribute.value().id.value() < - b->compiled_attribute.value().id.value(); + return a->compiled_attribute.value().id.value() < b->compiled_attribute.value().id.value(); } return true; } else if (!b->compiled_attribute) { @@ -75,17 +73,6 @@ class XmlFlattenerVisitor : public xml::Visitor { XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options) : buffer_(buffer), options_(options) {} - void Visit(xml::Namespace* node) override { - if (node->namespace_uri == xml::kSchemaTools) { - // Skip dedicated tools namespace. - xml::Visitor::Visit(node); - } else { - WriteNamespace(node, android::RES_XML_START_NAMESPACE_TYPE); - xml::Visitor::Visit(node); - WriteNamespace(node, android::RES_XML_END_NAMESPACE_TYPE); - } - } - void Visit(xml::Text* node) override { if (util::TrimWhitespace(node->text).empty()) { // Skip whitespace only text nodes. @@ -93,8 +80,7 @@ class XmlFlattenerVisitor : public xml::Visitor { } ChunkWriter writer(buffer_); - ResXMLTree_node* flat_node = - writer.StartChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE); + ResXMLTree_node* flat_node = writer.StartChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE); flat_node->lineNumber = util::HostToDevice32(node->line_number); flat_node->comment.index = util::HostToDevice32(-1); @@ -109,6 +95,13 @@ class XmlFlattenerVisitor : public xml::Visitor { } void Visit(xml::Element* node) override { + for (const xml::NamespaceDecl& decl : node->namespace_decls) { + // Skip dedicated tools namespace. + if (decl.uri != xml::kSchemaTools) { + WriteNamespace(decl, android::RES_XML_START_NAMESPACE_TYPE); + } + } + { ChunkWriter start_writer(buffer_); ResXMLTree_node* flat_node = @@ -116,19 +109,15 @@ class XmlFlattenerVisitor : public xml::Visitor { flat_node->lineNumber = util::HostToDevice32(node->line_number); flat_node->comment.index = util::HostToDevice32(-1); - ResXMLTree_attrExt* flat_elem = - start_writer.NextBlock<ResXMLTree_attrExt>(); + ResXMLTree_attrExt* flat_elem = start_writer.NextBlock<ResXMLTree_attrExt>(); - // A missing namespace must be null, not an empty string. Otherwise the - // runtime complains. + // A missing namespace must be null, not an empty string. Otherwise the runtime complains. AddString(node->namespace_uri, kLowPriority, &flat_elem->ns, true /* treat_empty_string_as_null */); - AddString(node->name, kLowPriority, &flat_elem->name, - true /* treat_empty_string_as_null */); + AddString(node->name, kLowPriority, &flat_elem->name, true /* treat_empty_string_as_null */); flat_elem->attributeStart = util::HostToDevice16(sizeof(*flat_elem)); - flat_elem->attributeSize = - util::HostToDevice16(sizeof(ResXMLTree_attribute)); + flat_elem->attributeSize = util::HostToDevice16(sizeof(ResXMLTree_attribute)); WriteAttributes(node, flat_elem, &start_writer); @@ -144,14 +133,20 @@ class XmlFlattenerVisitor : public xml::Visitor { flat_end_node->lineNumber = util::HostToDevice32(node->line_number); flat_end_node->comment.index = util::HostToDevice32(-1); - ResXMLTree_endElementExt* flat_end_elem = - end_writer.NextBlock<ResXMLTree_endElementExt>(); + ResXMLTree_endElementExt* flat_end_elem = end_writer.NextBlock<ResXMLTree_endElementExt>(); AddString(node->namespace_uri, kLowPriority, &flat_end_elem->ns, true /* treat_empty_string_as_null */); AddString(node->name, kLowPriority, &flat_end_elem->name); end_writer.Finish(); } + + for (auto iter = node->namespace_decls.rbegin(); iter != node->namespace_decls.rend(); ++iter) { + // Skip dedicated tools namespace. + if (iter->uri != xml::kSchemaTools) { + WriteNamespace(*iter, android::RES_XML_END_NAMESPACE_TYPE); + } + } } private: @@ -173,16 +168,16 @@ class XmlFlattenerVisitor : public xml::Visitor { string_refs.push_back(StringFlattenDest{ref, dest}); } - void WriteNamespace(xml::Namespace* node, uint16_t type) { + void WriteNamespace(const xml::NamespaceDecl& decl, uint16_t type) { ChunkWriter writer(buffer_); ResXMLTree_node* flatNode = writer.StartChunk<ResXMLTree_node>(type); - flatNode->lineNumber = util::HostToDevice32(node->line_number); + flatNode->lineNumber = util::HostToDevice32(decl.line_number); flatNode->comment.index = util::HostToDevice32(-1); ResXMLTree_namespaceExt* flat_ns = writer.NextBlock<ResXMLTree_namespaceExt>(); - AddString(node->namespace_prefix, kLowPriority, &flat_ns->prefix); - AddString(node->namespace_uri, kLowPriority, &flat_ns->uri); + AddString(decl.prefix, kLowPriority, &flat_ns->prefix); + AddString(decl.uri, kLowPriority, &flat_ns->uri); writer.Finish(); } diff --git a/tools/aapt2/integration-tests/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/io/FileInputStream.cpp b/tools/aapt2/io/FileInputStream.cpp new file mode 100644 index 000000000000..07dbb5a98add --- /dev/null +++ b/tools/aapt2/io/FileInputStream.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "io/FileInputStream.h" + +#include <errno.h> // for errno +#include <fcntl.h> // for O_RDONLY +#include <unistd.h> // for read + +#include "android-base/errors.h" +#include "android-base/file.h" // for O_BINARY +#include "android-base/macros.h" +#include "android-base/utf8.h" + +using ::android::base::SystemErrorCodeToString; + +namespace aapt { +namespace io { + +FileInputStream::FileInputStream(const std::string& path, size_t buffer_capacity) + : FileInputStream(::android::base::utf8::open(path.c_str(), O_RDONLY | O_BINARY), + buffer_capacity) { +} + +FileInputStream::FileInputStream(int fd, size_t buffer_capacity) + : fd_(fd), + buffer_capacity_(buffer_capacity), + buffer_offset_(0u), + buffer_size_(0u), + total_byte_count_(0u) { + if (fd_ == -1) { + error_ = SystemErrorCodeToString(errno); + } else { + buffer_.reset(new uint8_t[buffer_capacity_]); + } +} + +bool FileInputStream::Next(const void** data, size_t* size) { + if (HadError()) { + return false; + } + + // Deal with any remaining bytes after BackUp was called. + if (buffer_offset_ != buffer_size_) { + *data = buffer_.get() + buffer_offset_; + *size = buffer_size_ - buffer_offset_; + total_byte_count_ += buffer_size_ - buffer_offset_; + buffer_offset_ = buffer_size_; + return true; + } + + ssize_t n = TEMP_FAILURE_RETRY(read(fd_, buffer_.get(), buffer_capacity_)); + if (n < 0) { + error_ = SystemErrorCodeToString(errno); + fd_.reset(); + return false; + } + + buffer_size_ = static_cast<size_t>(n); + buffer_offset_ = buffer_size_; + total_byte_count_ += buffer_size_; + + *data = buffer_.get(); + *size = buffer_size_; + return buffer_size_ != 0u; +} + +void FileInputStream::BackUp(size_t count) { + if (count > buffer_offset_) { + count = buffer_offset_; + } + buffer_offset_ -= count; + total_byte_count_ -= count; +} + +size_t FileInputStream::ByteCount() const { + return total_byte_count_; +} + +bool FileInputStream::HadError() const { + return !error_.empty(); +} + +std::string FileInputStream::GetError() const { + return error_; +} + +} // namespace io +} // namespace aapt diff --git a/tools/aapt2/io/FileInputStream.h b/tools/aapt2/io/FileInputStream.h new file mode 100644 index 000000000000..6beb9a186ce5 --- /dev/null +++ b/tools/aapt2/io/FileInputStream.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_IO_FILEINPUTSTREAM_H +#define AAPT_IO_FILEINPUTSTREAM_H + +#include "io/Io.h" + +#include <memory> +#include <string> + +#include "android-base/macros.h" +#include "android-base/unique_fd.h" + +namespace aapt { +namespace io { + +class FileInputStream : public InputStream { + public: + explicit FileInputStream(const std::string& path, size_t buffer_capacity = 4096); + + // Takes ownership of `fd`. + explicit FileInputStream(int fd, size_t buffer_capacity = 4096); + + bool Next(const void** data, size_t* size) override; + + void BackUp(size_t count) override; + + size_t ByteCount() const override; + + bool HadError() const override; + + std::string GetError() const override; + + private: + DISALLOW_COPY_AND_ASSIGN(FileInputStream); + + android::base::unique_fd fd_; + std::string error_; + std::unique_ptr<uint8_t[]> buffer_; + size_t buffer_capacity_; + size_t buffer_offset_; + size_t buffer_size_; + size_t total_byte_count_; +}; + +} // namespace io +} // namespace aapt + +#endif // AAPT_IO_FILEINPUTSTREAM_H diff --git a/tools/aapt2/io/FileInputStream_test.cpp b/tools/aapt2/io/FileInputStream_test.cpp new file mode 100644 index 000000000000..7314ab7beeba --- /dev/null +++ b/tools/aapt2/io/FileInputStream_test.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "io/FileInputStream.h" + +#include "android-base/macros.h" +#include "android-base/test_utils.h" + +#include "test/Test.h" + +using ::android::StringPiece; +using ::testing::Eq; +using ::testing::NotNull; +using ::testing::StrEq; + +namespace aapt { +namespace io { + +TEST(FileInputStreamTest, NextAndBackup) { + std::string input = "this is a cool string"; + TemporaryFile file; + ASSERT_THAT(TEMP_FAILURE_RETRY(write(file.fd, input.c_str(), input.size())), Eq(21)); + lseek64(file.fd, 0, SEEK_SET); + + // Use a small buffer size so that we can call Next() a few times. + FileInputStream in(file.fd, 10u); + ASSERT_FALSE(in.HadError()); + EXPECT_THAT(in.ByteCount(), Eq(0u)); + + const char* buffer; + size_t size; + ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)) << in.GetError(); + ASSERT_THAT(size, Eq(10u)); + ASSERT_THAT(buffer, NotNull()); + EXPECT_THAT(in.ByteCount(), Eq(10u)); + EXPECT_THAT(StringPiece(buffer, size), Eq("this is a ")); + + ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); + ASSERT_THAT(size, Eq(10u)); + ASSERT_THAT(buffer, NotNull()); + EXPECT_THAT(in.ByteCount(), Eq(20u)); + EXPECT_THAT(StringPiece(buffer, size), Eq("cool strin")); + + in.BackUp(5u); + EXPECT_THAT(in.ByteCount(), Eq(15u)); + + ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); + ASSERT_THAT(size, Eq(5u)); + ASSERT_THAT(buffer, NotNull()); + ASSERT_THAT(in.ByteCount(), Eq(20u)); + EXPECT_THAT(StringPiece(buffer, size), Eq("strin")); + + // Backup 1 more than possible. Should clamp. + in.BackUp(11u); + EXPECT_THAT(in.ByteCount(), Eq(10u)); + + ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); + ASSERT_THAT(size, Eq(10u)); + ASSERT_THAT(buffer, NotNull()); + ASSERT_THAT(in.ByteCount(), Eq(20u)); + EXPECT_THAT(StringPiece(buffer, size), Eq("cool strin")); + + ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); + ASSERT_THAT(size, Eq(1u)); + ASSERT_THAT(buffer, NotNull()); + ASSERT_THAT(in.ByteCount(), Eq(21u)); + EXPECT_THAT(StringPiece(buffer, size), Eq("g")); + + EXPECT_FALSE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); + EXPECT_FALSE(in.HadError()); +} + +} // namespace io +} // namespace aapt diff --git a/tools/aapt2/io/StringInputStream.cpp b/tools/aapt2/io/StringInputStream.cpp new file mode 100644 index 000000000000..51a18a7d8a9f --- /dev/null +++ b/tools/aapt2/io/StringInputStream.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "io/StringInputStream.h" + +using ::android::StringPiece; + +namespace aapt { +namespace io { + +StringInputStream::StringInputStream(const StringPiece& str) : str_(str), offset_(0u) { +} + +bool StringInputStream::Next(const void** data, size_t* size) { + if (offset_ == str_.size()) { + return false; + } + + *data = str_.data() + offset_; + *size = str_.size() - offset_; + offset_ = str_.size(); + return true; +} + +void StringInputStream::BackUp(size_t count) { + if (count > offset_) { + count = offset_; + } + offset_ -= count; +} + +size_t StringInputStream::ByteCount() const { + return offset_; +} + +} // namespace io +} // namespace aapt diff --git a/tools/aapt2/io/StringInputStream.h b/tools/aapt2/io/StringInputStream.h new file mode 100644 index 000000000000..ff5b112ef274 --- /dev/null +++ b/tools/aapt2/io/StringInputStream.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT_IO_STRINGINPUTSTREAM_H +#define AAPT_IO_STRINGINPUTSTREAM_H + +#include "io/Io.h" + +#include "android-base/macros.h" +#include "androidfw/StringPiece.h" + +namespace aapt { +namespace io { + +class StringInputStream : public InputStream { + public: + explicit StringInputStream(const android::StringPiece& str); + + bool Next(const void** data, size_t* size) override; + + void BackUp(size_t count) override; + + size_t ByteCount() const override; + + inline bool HadError() const override { + return false; + } + + inline std::string GetError() const override { + return {}; + } + + private: + DISALLOW_COPY_AND_ASSIGN(StringInputStream); + + android::StringPiece str_; + size_t offset_; +}; + +} // namespace io +} // namespace aapt + +#endif // AAPT_IO_STRINGINPUTSTREAM_H diff --git a/tools/aapt2/io/StringInputStream_test.cpp b/tools/aapt2/io/StringInputStream_test.cpp new file mode 100644 index 000000000000..cc57bc498313 --- /dev/null +++ b/tools/aapt2/io/StringInputStream_test.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "io/StringInputStream.h" + +#include "test/Test.h" + +using ::android::StringPiece; +using ::testing::Eq; +using ::testing::NotNull; +using ::testing::StrEq; + +namespace aapt { +namespace io { + +TEST(StringInputStreamTest, OneCallToNextShouldReturnEntireBuffer) { + constexpr const size_t kCount = 1000; + std::string input; + input.resize(kCount, 0x7f); + input[0] = 0x00; + input[kCount - 1] = 0xff; + StringInputStream in(input); + + const char* buffer; + size_t size; + ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); + ASSERT_THAT(size, Eq(kCount)); + ASSERT_THAT(buffer, NotNull()); + + EXPECT_THAT(buffer[0], Eq(0x00)); + EXPECT_THAT(buffer[kCount - 1], Eq('\xff')); + + EXPECT_FALSE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); + EXPECT_FALSE(in.HadError()); +} + +TEST(StringInputStreamTest, BackUp) { + std::string input = "hello this is a string"; + StringInputStream in(input); + + const char* buffer; + size_t size; + ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); + ASSERT_THAT(size, Eq(input.size())); + ASSERT_THAT(buffer, NotNull()); + EXPECT_THAT(in.ByteCount(), Eq(input.size())); + + in.BackUp(6u); + EXPECT_THAT(in.ByteCount(), Eq(input.size() - 6u)); + + ASSERT_TRUE(in.Next(reinterpret_cast<const void**>(&buffer), &size)); + ASSERT_THAT(size, Eq(6u)); + ASSERT_THAT(buffer, NotNull()); + ASSERT_THAT(buffer, StrEq("string")); + EXPECT_THAT(in.ByteCount(), Eq(input.size())); +} + +} // namespace io +} // namespace aapt diff --git a/tools/aapt2/java/AnnotationProcessor.cpp b/tools/aapt2/java/AnnotationProcessor.cpp index 1f83fa098d74..c93461a66899 100644 --- a/tools/aapt2/java/AnnotationProcessor.cpp +++ b/tools/aapt2/java/AnnotationProcessor.cpp @@ -17,6 +17,7 @@ #include "java/AnnotationProcessor.h" #include <algorithm> +#include <array> #include "text/Unicode.h" #include "text/Utf8Iterator.h" @@ -41,30 +42,54 @@ StringPiece AnnotationProcessor::ExtractFirstSentence(const StringPiece& comment return comment; } -void AnnotationProcessor::AppendCommentLine(std::string& comment) { +struct AnnotationRule { + enum : uint32_t { + kDeprecated = 0x01, + kSystemApi = 0x02, + kTestApi = 0x04, + }; + + StringPiece doc_str; + uint32_t bit_mask; + StringPiece annotation; +}; + +static std::array<AnnotationRule, 2> sAnnotationRules = {{ + {"@SystemApi", AnnotationRule::kSystemApi, "@android.annotation.SystemApi"}, + {"@TestApi", AnnotationRule::kTestApi, "@android.annotation.TestApi"}, +}}; + +void AnnotationProcessor::AppendCommentLine(std::string comment) { static const std::string sDeprecated = "@deprecated"; - static const std::string sSystemApi = "@SystemApi"; + // Treat deprecated specially, since we don't remove it from the source comment. if (comment.find(sDeprecated) != std::string::npos) { - annotation_bit_mask_ |= kDeprecated; + annotation_bit_mask_ |= AnnotationRule::kDeprecated; } - std::string::size_type idx = comment.find(sSystemApi); - if (idx != std::string::npos) { - annotation_bit_mask_ |= kSystemApi; - comment.erase(comment.begin() + idx, - comment.begin() + idx + sSystemApi.size()); + for (const AnnotationRule& rule : sAnnotationRules) { + std::string::size_type idx = comment.find(rule.doc_str.data()); + if (idx != std::string::npos) { + annotation_bit_mask_ |= rule.bit_mask; + comment.erase(comment.begin() + idx, comment.begin() + idx + rule.doc_str.size()); + } } - if (util::TrimWhitespace(comment).empty()) { + // Check if after removal of annotations the line is empty. + const StringPiece trimmed = util::TrimWhitespace(comment); + if (trimmed.empty()) { return; } + // If there was trimming to do, copy the string. + if (trimmed.size() != comment.size()) { + comment = trimmed.to_string(); + } + if (!has_comments_) { has_comments_ = true; comment_ << "/**"; } - comment_ << "\n * " << std::move(comment); } @@ -73,16 +98,18 @@ void AnnotationProcessor::AppendComment(const StringPiece& comment) { for (StringPiece line : util::Tokenize(comment, '\n')) { line = util::TrimWhitespace(line); if (!line.empty()) { - std::string lineCopy = line.to_string(); - AppendCommentLine(lineCopy); + AppendCommentLine(line.to_string()); } } } -void AnnotationProcessor::AppendNewLine() { comment_ << "\n *"; } +void AnnotationProcessor::AppendNewLine() { + if (has_comments_) { + comment_ << "\n *"; + } +} -void AnnotationProcessor::WriteToStream(std::ostream* out, - const StringPiece& prefix) const { +void AnnotationProcessor::WriteToStream(const StringPiece& prefix, std::ostream* out) const { if (has_comments_) { std::string result = comment_.str(); for (StringPiece line : util::Tokenize(result, '\n')) { @@ -92,12 +119,14 @@ void AnnotationProcessor::WriteToStream(std::ostream* out, << "\n"; } - if (annotation_bit_mask_ & kDeprecated) { + if (annotation_bit_mask_ & AnnotationRule::kDeprecated) { *out << prefix << "@Deprecated\n"; } - if (annotation_bit_mask_ & kSystemApi) { - *out << prefix << "@android.annotation.SystemApi\n"; + for (const AnnotationRule& rule : sAnnotationRules) { + if (annotation_bit_mask_ & rule.bit_mask) { + *out << prefix << rule.annotation << "\n"; + } } } diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h index a06eda0f9c5c..a7bf73f50de5 100644 --- a/tools/aapt2/java/AnnotationProcessor.h +++ b/tools/aapt2/java/AnnotationProcessor.h @@ -24,64 +24,53 @@ namespace aapt { -/** - * Builds a JavaDoc comment from a set of XML comments. - * This will also look for instances of @SystemApi and convert them to - * actual Java annotations. - * - * Example: - * - * Input XML: - * - * <!-- This is meant to be hidden because - * It is system api. Also it is @deprecated - * @SystemApi - * --> - * - * Output JavaDoc: - * - * /\* - * * This is meant to be hidden because - * * It is system api. Also it is @deprecated - * *\/ - * - * Output Annotations: - * - * @Deprecated - * @android.annotation.SystemApi - * - */ +// Builds a JavaDoc comment from a set of XML comments. +// This will also look for instances of @SystemApi and convert them to +// actual Java annotations. +// +// Example: +// +// Input XML: +// +// <!-- This is meant to be hidden because +// It is system api. Also it is @deprecated +// @SystemApi +// --> +// +// Output JavaDoc: +// +// /** +// * This is meant to be hidden because +// * It is system api. Also it is @deprecated +// */ +// +// Output Annotations: +// +// @Deprecated +// @android.annotation.SystemApi class AnnotationProcessor { public: + // Extracts the first sentence of a comment. The algorithm selects the substring starting from + // the beginning of the string, and ending at the first '.' character that is followed by a + // whitespace character. If these requirements are not met, the whole string is returned. static android::StringPiece ExtractFirstSentence(const android::StringPiece& comment); - /** - * Adds more comments. Since resources can have various values with different - * configurations, - * we need to collect all the comments. - */ + // Adds more comments. Resources can have value definitions for various configurations, and + // each of the definitions may have comments that need to be processed. void AppendComment(const android::StringPiece& comment); void AppendNewLine(); - /** - * Writes the comments and annotations to the stream, with the given prefix - * before each line. - */ - void WriteToStream(std::ostream* out, const android::StringPiece& prefix) const; + // Writes the comments and annotations to the stream, with the given prefix before each line. + void WriteToStream(const android::StringPiece& prefix, std::ostream* out) const; private: - enum : uint32_t { - kDeprecated = 0x01, - kSystemApi = 0x02, - }; - std::stringstream comment_; std::stringstream mAnnotations; bool has_comments_ = false; uint32_t annotation_bit_mask_ = 0; - void AppendCommentLine(std::string& line); + void AppendCommentLine(std::string line); }; } // namespace aapt diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp index 9ccac8888426..856f4ccbd7f0 100644 --- a/tools/aapt2/java/AnnotationProcessor_test.cpp +++ b/tools/aapt2/java/AnnotationProcessor_test.cpp @@ -34,7 +34,7 @@ TEST(AnnotationProcessorTest, EmitsDeprecated) { processor.AppendComment(comment); std::stringstream result; - processor.WriteToStream(&result, ""); + processor.WriteToStream("", &result); std::string annotations = result.str(); EXPECT_THAT(annotations, HasSubstr("@Deprecated")); @@ -45,7 +45,7 @@ TEST(AnnotationProcessorTest, EmitsSystemApiAnnotationAndRemovesFromComment) { processor.AppendComment("@SystemApi This is a system API"); std::stringstream result; - processor.WriteToStream(&result, ""); + processor.WriteToStream("", &result); std::string annotations = result.str(); EXPECT_THAT(annotations, HasSubstr("@android.annotation.SystemApi")); @@ -53,6 +53,19 @@ TEST(AnnotationProcessorTest, EmitsSystemApiAnnotationAndRemovesFromComment) { EXPECT_THAT(annotations, HasSubstr("This is a system API")); } +TEST(AnnotationProcessorTest, EmitsTestApiAnnotationAndRemovesFromComment) { + AnnotationProcessor processor; + processor.AppendComment("@TestApi This is a test API"); + + std::stringstream result; + processor.WriteToStream("", &result); + std::string annotations = result.str(); + + EXPECT_THAT(annotations, HasSubstr("@android.annotation.TestApi")); + EXPECT_THAT(annotations, Not(HasSubstr("@TestApi"))); + EXPECT_THAT(annotations, HasSubstr("This is a test API")); +} + TEST(AnnotationProcessor, ExtractsFirstSentence) { EXPECT_THAT(AnnotationProcessor::ExtractFirstSentence("This is the only sentence"), Eq("This is the only sentence")); diff --git a/tools/aapt2/java/ClassDefinition.cpp b/tools/aapt2/java/ClassDefinition.cpp index 0cec9ae407f5..45130a4d7681 100644 --- a/tools/aapt2/java/ClassDefinition.cpp +++ b/tools/aapt2/java/ClassDefinition.cpp @@ -18,12 +18,12 @@ #include "androidfw/StringPiece.h" -using android::StringPiece; +using ::android::StringPiece; namespace aapt { void ClassMember::WriteToStream(const StringPiece& prefix, bool final, std::ostream* out) const { - processor_.WriteToStream(out, prefix); + processor_.WriteToStream(prefix, out); } void MethodDefinition::AppendStatement(const StringPiece& statement) { @@ -81,9 +81,8 @@ constexpr static const char* sWarningHeader = " * should not be modified by hand.\n" " */\n\n"; -bool ClassDefinition::WriteJavaFile(const ClassDefinition* def, - const StringPiece& package, bool final, - std::ostream* out) { +bool ClassDefinition::WriteJavaFile(const ClassDefinition* def, const StringPiece& package, + bool final, std::ostream* out) { *out << sWarningHeader << "package " << package << ";\n\n"; def->WriteToStream("", final, out); return bool(*out); diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp index 271279ff5e92..4f449f0db41a 100644 --- a/tools/aapt2/java/JavaClassGenerator_test.cpp +++ b/tools/aapt2/java/JavaClassGenerator_test.cpp @@ -22,7 +22,9 @@ #include "test/Test.h" #include "util/Util.h" -using android::StringPiece; +using ::android::StringPiece; +using ::testing::HasSubstr; +using ::testing::Not; namespace aapt { @@ -52,17 +54,15 @@ TEST(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) { .AddSimple("android:id/hey-man", ResourceId(0x01020000)) .AddValue("android:attr/cool.attr", ResourceId(0x01010000), test::AttributeBuilder(false).Build()) - .AddValue( - "android:styleable/hey.dude", ResourceId(0x01030000), - test::StyleableBuilder() - .AddItem("android:attr/cool.attr", ResourceId(0x01010000)) - .Build()) + .AddValue("android:styleable/hey.dude", ResourceId(0x01030000), + test::StyleableBuilder() + .AddItem("android:attr/cool.attr", ResourceId(0x01010000)) + .Build()) .Build(); std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGenerator generator(context.get(), table.get(), {}); @@ -72,14 +72,9 @@ TEST(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) { std::string output = out.str(); - EXPECT_NE(std::string::npos, - output.find("public static final int hey_man=0x01020000;")); - - EXPECT_NE(std::string::npos, - output.find("public static final int[] hey_dude={")); - - EXPECT_NE(std::string::npos, - output.find("public static final int hey_dude_cool_attr=0;")); + EXPECT_THAT(output, HasSubstr("public static final int hey_man=0x01020000;")); + EXPECT_THAT(output, HasSubstr("public static final int[] hey_dude={")); + EXPECT_THAT(output, HasSubstr("public static final int hey_dude_cool_attr=0;")); } TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) { @@ -92,8 +87,7 @@ TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGenerator generator(context.get(), table.get(), {}); @@ -101,11 +95,10 @@ TEST(JavaClassGeneratorTest, CorrectPackageNameIsUsed) { ASSERT_TRUE(generator.Generate("android", "com.android.internal", &out)); std::string output = out.str(); - EXPECT_NE(std::string::npos, output.find("package com.android.internal;")); - EXPECT_NE(std::string::npos, - output.find("public static final int one=0x01020000;")); - EXPECT_EQ(std::string::npos, output.find("two")); - EXPECT_EQ(std::string::npos, output.find("com_foo$two")); + EXPECT_THAT(output, HasSubstr("package com.android.internal;")); + EXPECT_THAT(output, HasSubstr("public static final int one=0x01020000;")); + EXPECT_THAT(output, Not(HasSubstr("two"))); + EXPECT_THAT(output, Not(HasSubstr("com_foo$two"))); } TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) { @@ -118,8 +111,7 @@ TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGenerator generator(context.get(), table.get(), {}); @@ -127,9 +119,8 @@ TEST(JavaClassGeneratorTest, AttrPrivateIsWrittenAsAttr) { ASSERT_TRUE(generator.Generate("android", &out)); std::string output = out.str(); - EXPECT_NE(std::string::npos, output.find("public static final class attr")); - EXPECT_EQ(std::string::npos, - output.find("public static final class ^attr-private")); + EXPECT_THAT(output, HasSubstr("public static final class attr")); + EXPECT_THAT(output, Not(HasSubstr("public static final class ^attr-private"))); } TEST(JavaClassGeneratorTest, OnlyWritePublicResources) { @@ -140,16 +131,13 @@ TEST(JavaClassGeneratorTest, OnlyWritePublicResources) { .AddSimple("android:id/one", ResourceId(0x01020000)) .AddSimple("android:id/two", ResourceId(0x01020001)) .AddSimple("android:id/three", ResourceId(0x01020002)) - .SetSymbolState("android:id/one", ResourceId(0x01020000), - SymbolState::kPublic) - .SetSymbolState("android:id/two", ResourceId(0x01020001), - SymbolState::kPrivate) + .SetSymbolState("android:id/one", ResourceId(0x01020000), SymbolState::kPublic) + .SetSymbolState("android:id/two", ResourceId(0x01020001), SymbolState::kPrivate) .Build(); std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); @@ -160,10 +148,9 @@ TEST(JavaClassGeneratorTest, OnlyWritePublicResources) { std::stringstream out; ASSERT_TRUE(generator.Generate("android", &out)); std::string output = out.str(); - EXPECT_NE(std::string::npos, - output.find("public static final int one=0x01020000;")); - EXPECT_EQ(std::string::npos, output.find("two")); - EXPECT_EQ(std::string::npos, output.find("three")); + EXPECT_THAT(output, HasSubstr("public static final int one=0x01020000;")); + EXPECT_THAT(output, Not(HasSubstr("two"))); + EXPECT_THAT(output, Not(HasSubstr("three"))); } options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate; @@ -172,11 +159,9 @@ TEST(JavaClassGeneratorTest, OnlyWritePublicResources) { std::stringstream out; ASSERT_TRUE(generator.Generate("android", &out)); std::string output = out.str(); - EXPECT_NE(std::string::npos, - output.find("public static final int one=0x01020000;")); - EXPECT_NE(std::string::npos, - output.find("public static final int two=0x01020001;")); - EXPECT_EQ(std::string::npos, output.find("three")); + EXPECT_THAT(output, HasSubstr("public static final int one=0x01020000;")); + EXPECT_THAT(output, HasSubstr("public static final int two=0x01020001;")); + EXPECT_THAT(output, Not(HasSubstr("three"))); } options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; @@ -185,12 +170,9 @@ TEST(JavaClassGeneratorTest, OnlyWritePublicResources) { std::stringstream out; ASSERT_TRUE(generator.Generate("android", &out)); std::string output = out.str(); - EXPECT_NE(std::string::npos, - output.find("public static final int one=0x01020000;")); - EXPECT_NE(std::string::npos, - output.find("public static final int two=0x01020001;")); - EXPECT_NE(std::string::npos, - output.find("public static final int three=0x01020002;")); + EXPECT_THAT(output, HasSubstr("public static final int one=0x01020000;")); + EXPECT_THAT(output, HasSubstr("public static final int two=0x01020001;")); + EXPECT_THAT(output, HasSubstr("public static final int three=0x01020002;")); } } @@ -246,8 +228,7 @@ TEST(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGenerator generator(context.get(), table.get(), {}); @@ -256,8 +237,8 @@ TEST(JavaClassGeneratorTest, EmitOtherPackagesAttributesInStyleable) { EXPECT_TRUE(generator.Generate("android", &out)); std::string output = out.str(); - EXPECT_NE(std::string::npos, output.find("int foo_bar=")); - EXPECT_NE(std::string::npos, output.find("int foo_com_lib_bar=")); + EXPECT_THAT(output, HasSubstr("int foo_bar=")); + EXPECT_THAT(output, HasSubstr("int foo_com_lib_bar=")); } TEST(JavaClassGeneratorTest, CommentsForSimpleResourcesArePresent) { @@ -271,24 +252,22 @@ TEST(JavaClassGeneratorTest, CommentsForSimpleResourcesArePresent) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGenerator generator(context.get(), table.get(), {}); std::stringstream out; ASSERT_TRUE(generator.Generate("android", &out)); - std::string actual = out.str(); + std::string output = out.str(); - const char* expectedText = + const char* expected_text = R"EOF(/** * This is a comment * @deprecated */ @Deprecated public static final int foo=0x01010000;)EOF"; - - EXPECT_NE(std::string::npos, actual.find(expectedText)); + EXPECT_THAT(output, HasSubstr(expected_text)); } TEST(JavaClassGeneratorTest, CommentsForEnumAndFlagAttributesArePresent) {} @@ -298,8 +277,7 @@ TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) attr.SetComment(StringPiece("This is an attribute")); Styleable styleable; - styleable.entries.push_back( - Reference(test::ParseNameOrDie("android:attr/one"))); + styleable.entries.push_back(Reference(test::ParseNameOrDie("android:attr/one"))); styleable.SetComment(StringPiece("This is a styleable")); std::unique_ptr<ResourceTable> table = @@ -312,8 +290,7 @@ TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGeneratorOptions options; @@ -321,12 +298,12 @@ TEST(JavaClassGeneratorTest, CommentsForStyleablesAndNestedAttributesArePresent) JavaClassGenerator generator(context.get(), table.get(), options); std::stringstream out; ASSERT_TRUE(generator.Generate("android", &out)); - std::string actual = out.str(); + std::string output = out.str(); - EXPECT_NE(std::string::npos, actual.find("attr name android:one")); - EXPECT_NE(std::string::npos, actual.find("attr description")); - EXPECT_NE(std::string::npos, actual.find(attr.GetComment().data())); - EXPECT_NE(std::string::npos, actual.find(styleable.GetComment().data())); + EXPECT_THAT(output, HasSubstr("attr name android:one")); + EXPECT_THAT(output, HasSubstr("attr description")); + EXPECT_THAT(output, HasSubstr(attr.GetComment())); + EXPECT_THAT(output, HasSubstr(styleable.GetComment())); } TEST(JavaClassGeneratorTest, CommentsForRemovedAttributesAreNotPresentInClass) { @@ -341,8 +318,7 @@ TEST(JavaClassGeneratorTest, CommentsForRemovedAttributesAreNotPresentInClass) { std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .AddSymbolSource( - util::make_unique<ResourceTableSymbolSource>(table.get())) + .AddSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) .SetNameManglerPolicy(NameManglerPolicy{"android"}) .Build(); JavaClassGeneratorOptions options; @@ -350,17 +326,17 @@ TEST(JavaClassGeneratorTest, CommentsForRemovedAttributesAreNotPresentInClass) { JavaClassGenerator generator(context.get(), table.get(), options); std::stringstream out; ASSERT_TRUE(generator.Generate("android", &out)); - std::string actual = out.str(); + std::string output = out.str(); - EXPECT_EQ(std::string::npos, actual.find("@attr name android:one")); - EXPECT_EQ(std::string::npos, actual.find("@attr description")); + EXPECT_THAT(output, Not(HasSubstr("@attr name android:one"))); + EXPECT_THAT(output, Not(HasSubstr("@attr description"))); // We should find @removed only in the attribute javadoc and not anywhere else - // (i.e. the class - // javadoc). - const size_t pos = actual.find("removed"); - EXPECT_NE(std::string::npos, pos); - EXPECT_EQ(std::string::npos, actual.find("removed", pos + 1)); + // (i.e. the class javadoc). + const std::string kRemoved("removed"); + ASSERT_THAT(output, HasSubstr(kRemoved)); + std::string after_first_match = output.substr(output.find(kRemoved) + kRemoved.size()); + EXPECT_THAT(after_first_match, Not(HasSubstr(kRemoved))); } TEST(JavaClassGeneratorTest, GenerateOnResourcesLoadedCallbackForSharedLibrary) { @@ -381,19 +357,17 @@ TEST(JavaClassGeneratorTest, GenerateOnResourcesLoadedCallbackForSharedLibrary) JavaClassGeneratorOptions options; options.use_final = false; - options.rewrite_callback_options = OnResourcesLoadedCallbackOptions{ - {"com.foo", "com.boo"}, - }; + options.rewrite_callback_options = OnResourcesLoadedCallbackOptions{{"com.foo", "com.boo"}}; JavaClassGenerator generator(context.get(), table.get(), options); std::stringstream out; ASSERT_TRUE(generator.Generate("android", &out)); - std::string actual = out.str(); + std::string output = out.str(); - EXPECT_NE(std::string::npos, actual.find("void onResourcesLoaded")); - EXPECT_NE(std::string::npos, actual.find("com.foo.R.onResourcesLoaded")); - EXPECT_NE(std::string::npos, actual.find("com.boo.R.onResourcesLoaded")); + EXPECT_THAT(output, HasSubstr("void onResourcesLoaded")); + EXPECT_THAT(output, HasSubstr("com.foo.R.onResourcesLoaded")); + EXPECT_THAT(output, HasSubstr("com.boo.R.onResourcesLoaded")); } } // namespace aapt diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp index f49e4985fcf1..4ef32c92dd84 100644 --- a/tools/aapt2/java/ManifestClassGenerator.cpp +++ b/tools/aapt2/java/ManifestClassGenerator.cpp @@ -21,24 +21,21 @@ #include "Source.h" #include "java/AnnotationProcessor.h" #include "java/ClassDefinition.h" +#include "text/Unicode.h" #include "util/Maybe.h" #include "xml/XmlDom.h" -using android::StringPiece; +using ::android::StringPiece; +using ::aapt::text::IsJavaIdentifier; namespace aapt { -static Maybe<StringPiece> ExtractJavaIdentifier(IDiagnostics* diag, - const Source& source, - const StringPiece& value) { - const StringPiece sep = "."; - auto iter = std::find_end(value.begin(), value.end(), sep.begin(), sep.end()); - - StringPiece result; - if (iter != value.end()) { - result.assign(iter + sep.size(), value.end() - (iter + sep.size())); - } else { - result = value; +static Maybe<StringPiece> ExtractJavaIdentifier(IDiagnostics* diag, const Source& source, + const std::string& value) { + StringPiece result = value; + size_t pos = value.rfind('.'); + if (pos != std::string::npos) { + result = result.substr(pos + 1); } if (result.empty()) { @@ -46,33 +43,23 @@ static Maybe<StringPiece> ExtractJavaIdentifier(IDiagnostics* diag, return {}; } - iter = util::FindNonAlphaNumericAndNotInSet(result, "_"); - if (iter != result.end()) { - diag->Error(DiagMessage(source) << "invalid character '" - << StringPiece(iter, 1) << "' in '" - << result << "'"); + if (!IsJavaIdentifier(result)) { + diag->Error(DiagMessage(source) << "invalid Java identifier '" << result << "'"); return {}; } - - if (*result.begin() >= '0' && *result.begin() <= '9') { - diag->Error(DiagMessage(source) << "symbol can not start with a digit"); - return {}; - } - return result; } -static bool WriteSymbol(const Source& source, IDiagnostics* diag, - xml::Element* el, ClassDefinition* class_def) { +static bool WriteSymbol(const Source& source, IDiagnostics* diag, xml::Element* el, + ClassDefinition* class_def) { xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name"); if (!attr) { - diag->Error(DiagMessage(source) << "<" << el->name - << "> must define 'android:name'"); + diag->Error(DiagMessage(source) << "<" << el->name << "> must define 'android:name'"); return false; } - Maybe<StringPiece> result = ExtractJavaIdentifier( - diag, source.WithLine(el->line_number), attr->value); + Maybe<StringPiece> result = + ExtractJavaIdentifier(diag, source.WithLine(el->line_number), attr->value); if (!result) { return false; } @@ -85,8 +72,7 @@ static bool WriteSymbol(const Source& source, IDiagnostics* diag, return true; } -std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, - xml::XmlResource* res) { +std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, xml::XmlResource* res) { xml::Element* el = xml::FindRootElement(res->root.get()); if (!el) { diag->Error(DiagMessage(res->file.source) << "no root tag defined"); @@ -94,8 +80,7 @@ std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, } if (el->name != "manifest" && !el->namespace_uri.empty()) { - diag->Error(DiagMessage(res->file.source) - << "no <manifest> root tag defined"); + diag->Error(DiagMessage(res->file.source) << "no <manifest> root tag defined"); return {}; } @@ -109,11 +94,9 @@ std::unique_ptr<ClassDefinition> GenerateManifestClass(IDiagnostics* diag, for (xml::Element* child_el : children) { if (child_el->namespace_uri.empty()) { if (child_el->name == "permission") { - error |= !WriteSymbol(res->file.source, diag, child_el, - permission_class.get()); + error |= !WriteSymbol(res->file.source, diag, child_el, permission_class.get()); } else if (child_el->name == "permission-group") { - error |= !WriteSymbol(res->file.source, diag, child_el, - permission_group_class.get()); + error |= !WriteSymbol(res->file.source, diag, child_el, permission_group_class.get()); } } } diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp index 9f6ec210a6a7..c744e9bd4fc9 100644 --- a/tools/aapt2/java/ManifestClassGenerator_test.cpp +++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp @@ -84,6 +84,8 @@ TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) { @hide @SystemApi --> <permission android:name="android.permission.SECRET" /> + <!-- @TestApi This is a test only permission. --> + <permission android:name="android.permission.TEST_ONLY" /> </manifest>)"); std::string actual; @@ -110,6 +112,13 @@ TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) { @android.annotation.SystemApi public static final String SECRET="android.permission.SECRET";)"; EXPECT_THAT(actual, HasSubstr(expected_secret)); + + const char* expected_test = R"( /** + * This is a test only permission. + */ + @android.annotation.TestApi + public static final String TEST_ONLY="android.permission.TEST_ONLY";)"; + EXPECT_THAT(actual, HasSubstr(expected_test)); } static ::testing::AssertionResult GetManifestClassText(IAaptContext* context, xml::XmlResource* res, diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp index 5f61faeeebe7..10c46101123c 100644 --- a/tools/aapt2/java/ProguardRules.cpp +++ b/tools/aapt2/java/ProguardRules.cpp @@ -29,18 +29,12 @@ namespace proguard { class BaseVisitor : public xml::Visitor { public: - BaseVisitor(const Source& source, KeepSet* keep_set) - : source_(source), keep_set_(keep_set) {} + using xml::Visitor::Visit; - virtual void Visit(xml::Text*) override{}; - - virtual void Visit(xml::Namespace* node) override { - for (const auto& child : node->children) { - child->Accept(this); - } + BaseVisitor(const Source& source, KeepSet* keep_set) : source_(source), keep_set_(keep_set) { } - virtual void Visit(xml::Element* node) override { + void Visit(xml::Element* node) override { if (!node->namespace_uri.empty()) { Maybe<xml::ExtractedPackage> maybe_package = xml::ExtractPackageFromNamespace(node->namespace_uri); @@ -78,10 +72,10 @@ class BaseVisitor : public xml::Visitor { class LayoutVisitor : public BaseVisitor { public: - LayoutVisitor(const Source& source, KeepSet* keep_set) - : BaseVisitor(source, keep_set) {} + LayoutVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) { + } - virtual void Visit(xml::Element* node) override { + void Visit(xml::Element* node) override { bool check_class = false; bool check_name = false; if (node->namespace_uri.empty()) { @@ -119,7 +113,7 @@ class MenuVisitor : public BaseVisitor { MenuVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) { } - virtual void Visit(xml::Element* node) override { + void Visit(xml::Element* node) override { if (node->namespace_uri.empty() && node->name == "item") { for (const auto& attr : node->attributes) { if (attr.namespace_uri == xml::kSchemaAndroid) { @@ -142,10 +136,10 @@ class MenuVisitor : public BaseVisitor { class XmlResourceVisitor : public BaseVisitor { public: - XmlResourceVisitor(const Source& source, KeepSet* keep_set) - : BaseVisitor(source, keep_set) {} + XmlResourceVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) { + } - virtual void Visit(xml::Element* node) override { + void Visit(xml::Element* node) override { bool check_fragment = false; if (node->namespace_uri.empty()) { check_fragment = @@ -169,13 +163,12 @@ class XmlResourceVisitor : public BaseVisitor { class TransitionVisitor : public BaseVisitor { public: - TransitionVisitor(const Source& source, KeepSet* keep_set) - : BaseVisitor(source, keep_set) {} + TransitionVisitor(const Source& source, KeepSet* keep_set) : BaseVisitor(source, keep_set) { + } - virtual void Visit(xml::Element* node) override { + void Visit(xml::Element* node) override { bool check_class = - node->namespace_uri.empty() && - (node->name == "transition" || node->name == "pathMotion"); + node->namespace_uri.empty() && (node->name == "transition" || node->name == "pathMotion"); if (check_class) { xml::Attribute* attr = node->FindAttribute({}, "class"); if (attr && util::IsJavaClassName(attr->value)) { @@ -195,7 +188,7 @@ class ManifestVisitor : public BaseVisitor { ManifestVisitor(const Source& source, KeepSet* keep_set, bool main_dex_only) : BaseVisitor(source, keep_set), main_dex_only_(main_dex_only) {} - virtual void Visit(xml::Element* node) override { + void Visit(xml::Element* node) override { if (node->namespace_uri.empty()) { bool get_name = false; if (node->name == "manifest") { @@ -205,18 +198,15 @@ class ManifestVisitor : public BaseVisitor { } } else if (node->name == "application") { get_name = true; - xml::Attribute* attr = - node->FindAttribute(xml::kSchemaAndroid, "backupAgent"); + xml::Attribute* attr = node->FindAttribute(xml::kSchemaAndroid, "backupAgent"); if (attr) { - Maybe<std::string> result = - util::GetFullyQualifiedClassName(package_, attr->value); + Maybe<std::string> result = util::GetFullyQualifiedClassName(package_, attr->value); if (result) { AddClass(node->line_number, result.value()); } } if (main_dex_only_) { - xml::Attribute* default_process = - node->FindAttribute(xml::kSchemaAndroid, "process"); + xml::Attribute* default_process = node->FindAttribute(xml::kSchemaAndroid, "process"); if (default_process) { default_process_ = default_process->value; } @@ -226,8 +216,7 @@ class ManifestVisitor : public BaseVisitor { get_name = true; if (main_dex_only_) { - xml::Attribute* component_process = - node->FindAttribute(xml::kSchemaAndroid, "process"); + xml::Attribute* component_process = node->FindAttribute(xml::kSchemaAndroid, "process"); const std::string& process = component_process ? component_process->value : default_process_; @@ -242,8 +231,7 @@ class ManifestVisitor : public BaseVisitor { get_name = attr != nullptr; if (get_name) { - Maybe<std::string> result = - util::GetFullyQualifiedClassName(package_, attr->value); + Maybe<std::string> result = util::GetFullyQualifiedClassName(package_, attr->value); if (result) { AddClass(node->line_number, result.value()); } @@ -261,8 +249,7 @@ class ManifestVisitor : public BaseVisitor { std::string default_process_; }; -bool CollectProguardRulesForManifest(const Source& source, - xml::XmlResource* res, KeepSet* keep_set, +bool CollectProguardRulesForManifest(const Source& source, xml::XmlResource* res, KeepSet* keep_set, bool main_dex_only) { ManifestVisitor visitor(source, keep_set, main_dex_only); if (res->root) { @@ -272,8 +259,7 @@ bool CollectProguardRulesForManifest(const Source& source, return false; } -bool CollectProguardRules(const Source& source, xml::XmlResource* res, - KeepSet* keep_set) { +bool CollectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keep_set) { if (!res->root) { return false; } @@ -321,8 +307,7 @@ bool WriteKeepSet(std::ostream* out, const KeepSet& keep_set) { for (const Source& source : entry.second) { *out << "# Referenced at " << source << "\n"; } - *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" - << std::endl; + *out << "-keepclassmembers class * { *** " << entry.first << "(...); }\n" << std::endl; } return true; } diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp index 80edb352f42c..da7f410b8b08 100644 --- a/tools/aapt2/link/ManifestFixer_test.cpp +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -122,7 +122,7 @@ TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) { xml::Element* el; xml::Attribute* attr; - el = xml::FindRootElement(doc.get()); + el = doc->root.get(); ASSERT_NE(nullptr, el); el = el->FindChild({}, "uses-sdk"); ASSERT_NE(nullptr, el); @@ -141,7 +141,7 @@ TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) { options); ASSERT_NE(nullptr, doc); - el = xml::FindRootElement(doc.get()); + el = doc->root.get(); ASSERT_NE(nullptr, el); el = el->FindChild({}, "uses-sdk"); ASSERT_NE(nullptr, el); @@ -160,7 +160,7 @@ TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) { options); ASSERT_NE(nullptr, doc); - el = xml::FindRootElement(doc.get()); + el = doc->root.get(); ASSERT_NE(nullptr, el); el = el->FindChild({}, "uses-sdk"); ASSERT_NE(nullptr, el); @@ -177,7 +177,7 @@ TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) { options); ASSERT_NE(nullptr, doc); - el = xml::FindRootElement(doc.get()); + el = doc->root.get(); ASSERT_NE(nullptr, el); el = el->FindChild({}, "uses-sdk"); ASSERT_NE(nullptr, el); @@ -199,7 +199,7 @@ TEST_F(ManifestFixerTest, UsesSdkMustComeBeforeApplication) { options); ASSERT_NE(nullptr, doc); - xml::Element* manifest_el = xml::FindRootElement(doc.get()); + xml::Element* manifest_el = doc->root.get(); ASSERT_NE(nullptr, manifest_el); ASSERT_EQ("manifest", manifest_el->name); @@ -248,7 +248,7 @@ TEST_F(ManifestFixerTest, RenameManifestPackageAndFullyQualifyClasses) { options); ASSERT_NE(nullptr, doc); - xml::Element* manifestEl = xml::FindRootElement(doc.get()); + xml::Element* manifestEl = doc->root.get(); ASSERT_NE(nullptr, manifestEl); xml::Attribute* attr = nullptr; @@ -297,7 +297,7 @@ TEST_F(ManifestFixerTest, options); ASSERT_NE(nullptr, doc); - xml::Element* manifest_el = xml::FindRootElement(doc.get()); + xml::Element* manifest_el = doc->root.get(); ASSERT_NE(nullptr, manifest_el); xml::Element* instrumentation_el = @@ -321,7 +321,7 @@ TEST_F(ManifestFixerTest, UseDefaultVersionNameAndCode) { options); ASSERT_NE(nullptr, doc); - xml::Element* manifest_el = xml::FindRootElement(doc.get()); + xml::Element* manifest_el = doc->root.get(); ASSERT_NE(nullptr, manifest_el); xml::Attribute* attr = @@ -344,7 +344,7 @@ TEST_F(ManifestFixerTest, EnsureManifestAttributesAreTyped) { Verify("<manifest package=\"android\" coreApp=\"true\" />"); ASSERT_NE(nullptr, doc); - xml::Element* el = xml::FindRootElement(doc.get()); + xml::Element* el = doc->root.get(); ASSERT_NE(nullptr, el); EXPECT_EQ("manifest", el->name); diff --git a/tools/aapt2/link/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 228cfb9e3d66..ef99355e5b5f 100644 --- a/tools/aapt2/link/XmlReferenceLinker_test.cpp +++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp @@ -79,20 +79,20 @@ class XmlReferenceLinkerTest : public ::testing::Test { }; TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( - <View xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:background="@color/green" - android:text="hello" - android:attr="\?hello" - nonAaptAttr="1" - nonAaptAttrRef="@id/id" - class="hello" />)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( + <View xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:background="@color/green" + android:text="hello" + android:attr="\?hello" + nonAaptAttr="1" + nonAaptAttrRef="@id/id" + class="hello" />)"); XmlReferenceLinker linker; ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); - xml::Element* view_el = xml::FindRootElement(doc.get()); + xml::Element* view_el = doc->root.get(); ASSERT_THAT(view_el, NotNull()); xml::Attribute* xml_attr = view_el->FindAttribute(xml::kSchemaAndroid, "layout_width"); @@ -138,32 +138,32 @@ TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) { } TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreNotLinked) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( - <View xmlns:android="http://schemas.android.com/apk/res/android" - android:colorAccent="@android:color/hidden" />)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( + <View xmlns:android="http://schemas.android.com/apk/res/android" + android:colorAccent="@android:color/hidden" />)"); XmlReferenceLinker linker; ASSERT_FALSE(linker.Consume(context_.get(), doc.get())); } TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreLinkedWhenReferenceHasStarPrefix) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( <View xmlns:android="http://schemas.android.com/apk/res/android" - android:colorAccent="@*android:color/hidden" />)EOF"); + android:colorAccent="@*android:color/hidden" />)"); XmlReferenceLinker linker; ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); } TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( - <View xmlns:support="http://schemas.android.com/apk/res/com.android.support" - support:colorAccent="#ff0000" />)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( + <View xmlns:support="http://schemas.android.com/apk/res/com.android.support" + support:colorAccent="#ff0000" />)"); XmlReferenceLinker linker; ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); - xml::Element* view_el = xml::FindRootElement(doc.get()); + xml::Element* view_el = doc->root.get(); ASSERT_THAT(view_el, NotNull()); xml::Attribute* xml_attr = @@ -175,14 +175,14 @@ TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) { } TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( - <View xmlns:app="http://schemas.android.com/apk/res-auto" - app:colorAccent="@app:color/red" />)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( + <View xmlns:app="http://schemas.android.com/apk/res-auto" + app:colorAccent="@app:color/red" />)"); XmlReferenceLinker linker; ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); - xml::Element* view_el = xml::FindRootElement(doc.get()); + xml::Element* view_el = doc->root.get(); ASSERT_THAT(view_el, NotNull()); xml::Attribute* xml_attr = view_el->FindAttribute(xml::kSchemaAuto, "colorAccent"); @@ -196,17 +196,15 @@ TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) { } TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( - <View xmlns:app="http://schemas.android.com/apk/res/android" - app:attr="@app:id/id"> - <View xmlns:app="http://schemas.android.com/apk/res/com.app.test" - app:attr="@app:id/id"/> - </View>)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( + <View xmlns:app="http://schemas.android.com/apk/res/android" app:attr="@app:id/id"> + <View xmlns:app="http://schemas.android.com/apk/res/com.app.test" app:attr="@app:id/id"/> + </View>)"); XmlReferenceLinker linker; ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); - xml::Element* view_el = xml::FindRootElement(doc.get()); + xml::Element* view_el = doc->root.get(); ASSERT_THAT(view_el, NotNull()); // All attributes and references in this element should be referring to @@ -235,14 +233,14 @@ TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) { } TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( - <View xmlns:android="http://schemas.android.com/apk/res/com.app.test" - android:attr="@id/id"/>)EOF"); + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( + <View xmlns:android="http://schemas.android.com/apk/res/com.app.test" + android:attr="@id/id"/>)"); XmlReferenceLinker linker; ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); - xml::Element* view_el = xml::FindRootElement(doc.get()); + xml::Element* view_el = doc->root.get(); ASSERT_THAT(view_el, NotNull()); // All attributes and references in this element should be referring to diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp new file mode 100644 index 000000000000..f413ee960264 --- /dev/null +++ b/tools/aapt2/optimize/MultiApkGenerator.cpp @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "MultiApkGenerator.h" + +#include <algorithm> +#include <string> + +#include "androidfw/StringPiece.h" + +#include "LoadedApk.h" +#include "configuration/ConfigurationParser.h" +#include "filter/AbiFilter.h" +#include "filter/Filter.h" +#include "flatten/Archive.h" +#include "process/IResourceTableConsumer.h" +#include "split/TableSplitter.h" +#include "util/Files.h" + +namespace aapt { + +using ::aapt::configuration::Artifact; +using ::aapt::configuration::PostProcessingConfiguration; +using ::android::StringPiece; + +MultiApkGenerator::MultiApkGenerator(LoadedApk* apk, IAaptContext* context) + : apk_(apk), context_(context) { +} + +bool MultiApkGenerator::FromBaseApk(const std::string& out_dir, + const PostProcessingConfiguration& config, + const TableFlattenerOptions& table_flattener_options) { + // TODO(safarmer): Handle APK version codes for the generated APKs. + // TODO(safarmer): Handle explicit outputs/generating an output file list for other tools. + + const std::string& apk_path = apk_->GetSource().path; + const StringPiece ext = file::GetExtension(apk_path); + const std::string base_name = apk_path.substr(0, apk_path.rfind(ext.to_string())); + + // For now, just write out the stripped APK since ABI splitting doesn't modify anything else. + for (const Artifact& artifact : config.artifacts) { + FilterChain filters; + TableSplitterOptions splits; + AxisConfigFilter axis_filter; + + if (!artifact.name && !config.artifact_format) { + context_->GetDiagnostics()->Error( + DiagMessage() << "Artifact does not have a name and no global name template defined"); + return false; + } + + Maybe<std::string> artifact_name = + (artifact.name) + ? artifact.Name(base_name, ext.substr(1), context_->GetDiagnostics()) + : artifact.ToArtifactName(config.artifact_format.value(), context_->GetDiagnostics(), + base_name, ext.substr(1)); + + if (!artifact_name) { + context_->GetDiagnostics()->Error(DiagMessage() + << "Could not determine split APK artifact name"); + return false; + } + + if (artifact.abi_group) { + const std::string& group_name = artifact.abi_group.value(); + + auto group = config.abi_groups.find(group_name); + // TODO: Remove validation when configuration parser ensures referential integrity. + if (group == config.abi_groups.end()) { + context_->GetDiagnostics()->Error(DiagMessage() << "could not find referenced ABI group '" + << group_name << "'"); + return false; + } + filters.AddFilter(AbiFilter::FromAbiList(group->second)); + } + + if (artifact.screen_density_group) { + const std::string& group_name = artifact.screen_density_group.value(); + + auto group = config.screen_density_groups.find(group_name); + // TODO: Remove validation when configuration parser ensures referential integrity. + if (group == config.screen_density_groups.end()) { + context_->GetDiagnostics()->Error(DiagMessage() << "could not find referenced group '" + << group_name << "'"); + return false; + } + + const std::vector<ConfigDescription>& densities = group->second; + std::for_each(densities.begin(), densities.end(), [&](const ConfigDescription& c) { + splits.preferred_densities.push_back(c.density); + }); + } + + if (artifact.locale_group) { + const std::string& group_name = artifact.locale_group.value(); + auto group = config.locale_groups.find(group_name); + // TODO: Remove validation when configuration parser ensures referential integrity. + if (group == config.locale_groups.end()) { + context_->GetDiagnostics()->Error(DiagMessage() << "could not find referenced group '" + << group_name << "'"); + return false; + } + + const std::vector<ConfigDescription>& locales = group->second; + std::for_each(locales.begin(), locales.end(), + [&](const ConfigDescription& c) { axis_filter.AddConfig(c); }); + splits.config_filter = &axis_filter; + } + + std::unique_ptr<ResourceTable> table = apk_->GetResourceTable()->Clone(); + + TableSplitter splitter{{}, splits}; + splitter.SplitTable(table.get()); + + std::string out = out_dir; + file::AppendPath(&out, artifact_name.value()); + + std::unique_ptr<IArchiveWriter> writer = + CreateZipFileArchiveWriter(context_->GetDiagnostics(), out); + + if (context_->IsVerbose()) { + context_->GetDiagnostics()->Note(DiagMessage() << "Writing output: " << out); + } + + if (!apk_->WriteToArchive(context_, table.get(), table_flattener_options, &filters, + writer.get())) { + return false; + } + } + + return true; +} + +} // namespace aapt diff --git a/tools/aapt2/optimize/MultiApkGenerator.h b/tools/aapt2/optimize/MultiApkGenerator.h new file mode 100644 index 000000000000..f325d83053f4 --- /dev/null +++ b/tools/aapt2/optimize/MultiApkGenerator.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAPT2_APKSPLITTER_H +#define AAPT2_APKSPLITTER_H + +#include "Diagnostics.h" +#include "LoadedApk.h" +#include "configuration/ConfigurationParser.h" + +namespace aapt { + +/** + * Generates a set of APKs that are a subset of the original base APKs. Each of the new APKs contain + * only the resources and assets for an artifact in the configuration file. + */ +class MultiApkGenerator { + public: + MultiApkGenerator(LoadedApk* apk, IAaptContext* context); + + /** + * Writes a set of APKs to the provided output directory. Each APK is a subset fo the base APK and + * represents an artifact in the post processing configuration. + */ + bool FromBaseApk(const std::string& out_dir, + const configuration::PostProcessingConfiguration& config, + const TableFlattenerOptions& table_flattener_options); + + private: + IDiagnostics* GetDiagnostics() { + return context_->GetDiagnostics(); + } + + LoadedApk* apk_; + IAaptContext* context_; +}; + +} // namespace aapt + +#endif // AAPT2_APKSPLITTER_H diff --git a/tools/aapt2/optimize/MultiApkGenerator_test.cpp b/tools/aapt2/optimize/MultiApkGenerator_test.cpp new file mode 100644 index 000000000000..6c928d94e879 --- /dev/null +++ b/tools/aapt2/optimize/MultiApkGenerator_test.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "optimize/MultiApkGenerator.h" + +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +#include "LoadedApk.h" +#include "ResourceTable.h" +#include "configuration/ConfigurationParser.h" +#include "filter/Filter.h" +#include "flatten/Archive.h" +#include "flatten/TableFlattener.h" +#include "process/IResourceTableConsumer.h" +#include "test/Context.h" +#include "test/Test.h" + +namespace aapt { +namespace { + +using ::aapt::configuration::Abi; +using ::aapt::configuration::Artifact; +using ::aapt::configuration::PostProcessingConfiguration; + +using ::testing::Eq; +using ::testing::Return; +using ::testing::_; + +class MockApk : public LoadedApk { + public: + MockApk(std::unique_ptr<ResourceTable> table) : LoadedApk({"test.apk"}, {}, std::move(table)){}; + MOCK_METHOD5(WriteToArchive, bool(IAaptContext*, ResourceTable*, const TableFlattenerOptions&, + FilterChain*, IArchiveWriter*)); +}; + +TEST(MultiApkGeneratorTest, FromBaseApk) { + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddFileReference("android:drawable/icon", "res/drawable-mdpi/icon.png", + test::ParseConfigOrDie("mdpi")) + .AddFileReference("android:drawable/icon", "res/drawable-hdpi/icon.png", + test::ParseConfigOrDie("hdpi")) + .AddFileReference("android:drawable/icon", "res/drawable-xhdpi/icon.png", + test::ParseConfigOrDie("xhdpi")) + .AddFileReference("android:drawable/icon", "res/drawable-xxhdpi/icon.png", + test::ParseConfigOrDie("xxhdpi")) + .AddSimple("android:string/one") + .Build(); + + MockApk apk{std::move(table)}; + + EXPECT_CALL(apk, WriteToArchive(_, _, _, _, _)).Times(0); + + test::Context ctx; + PostProcessingConfiguration empty_config; + TableFlattenerOptions table_flattener_options; + + MultiApkGenerator generator{&apk, &ctx}; + EXPECT_TRUE(generator.FromBaseApk("out", empty_config, table_flattener_options)); + + Artifact x64 = test::ArtifactBuilder() + .SetName("${basename}.x64.apk") + .SetAbiGroup("x64") + .SetLocaleGroup("en") + .SetDensityGroup("xhdpi") + .Build(); + + Artifact intel = test::ArtifactBuilder() + .SetName("${basename}.intel.apk") + .SetAbiGroup("intel") + .SetLocaleGroup("europe") + .SetDensityGroup("large") + .Build(); + + auto config = test::PostProcessingConfigurationBuilder() + .SetLocaleGroup("en", {"en"}) + .SetLocaleGroup("europe", {"en", "fr", "de", "es"}) + .SetAbiGroup("x64", {Abi::kX86_64}) + .SetAbiGroup("intel", {Abi::kX86_64, Abi::kX86}) + .SetDensityGroup("xhdpi", {"xhdpi"}) + .SetDensityGroup("large", {"xhdpi", "xxhdpi", "xxxhdpi"}) + .AddArtifact(x64) + .AddArtifact(intel) + .Build(); + + // Called once for each artifact. + EXPECT_CALL(apk, WriteToArchive(Eq(&ctx), _, _, _, _)).Times(2).WillRepeatedly(Return(true)); + EXPECT_TRUE(generator.FromBaseApk("out", config, table_flattener_options)); +} + +} // namespace +} // namespace aapt diff --git a/tools/aapt2/proto/ProtoHelpers.cpp b/tools/aapt2/proto/ProtoHelpers.cpp index 6b21364b5eb2..aa99c982f6ae 100644 --- a/tools/aapt2/proto/ProtoHelpers.cpp +++ b/tools/aapt2/proto/ProtoHelpers.cpp @@ -36,7 +36,7 @@ void SerializeSourceToPb(const Source& source, StringPool* src_pool, pb::Source* StringPool::Ref ref = src_pool->MakeRef(source.path); out_pb_source->set_path_idx(static_cast<uint32_t>(ref.index())); if (source.line) { - out_pb_source->set_line_no(static_cast<uint32_t>(source.line.value())); + out_pb_source->mutable_position()->set_line_number(static_cast<uint32_t>(source.line.value())); } } @@ -46,29 +46,28 @@ void DeserializeSourceFromPb(const pb::Source& pb_source, const android::ResStri out_source->path = util::GetString(src_pool, pb_source.path_idx()); } - if (pb_source.has_line_no()) { - out_source->line = static_cast<size_t>(pb_source.line_no()); + if (pb_source.has_position()) { + out_source->line = static_cast<size_t>(pb_source.position().line_number()); } } pb::SymbolStatus_Visibility SerializeVisibilityToPb(SymbolState state) { switch (state) { case SymbolState::kPrivate: - return pb::SymbolStatus_Visibility_Private; + return pb::SymbolStatus_Visibility_PRIVATE; case SymbolState::kPublic: - return pb::SymbolStatus_Visibility_Public; + return pb::SymbolStatus_Visibility_PUBLIC; default: break; } - return pb::SymbolStatus_Visibility_Unknown; + return pb::SymbolStatus_Visibility_UNKNOWN; } -SymbolState DeserializeVisibilityFromPb( - pb::SymbolStatus_Visibility pb_visibility) { +SymbolState DeserializeVisibilityFromPb(pb::SymbolStatus_Visibility pb_visibility) { switch (pb_visibility) { - case pb::SymbolStatus_Visibility_Private: + case pb::SymbolStatus_Visibility_PRIVATE: return SymbolState::kPrivate; - case pb::SymbolStatus_Visibility_Public: + case pb::SymbolStatus_Visibility_PUBLIC: return SymbolState::kPublic; default: break; @@ -102,20 +101,20 @@ bool DeserializeConfigDescriptionFromPb(const pb::ConfigDescription& pb_config, pb::Reference_Type SerializeReferenceTypeToPb(Reference::Type type) { switch (type) { case Reference::Type::kResource: - return pb::Reference_Type_Ref; + return pb::Reference_Type_REFERENCE; case Reference::Type::kAttribute: - return pb::Reference_Type_Attr; + return pb::Reference_Type_ATTRIBUTE; default: break; } - return pb::Reference_Type_Ref; + return pb::Reference_Type_REFERENCE; } Reference::Type DeserializeReferenceTypeFromPb(pb::Reference_Type pb_type) { switch (pb_type) { - case pb::Reference_Type_Ref: + case pb::Reference_Type_REFERENCE: return Reference::Type::kResource; - case pb::Reference_Type_Attr: + case pb::Reference_Type_ATTRIBUTE: return Reference::Type::kAttribute; default: break; @@ -126,32 +125,32 @@ Reference::Type DeserializeReferenceTypeFromPb(pb::Reference_Type pb_type) { pb::Plural_Arity SerializePluralEnumToPb(size_t plural_idx) { switch (plural_idx) { case Plural::Zero: - return pb::Plural_Arity_Zero; + return pb::Plural_Arity_ZERO; case Plural::One: - return pb::Plural_Arity_One; + return pb::Plural_Arity_ONE; case Plural::Two: - return pb::Plural_Arity_Two; + return pb::Plural_Arity_TWO; case Plural::Few: - return pb::Plural_Arity_Few; + return pb::Plural_Arity_FEW; case Plural::Many: - return pb::Plural_Arity_Many; + return pb::Plural_Arity_MANY; default: break; } - return pb::Plural_Arity_Other; + return pb::Plural_Arity_OTHER; } size_t DeserializePluralEnumFromPb(pb::Plural_Arity arity) { switch (arity) { - case pb::Plural_Arity_Zero: + case pb::Plural_Arity_ZERO: return Plural::Zero; - case pb::Plural_Arity_One: + case pb::Plural_Arity_ONE: return Plural::One; - case pb::Plural_Arity_Two: + case pb::Plural_Arity_TWO: return Plural::Two; - case pb::Plural_Arity_Few: + case pb::Plural_Arity_FEW: return Plural::Few; - case pb::Plural_Arity_Many: + case pb::Plural_Arity_MANY: return Plural::Many; default: break; diff --git a/tools/aapt2/proto/ProtoHelpers.h b/tools/aapt2/proto/ProtoHelpers.h index 344e9477ea71..2f268f44752c 100644 --- a/tools/aapt2/proto/ProtoHelpers.h +++ b/tools/aapt2/proto/ProtoHelpers.h @@ -23,27 +23,23 @@ #include "ResourceTable.h" #include "Source.h" #include "StringPool.h" -#include "Format.pb.h" +#include "Resources.pb.h" +#include "ResourcesInternal.pb.h" namespace aapt { -void SerializeStringPoolToPb(const StringPool& pool, - pb::StringPool* out_pb_pool); +void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool); -void SerializeSourceToPb(const Source& source, StringPool* src_pool, - pb::Source* out_pb_source); +void SerializeSourceToPb(const Source& source, StringPool* src_pool, pb::Source* out_pb_source); -void DeserializeSourceFromPb(const pb::Source& pb_source, - const android::ResStringPool& src_pool, +void DeserializeSourceFromPb(const pb::Source& pb_source, const android::ResStringPool& src_pool, Source* out_source); pb::SymbolStatus_Visibility SerializeVisibilityToPb(SymbolState state); -SymbolState DeserializeVisibilityFromPb( - pb::SymbolStatus_Visibility pb_visibility); +SymbolState DeserializeVisibilityFromPb(pb::SymbolStatus_Visibility pb_visibility); -void SerializeConfig(const ConfigDescription& config, - pb::ConfigDescription* out_pb_config); +void SerializeConfig(const ConfigDescription& config, pb::ConfigDescription* out_pb_config); bool DeserializeConfigDescriptionFromPb(const pb::ConfigDescription& pb_config, ConfigDescription* out_config); diff --git a/tools/aapt2/proto/ProtoSerialize.h b/tools/aapt2/proto/ProtoSerialize.h index 39c50038d599..8c46642e9090 100644 --- a/tools/aapt2/proto/ProtoSerialize.h +++ b/tools/aapt2/proto/ProtoSerialize.h @@ -30,11 +30,10 @@ namespace aapt { class CompiledFileOutputStream { public: - explicit CompiledFileOutputStream( - google::protobuf::io::ZeroCopyOutputStream* out); + explicit CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out); void WriteLittleEndian32(uint32_t value); - void WriteCompiledFile(const pb::CompiledFile* compiledFile); + void WriteCompiledFile(const pb::internal::CompiledFile* compiledFile); void WriteData(const BigBuffer* buffer); void WriteData(const void* data, size_t len); bool HadError(); @@ -52,7 +51,7 @@ class CompiledFileInputStream { explicit CompiledFileInputStream(const void* data, size_t size); bool ReadLittleEndian32(uint32_t* outVal); - bool ReadCompiledFile(pb::CompiledFile* outVal); + bool ReadCompiledFile(pb::internal::CompiledFile* outVal); bool ReadDataMetaData(uint64_t* outOffset, uint64_t* outLen); private: @@ -64,13 +63,12 @@ class CompiledFileInputStream { }; std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table); -std::unique_ptr<ResourceTable> DeserializeTableFromPb( - const pb::ResourceTable& pbTable, const Source& source, IDiagnostics* diag); +std::unique_ptr<ResourceTable> DeserializeTableFromPb(const pb::ResourceTable& pbTable, + const Source& source, IDiagnostics* diag); -std::unique_ptr<pb::CompiledFile> SerializeCompiledFileToPb( - const ResourceFile& file); +std::unique_ptr<pb::internal::CompiledFile> SerializeCompiledFileToPb(const ResourceFile& file); std::unique_ptr<ResourceFile> DeserializeCompiledFileFromPb( - const pb::CompiledFile& pbFile, const Source& source, IDiagnostics* diag); + const pb::internal::CompiledFile& pbFile, const Source& source, IDiagnostics* diag); } // namespace aapt diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp index 37d5ed0cf59d..b9d5878f2f71 100644 --- a/tools/aapt2/proto/TableProtoDeserializer.cpp +++ b/tools/aapt2/proto/TableProtoDeserializer.cpp @@ -55,66 +55,61 @@ class ReferenceIdToNameVisitor : public ValueVisitor { class PackagePbDeserializer { public: - PackagePbDeserializer(const android::ResStringPool* valuePool, - const android::ResStringPool* sourcePool, - const android::ResStringPool* symbolPool, - const Source& source, IDiagnostics* diag) - : value_pool_(valuePool), - source_pool_(sourcePool), - symbol_pool_(symbolPool), - source_(source), - diag_(diag) {} + PackagePbDeserializer(const android::ResStringPool* sourcePool, const Source& source, + IDiagnostics* diag) + : source_pool_(sourcePool), source_(source), diag_(diag) { + } public: - bool DeserializeFromPb(const pb::Package& pbPackage, ResourceTable* table) { + bool DeserializeFromPb(const pb::Package& pb_package, ResourceTable* table) { Maybe<uint8_t> id; - if (pbPackage.has_package_id()) { - id = static_cast<uint8_t>(pbPackage.package_id()); + if (pb_package.has_package_id()) { + id = static_cast<uint8_t>(pb_package.package_id()); } - std::map<ResourceId, ResourceNameRef> idIndex; + std::map<ResourceId, ResourceNameRef> id_index; - ResourceTablePackage* pkg = table->CreatePackage(pbPackage.package_name(), id); - for (const pb::Type& pbType : pbPackage.types()) { - const ResourceType* resType = ParseResourceType(pbType.name()); - if (!resType) { - diag_->Error(DiagMessage(source_) << "unknown type '" << pbType.name() << "'"); + ResourceTablePackage* pkg = table->CreatePackage(pb_package.package_name(), id); + for (const pb::Type& pb_type : pb_package.type()) { + const ResourceType* res_type = ParseResourceType(pb_type.name()); + if (res_type == nullptr) { + diag_->Error(DiagMessage(source_) << "unknown type '" << pb_type.name() << "'"); return {}; } - ResourceTableType* type = pkg->FindOrCreateType(*resType); + ResourceTableType* type = pkg->FindOrCreateType(*res_type); - for (const pb::Entry& pbEntry : pbType.entries()) { - ResourceEntry* entry = type->FindOrCreateEntry(pbEntry.name()); + for (const pb::Entry& pb_entry : pb_type.entry()) { + ResourceEntry* entry = type->FindOrCreateEntry(pb_entry.name()); - // Deserialize the symbol status (public/private with source and - // comments). - if (pbEntry.has_symbol_status()) { - const pb::SymbolStatus& pbStatus = pbEntry.symbol_status(); - if (pbStatus.has_source()) { - DeserializeSourceFromPb(pbStatus.source(), *source_pool_, &entry->symbol_status.source); + // Deserialize the symbol status (public/private with source and comments). + if (pb_entry.has_symbol_status()) { + const pb::SymbolStatus& pb_status = pb_entry.symbol_status(); + if (pb_status.has_source()) { + DeserializeSourceFromPb(pb_status.source(), *source_pool_, + &entry->symbol_status.source); } - if (pbStatus.has_comment()) { - entry->symbol_status.comment = pbStatus.comment(); + if (pb_status.has_comment()) { + entry->symbol_status.comment = pb_status.comment(); } - entry->symbol_status.allow_new = pbStatus.allow_new(); + entry->symbol_status.allow_new = pb_status.allow_new(); - SymbolState visibility = DeserializeVisibilityFromPb(pbStatus.visibility()); + SymbolState visibility = DeserializeVisibilityFromPb(pb_status.visibility()); entry->symbol_status.state = visibility; if (visibility == SymbolState::kPublic) { // This is a public symbol, we must encode the ID now if there is one. - if (pbEntry.has_id()) { - entry->id = static_cast<uint16_t>(pbEntry.id()); + if (pb_entry.has_id()) { + entry->id = static_cast<uint16_t>(pb_entry.id()); } if (type->symbol_status.state != SymbolState::kPublic) { // If the type has not been made public, do so now. type->symbol_status.state = SymbolState::kPublic; - if (pbType.has_id()) { - type->id = static_cast<uint8_t>(pbType.id()); + if (pb_type.has_id()) { + type->id = static_cast<uint8_t>(pb_type.id()); } } } else if (visibility == SymbolState::kPrivate) { @@ -124,45 +119,44 @@ class PackagePbDeserializer { } } - ResourceId resId(pbPackage.package_id(), pbType.id(), pbEntry.id()); - if (resId.is_valid()) { - idIndex[resId] = ResourceNameRef(pkg->name, type->type, entry->name); + ResourceId resid(pb_package.package_id(), pb_type.id(), pb_entry.id()); + if (resid.is_valid()) { + id_index[resid] = ResourceNameRef(pkg->name, type->type, entry->name); } - for (const pb::ConfigValue& pbConfigValue : pbEntry.config_values()) { - const pb::ConfigDescription& pbConfig = pbConfigValue.config(); + for (const pb::ConfigValue& pb_config_value : pb_entry.config_value()) { + const pb::ConfigDescription& pb_config = pb_config_value.config(); ConfigDescription config; - if (!DeserializeConfigDescriptionFromPb(pbConfig, &config)) { + if (!DeserializeConfigDescriptionFromPb(pb_config, &config)) { diag_->Error(DiagMessage(source_) << "invalid configuration"); return {}; } - ResourceConfigValue* configValue = entry->FindOrCreateValue(config, pbConfig.product()); - if (configValue->value) { + ResourceConfigValue* config_value = entry->FindOrCreateValue(config, pb_config.product()); + if (config_value->value) { // Duplicate config. diag_->Error(DiagMessage(source_) << "duplicate configuration"); return {}; } - configValue->value = - DeserializeValueFromPb(pbConfigValue.value(), config, &table->string_pool); - if (!configValue->value) { + config_value->value = + DeserializeValueFromPb(pb_config_value.value(), config, &table->string_pool); + if (!config_value->value) { return {}; } } } } - ReferenceIdToNameVisitor visitor(&idIndex); + ReferenceIdToNameVisitor visitor(&id_index); VisitAllValuesInPackage(pkg, &visitor); return true; } private: std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item, - const ConfigDescription& config, - StringPool* pool) { + const ConfigDescription& config, StringPool* pool) { if (pb_item.has_ref()) { const pb::Reference& pb_ref = pb_item.ref(); std::unique_ptr<Reference> ref = util::make_unique<Reference>(); @@ -173,45 +167,32 @@ class PackagePbDeserializer { } else if (pb_item.has_prim()) { const pb::Primitive& pb_prim = pb_item.prim(); - android::Res_value prim = {}; - prim.dataType = static_cast<uint8_t>(pb_prim.type()); - prim.data = pb_prim.data(); - return util::make_unique<BinaryPrimitive>(prim); + return util::make_unique<BinaryPrimitive>(static_cast<uint8_t>(pb_prim.type()), + pb_prim.data()); } else if (pb_item.has_id()) { return util::make_unique<Id>(); } else if (pb_item.has_str()) { - const uint32_t idx = pb_item.str().idx(); - const std::string str = util::GetString(*value_pool_, idx); - - const android::ResStringPool_span* spans = value_pool_->styleAt(idx); - if (spans && spans->name.index != android::ResStringPool_span::END) { - StyleString style_str = {str}; - while (spans->name.index != android::ResStringPool_span::END) { - style_str.spans.push_back( - Span{util::GetString(*value_pool_, spans->name.index), - spans->firstChar, spans->lastChar}); - spans++; - } - return util::make_unique<StyledString>(pool->MakeRef( - style_str, StringPool::Context(StringPool::Context::kNormalPriority, config))); - } return util::make_unique<String>( - pool->MakeRef(str, StringPool::Context(config))); + pool->MakeRef(pb_item.str().value(), StringPool::Context(config))); } else if (pb_item.has_raw_str()) { - const uint32_t idx = pb_item.raw_str().idx(); - const std::string str = util::GetString(*value_pool_, idx); return util::make_unique<RawString>( - pool->MakeRef(str, StringPool::Context(config))); + pool->MakeRef(pb_item.raw_str().value(), StringPool::Context(config))); + + } else if (pb_item.has_styled_str()) { + const pb::StyledString& pb_str = pb_item.styled_str(); + StyleString style_str{pb_str.value()}; + for (const pb::StyledString::Span& pb_span : pb_str.span()) { + style_str.spans.push_back(Span{pb_span.tag(), pb_span.first_char(), pb_span.last_char()}); + } + return util::make_unique<StyledString>(pool->MakeRef( + style_str, StringPool::Context(StringPool::Context::kNormalPriority, config))); } else if (pb_item.has_file()) { - const uint32_t idx = pb_item.file().path_idx(); - const std::string str = util::GetString(*value_pool_, idx); return util::make_unique<FileReference>(pool->MakeRef( - str, - StringPool::Context(StringPool::Context::kHighPriority, config))); + pb_item.file().path(), StringPool::Context(StringPool::Context::kHighPriority, config))); } else { diag_->Error(DiagMessage(source_) << "unknown item"); @@ -222,8 +203,6 @@ class PackagePbDeserializer { std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value, const ConfigDescription& config, StringPool* pool) { - const bool is_weak = pb_value.has_weak() ? pb_value.weak() : false; - std::unique_ptr<Value> value; if (pb_value.has_item()) { value = DeserializeItemFromPb(pb_value.item(), config, pool); @@ -235,11 +214,11 @@ class PackagePbDeserializer { const pb::CompoundValue& pb_compound_value = pb_value.compound_value(); if (pb_compound_value.has_attr()) { const pb::Attribute& pb_attr = pb_compound_value.attr(); - std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(is_weak); + std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(); attr->type_mask = pb_attr.format_flags(); attr->min_int = pb_attr.min_int(); attr->max_int = pb_attr.max_int(); - for (const pb::Attribute_Symbol& pb_symbol : pb_attr.symbols()) { + for (const pb::Attribute_Symbol& pb_symbol : pb_attr.symbol()) { Attribute::Symbol symbol; DeserializeItemCommon(pb_symbol, &symbol.symbol); if (!DeserializeReferenceFromPb(pb_symbol.name(), &symbol.symbol)) { @@ -255,20 +234,18 @@ class PackagePbDeserializer { std::unique_ptr<Style> style = util::make_unique<Style>(); if (pb_style.has_parent()) { style->parent = Reference(); - if (!DeserializeReferenceFromPb(pb_style.parent(), - &style->parent.value())) { + if (!DeserializeReferenceFromPb(pb_style.parent(), &style->parent.value())) { return {}; } if (pb_style.has_parent_source()) { Source parent_source; - DeserializeSourceFromPb(pb_style.parent_source(), *source_pool_, - &parent_source); + DeserializeSourceFromPb(pb_style.parent_source(), *source_pool_, &parent_source); style->parent.value().SetSource(std::move(parent_source)); } } - for (const pb::Style_Entry& pb_entry : pb_style.entries()) { + for (const pb::Style_Entry& pb_entry : pb_style.entry()) { Style::Entry entry; DeserializeItemCommon(pb_entry, &entry.key); if (!DeserializeReferenceFromPb(pb_entry.key(), &entry.key)) { @@ -288,7 +265,7 @@ class PackagePbDeserializer { } else if (pb_compound_value.has_styleable()) { const pb::Styleable& pb_styleable = pb_compound_value.styleable(); std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); - for (const pb::Styleable_Entry& pb_entry : pb_styleable.entries()) { + for (const pb::Styleable_Entry& pb_entry : pb_styleable.entry()) { Reference attr_ref; DeserializeItemCommon(pb_entry, &attr_ref); DeserializeReferenceFromPb(pb_entry.attr(), &attr_ref); @@ -299,25 +276,23 @@ class PackagePbDeserializer { } else if (pb_compound_value.has_array()) { const pb::Array& pb_array = pb_compound_value.array(); std::unique_ptr<Array> array = util::make_unique<Array>(); - for (const pb::Array_Entry& pb_entry : pb_array.entries()) { - std::unique_ptr<Item> item = - DeserializeItemFromPb(pb_entry.item(), config, pool); + for (const pb::Array_Element& pb_entry : pb_array.element()) { + std::unique_ptr<Item> item = DeserializeItemFromPb(pb_entry.item(), config, pool); if (!item) { return {}; } DeserializeItemCommon(pb_entry, item.get()); - array->items.push_back(std::move(item)); + array->elements.push_back(std::move(item)); } value = std::move(array); } else if (pb_compound_value.has_plural()) { const pb::Plural& pb_plural = pb_compound_value.plural(); std::unique_ptr<Plural> plural = util::make_unique<Plural>(); - for (const pb::Plural_Entry& pb_entry : pb_plural.entries()) { + for (const pb::Plural_Entry& pb_entry : pb_plural.entry()) { size_t pluralIdx = DeserializePluralEnumFromPb(pb_entry.arity()); - plural->values[pluralIdx] = - DeserializeItemFromPb(pb_entry.item(), config, pool); + plural->values[pluralIdx] = DeserializeItemFromPb(pb_entry.item(), config, pool); if (!plural->values[pluralIdx]) { return {}; } @@ -337,7 +312,7 @@ class PackagePbDeserializer { CHECK(value) << "forgot to set value"; - value->SetWeak(is_weak); + value->SetWeak(pb_value.weak()); DeserializeItemCommon(pb_value, value.get()); return value; } @@ -350,11 +325,10 @@ class PackagePbDeserializer { out_ref->id = ResourceId(pb_ref.id()); } - if (pb_ref.has_symbol_idx()) { - const std::string str_symbol = util::GetString(*symbol_pool_, pb_ref.symbol_idx()); + if (pb_ref.has_name()) { ResourceNameRef name_ref; - if (!ResourceUtils::ParseResourceName(str_symbol, &name_ref, nullptr)) { - diag_->Error(DiagMessage(source_) << "invalid reference name '" << str_symbol << "'"); + if (!ResourceUtils::ParseResourceName(pb_ref.name(), &name_ref, nullptr)) { + diag_->Error(DiagMessage(source_) << "invalid reference name '" << pb_ref.name() << "'"); return false; } @@ -377,61 +351,33 @@ class PackagePbDeserializer { } private: - const android::ResStringPool* value_pool_; const android::ResStringPool* source_pool_; - const android::ResStringPool* symbol_pool_; const Source source_; IDiagnostics* diag_; }; } // namespace -std::unique_ptr<ResourceTable> DeserializeTableFromPb( - const pb::ResourceTable& pb_table, const Source& source, - IDiagnostics* diag) { - // We import the android namespace because on Windows NO_ERROR is a macro, not - // an enum, which +std::unique_ptr<ResourceTable> DeserializeTableFromPb(const pb::ResourceTable& pb_table, + const Source& source, IDiagnostics* diag) { + // We import the android namespace because on Windows NO_ERROR is a macro, not an enum, which // causes errors when qualifying it with android:: using namespace android; std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); - if (!pb_table.has_string_pool()) { - diag->Error(DiagMessage(source) << "no string pool found"); - return {}; - } - - ResStringPool value_pool; - status_t result = value_pool.setTo(pb_table.string_pool().data().data(), - pb_table.string_pool().data().size()); - if (result != NO_ERROR) { - diag->Error(DiagMessage(source) << "invalid string pool"); - return {}; - } - ResStringPool source_pool; if (pb_table.has_source_pool()) { - result = source_pool.setTo(pb_table.source_pool().data().data(), - pb_table.source_pool().data().size()); + status_t result = source_pool.setTo(pb_table.source_pool().data().data(), + pb_table.source_pool().data().size()); if (result != NO_ERROR) { diag->Error(DiagMessage(source) << "invalid source pool"); return {}; } } - ResStringPool symbol_pool; - if (pb_table.has_symbol_pool()) { - result = symbol_pool.setTo(pb_table.symbol_pool().data().data(), - pb_table.symbol_pool().data().size()); - if (result != NO_ERROR) { - diag->Error(DiagMessage(source) << "invalid symbol pool"); - return {}; - } - } - - PackagePbDeserializer package_pb_deserializer(&value_pool, &source_pool, - &symbol_pool, source, diag); - for (const pb::Package& pb_package : pb_table.packages()) { + PackagePbDeserializer package_pb_deserializer(&source_pool, source, diag); + for (const pb::Package& pb_package : pb_table.package()) { if (!package_pb_deserializer.DeserializeFromPb(pb_package, table.get())) { return {}; } @@ -440,7 +386,7 @@ std::unique_ptr<ResourceTable> DeserializeTableFromPb( } std::unique_ptr<ResourceFile> DeserializeCompiledFileFromPb( - const pb::CompiledFile& pb_file, const Source& source, IDiagnostics* diag) { + const pb::internal::CompiledFile& pb_file, const Source& source, IDiagnostics* diag) { std::unique_ptr<ResourceFile> file = util::make_unique<ResourceFile>(); ResourceNameRef name_ref; @@ -456,19 +402,20 @@ std::unique_ptr<ResourceFile> DeserializeCompiledFileFromPb( file->source.path = pb_file.source_path(); DeserializeConfigDescriptionFromPb(pb_file.config(), &file->config); - for (const pb::CompiledFile_Symbol& pb_symbol : pb_file.exported_symbols()) { - // Need to create an lvalue here so that nameRef can point to something - // real. - if (!ResourceUtils::ParseResourceName(pb_symbol.resource_name(), - &name_ref)) { + for (const pb::internal::CompiledFile_Symbol& pb_symbol : pb_file.exported_symbol()) { + // Need to create an lvalue here so that nameRef can point to something real. + if (!ResourceUtils::ParseResourceName(pb_symbol.resource_name(), &name_ref)) { diag->Error(DiagMessage(source) << "invalid resource name for exported symbol in " "compiled file header: " << pb_file.resource_name()); return {}; } - file->exported_symbols.push_back( - SourcedResourceName{name_ref.ToResourceName(), pb_symbol.line_no()}); + size_t line = 0u; + if (pb_symbol.has_source()) { + line = pb_symbol.source().line_number(); + } + file->exported_symbols.push_back(SourcedResourceName{name_ref.ToResourceName(), line}); } return file; } diff --git a/tools/aapt2/proto/TableProtoSerializer.cpp b/tools/aapt2/proto/TableProtoSerializer.cpp index 730442c62836..a08df71eae1e 100644 --- a/tools/aapt2/proto/TableProtoSerializer.cpp +++ b/tools/aapt2/proto/TableProtoSerializer.cpp @@ -24,9 +24,9 @@ #include "android-base/logging.h" -using google::protobuf::io::CodedOutputStream; -using google::protobuf::io::CodedInputStream; -using google::protobuf::io::ZeroCopyOutputStream; +using ::google::protobuf::io::CodedInputStream; +using ::google::protobuf::io::CodedOutputStream; +using ::google::protobuf::io::ZeroCopyOutputStream; namespace aapt { @@ -36,46 +36,46 @@ class PbSerializerVisitor : public RawValueVisitor { public: using RawValueVisitor::Visit; - /** - * Constructor to use when expecting to serialize any value. - */ - PbSerializerVisitor(StringPool* source_pool, StringPool* symbol_pool, - pb::Value* out_pb_value) - : source_pool_(source_pool), - symbol_pool_(symbol_pool), - out_pb_value_(out_pb_value), - out_pb_item_(nullptr) {} - - /** - * Constructor to use when expecting to serialize an Item. - */ - PbSerializerVisitor(StringPool* sourcePool, StringPool* symbolPool, - pb::Item* outPbItem) - : source_pool_(sourcePool), - symbol_pool_(symbolPool), - out_pb_value_(nullptr), - out_pb_item_(outPbItem) {} + // Constructor to use when expecting to serialize any value. + PbSerializerVisitor(StringPool* source_pool, pb::Value* out_pb_value) + : source_pool_(source_pool), out_pb_value_(out_pb_value), out_pb_item_(nullptr) { + } + + // Constructor to use when expecting to serialize an Item. + PbSerializerVisitor(StringPool* sourcePool, pb::Item* outPbItem) + : source_pool_(sourcePool), out_pb_value_(nullptr), out_pb_item_(outPbItem) { + } void Visit(Reference* ref) override { SerializeReferenceToPb(*ref, pb_item()->mutable_ref()); } void Visit(String* str) override { - pb_item()->mutable_str()->set_idx(str->value.index()); + pb_item()->mutable_str()->set_value(*str->value); + } + + void Visit(RawString* str) override { + pb_item()->mutable_raw_str()->set_value(*str->value); } void Visit(StyledString* str) override { - pb_item()->mutable_str()->set_idx(str->value.index()); + pb::StyledString* pb_str = pb_item()->mutable_styled_str(); + pb_str->set_value(str->value->value); + + for (const StringPool::Span& span : str->value->spans) { + pb::StyledString::Span* pb_span = pb_str->add_span(); + pb_span->set_tag(*span.name); + pb_span->set_first_char(span.first_char); + pb_span->set_last_char(span.last_char); + } } void Visit(FileReference* file) override { - pb_item()->mutable_file()->set_path_idx(file->path.index()); + pb_item()->mutable_file()->set_path(*file->path); } - void Visit(Id* id) override { pb_item()->mutable_id(); } - - void Visit(RawString* raw_str) override { - pb_item()->mutable_raw_str()->set_idx(raw_str->value.index()); + void Visit(Id* /*id*/) override { + pb_item()->mutable_id(); } void Visit(BinaryPrimitive* prim) override { @@ -98,7 +98,7 @@ class PbSerializerVisitor : public RawValueVisitor { pb_attr->set_max_int(attr->max_int); for (auto& symbol : attr->symbols) { - pb::Attribute_Symbol* pb_symbol = pb_attr->add_symbols(); + pb::Attribute_Symbol* pb_symbol = pb_attr->add_symbol(); SerializeItemCommonToPb(symbol.symbol, pb_symbol); SerializeReferenceToPb(symbol.symbol, pb_symbol->mutable_name()); pb_symbol->set_value(symbol.value); @@ -114,12 +114,12 @@ class PbSerializerVisitor : public RawValueVisitor { } for (Style::Entry& entry : style->entries) { - pb::Style_Entry* pb_entry = pb_style->add_entries(); + pb::Style_Entry* pb_entry = pb_style->add_entry(); SerializeReferenceToPb(entry.key, pb_entry->mutable_key()); pb::Item* pb_item = pb_entry->mutable_item(); SerializeItemCommonToPb(entry.key, pb_entry); - PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_, pb_item); + PbSerializerVisitor sub_visitor(source_pool_, pb_item); entry.value->Accept(&sub_visitor); } } @@ -127,7 +127,7 @@ class PbSerializerVisitor : public RawValueVisitor { void Visit(Styleable* styleable) override { pb::Styleable* pb_styleable = pb_compound_value()->mutable_styleable(); for (Reference& entry : styleable->entries) { - pb::Styleable_Entry* pb_entry = pb_styleable->add_entries(); + pb::Styleable_Entry* pb_entry = pb_styleable->add_entry(); SerializeItemCommonToPb(entry, pb_entry); SerializeReferenceToPb(entry, pb_entry->mutable_attr()); } @@ -135,11 +135,10 @@ class PbSerializerVisitor : public RawValueVisitor { void Visit(Array* array) override { pb::Array* pb_array = pb_compound_value()->mutable_array(); - for (auto& value : array->items) { - pb::Array_Entry* pb_entry = pb_array->add_entries(); - SerializeItemCommonToPb(*value, pb_entry); - PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_, - pb_entry->mutable_item()); + for (auto& value : array->elements) { + pb::Array_Element* pb_element = pb_array->add_element(); + SerializeItemCommonToPb(*value, pb_element); + PbSerializerVisitor sub_visitor(source_pool_, pb_element->mutable_item()); value->Accept(&sub_visitor); } } @@ -153,11 +152,11 @@ class PbSerializerVisitor : public RawValueVisitor { continue; } - pb::Plural_Entry* pb_entry = pb_plural->add_entries(); + pb::Plural_Entry* pb_entry = pb_plural->add_entry(); pb_entry->set_arity(SerializePluralEnumToPb(i)); pb::Item* pb_element = pb_entry->mutable_item(); SerializeItemCommonToPb(*plural->values[i], pb_entry); - PbSerializerVisitor sub_visitor(source_pool_, symbol_pool_, pb_element); + PbSerializerVisitor sub_visitor(source_pool_, pb_element); plural->values[i]->Accept(&sub_visitor); } } @@ -179,8 +178,7 @@ class PbSerializerVisitor : public RawValueVisitor { template <typename T> void SerializeItemCommonToPb(const Item& item, T* pb_item) { - SerializeSourceToPb(item.GetSource(), source_pool_, - pb_item->mutable_source()); + SerializeSourceToPb(item.GetSource(), source_pool_, pb_item->mutable_source()); if (!item.GetComment().empty()) { pb_item->set_comment(item.GetComment()); } @@ -192,8 +190,7 @@ class PbSerializerVisitor : public RawValueVisitor { } if (ref.name) { - StringPool::Ref symbol_ref = symbol_pool_->MakeRef(ref.name.value().ToString()); - pb_ref->set_symbol_idx(static_cast<uint32_t>(symbol_ref.index())); + pb_ref->set_name(ref.name.value().ToString()); } pb_ref->set_private_(ref.private_reference); @@ -201,7 +198,6 @@ class PbSerializerVisitor : public RawValueVisitor { } StringPool* source_pool_; - StringPool* symbol_pool_; pb::Value* out_pb_value_; pb::Item* out_pb_item_; }; @@ -220,26 +216,24 @@ std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) { }); auto pb_table = util::make_unique<pb::ResourceTable>(); - SerializeStringPoolToPb(table->string_pool, pb_table->mutable_string_pool()); - - StringPool source_pool, symbol_pool; + StringPool source_pool; for (auto& package : table->packages) { - pb::Package* pb_package = pb_table->add_packages(); + pb::Package* pb_package = pb_table->add_package(); if (package->id) { pb_package->set_package_id(package->id.value()); } pb_package->set_package_name(package->name); for (auto& type : package->types) { - pb::Type* pb_type = pb_package->add_types(); + pb::Type* pb_type = pb_package->add_type(); if (type->id) { pb_type->set_id(type->id.value()); } pb_type->set_name(ToString(type->type).to_string()); for (auto& entry : type->entries) { - pb::Entry* pb_entry = pb_type->add_entries(); + pb::Entry* pb_entry = pb_type->add_entry(); if (entry->id) { pb_entry->set_id(entry->id.value()); } @@ -253,7 +247,7 @@ std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) { pb_status->set_allow_new(entry->symbol_status.allow_new); for (auto& config_value : entry->values) { - pb::ConfigValue* pb_config_value = pb_entry->add_config_values(); + pb::ConfigValue* pb_config_value = pb_entry->add_config_value(); SerializeConfig(config_value->config, pb_config_value->mutable_config()); if (!config_value->product.empty()) { pb_config_value->mutable_config()->set_product(config_value->product); @@ -270,7 +264,7 @@ std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) { pb_value->set_weak(true); } - PbSerializerVisitor visitor(&source_pool, &symbol_pool, pb_value); + PbSerializerVisitor visitor(&source_pool, pb_value); config_value->value->Accept(&visitor); } } @@ -278,27 +272,25 @@ std::unique_ptr<pb::ResourceTable> SerializeTableToPb(ResourceTable* table) { } SerializeStringPoolToPb(source_pool, pb_table->mutable_source_pool()); - SerializeStringPoolToPb(symbol_pool, pb_table->mutable_symbol_pool()); return pb_table; } -std::unique_ptr<pb::CompiledFile> SerializeCompiledFileToPb( - const ResourceFile& file) { - auto pb_file = util::make_unique<pb::CompiledFile>(); +std::unique_ptr<pb::internal::CompiledFile> SerializeCompiledFileToPb(const ResourceFile& file) { + auto pb_file = util::make_unique<pb::internal::CompiledFile>(); pb_file->set_resource_name(file.name.ToString()); pb_file->set_source_path(file.source.path); SerializeConfig(file.config, pb_file->mutable_config()); for (const SourcedResourceName& exported : file.exported_symbols) { - pb::CompiledFile_Symbol* pb_symbol = pb_file->add_exported_symbols(); + pb::internal::CompiledFile_Symbol* pb_symbol = pb_file->add_exported_symbol(); pb_symbol->set_resource_name(exported.name.ToString()); - pb_symbol->set_line_no(exported.line); + pb_symbol->mutable_source()->set_line_number(exported.line); } return pb_file; } -CompiledFileOutputStream::CompiledFileOutputStream(ZeroCopyOutputStream* out) - : out_(out) {} +CompiledFileOutputStream::CompiledFileOutputStream(ZeroCopyOutputStream* out) : out_(out) { +} void CompiledFileOutputStream::EnsureAlignedWrite() { const int padding = out_.ByteCount() % 4; @@ -313,8 +305,7 @@ void CompiledFileOutputStream::WriteLittleEndian32(uint32_t val) { out_.WriteLittleEndian32(val); } -void CompiledFileOutputStream::WriteCompiledFile( - const pb::CompiledFile* compiled_file) { +void CompiledFileOutputStream::WriteCompiledFile(const pb::internal::CompiledFile* compiled_file) { EnsureAlignedWrite(); out_.WriteLittleEndian64(static_cast<uint64_t>(compiled_file->ByteSize())); compiled_file->SerializeWithCachedSizes(&out_); @@ -334,7 +325,9 @@ void CompiledFileOutputStream::WriteData(const void* data, size_t len) { out_.WriteRaw(data, len); } -bool CompiledFileOutputStream::HadError() { return out_.HadError(); } +bool CompiledFileOutputStream::HadError() { + return out_.HadError(); +} CompiledFileInputStream::CompiledFileInputStream(const void* data, size_t size) : in_(static_cast<const uint8_t*>(data), size) {} @@ -352,7 +345,7 @@ bool CompiledFileInputStream::ReadLittleEndian32(uint32_t* out_val) { return in_.ReadLittleEndian32(out_val); } -bool CompiledFileInputStream::ReadCompiledFile(pb::CompiledFile* out_val) { +bool CompiledFileInputStream::ReadCompiledFile(pb::internal::CompiledFile* out_val) { EnsureAlignedRead(); google::protobuf::uint64 pb_size = 0u; @@ -379,8 +372,7 @@ bool CompiledFileInputStream::ReadCompiledFile(pb::CompiledFile* out_val) { return true; } -bool CompiledFileInputStream::ReadDataMetaData(uint64_t* out_offset, - uint64_t* out_len) { +bool CompiledFileInputStream::ReadDataMetaData(uint64_t* out_offset, uint64_t* out_len) { EnsureAlignedRead(); google::protobuf::uint64 pb_size = 0u; diff --git a/tools/aapt2/proto/TableProtoSerializer_test.cpp b/tools/aapt2/proto/TableProtoSerializer_test.cpp index 3ebb08eb791e..80608b3d9c05 100644 --- a/tools/aapt2/proto/TableProtoSerializer_test.cpp +++ b/tools/aapt2/proto/TableProtoSerializer_test.cpp @@ -20,7 +20,9 @@ #include "test/Test.h" using ::google::protobuf::io::StringOutputStream; +using ::testing::Eq; using ::testing::NotNull; +using ::testing::SizeIs; namespace aapt { @@ -38,12 +40,12 @@ TEST(TableProtoSerializer, SerializeSinglePackage) { Symbol public_symbol; public_symbol.state = SymbolState::kPublic; - ASSERT_TRUE(table->SetSymbolState( - test::ParseNameOrDie("com.app.a:layout/main"), ResourceId(0x7f020000), - public_symbol, context->GetDiagnostics())); + ASSERT_TRUE(table->SetSymbolState(test::ParseNameOrDie("com.app.a:layout/main"), + ResourceId(0x7f020000), public_symbol, + context->GetDiagnostics())); Id* id = test::GetValue<Id>(table.get(), "com.app.a:id/foo"); - ASSERT_NE(nullptr, id); + ASSERT_THAT(id, NotNull()); // Make a plural. std::unique_ptr<Plural> plural = util::make_unique<Plural>(); @@ -52,6 +54,15 @@ TEST(TableProtoSerializer, SerializeSinglePackage) { ConfigDescription{}, {}, std::move(plural), context->GetDiagnostics())); + // Make a styled string. + StyleString style_string; + style_string.str = "hello"; + style_string.spans.push_back(Span{"b", 0u, 4u}); + ASSERT_TRUE( + table->AddResource(test::ParseNameOrDie("com.app.a:string/styled"), ConfigDescription{}, {}, + util::make_unique<StyledString>(table->string_pool.MakeRef(style_string)), + context->GetDiagnostics())); + // Make a resource with different products. ASSERT_TRUE(table->AddResource( test::ParseNameOrDie("com.app.a:integer/one"), @@ -65,9 +76,8 @@ TEST(TableProtoSerializer, SerializeSinglePackage) { context->GetDiagnostics())); // Make a reference with both resource name and resource ID. - // The reference should point to a resource outside of this table to test that - // both - // name and id get serialized. + // The reference should point to a resource outside of this table to test that both name and id + // get serialized. Reference expected_ref; expected_ref.name = test::ParseNameOrDie("android:layout/main"); expected_ref.id = ResourceId(0x01020000); @@ -85,36 +95,45 @@ TEST(TableProtoSerializer, SerializeSinglePackage) { Id* new_id = test::GetValue<Id>(new_table.get(), "com.app.a:id/foo"); ASSERT_THAT(new_id, NotNull()); - EXPECT_EQ(id->IsWeak(), new_id->IsWeak()); + EXPECT_THAT(new_id->IsWeak(), Eq(id->IsWeak())); Maybe<ResourceTable::SearchResult> result = new_table->FindResource(test::ParseNameOrDie("com.app.a:layout/main")); ASSERT_TRUE(result); - EXPECT_EQ(SymbolState::kPublic, result.value().type->symbol_status.state); - EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbol_status.state); + + EXPECT_THAT(result.value().type->symbol_status.state, Eq(SymbolState::kPublic)); + EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kPublic)); result = new_table->FindResource(test::ParseNameOrDie("com.app.a:bool/foo")); ASSERT_TRUE(result); - EXPECT_EQ(SymbolState::kUndefined, result.value().entry->symbol_status.state); + EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kUndefined)); EXPECT_TRUE(result.value().entry->symbol_status.allow_new); // Find the product-dependent values BinaryPrimitive* prim = test::GetValueForConfigAndProduct<BinaryPrimitive>( new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"), ""); ASSERT_THAT(prim, NotNull()); - EXPECT_EQ(123u, prim->value.data); + EXPECT_THAT(prim->value.data, Eq(123u)); prim = test::GetValueForConfigAndProduct<BinaryPrimitive>( new_table.get(), "com.app.a:integer/one", test::ParseConfigOrDie("land"), "tablet"); ASSERT_THAT(prim, NotNull()); - EXPECT_EQ(321u, prim->value.data); + EXPECT_THAT(prim->value.data, Eq(321u)); Reference* actual_ref = test::GetValue<Reference>(new_table.get(), "com.app.a:layout/abc"); ASSERT_THAT(actual_ref, NotNull()); ASSERT_TRUE(actual_ref->name); ASSERT_TRUE(actual_ref->id); - EXPECT_EQ(expected_ref.name.value(), actual_ref->name.value()); - EXPECT_EQ(expected_ref.id.value(), actual_ref->id.value()); + EXPECT_THAT(*actual_ref, Eq(expected_ref)); + + StyledString* actual_styled_str = + test::GetValue<StyledString>(new_table.get(), "com.app.a:string/styled"); + ASSERT_THAT(actual_styled_str, NotNull()); + EXPECT_THAT(actual_styled_str->value->value, Eq("hello")); + ASSERT_THAT(actual_styled_str->value->spans, SizeIs(1u)); + EXPECT_THAT(*actual_styled_str->value->spans[0].name, Eq("b")); + EXPECT_THAT(actual_styled_str->value->spans[0].first_char, Eq(0u)); + EXPECT_THAT(actual_styled_str->value->spans[0].last_char, Eq(4u)); } TEST(TableProtoSerializer, SerializeFileHeader) { @@ -132,10 +151,10 @@ TEST(TableProtoSerializer, SerializeFileHeader) { std::string output_str; { - std::unique_ptr<pb::CompiledFile> pb_file1 = SerializeCompiledFileToPb(f); + std::unique_ptr<pb::internal::CompiledFile> pb_file1 = SerializeCompiledFileToPb(f); f.name.entry = "__" + f.name.entry + "$0"; - std::unique_ptr<pb::CompiledFile> pb_file2 = SerializeCompiledFileToPb(f); + std::unique_ptr<pb::internal::CompiledFile> pb_file2 = SerializeCompiledFileToPb(f); StringOutputStream out_stream(&output_str); CompiledFileOutputStream out_file_stream(&out_stream); @@ -154,7 +173,7 @@ TEST(TableProtoSerializer, SerializeFileHeader) { // Read the first compiled file. - pb::CompiledFile new_pb_file; + pb::internal::CompiledFile new_pb_file; ASSERT_TRUE(in_file_stream.ReadCompiledFile(&new_pb_file)); std::unique_ptr<ResourceFile> file = DeserializeCompiledFileFromPb( @@ -191,7 +210,7 @@ TEST(TableProtoSerializer, SerializeFileHeader) { TEST(TableProtoSerializer, DeserializeCorruptHeaderSafely) { ResourceFile f; - std::unique_ptr<pb::CompiledFile> pb_file = SerializeCompiledFileToPb(f); + std::unique_ptr<pb::internal::CompiledFile> pb_file = SerializeCompiledFileToPb(f); const std::string expected_data = "1234"; @@ -213,7 +232,7 @@ TEST(TableProtoSerializer, DeserializeCorruptHeaderSafely) { EXPECT_TRUE(in_file_stream.ReadLittleEndian32(&num_files)); EXPECT_EQ(1u, num_files); - pb::CompiledFile new_pb_file; + pb::internal::CompiledFile new_pb_file; EXPECT_FALSE(in_file_stream.ReadCompiledFile(&new_pb_file)); uint64_t offset, len; diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md index c8d36177beb4..2645d54867de 100644 --- a/tools/aapt2/readme.md +++ b/tools/aapt2/readme.md @@ -1,5 +1,8 @@ # Android Asset Packaging Tool 2.0 (AAPT2) release notes +## Version 2.19 +- Added navigation resource type. + ## Version 2.18 ### `aapt2 ...` - Fixed issue where enum values were interpreted as integers and range checked. (bug 62358540) diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp index 27e13d81ff49..9d49ca6c0aa9 100644 --- a/tools/aapt2/split/TableSplitter.cpp +++ b/tools/aapt2/split/TableSplitter.cpp @@ -32,8 +32,7 @@ namespace aapt { using ConfigClaimedMap = std::unordered_map<ResourceConfigValue*, bool>; -using ConfigDensityGroups = - std::map<ConfigDescription, std::vector<ResourceConfigValue*>>; +using ConfigDensityGroups = std::map<ConfigDescription, std::vector<ResourceConfigValue*>>; static ConfigDescription CopyWithoutDensity(const ConfigDescription& config) { ConfigDescription without_density = config; @@ -51,8 +50,7 @@ class SplitValueSelector { if (config.density == 0) { density_independent_configs_.insert(config); } else { - density_dependent_config_to_density_map_[CopyWithoutDensity(config)] = - config.density; + density_dependent_config_to_density_map_[CopyWithoutDensity(config)] = config.density; } } } @@ -94,9 +92,7 @@ class SplitValueSelector { ResourceConfigValue* best_value = nullptr; for (ResourceConfigValue* this_value : related_values) { - if (!best_value || - this_value->config.isBetterThan(best_value->config, - &target_density)) { + if (!best_value || this_value->config.isBetterThan(best_value->config, &target_density)) { best_value = this_value; } } @@ -120,9 +116,8 @@ class SplitValueSelector { }; /** - * Marking non-preferred densities as claimed will make sure the base doesn't - * include them, - * leaving only the preferred density behind. + * Marking non-preferred densities as claimed will make sure the base doesn't include them, leaving + * only the preferred density behind. */ static void MarkNonPreferredDensitiesAsClaimed( const std::vector<uint16_t>& preferred_densities, const ConfigDensityGroups& density_groups, @@ -161,8 +156,7 @@ bool TableSplitter::VerifySplitConstraints(IAaptContext* context) { for (size_t i = 0; i < split_constraints_.size(); i++) { for (size_t j = i + 1; j < split_constraints_.size(); j++) { for (const ConfigDescription& config : split_constraints_[i].configs) { - if (split_constraints_[j].configs.find(config) != - split_constraints_[j].configs.end()) { + if (split_constraints_[j].configs.find(config) != split_constraints_[j].configs.end()) { context->GetDiagnostics()->Error(DiagMessage() << "config '" << config << "' appears in multiple splits, " @@ -193,28 +187,22 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { for (auto& entry : type->entries) { if (options_.config_filter) { // First eliminate any resource that we definitely don't want. - for (std::unique_ptr<ResourceConfigValue>& config_value : - entry->values) { + for (std::unique_ptr<ResourceConfigValue>& config_value : entry->values) { if (!options_.config_filter->Match(config_value->config)) { - // null out the entry. We will clean up and remove nulls at the - // end for performance reasons. + // null out the entry. We will clean up and remove nulls at the end for performance + // reasons. config_value.reset(); } } } - // Organize the values into two separate buckets. Those that are - // density-dependent - // and those that are density-independent. - // One density technically matches all density, it's just that some - // densities - // match better. So we need to be aware of the full set of densities to - // make this - // decision. + // Organize the values into two separate buckets. Those that are density-dependent and those + // that are density-independent. One density technically matches all density, it's just that + // some densities match better. So we need to be aware of the full set of densities to make + // this decision. ConfigDensityGroups density_groups; ConfigClaimedMap config_claimed_map; - for (const std::unique_ptr<ResourceConfigValue>& config_value : - entry->values) { + for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) { if (config_value) { config_claimed_map[config_value.get()] = false; @@ -226,9 +214,8 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { } } - // First we check all the splits. If it doesn't match one of the splits, - // we - // leave it in the base. + // First we check all the splits. If it doesn't match one of the splits, we leave it in the + // base. for (size_t idx = 0; idx < split_count; idx++) { const SplitConstraints& split_constraint = split_constraints_[idx]; ResourceTable* split_table = splits_[idx].get(); @@ -240,20 +227,16 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { // No need to do any work if we selected nothing. if (!selected_values.empty()) { - // Create the same resource structure in the split. We do this - // lazily because we might not have actual values for each - // type/entry. - ResourceTablePackage* split_pkg = - split_table->FindPackage(pkg->name); - ResourceTableType* split_type = - split_pkg->FindOrCreateType(type->type); + // Create the same resource structure in the split. We do this lazily because we might + // not have actual values for each type/entry. + ResourceTablePackage* split_pkg = split_table->FindPackage(pkg->name); + ResourceTableType* split_type = split_pkg->FindOrCreateType(type->type); if (!split_type->id) { split_type->id = type->id; split_type->symbol_status = type->symbol_status; } - ResourceEntry* split_entry = - split_type->FindOrCreateEntry(entry->name); + ResourceEntry* split_entry = split_type->FindOrCreateEntry(entry->name); if (!split_entry->id) { split_entry->id = entry->id; split_entry->symbol_status = entry->symbol_status; @@ -262,8 +245,7 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { // Copy the selected values into the new Split Entry. for (ResourceConfigValue* config_value : selected_values) { ResourceConfigValue* new_config_value = - split_entry->FindOrCreateValue(config_value->config, - config_value->product); + split_entry->FindOrCreateValue(config_value->config, config_value->product); new_config_value->value = std::unique_ptr<Value>( config_value->value->Clone(&split_table->string_pool)); } @@ -276,11 +258,9 @@ void TableSplitter::SplitTable(ResourceTable* original_table) { &config_claimed_map); } - // All splits are handled, now check to see what wasn't claimed and - // remove - // whatever exists in other splits. - for (std::unique_ptr<ResourceConfigValue>& config_value : - entry->values) { + // All splits are handled, now check to see what wasn't claimed and remove whatever exists + // in other splits. + for (std::unique_ptr<ResourceConfigValue>& config_value : entry->values) { if (config_value && config_claimed_map[config_value.get()]) { // Claimed, remove from base. config_value.reset(); diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp new file mode 100644 index 000000000000..57e3df442e3c --- /dev/null +++ b/tools/aapt2/test/Builders.cpp @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "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; +} + +PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::SetAbiGroup( + const std::string& name, const std::vector<configuration::Abi>& abis) { + config_.abi_groups[name] = abis; + return *this; +} + +PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::SetLocaleGroup( + const std::string& name, const std::vector<std::string>& locales) { + auto& group = config_.locale_groups[name]; + for (const auto& locale : locales) { + group.push_back(ParseConfigOrDie(locale)); + } + return *this; +} + +PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::SetDensityGroup( + const std::string& name, const std::vector<std::string>& densities) { + auto& group = config_.screen_density_groups[name]; + for (const auto& density : densities) { + group.push_back(ParseConfigOrDie(density)); + } + return *this; +} + +PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddArtifact( + const configuration::Artifact& artifact) { + config_.artifacts.push_back(artifact); + return *this; +} + +configuration::PostProcessingConfiguration PostProcessingConfigurationBuilder::Build() { + return config_; +} + +ArtifactBuilder& ArtifactBuilder::SetName(const std::string& name) { + artifact_.name = {name}; + return *this; +} + +ArtifactBuilder& ArtifactBuilder::SetAbiGroup(const std::string& name) { + artifact_.abi_group = {name}; + return *this; +} + +ArtifactBuilder& ArtifactBuilder::SetDensityGroup(const std::string& name) { + artifact_.screen_density_group = {name}; + return *this; +} + +ArtifactBuilder& ArtifactBuilder::SetLocaleGroup(const std::string& name) { + artifact_.locale_group = {name}; + return *this; +} + +configuration::Artifact ArtifactBuilder::Build() { + return artifact_; +} + +} // namespace test +} // namespace aapt diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index 6b8207647471..e8cefc1853b6 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -19,13 +19,15 @@ #include <memory> -#include "android-base/logging.h" #include "android-base/macros.h" +#include "Resource.h" #include "ResourceTable.h" #include "ResourceValues.h" +#include "configuration/ConfigurationParser.h" +#include "process/IResourceTableConsumer.h" #include "test/Common.h" -#include "util/Util.h" +#include "util/Maybe.h" #include "xml/XmlDom.h" namespace aapt { @@ -35,97 +37,37 @@ class ResourceTableBuilder { public: ResourceTableBuilder() = default; - StringPool* string_pool() { return &table_->string_pool; } - - ResourceTableBuilder& SetPackageId(const android::StringPiece& package_name, uint8_t id) { - ResourceTablePackage* package = table_->CreatePackage(package_name, id); - CHECK(package != nullptr); - return *this; - } - - ResourceTableBuilder& AddSimple(const android::StringPiece& name, const ResourceId& id = {}) { - return AddValue(name, id, util::make_unique<Id>()); - } - + ResourceTableBuilder& SetPackageId(const android::StringPiece& package_name, uint8_t id); + ResourceTableBuilder& AddSimple(const android::StringPiece& name, const ResourceId& id = {}); ResourceTableBuilder& AddSimple(const android::StringPiece& name, const ConfigDescription& config, - const ResourceId& id = {}) { - return AddValue(name, config, id, util::make_unique<Id>()); - } - + const ResourceId& id = {}); ResourceTableBuilder& AddReference(const android::StringPiece& name, - const android::StringPiece& ref) { - return AddReference(name, {}, ref); - } - + const android::StringPiece& ref); ResourceTableBuilder& AddReference(const android::StringPiece& name, const ResourceId& id, - const android::StringPiece& ref) { - return AddValue(name, id, util::make_unique<Reference>(ParseNameOrDie(ref))); - } - + const android::StringPiece& ref); ResourceTableBuilder& AddString(const android::StringPiece& name, - const android::StringPiece& str) { - return AddString(name, {}, str); - } - + const android::StringPiece& str); ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id, - const android::StringPiece& str) { - return AddValue( - name, id, util::make_unique<String>(table_->string_pool.MakeRef(str))); - } - + const android::StringPiece& str); ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id, - const ConfigDescription& config, - const android::StringPiece& str) { - return AddValue(name, config, id, util::make_unique<String>( - table_->string_pool.MakeRef(str))); - } - + const ConfigDescription& config, const android::StringPiece& str); ResourceTableBuilder& AddFileReference(const android::StringPiece& name, - const android::StringPiece& path) { - return AddFileReference(name, {}, path); - } - + const android::StringPiece& path); ResourceTableBuilder& AddFileReference(const android::StringPiece& name, const ResourceId& id, - const android::StringPiece& path) { - return AddValue(name, id, util::make_unique<FileReference>( - table_->string_pool.MakeRef(path))); - } - + const android::StringPiece& path); ResourceTableBuilder& AddFileReference(const android::StringPiece& name, const android::StringPiece& path, - const ConfigDescription& config) { - return AddValue(name, config, {}, util::make_unique<FileReference>( - table_->string_pool.MakeRef(path))); - } - - ResourceTableBuilder& AddValue(const android::StringPiece& name, std::unique_ptr<Value> value) { - return AddValue(name, {}, std::move(value)); - } - + const ConfigDescription& config); + ResourceTableBuilder& AddValue(const android::StringPiece& name, std::unique_ptr<Value> value); ResourceTableBuilder& AddValue(const android::StringPiece& name, const ResourceId& id, - std::unique_ptr<Value> value) { - return AddValue(name, {}, id, std::move(value)); - } - + std::unique_ptr<Value> value); ResourceTableBuilder& AddValue(const android::StringPiece& name, const ConfigDescription& config, - const ResourceId& id, std::unique_ptr<Value> value) { - ResourceName res_name = ParseNameOrDie(name); - CHECK(table_->AddResourceAllowMangled(res_name, id, config, {}, std::move(value), - GetDiagnostics())); - return *this; - } - + const ResourceId& id, std::unique_ptr<Value> value); ResourceTableBuilder& SetSymbolState(const android::StringPiece& name, const ResourceId& id, - SymbolState state, bool allow_new = false) { - ResourceName res_name = ParseNameOrDie(name); - Symbol symbol; - symbol.state = state; - symbol.allow_new = allow_new; - CHECK(table_->SetSymbolStateAllowMangled(res_name, id, symbol, GetDiagnostics())); - return *this; - } + SymbolState state, bool allow_new = false); - std::unique_ptr<ResourceTable> Build() { return std::move(table_); } + StringPool* string_pool(); + std::unique_ptr<ResourceTable> Build(); private: DISALLOW_COPY_AND_ASSIGN(ResourceTableBuilder); @@ -133,29 +75,16 @@ class ResourceTableBuilder { std::unique_ptr<ResourceTable> table_ = util::make_unique<ResourceTable>(); }; -inline std::unique_ptr<Reference> BuildReference(const android::StringPiece& ref, - const Maybe<ResourceId>& id = {}) { - std::unique_ptr<Reference> reference = - util::make_unique<Reference>(ParseNameOrDie(ref)); - reference->id = id; - return reference; -} - -inline std::unique_ptr<BinaryPrimitive> BuildPrimitive(uint8_t type, - uint32_t data) { - android::Res_value value = {}; - value.size = sizeof(value); - value.dataType = type; - value.data = data; - return util::make_unique<BinaryPrimitive>(value); -} +std::unique_ptr<Reference> BuildReference(const android::StringPiece& ref, + const Maybe<ResourceId>& id = {}); +std::unique_ptr<BinaryPrimitive> BuildPrimitive(uint8_t type, uint32_t data); template <typename T> class ValueBuilder { public: template <typename... Args> - explicit ValueBuilder(Args&&... args) - : value_(new T{std::forward<Args>(args)...}) {} + explicit ValueBuilder(Args&&... args) : value_(new T{std::forward<Args>(args)...}) { + } template <typename... Args> ValueBuilder& SetSource(Args&&... args) { @@ -168,7 +97,9 @@ class ValueBuilder { return *this; } - std::unique_ptr<Value> Build() { return std::move(value_); } + std::unique_ptr<Value> Build() { + return std::move(value_); + } private: DISALLOW_COPY_AND_ASSIGN(ValueBuilder); @@ -178,23 +109,10 @@ class ValueBuilder { class AttributeBuilder { public: - explicit AttributeBuilder(bool weak = false) - : attr_(util::make_unique<Attribute>(weak)) { - attr_->type_mask = android::ResTable_map::TYPE_ANY; - } - - AttributeBuilder& SetTypeMask(uint32_t typeMask) { - attr_->type_mask = typeMask; - return *this; - } - - AttributeBuilder& AddItem(const android::StringPiece& name, uint32_t value) { - attr_->symbols.push_back(Attribute::Symbol{ - Reference(ResourceName({}, ResourceType::kId, name)), value}); - return *this; - } - - std::unique_ptr<Attribute> Build() { return std::move(attr_); } + explicit AttributeBuilder(bool weak = false); + AttributeBuilder& SetTypeMask(uint32_t typeMask); + AttributeBuilder& AddItem(const android::StringPiece& name, uint32_t value); + std::unique_ptr<Attribute> Build(); private: DISALLOW_COPY_AND_ASSIGN(AttributeBuilder); @@ -205,27 +123,11 @@ class AttributeBuilder { class StyleBuilder { public: StyleBuilder() = default; - - StyleBuilder& SetParent(const android::StringPiece& str) { - style_->parent = Reference(ParseNameOrDie(str)); - return *this; - } - - StyleBuilder& AddItem(const android::StringPiece& str, std::unique_ptr<Item> value) { - style_->entries.push_back(Style::Entry{Reference(ParseNameOrDie(str)), std::move(value)}); - return *this; - } - + StyleBuilder& SetParent(const android::StringPiece& str); + StyleBuilder& AddItem(const android::StringPiece& str, std::unique_ptr<Item> value); StyleBuilder& AddItem(const android::StringPiece& str, const ResourceId& id, - std::unique_ptr<Item> value) { - AddItem(str, std::move(value)); - style_->entries.back().key.id = id; - return *this; - } - - std::unique_ptr<Style> Build() { - return std::move(style_); - } + std::unique_ptr<Item> value); + std::unique_ptr<Style> Build(); private: DISALLOW_COPY_AND_ASSIGN(StyleBuilder); @@ -236,14 +138,8 @@ class StyleBuilder { class StyleableBuilder { public: StyleableBuilder() = default; - - StyleableBuilder& AddItem(const android::StringPiece& str, const Maybe<ResourceId>& id = {}) { - styleable_->entries.push_back(Reference(ParseNameOrDie(str))); - styleable_->entries.back().id = id; - return *this; - } - - std::unique_ptr<Styleable> Build() { return std::move(styleable_); } + StyleableBuilder& AddItem(const android::StringPiece& str, const Maybe<ResourceId>& id = {}); + std::unique_ptr<Styleable> Build(); private: DISALLOW_COPY_AND_ASSIGN(StyleableBuilder); @@ -251,22 +147,40 @@ class StyleableBuilder { std::unique_ptr<Styleable> styleable_ = util::make_unique<Styleable>(); }; -inline std::unique_ptr<xml::XmlResource> BuildXmlDom(const android::StringPiece& str) { - std::stringstream in; - in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str; - StdErrDiagnostics diag; - std::unique_ptr<xml::XmlResource> doc = - xml::Inflate(&in, &diag, Source("test.xml")); - CHECK(doc != nullptr) << "failed to parse inline XML string"; - return doc; -} - -inline std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName( - IAaptContext* context, const android::StringPiece& str) { - std::unique_ptr<xml::XmlResource> doc = BuildXmlDom(str); - doc->file.name.package = context->GetCompilationPackage(); - return doc; -} +std::unique_ptr<xml::XmlResource> BuildXmlDom(const android::StringPiece& str); +std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* context, + const android::StringPiece& str); + +class PostProcessingConfigurationBuilder { + public: + PostProcessingConfigurationBuilder() = default; + + PostProcessingConfigurationBuilder& SetAbiGroup(const std::string& name, + const std::vector<configuration::Abi>& abis); + PostProcessingConfigurationBuilder& SetLocaleGroup(const std::string& name, + const std::vector<std::string>& locales); + PostProcessingConfigurationBuilder& SetDensityGroup(const std::string& name, + const std::vector<std::string>& densities); + PostProcessingConfigurationBuilder& AddArtifact(const configuration::Artifact& artifact); + configuration::PostProcessingConfiguration Build(); + + private: + configuration::PostProcessingConfiguration config_; +}; + +class ArtifactBuilder { + public: + ArtifactBuilder() = default; + + ArtifactBuilder& SetName(const std::string& name); + ArtifactBuilder& SetAbiGroup(const std::string& name); + ArtifactBuilder& SetDensityGroup(const std::string& name); + ArtifactBuilder& SetLocaleGroup(const std::string& name); + configuration::Artifact Build(); + + private: + configuration::Artifact artifact_; +}; } // namespace test } // namespace aapt diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h index d7b46caf8c94..7482b8b627d3 100644 --- a/tools/aapt2/test/Common.h +++ b/tools/aapt2/test/Common.h @@ -41,7 +41,7 @@ IDiagnostics* GetDiagnostics(); inline ResourceName ParseNameOrDie(const android::StringPiece& str) { ResourceNameRef ref; - CHECK(ResourceUtils::ParseResourceName(str, &ref)) << "invalid resource name"; + CHECK(ResourceUtils::ParseResourceName(str, &ref)) << "invalid resource name: " << str; return ref.ToResourceName(); } @@ -142,10 +142,97 @@ MATCHER_P(StrEq, a, return android::StringPiece16(arg) == a; } -MATCHER_P(ValueEq, a, - std::string(negation ? "isn't" : "is") + " equal to " + ::testing::PrintToString(a)) { - return arg.Equals(&a); -} +class ValueEq { + public: + template <typename arg_type> + class BaseImpl : public ::testing::MatcherInterface<arg_type> { + BaseImpl(const BaseImpl&) = default; + + void DescribeTo(::std::ostream* os) const override { + *os << "is equal to " << *expected_; + } + + void DescribeNegationTo(::std::ostream* os) const override { + *os << "is not equal to " << *expected_; + } + + protected: + BaseImpl(const Value* expected) : expected_(expected) { + } + + const Value* expected_; + }; + + template <typename T, bool> + class Impl {}; + + template <typename T> + class Impl<T, false> : public ::testing::MatcherInterface<T> { + public: + explicit Impl(const Value* expected) : expected_(expected) { + } + + bool MatchAndExplain(T x, ::testing::MatchResultListener* listener) const override { + return expected_->Equals(&x); + } + + void DescribeTo(::std::ostream* os) const override { + *os << "is equal to " << *expected_; + } + + void DescribeNegationTo(::std::ostream* os) const override { + *os << "is not equal to " << *expected_; + } + + private: + DISALLOW_COPY_AND_ASSIGN(Impl); + + const Value* expected_; + }; + + template <typename T> + class Impl<T, true> : public ::testing::MatcherInterface<T> { + public: + explicit Impl(const Value* expected) : expected_(expected) { + } + + bool MatchAndExplain(T x, ::testing::MatchResultListener* listener) const override { + return expected_->Equals(x); + } + + void DescribeTo(::std::ostream* os) const override { + *os << "is equal to " << *expected_; + } + + void DescribeNegationTo(::std::ostream* os) const override { + *os << "is not equal to " << *expected_; + } + + private: + DISALLOW_COPY_AND_ASSIGN(Impl); + + const Value* expected_; + }; + + ValueEq(const Value& expected) : expected_(&expected) { + } + ValueEq(const Value* expected) : expected_(expected) { + } + ValueEq(const ValueEq&) = default; + + template <typename T> + operator ::testing::Matcher<T>() const { + return ::testing::Matcher<T>(new Impl<T, std::is_pointer<T>::value>(expected_)); + } + + private: + const Value* expected_; +}; + +// MATCHER_P(ValueEq, a, +// std::string(negation ? "isn't" : "is") + " equal to " + ::testing::PrintToString(a)) { +// return arg.Equals(&a); +//} MATCHER_P(StrValueEq, a, std::string(negation ? "isn't" : "is") + " equal to " + ::testing::PrintToString(a)) { diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp index 728d1f4207c4..892aee6fadcb 100644 --- a/tools/aapt2/unflatten/BinaryResourceParser.cpp +++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp @@ -544,7 +544,7 @@ std::unique_ptr<Array> BinaryResourceParser::ParseArray( const ResTable_map_entry* map) { std::unique_ptr<Array> array = util::make_unique<Array>(); for (const ResTable_map& map_entry : map) { - array->items.push_back(ParseValue(name, config, map_entry.value)); + array->elements.push_back(ParseValue(name, config, map_entry.value)); } return array; } diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp index 1bf25947ea93..bf8dc4d727f7 100644 --- a/tools/aapt2/util/Files.cpp +++ b/tools/aapt2/util/Files.cpp @@ -27,6 +27,8 @@ #include "android-base/errors.h" #include "android-base/file.h" #include "android-base/logging.h" +#include "android-base/unique_fd.h" +#include "android-base/utf8.h" #include "util/Util.h" @@ -35,14 +37,32 @@ #include <direct.h> #endif -using android::StringPiece; +using ::android::FileMap; +using ::android::StringPiece; +using ::android::base::ReadFileToString; +using ::android::base::SystemErrorCodeToString; +using ::android::base::unique_fd; namespace aapt { namespace file { -FileType GetFileType(const StringPiece& path) { +FileType GetFileType(const std::string& path) { +// TODO(adamlesinski): I'd like to move this to ::android::base::utf8 but Windows does some macro +// trickery with 'stat' and things don't override very well. +#ifdef _WIN32 + std::wstring path_utf16; + if (!::android::base::UTF8PathToWindowsLongPath(path.c_str(), &path_utf16)) { + return FileType::kNonexistant; + } + + struct _stat64 sb; + int result = _wstat64(path_utf16.c_str(), &sb); +#else struct stat sb; - if (stat(path.data(), &sb) < 0) { + int result = stat(path.c_str(), &sb); +#endif + + if (result == -1) { if (errno == ENOENT || errno == ENOTDIR) { return FileType::kNonexistant; } @@ -72,27 +92,20 @@ FileType GetFileType(const StringPiece& path) { } } -inline static int MkdirImpl(const StringPiece& path) { -#ifdef _WIN32 - return _mkdir(path.to_string().c_str()); -#else - return mkdir(path.to_string().c_str(), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP); -#endif -} - -bool mkdirs(const StringPiece& path) { - const char* start = path.begin(); - const char* end = path.end(); - for (const char* current = start; current != end; ++current) { - if (*current == sDirSep && current != start) { - StringPiece parent_path(start, current - start); - int result = MkdirImpl(parent_path); - if (result < 0 && errno != EEXIST) { - return false; - } +bool mkdirs(const std::string& path) { + constexpr const mode_t mode = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP; + // Start after the first character so that we don't consume the root '/'. + // This is safe to do with unicode because '/' will never match with a continuation character. + size_t current_pos = 1u; + while ((current_pos = path.find(sDirSep, current_pos)) != std::string::npos) { + std::string parent_path = path.substr(0, current_pos); + int result = ::android::base::utf8::mkdir(parent_path.c_str(), mode); + if (result < 0 && errno != EEXIST) { + return false; } + current_pos += 1; } - return MkdirImpl(path) == 0 || errno == EEXIST; + return ::android::base::utf8::mkdir(path.c_str(), mode) == 0 || errno == EEXIST; } StringPiece GetStem(const StringPiece& path) { @@ -129,10 +142,8 @@ StringPiece GetExtension(const StringPiece& path) { void AppendPath(std::string* base, StringPiece part) { CHECK(base != nullptr); - const bool base_has_trailing_sep = - (!base->empty() && *(base->end() - 1) == sDirSep); - const bool part_has_leading_sep = - (!part.empty() && *(part.begin()) == sDirSep); + const bool base_has_trailing_sep = (!base->empty() && *(base->end() - 1) == sDirSep); + const bool part_has_leading_sep = (!part.empty() && *(part.begin()) == sDirSep); if (base_has_trailing_sep && part_has_leading_sep) { // Remove the part's leading sep part = part.substr(1, part.size() - 1); @@ -151,31 +162,34 @@ std::string PackageToPath(const StringPiece& package) { return out_path; } -Maybe<android::FileMap> MmapPath(const StringPiece& path, - std::string* out_error) { - std::unique_ptr<FILE, decltype(fclose)*> f = {fopen(path.data(), "rb"), - fclose}; - if (!f) { - if (out_error) *out_error = android::base::SystemErrorCodeToString(errno); +Maybe<FileMap> MmapPath(const std::string& path, std::string* out_error) { + int flags = O_RDONLY | O_CLOEXEC | O_BINARY; + unique_fd fd(TEMP_FAILURE_RETRY(::android::base::utf8::open(path.c_str(), flags))); + if (fd == -1) { + if (out_error) { + *out_error = SystemErrorCodeToString(errno); + } return {}; } - int fd = fileno(f.get()); - struct stat filestats = {}; if (fstat(fd, &filestats) != 0) { - if (out_error) *out_error = android::base::SystemErrorCodeToString(errno); + if (out_error) { + *out_error = SystemErrorCodeToString(errno); + } return {}; } - android::FileMap filemap; + FileMap filemap; if (filestats.st_size == 0) { // mmap doesn't like a length of 0. Instead we return an empty FileMap. return std::move(filemap); } - if (!filemap.create(path.data(), fd, 0, filestats.st_size, true)) { - if (out_error) *out_error = android::base::SystemErrorCodeToString(errno); + if (!filemap.create(path.c_str(), fd, 0, filestats.st_size, true)) { + if (out_error) { + *out_error = SystemErrorCodeToString(errno); + } return {}; } return std::move(filemap); @@ -184,7 +198,7 @@ Maybe<android::FileMap> MmapPath(const StringPiece& path, bool AppendArgsFromFile(const StringPiece& path, std::vector<std::string>* out_arglist, std::string* out_error) { std::string contents; - if (!android::base::ReadFileToString(path.to_string(), &contents, true /*follow_symlinks*/)) { + if (!ReadFileToString(path.to_string(), &contents, true /*follow_symlinks*/)) { if (out_error) { *out_error = "failed to read argument-list file"; } @@ -270,7 +284,7 @@ Maybe<std::vector<std::string>> FindFiles(const android::StringPiece& path, IDia const std::string root_dir = path.to_string(); std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir); if (!d) { - diag->Error(DiagMessage() << android::base::SystemErrorCodeToString(errno)); + diag->Error(DiagMessage() << SystemErrorCodeToString(errno)); return {}; } diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h index b3b1e484d27b..b26e4fa26de6 100644 --- a/tools/aapt2/util/Files.h +++ b/tools/aapt2/util/Files.h @@ -34,8 +34,10 @@ namespace file { #ifdef _WIN32 constexpr const char sDirSep = '\\'; +constexpr const char sPathSep = ';'; #else constexpr const char sDirSep = '/'; +constexpr const char sPathSep = ':'; #endif enum class FileType { @@ -50,79 +52,56 @@ enum class FileType { kSocket, }; -FileType GetFileType(const android::StringPiece& path); +FileType GetFileType(const std::string& path); -/* - * Appends a path to `base`, separated by the directory separator. - */ +// Appends a path to `base`, separated by the directory separator. void AppendPath(std::string* base, android::StringPiece part); -/* - * Makes all the directories in `path`. The last element in the path - * is interpreted as a directory. - */ -bool mkdirs(const android::StringPiece& path); +// Makes all the directories in `path`. The last element in the path is interpreted as a directory. +bool mkdirs(const std::string& path); -/** - * Returns all but the last part of the path. - */ +// Returns all but the last part of the path. android::StringPiece GetStem(const android::StringPiece& path); -/** - * Returns the last part of the path with extension. - */ +// Returns the last part of the path with extension. android::StringPiece GetFilename(const android::StringPiece& path); -/** - * Returns the extension of the path. This is the entire string after - * the first '.' of the last part of the path. - */ +// Returns the extension of the path. This is the entire string after the first '.' of the last part +// of the path. android::StringPiece GetExtension(const android::StringPiece& path); -/** - * Converts a package name (com.android.app) to a path: com/android/app - */ +// Converts a package name (com.android.app) to a path: com/android/app std::string PackageToPath(const android::StringPiece& package); -/** - * Creates a FileMap for the file at path. - */ -Maybe<android::FileMap> MmapPath(const android::StringPiece& path, std::string* out_error); +// Creates a FileMap for the file at path. +Maybe<android::FileMap> MmapPath(const std::string& path, std::string* out_error); -/** - * Reads the file at path and appends each line to the outArgList vector. - */ +// Reads the file at path and appends each line to the outArgList vector. bool AppendArgsFromFile(const android::StringPiece& path, std::vector<std::string>* out_arglist, std::string* out_error); -/* - * Filter that determines which resource files/directories are - * processed by AAPT. Takes a pattern string supplied by the user. - * Pattern format is specified in the FileFilter::SetPattern() method. - */ +// Filter that determines which resource files/directories are +// processed by AAPT. Takes a pattern string supplied by the user. +// Pattern format is specified in the FileFilter::SetPattern() method. class FileFilter { public: explicit FileFilter(IDiagnostics* diag) : diag_(diag) {} - /* - * Patterns syntax: - * - Delimiter is : - * - Entry can start with the flag ! to avoid printing a warning - * about the file being ignored. - * - Entry can have the flag "<dir>" to match only directories - * or <file> to match only files. Default is to match both. - * - Entry can be a simplified glob "<prefix>*" or "*<suffix>" - * where prefix/suffix must have at least 1 character (so that - * we don't match a '*' catch-all pattern.) - * - The special filenames "." and ".." are always ignored. - * - Otherwise the full string is matched. - * - match is not case-sensitive. - */ + // Patterns syntax: + // - Delimiter is : + // - Entry can start with the flag ! to avoid printing a warning + // about the file being ignored. + // - Entry can have the flag "<dir>" to match only directories + // or <file> to match only files. Default is to match both. + // - Entry can be a simplified glob "<prefix>*" or "*<suffix>" + // where prefix/suffix must have at least 1 character (so that + // we don't match a '*' catch-all pattern.) + // - The special filenames "." and ".." are always ignored. + // - Otherwise the full string is matched. + // - match is not case-sensitive. bool SetPattern(const android::StringPiece& pattern); - /** - * Applies the filter, returning true for pass, false for fail. - */ + // Applies the filter, returning true for pass, false for fail. bool operator()(const std::string& filename, FileType type) const; private: diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp index 51a75d7556ad..a9b49d9d9904 100644 --- a/tools/aapt2/util/Util.cpp +++ b/tools/aapt2/util/Util.cpp @@ -520,11 +520,10 @@ bool Tokenizer::iterator::operator!=(const iterator& rhs) const { return !(*this == rhs); } -Tokenizer::iterator::iterator(StringPiece s, char sep, StringPiece tok, - bool end) +Tokenizer::iterator::iterator(const StringPiece& s, char sep, const StringPiece& tok, bool end) : str_(s), separator_(sep), token_(tok), end_(end) {} -Tokenizer::Tokenizer(StringPiece str, char sep) +Tokenizer::Tokenizer(const StringPiece& str, char sep) : begin_(++iterator(str, sep, StringPiece(str.begin() - 1, 0), false)), end_(str, sep, StringPiece(str.end(), 0), true) {} diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h index ad3989ea430f..c928458786e4 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -228,6 +228,12 @@ class Tokenizer { public: class iterator { public: + using reference = android::StringPiece&; + using value_type = android::StringPiece; + using difference_type = size_t; + using pointer = android::StringPiece*; + using iterator_category = std::forward_iterator_tag; + iterator(const iterator&) = default; iterator& operator=(const iterator&) = default; @@ -240,7 +246,7 @@ class Tokenizer { private: friend class Tokenizer; - iterator(android::StringPiece s, char sep, android::StringPiece tok, bool end); + iterator(const android::StringPiece& s, char sep, const android::StringPiece& tok, bool end); android::StringPiece str_; char separator_; @@ -248,11 +254,15 @@ class Tokenizer { bool end_; }; - Tokenizer(android::StringPiece str, char sep); + Tokenizer(const android::StringPiece& str, char sep); - iterator begin() { return begin_; } + iterator begin() const { + return begin_; + } - iterator end() { return end_; } + iterator end() const { + return end_; + } private: const iterator begin_; diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp index 352a93361633..cc664a5de722 100644 --- a/tools/aapt2/xml/XmlActionExecutor.cpp +++ b/tools/aapt2/xml/XmlActionExecutor.cpp @@ -78,7 +78,7 @@ bool XmlActionExecutor::Execute(XmlActionExecutorPolicy policy, IDiagnostics* di XmlResource* doc) const { SourcePathDiagnostics source_diag(doc->file.source, diag); - Element* el = FindRootElement(doc); + Element* el = doc->root.get(); if (!el) { if (policy == XmlActionExecutorPolicy::kWhitelist) { source_diag.Error(DiagMessage() << "no root XML tag found"); diff --git a/tools/aapt2/xml/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp index 885ab3e33fed..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 f0122e8c617a..6ed2d616f782 100644 --- a/tools/aapt2/xml/XmlDom_test.cpp +++ b/tools/aapt2/xml/XmlDom_test.cpp @@ -16,23 +16,22 @@ #include "xml/XmlDom.h" -#include <sstream> #include <string> +#include "io/StringInputStream.h" #include "test/Test.h" +using ::aapt::io::StringInputStream; using ::testing::Eq; using ::testing::NotNull; using ::testing::SizeIs; +using ::testing::StrEq; namespace aapt { - -constexpr const char* kXmlPreamble = - "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; +namespace xml { TEST(XmlDomTest, Inflate) { - std::stringstream in(kXmlPreamble); - in << R"( + std::string input = R"(<?xml version="1.0" encoding="utf-8"?> <Layout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content"> @@ -41,26 +40,25 @@ TEST(XmlDomTest, Inflate) { android:layout_height="wrap_content" /> </Layout>)"; - const Source source("test.xml"); StdErrDiagnostics diag; - std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, &diag, source); + StringInputStream in(input); + std::unique_ptr<XmlResource> doc = Inflate(&in, &diag, Source("test.xml")); ASSERT_THAT(doc, NotNull()); - xml::Namespace* ns = xml::NodeCast<xml::Namespace>(doc->root.get()); - ASSERT_THAT(ns, NotNull()); - EXPECT_THAT(ns->namespace_uri, Eq(xml::kSchemaAndroid)); - EXPECT_THAT(ns->namespace_prefix, Eq("android")); + Element* el = doc->root.get(); + EXPECT_THAT(el->namespace_decls, SizeIs(1u)); + EXPECT_THAT(el->namespace_decls[0].uri, StrEq(xml::kSchemaAndroid)); + EXPECT_THAT(el->namespace_decls[0].prefix, StrEq("android")); } // Escaping is handled after parsing of the values for resource-specific values. TEST(XmlDomTest, ForwardEscapes) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"( + std::unique_ptr<XmlResource> doc = test::BuildXmlDom(R"( <element value="\?hello" pattern="\\d{5}">\\d{5}</element>)"); - xml::Element* el = xml::FindRootElement(doc.get()); - ASSERT_THAT(el, NotNull()); + Element* el = doc->root.get(); - xml::Attribute* attr = el->FindAttribute({}, "pattern"); + Attribute* attr = el->FindAttribute({}, "pattern"); ASSERT_THAT(attr, NotNull()); EXPECT_THAT(attr->value, Eq("\\\\d{5}")); @@ -70,21 +68,54 @@ TEST(XmlDomTest, ForwardEscapes) { ASSERT_THAT(el->children, SizeIs(1u)); - xml::Text* text = xml::NodeCast<xml::Text>(el->children[0].get()); + Text* text = xml::NodeCast<xml::Text>(el->children[0].get()); ASSERT_THAT(text, NotNull()); EXPECT_THAT(text->text, Eq("\\\\d{5}")); } TEST(XmlDomTest, XmlEscapeSequencesAreParsed) { - std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"(<element value=""" />)"); - - xml::Element* el = xml::FindRootElement(doc.get()); - ASSERT_THAT(el, NotNull()); - - xml::Attribute* attr = el->FindAttribute({}, "value"); + std::unique_ptr<XmlResource> doc = test::BuildXmlDom(R"(<element value=""" />)"); + Attribute* attr = doc->root->FindAttribute({}, "value"); ASSERT_THAT(attr, NotNull()); - EXPECT_THAT(attr->value, Eq("\"")); } +class TestVisitor : public PackageAwareVisitor { + public: + using PackageAwareVisitor::Visit; + + void Visit(Element* el) override { + if (el->name == "View1") { + EXPECT_THAT(TransformPackageAlias("one", "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/incident_report/main.cpp b/tools/incident_report/main.cpp index 1d8809f6f603..250d1186c672 100644 --- a/tools/incident_report/main.cpp +++ b/tools/incident_report/main.cpp @@ -97,6 +97,8 @@ read_message(CodedInputStream* in, Descriptor const* descriptor, GenericMessage* message->addInt64(fieldId, value64); break; } else { + fprintf(stderr, "bad VARINT: 0x%x (%d) at index %d\n", tag, tag, + in->CurrentPosition()); return false; } case WireFormatLite::WIRETYPE_FIXED64: @@ -104,10 +106,14 @@ read_message(CodedInputStream* in, Descriptor const* descriptor, GenericMessage* message->addInt64(fieldId, value64); break; } else { + fprintf(stderr, "bad VARINT: 0x%x (%d) at index %d\n", tag, tag, + in->CurrentPosition()); return false; } case WireFormatLite::WIRETYPE_LENGTH_DELIMITED: if (!read_length_delimited(in, fieldId, descriptor, message)) { + fprintf(stderr, "bad LENGTH_DELIMITED: 0x%x (%d) at index %d\n", + tag, tag, in->CurrentPosition()); return false; } break; @@ -116,6 +122,8 @@ read_message(CodedInputStream* in, Descriptor const* descriptor, GenericMessage* message->addInt32(fieldId, value32); break; } else { + fprintf(stderr, "bad FIXED32: 0x%x (%d) at index %d\n", tag, tag, + in->CurrentPosition()); return false; } default: @@ -146,7 +154,7 @@ print_value(Out* out, FieldDescriptor const* field, GenericMessage::Node const& out->printf("%f", *(float*)&node.value32); break; default: - out->printf("(unexpected value %d (0x%x)", node.value32, node.value32); + out->printf("(unexpected value32 %d (0x%x)", node.value32, node.value32); break; } break; @@ -177,8 +185,11 @@ print_value(Out* out, FieldDescriptor const* field, GenericMessage::Node const& } break; case FieldDescriptor::TYPE_ENUM: + out->printf("%s", field->enum_type()->FindValueByNumber((int)node.value64) + ->name().c_str()); + break; default: - out->printf("(unexpected value %ld (0x%x))", node.value64, node.value64); + out->printf("(unexpected value64 %ld (0x%x))", node.value64, node.value64); break; } break; @@ -297,7 +308,7 @@ static int adb_incident_workaround(const char* adbSerial, const vector<string>& sections) { const int maxAllowedSize = 20 * 1024 * 1024; // 20MB - uint8_t* buffer = (uint8_t*)malloc(maxAllowedSize); + unique_ptr<uint8_t[]> buffer(new uint8_t[maxAllowedSize]); for (vector<string>::const_iterator it=sections.begin(); it!=sections.end(); it++) { Descriptor const* descriptor = IncidentProto::descriptor(); @@ -363,7 +374,7 @@ adb_incident_workaround(const char* adbSerial, const vector<string>& sections) size_t size = 0; while (size < maxAllowedSize) { - ssize_t amt = read(pfd[0], buffer + size, maxAllowedSize - size); + ssize_t amt = read(pfd[0], buffer.get() + size, maxAllowedSize - size); if (amt == 0) { break; } else if (amt == -1) { @@ -390,7 +401,7 @@ adb_incident_workaround(const char* adbSerial, const vector<string>& sections) fprintf(stderr, "write error: %s\n", strerror(err)); return 1; } - err = write_all(STDOUT_FILENO, buffer, size); + err = write_all(STDOUT_FILENO, buffer.get(), size); if (err != 0) { fprintf(stderr, "write error: %s\n", strerror(err)); return 1; @@ -401,7 +412,6 @@ adb_incident_workaround(const char* adbSerial, const vector<string>& sections) } } - free(buffer); return 0; } diff --git a/tools/incident_section_gen/main.cpp b/tools/incident_section_gen/main.cpp index 15f622cf9461..8e5c4f934c07 100644 --- a/tools/incident_section_gen/main.cpp +++ b/tools/incident_section_gen/main.cpp @@ -17,26 +17,30 @@ #include <frameworks/base/core/proto/android/os/incident.pb.h> - #include <map> +#include <string> +using namespace android; using namespace android::os; using namespace google::protobuf; using namespace google::protobuf::io; using namespace google::protobuf::internal; using namespace std; -int -main(int, const char**) -{ - map<string,FieldDescriptor const*> sections; - int N; - +static void generateHead(const char* header) { printf("// Auto generated file. Do not modify\n"); printf("\n"); - printf("#include \"incident_sections.h\"\n"); + printf("#include \"%s.h\"\n", header); printf("\n"); +} + +// ================================================================================ +static bool generateIncidentSectionsCpp() +{ + generateHead("incident_sections"); + map<string,FieldDescriptor const*> sections; + int N; Descriptor const* descriptor = IncidentProto::descriptor(); N = descriptor->field_count(); for (int i=0; i<N; i++) { @@ -63,5 +67,72 @@ main(int, const char**) printf("const int INCIDENT_SECTION_COUNT = %d;\n", N); - return 0; + return true; +} + +// ================================================================================ +static void splitAndPrint(const string& args) { + size_t base = 0; + size_t found; + while (true) { + found = args.find_first_of(" ", base); + if (found != base) { + string arg = args.substr(base, found - base); + printf(" \"%s\",", arg.c_str()); + } + if (found == args.npos) break; + base = found + 1; + } +} + +static bool generateSectionListCpp() { + generateHead("section_list"); + + printf("const Section* SECTION_LIST[] = {\n"); + Descriptor const* descriptor = IncidentProto::descriptor(); + for (int i=0; i<descriptor->field_count(); i++) { + const FieldDescriptor* field = descriptor->field(i); + + if (field->type() != FieldDescriptor::TYPE_MESSAGE) { + continue; + } + const SectionFlags s = field->options().GetExtension(section); + switch (s.type()) { + case SECTION_NONE: + continue; + case SECTION_FILE: + printf(" new FileSection(%d, \"%s\"),\n", field->number(), s.args().c_str()); + break; + case SECTION_COMMAND: + printf(" new CommandSection(%d,", field->number()); + splitAndPrint(s.args()); + printf(" NULL),\n"); + break; + case SECTION_DUMPSYS: + printf(" new DumpsysSection(%d,", field->number()); + splitAndPrint(s.args()); + printf(" NULL),\n"); + break; + } + } + printf(" NULL\n"); + printf("};\n"); + return true; +} + +// ================================================================================ +int main(int argc, char const *argv[]) +{ + if (argc != 2) return 1; + const char* module = argv[1]; + + if (strcmp(module, "incident") == 0) { + return !generateIncidentSectionsCpp(); + } + if (strcmp(module, "incidentd") == 0 ) { + return !generateSectionListCpp(); + } + + // return failure if not called by the whitelisted modules + return 1; } |