Merge changes Id8bdb14e,I573a6735,Ia804777f,Ia68122cb,Ia1997800, ... into oc-mr1-dev

* changes:
  AAPT2: Bump to version 2.19
  AAPT2: Fix issue with resource deduping
  AAPT: Dump badging should pickup strings from the right package
  AAPT2: Change the daemon mode to be line based
  AAPT2: better error handling for daemon mode
  AAPT2: Fix regression in Manifest.java permissions
  AAPT2: Define intermediate compiled XML proto
  AAPT2: Add a daemon mode
  AAPT2: Fix mkdirs implementation
  AAPT2: Change XmlDom to exclude Namespace as a node
  AAPT2: Fix windows unicode path issues
  AAPT2: Fix typo in Config parsing
  AAPT2: Change proto format to reduce usage of StringPool
  AAPT2: Add navigation type
  AAPT2: Document Format.proto
This commit is contained in:
Adam Lesinski
2017-09-15 16:49:34 +00:00
committed by Android (Google) Code Review
71 changed files with 2781 additions and 1771 deletions

View File

@@ -99,24 +99,40 @@ String8 getResolvedAttribute(const ResTable& resTable, const ResXMLTree& tree,
if (idx < 0) {
return String8();
}
Res_value value;
if (tree.getAttributeValue(idx, &value) != NO_ERROR) {
if (value.dataType == Res_value::TYPE_STRING) {
size_t len;
const char16_t* str = tree.getAttributeStringValue(idx, &len);
return str ? String8(str, len) : String8();
}
resTable.resolveReference(&value, 0);
if (value.dataType != Res_value::TYPE_STRING) {
if (outError != NULL) {
*outError = "attribute is not a string value";
}
return String8();
if (tree.getAttributeValue(idx, &value) == BAD_TYPE) {
if (outError != NULL) {
*outError = "attribute value is corrupt";
}
return String8();
}
// Check if the string is inline in the XML.
if (value.dataType == Res_value::TYPE_STRING) {
size_t len;
const char16_t* str = tree.getAttributeStringValue(idx, &len);
return str ? String8(str, len) : String8();
}
// Resolve the reference if there is one.
ssize_t block = resTable.resolveReference(&value, 0);
if (block < 0) {
if (outError != NULL) {
*outError = "attribute value reference does not exist";
}
return String8();
}
if (value.dataType != Res_value::TYPE_STRING) {
if (outError != NULL) {
*outError = "attribute is not a string value";
}
return String8();
}
size_t len;
const Res_value* value2 = &value;
const char16_t* str = resTable.valueToString(value2, 0, NULL, &len);
const char16_t* str = resTable.valueToString(&value, static_cast<size_t>(block), NULL, &len);
return str ? String8(str, len) : String8();
}

View File

@@ -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",
@@ -140,7 +142,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 +167,7 @@ cc_library_host_shared {
cc_test_host {
name: "aapt2_tests",
srcs: [
"test/Builders.cpp",
"test/Common.cpp",
"**/*_test.cpp",
],

View File

@@ -70,7 +70,7 @@ static bool parseMcc(const char* name, ResTable_config* out) {
static bool parseMnc(const char* name, ResTable_config* out) {
if (strcmp(name, kWildcardName) == 0) {
if (out) out->mcc = 0;
if (out) out->mnc = 0;
return true;
}
const char* c = name;
@@ -967,8 +967,6 @@ bool ConfigDescription::ConflictsWith(const ConfigDescription& o) const {
o.screenLayout & MASK_LAYOUTDIR) ||
!pred(screenLayout & MASK_SCREENLONG,
o.screenLayout & MASK_SCREENLONG) ||
!pred(screenLayout & MASK_UI_MODE_TYPE,
o.screenLayout & MASK_UI_MODE_TYPE) ||
!pred(uiMode & MASK_UI_MODE_TYPE, o.uiMode & MASK_UI_MODE_TYPE) ||
!pred(uiMode & MASK_UI_MODE_NIGHT, o.uiMode & MASK_UI_MODE_NIGHT) ||
!pred(screenLayout2 & MASK_SCREENROUND,

View File

@@ -140,4 +140,16 @@ TEST(ConfigDescriptionTest, ParseVrAttribute) {
EXPECT_EQ(std::string("vrheadset-v26"), config.toString().string());
}
TEST(ConfigDescriptionTest, RangeQualifiersDoNotConflict) {
using test::ParseConfigOrDie;
EXPECT_FALSE(ParseConfigOrDie("large").ConflictsWith(ParseConfigOrDie("normal-land")));
EXPECT_FALSE(ParseConfigOrDie("long-hdpi").ConflictsWith(ParseConfigOrDie("xhdpi")));
EXPECT_FALSE(ParseConfigOrDie("sw600dp").ConflictsWith(ParseConfigOrDie("sw700dp")));
EXPECT_FALSE(ParseConfigOrDie("v11").ConflictsWith(ParseConfigOrDie("v21")));
EXPECT_FALSE(ParseConfigOrDie("h600dp").ConflictsWith(ParseConfigOrDie("h300dp")));
EXPECT_FALSE(ParseConfigOrDie("w400dp").ConflictsWith(ParseConfigOrDie("w300dp")));
EXPECT_FALSE(ParseConfigOrDie("600x400").ConflictsWith(ParseConfigOrDie("300x200")));
}
} // namespace aapt

View File

@@ -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:

View File

@@ -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;
}

View File

@@ -14,12 +14,26 @@
* limitations under the License.
*/
#ifdef _WIN32
// clang-format off
#include <windows.h>
#include <shellapi.h>
// clang-format on
#endif
#include <iostream>
#include <vector>
#include "android-base/stringprintf.h"
#include "android-base/utf8.h"
#include "androidfw/StringPiece.h"
#include "Diagnostics.h"
#include "util/Files.h"
#include "util/Util.h"
using ::android::StringPiece;
using ::android::base::StringPrintf;
namespace aapt {
@@ -27,54 +41,136 @@ namespace aapt {
static const char* sMajorVersion = "2";
// Update minor version whenever a feature or flag is added.
static const char* sMinorVersion = "18";
static const char* sMinorVersion = "19";
int PrintVersion() {
std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "."
<< sMinorVersion << std::endl;
return 0;
static void PrintVersion() {
std::cerr << StringPrintf("Android Asset Packaging Tool (aapt) %s:%s", sMajorVersion,
sMinorVersion)
<< std::endl;
}
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;
}
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);
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;
}
static void RunDaemon(IDiagnostics* diagnostics) {
std::cout << "Ready" << std::endl;
// Run in daemon mode. The first line of input is the command. This can be 'quit' which ends
// the daemon mode. Each subsequent line is a single parameter to the command. The end of a
// invocation is signaled by providing an empty line. At any point, an EOF signal or the
// command 'quit' will end the daemon mode.
while (true) {
std::vector<std::string> raw_args;
for (std::string line; std::getline(std::cin, line) && !line.empty();) {
raw_args.push_back(line);
}
if (!std::cin) {
break;
}
// An empty command does nothing.
if (raw_args.empty()) {
continue;
}
if (raw_args[0] == "quit") {
break;
}
std::vector<StringPiece> args;
args.insert(args.end(), ++raw_args.begin(), raw_args.end());
int ret = ExecuteCommand(raw_args[0], args, diagnostics);
if (ret != 0) {
std::cerr << "Error" << std::endl;
}
std::cerr << "Done" << std::endl;
}
std::cout << "Exiting daemon" << std::endl;
}
} // namespace aapt
int main(int argc, char** argv) {
if (argc >= 2) {
argv += 1;
argc -= 1;
std::vector<android::StringPiece> args;
for (int i = 1; i < argc; i++) {
args.push_back(argv[i]);
}
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();
}
std::cerr << "unknown command '" << command << "'\n";
} else {
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);
}

View File

@@ -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},

View File

@@ -59,6 +59,7 @@ enum class ResourceType {
kLayout,
kMenu,
kMipmap,
kNavigation,
kPlurals,
kRaw,
kString,

View File

@@ -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()))

View File

@@ -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"));
}

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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()));

View File

@@ -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);

471
tools/aapt2/Resources.proto Normal file
View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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(&copying_adaptor);

View File

@@ -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;

View File

@@ -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))) {
@@ -1295,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");

View File

@@ -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 {};
}

View File

@@ -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;
}

View File

@@ -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_;
}

View File

@@ -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());
EXPECT_THAT(parser.GetExtractedInlineXmlDocuments(), SizeIs(1u));
xml::Element* el = xml::FindRootElement(doc.get());
ASSERT_NE(nullptr, el);
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());
ASSERT_THAT(parser.GetExtractedInlineXmlDocuments(), SizeIs(2u));
xml::Element* el = xml::FindRootElement(doc.get());
ASSERT_NE(nullptr, el);
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"));
EXPECT_THAT(el->FindChild(xml::kSchemaAapt, "attr"), IsNull());
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_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_NE(nullptr, extracted_doc_drawable);
el = xml::FindRootElement(extracted_doc_drawable);
ASSERT_NE(nullptr, el);
EXPECT_EQ("vector", el->name);
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

View File

@@ -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,9 +50,9 @@ 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;
@@ -182,15 +183,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 {};

View File

@@ -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;
@@ -192,7 +189,7 @@ 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());

View File

@@ -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());

View File

@@ -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));

View File

@@ -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();
}

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation />

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -39,6 +39,17 @@ void MethodDefinition::WriteToStream(const StringPiece& prefix, bool final,
*out << prefix << "}";
}
ClassDefinition::Result ClassDefinition::AddMember(std::unique_ptr<ClassMember> member) {
Result result = Result::kAdded;
auto iter = members_.find(member);
if (iter != members_.end()) {
members_.erase(iter);
result = Result::kOverridden;
}
members_.insert(std::move(member));
return result;
}
bool ClassDefinition::empty() const {
for (const std::unique_ptr<ClassMember>& member : members_) {
if (!member->empty()) {

View File

@@ -18,6 +18,7 @@
#define AAPT_JAVA_CLASSDEFINITION_H
#include <ostream>
#include <set>
#include <string>
#include "android-base/macros.h"
@@ -37,10 +38,14 @@ class ClassMember {
public:
virtual ~ClassMember() = default;
AnnotationProcessor* GetCommentBuilder() { return &processor_; }
AnnotationProcessor* GetCommentBuilder() {
return &processor_;
}
virtual bool empty() const = 0;
virtual const std::string& GetName() const = 0;
// Writes the class member to the out stream. Subclasses should derive this method
// to write their own data. Call this base method from the subclass to write out
// this member's comments/annotations.
@@ -57,7 +62,13 @@ class PrimitiveMember : public ClassMember {
PrimitiveMember(const android::StringPiece& name, const T& val)
: name_(name.to_string()), val_(val) {}
bool empty() const override { return false; }
bool empty() const override {
return false;
}
const std::string& GetName() const override {
return name_;
}
void WriteToStream(const android::StringPiece& prefix, bool final,
std::ostream* out) const override {
@@ -83,7 +94,13 @@ class PrimitiveMember<std::string> : public ClassMember {
PrimitiveMember(const android::StringPiece& name, const std::string& val)
: name_(name.to_string()), val_(val) {}
bool empty() const override { return false; }
bool empty() const override {
return false;
}
const std::string& GetName() const override {
return name_;
}
void WriteToStream(const android::StringPiece& prefix, bool final,
std::ostream* out) const override {
@@ -109,9 +126,17 @@ class PrimitiveArrayMember : public ClassMember {
public:
explicit PrimitiveArrayMember(const android::StringPiece& name) : name_(name.to_string()) {}
void AddElement(const T& val) { elements_.push_back(val); }
void AddElement(const T& val) {
elements_.push_back(val);
}
bool empty() const override { return false; }
bool empty() const override {
return false;
}
const std::string& GetName() const override {
return name_;
}
void WriteToStream(const android::StringPiece& prefix, bool final,
std::ostream* out) const override {
@@ -154,6 +179,11 @@ class MethodDefinition : public ClassMember {
// formatting may be broken.
void AppendStatement(const android::StringPiece& statement);
// Not quite the same as a name, but good enough.
const std::string& GetName() const override {
return signature_;
}
// Even if the method is empty, we always want to write the method signature.
bool empty() const override { return false; }
@@ -175,19 +205,34 @@ class ClassDefinition : public ClassMember {
ClassDefinition(const android::StringPiece& name, ClassQualifier qualifier, bool createIfEmpty)
: name_(name.to_string()), qualifier_(qualifier), create_if_empty_(createIfEmpty) {}
void AddMember(std::unique_ptr<ClassMember> member) {
members_.push_back(std::move(member));
}
enum class Result {
kAdded,
kOverridden,
};
Result AddMember(std::unique_ptr<ClassMember> member);
bool empty() const override;
const std::string& GetName() const override {
return name_;
}
void WriteToStream(const android::StringPiece& prefix, bool final,
std::ostream* out) const override;
private:
struct ClassMemberCompare {
using T = std::unique_ptr<ClassMember>;
bool operator()(const T& a, const T& b) const {
return a->GetName() < b->GetName();
}
};
std::string name_;
ClassQualifier qualifier_;
bool create_if_empty_;
std::vector<std::unique_ptr<ClassMember>> members_;
std::set<std::unique_ptr<ClassMember>, ClassMemberCompare> members_;
DISALLOW_COPY_AND_ASSIGN(ClassDefinition);
};

View File

@@ -81,7 +81,10 @@ static bool WriteSymbol(const Source& source, IDiagnostics* diag,
util::make_unique<StringMember>(result.value(), attr->value);
string_member->GetCommentBuilder()->AppendComment(el->comment);
class_def->AddMember(std::move(string_member));
if (class_def->AddMember(std::move(string_member)) == ClassDefinition::Result::kOverridden) {
diag->Warn(DiagMessage(source.WithLine(el->line_number))
<< "duplicate definitions of '" << result.value() << "', overriding previous");
}
return true;
}

View File

@@ -112,6 +112,21 @@ TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) {
EXPECT_THAT(actual, HasSubstr(expected_secret));
}
// This is bad but part of public API behaviour so we need to preserve it.
TEST(ManifestClassGeneratorTest, LastSeenPermissionWithSameLeafNameTakesPrecedence) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
std::unique_ptr<xml::XmlResource> manifest = test::BuildXmlDom(R"(
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<permission android:name="android.permission.ACCESS_INTERNET" />
<permission android:name="com.android.aapt.test.ACCESS_INTERNET" />
</manifest>)");
std::string actual;
ASSERT_TRUE(GetManifestClassText(context.get(), manifest.get(), &actual));
EXPECT_THAT(actual, HasSubstr("ACCESS_INTERNET=\"com.android.aapt.test.ACCESS_INTERNET\";"));
EXPECT_THAT(actual, Not(HasSubstr("ACCESS_INTERNET=\"android.permission.ACCESS_INTERNET\";")));
}
static ::testing::AssertionResult GetManifestClassText(IAaptContext* context, xml::XmlResource* res,
std::string* out_str) {
std::unique_ptr<ClassDefinition> manifest_class =

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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.

View File

@@ -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_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_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[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

View File

@@ -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;

View File

@@ -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 {
for (const auto& attr : el->attributes) {
EXPECT_EQ(std::string(), attr.namespace_uri);
}
EXPECT_EQ(std::string(), el->namespace_uri);
xml::Visitor::Visit(el);
}
EXPECT_THAT(el->namespace_decls, SizeIs(0u));
void Visit(xml::Namespace* ns) override {
EXPECT_EQ(std::string(), ns->namespace_uri);
xml::Visitor::Visit(ns);
for (const auto& attr : el->attributes) {
EXPECT_THAT(attr.namespace_uri, StrEq(""));
}
EXPECT_THAT(el->namespace_uri, StrEq(""));
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);

View File

@@ -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);

View File

@@ -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

View File

@@ -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;

View File

@@ -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);

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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 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, 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 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;

View File

@@ -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;

View File

@@ -1,5 +1,18 @@
# Android Asset Packaging Tool 2.0 (AAPT2) release notes
## Version 2.19
- Added navigation resource type.
- Fixed issue with resource deduplication. (bug 64397629)
- Added a daemon mode for issuing commands. This is invoked with `aapt2 daemon`.
Command line arguments are separated by newlines, with an empty line signalling the
end of a command. Sending `EOF (Ctrl+D)` to the daemon will exit.
- Fixed an issue where multiple permissions defined in AndroidManifest.xml would generate
conflicting definitions for the same Java constant in Manifest.java. Changed the implementation
to match that of `aapt`, which will take the last definition as the sole definition.
A warning is logged if such a scenario occurs. (bug 64472942)
- Made improvements to handling of paths on Windows. This should resolve a lot of issues with
Unicode paths. (bug 62336414, 63830502)
## Version 2.18
### `aapt2 ...`
- Fixed issue where enum values were interpreted as integers and range checked. (bug 62358540)

View File

@@ -0,0 +1,216 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "test/Builders.h"
#include "android-base/logging.h"
#include "androidfw/StringPiece.h"
#include "io/StringInputStream.h"
#include "test/Common.h"
#include "util/Util.h"
using ::aapt::io::StringInputStream;
using ::android::StringPiece;
namespace aapt {
namespace test {
ResourceTableBuilder& ResourceTableBuilder::SetPackageId(const StringPiece& package_name,
uint8_t id) {
ResourceTablePackage* package = table_->CreatePackage(package_name, id);
CHECK(package != nullptr);
return *this;
}
ResourceTableBuilder& ResourceTableBuilder::AddSimple(const StringPiece& name,
const ResourceId& id) {
return AddValue(name, id, util::make_unique<Id>());
}
ResourceTableBuilder& ResourceTableBuilder::AddSimple(const StringPiece& name,
const ConfigDescription& config,
const ResourceId& id) {
return AddValue(name, config, id, util::make_unique<Id>());
}
ResourceTableBuilder& ResourceTableBuilder::AddReference(const StringPiece& name,
const StringPiece& ref) {
return AddReference(name, {}, ref);
}
ResourceTableBuilder& ResourceTableBuilder::AddReference(const StringPiece& name,
const ResourceId& id,
const StringPiece& ref) {
return AddValue(name, id, util::make_unique<Reference>(ParseNameOrDie(ref)));
}
ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name,
const StringPiece& str) {
return AddString(name, {}, str);
}
ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, const ResourceId& id,
const StringPiece& str) {
return AddValue(name, id, util::make_unique<String>(table_->string_pool.MakeRef(str)));
}
ResourceTableBuilder& ResourceTableBuilder::AddString(const StringPiece& name, const ResourceId& id,
const ConfigDescription& config,
const StringPiece& str) {
return AddValue(name, config, id, util::make_unique<String>(table_->string_pool.MakeRef(str)));
}
ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
const StringPiece& path) {
return AddFileReference(name, {}, path);
}
ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
const ResourceId& id,
const StringPiece& path) {
return AddValue(name, id, util::make_unique<FileReference>(table_->string_pool.MakeRef(path)));
}
ResourceTableBuilder& ResourceTableBuilder::AddFileReference(const StringPiece& name,
const StringPiece& path,
const ConfigDescription& config) {
return AddValue(name, config, {},
util::make_unique<FileReference>(table_->string_pool.MakeRef(path)));
}
ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name,
std::unique_ptr<Value> value) {
return AddValue(name, {}, std::move(value));
}
ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name, const ResourceId& id,
std::unique_ptr<Value> value) {
return AddValue(name, {}, id, std::move(value));
}
ResourceTableBuilder& ResourceTableBuilder::AddValue(const StringPiece& name,
const ConfigDescription& config,
const ResourceId& id,
std::unique_ptr<Value> value) {
ResourceName res_name = ParseNameOrDie(name);
CHECK(table_->AddResourceAllowMangled(res_name, id, config, {}, std::move(value),
GetDiagnostics()));
return *this;
}
ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& name,
const ResourceId& id, SymbolState state,
bool allow_new) {
ResourceName res_name = ParseNameOrDie(name);
Symbol symbol;
symbol.state = state;
symbol.allow_new = allow_new;
CHECK(table_->SetSymbolStateAllowMangled(res_name, id, symbol, GetDiagnostics()));
return *this;
}
StringPool* ResourceTableBuilder::string_pool() {
return &table_->string_pool;
}
std::unique_ptr<ResourceTable> ResourceTableBuilder::Build() {
return std::move(table_);
}
std::unique_ptr<Reference> BuildReference(const StringPiece& ref, const Maybe<ResourceId>& id) {
std::unique_ptr<Reference> reference = util::make_unique<Reference>(ParseNameOrDie(ref));
reference->id = id;
return reference;
}
std::unique_ptr<BinaryPrimitive> BuildPrimitive(uint8_t type, uint32_t data) {
android::Res_value value = {};
value.size = sizeof(value);
value.dataType = type;
value.data = data;
return util::make_unique<BinaryPrimitive>(value);
}
AttributeBuilder::AttributeBuilder(bool weak) : attr_(util::make_unique<Attribute>(weak)) {
attr_->type_mask = android::ResTable_map::TYPE_ANY;
}
AttributeBuilder& AttributeBuilder::SetTypeMask(uint32_t typeMask) {
attr_->type_mask = typeMask;
return *this;
}
AttributeBuilder& AttributeBuilder::AddItem(const StringPiece& name, uint32_t value) {
attr_->symbols.push_back(
Attribute::Symbol{Reference(ResourceName({}, ResourceType::kId, name)), value});
return *this;
}
std::unique_ptr<Attribute> AttributeBuilder::Build() {
return std::move(attr_);
}
StyleBuilder& StyleBuilder::SetParent(const StringPiece& str) {
style_->parent = Reference(ParseNameOrDie(str));
return *this;
}
StyleBuilder& StyleBuilder::AddItem(const StringPiece& str, std::unique_ptr<Item> value) {
style_->entries.push_back(Style::Entry{Reference(ParseNameOrDie(str)), std::move(value)});
return *this;
}
StyleBuilder& StyleBuilder::AddItem(const StringPiece& str, const ResourceId& id,
std::unique_ptr<Item> value) {
AddItem(str, std::move(value));
style_->entries.back().key.id = id;
return *this;
}
std::unique_ptr<Style> StyleBuilder::Build() {
return std::move(style_);
}
StyleableBuilder& StyleableBuilder::AddItem(const StringPiece& str, const Maybe<ResourceId>& id) {
styleable_->entries.push_back(Reference(ParseNameOrDie(str)));
styleable_->entries.back().id = id;
return *this;
}
std::unique_ptr<Styleable> StyleableBuilder::Build() {
return std::move(styleable_);
}
std::unique_ptr<xml::XmlResource> BuildXmlDom(const StringPiece& str) {
std::string input = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
input.append(str.data(), str.size());
StringInputStream in(input);
StdErrDiagnostics diag;
std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, &diag, Source("test.xml"));
CHECK(doc != nullptr && doc->root != nullptr) << "failed to parse inline XML string";
return doc;
}
std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* context,
const StringPiece& str) {
std::unique_ptr<xml::XmlResource> doc = BuildXmlDom(str);
doc->file.name.package = context->GetCompilationPackage();
return doc;
}
} // namespace test
} // namespace aapt

View File

@@ -19,13 +19,13 @@
#include <memory>
#include "android-base/logging.h"
#include "android-base/macros.h"
#include "Resource.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "test/Common.h"
#include "util/Util.h"
#include "process/IResourceTableConsumer.h"
#include "util/Maybe.h"
#include "xml/XmlDom.h"
namespace aapt {
@@ -35,97 +35,37 @@ class ResourceTableBuilder {
public:
ResourceTableBuilder() = default;
StringPool* string_pool() { return &table_->string_pool; }
ResourceTableBuilder& SetPackageId(const android::StringPiece& package_name, uint8_t id) {
ResourceTablePackage* package = table_->CreatePackage(package_name, id);
CHECK(package != nullptr);
return *this;
}
ResourceTableBuilder& AddSimple(const android::StringPiece& name, const ResourceId& id = {}) {
return AddValue(name, id, util::make_unique<Id>());
}
ResourceTableBuilder& SetPackageId(const android::StringPiece& package_name, uint8_t id);
ResourceTableBuilder& AddSimple(const android::StringPiece& name, const ResourceId& id = {});
ResourceTableBuilder& AddSimple(const android::StringPiece& name, const ConfigDescription& config,
const ResourceId& id = {}) {
return AddValue(name, config, id, util::make_unique<Id>());
}
const ResourceId& id = {});
ResourceTableBuilder& AddReference(const android::StringPiece& name,
const android::StringPiece& ref) {
return AddReference(name, {}, ref);
}
const android::StringPiece& ref);
ResourceTableBuilder& AddReference(const android::StringPiece& name, const ResourceId& id,
const android::StringPiece& ref) {
return AddValue(name, id, util::make_unique<Reference>(ParseNameOrDie(ref)));
}
const android::StringPiece& ref);
ResourceTableBuilder& AddString(const android::StringPiece& name,
const android::StringPiece& str) {
return AddString(name, {}, str);
}
const android::StringPiece& str);
ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id,
const android::StringPiece& str) {
return AddValue(
name, id, util::make_unique<String>(table_->string_pool.MakeRef(str)));
}
const android::StringPiece& str);
ResourceTableBuilder& AddString(const android::StringPiece& name, const ResourceId& id,
const ConfigDescription& config,
const android::StringPiece& str) {
return AddValue(name, config, id, util::make_unique<String>(
table_->string_pool.MakeRef(str)));
}
const ConfigDescription& config, const android::StringPiece& str);
ResourceTableBuilder& AddFileReference(const android::StringPiece& name,
const android::StringPiece& path) {
return AddFileReference(name, {}, path);
}
const android::StringPiece& path);
ResourceTableBuilder& AddFileReference(const android::StringPiece& name, const ResourceId& id,
const android::StringPiece& path) {
return AddValue(name, id, util::make_unique<FileReference>(
table_->string_pool.MakeRef(path)));
}
const android::StringPiece& path);
ResourceTableBuilder& AddFileReference(const android::StringPiece& name,
const android::StringPiece& path,
const ConfigDescription& config) {
return AddValue(name, config, {}, util::make_unique<FileReference>(
table_->string_pool.MakeRef(path)));
}
ResourceTableBuilder& AddValue(const android::StringPiece& name, std::unique_ptr<Value> value) {
return AddValue(name, {}, std::move(value));
}
const ConfigDescription& config);
ResourceTableBuilder& AddValue(const android::StringPiece& name, std::unique_ptr<Value> value);
ResourceTableBuilder& AddValue(const android::StringPiece& name, const ResourceId& id,
std::unique_ptr<Value> value) {
return AddValue(name, {}, id, std::move(value));
}
std::unique_ptr<Value> value);
ResourceTableBuilder& AddValue(const android::StringPiece& name, const ConfigDescription& config,
const ResourceId& id, std::unique_ptr<Value> value) {
ResourceName res_name = ParseNameOrDie(name);
CHECK(table_->AddResourceAllowMangled(res_name, id, config, {}, std::move(value),
GetDiagnostics()));
return *this;
}
const ResourceId& id, std::unique_ptr<Value> value);
ResourceTableBuilder& SetSymbolState(const android::StringPiece& name, const ResourceId& id,
SymbolState state, bool allow_new = false) {
ResourceName res_name = ParseNameOrDie(name);
Symbol symbol;
symbol.state = state;
symbol.allow_new = allow_new;
CHECK(table_->SetSymbolStateAllowMangled(res_name, id, symbol, GetDiagnostics()));
return *this;
}
SymbolState state, bool allow_new = false);
std::unique_ptr<ResourceTable> Build() { return std::move(table_); }
StringPool* string_pool();
std::unique_ptr<ResourceTable> Build();
private:
DISALLOW_COPY_AND_ASSIGN(ResourceTableBuilder);
@@ -133,29 +73,16 @@ class ResourceTableBuilder {
std::unique_ptr<ResourceTable> table_ = util::make_unique<ResourceTable>();
};
inline std::unique_ptr<Reference> BuildReference(const android::StringPiece& ref,
const Maybe<ResourceId>& id = {}) {
std::unique_ptr<Reference> reference =
util::make_unique<Reference>(ParseNameOrDie(ref));
reference->id = id;
return reference;
}
inline std::unique_ptr<BinaryPrimitive> BuildPrimitive(uint8_t type,
uint32_t data) {
android::Res_value value = {};
value.size = sizeof(value);
value.dataType = type;
value.data = data;
return util::make_unique<BinaryPrimitive>(value);
}
std::unique_ptr<Reference> BuildReference(const android::StringPiece& ref,
const Maybe<ResourceId>& id = {});
std::unique_ptr<BinaryPrimitive> BuildPrimitive(uint8_t type, uint32_t data);
template <typename T>
class ValueBuilder {
public:
template <typename... Args>
explicit ValueBuilder(Args&&... args)
: value_(new T{std::forward<Args>(args)...}) {}
explicit ValueBuilder(Args&&... args) : value_(new T{std::forward<Args>(args)...}) {
}
template <typename... Args>
ValueBuilder& SetSource(Args&&... args) {
@@ -168,7 +95,9 @@ class ValueBuilder {
return *this;
}
std::unique_ptr<Value> Build() { return std::move(value_); }
std::unique_ptr<Value> Build() {
return std::move(value_);
}
private:
DISALLOW_COPY_AND_ASSIGN(ValueBuilder);
@@ -178,23 +107,10 @@ class ValueBuilder {
class AttributeBuilder {
public:
explicit AttributeBuilder(bool weak = false)
: attr_(util::make_unique<Attribute>(weak)) {
attr_->type_mask = android::ResTable_map::TYPE_ANY;
}
AttributeBuilder& SetTypeMask(uint32_t typeMask) {
attr_->type_mask = typeMask;
return *this;
}
AttributeBuilder& AddItem(const android::StringPiece& name, uint32_t value) {
attr_->symbols.push_back(Attribute::Symbol{
Reference(ResourceName({}, ResourceType::kId, name)), value});
return *this;
}
std::unique_ptr<Attribute> Build() { return std::move(attr_); }
explicit AttributeBuilder(bool weak = false);
AttributeBuilder& SetTypeMask(uint32_t typeMask);
AttributeBuilder& AddItem(const android::StringPiece& name, uint32_t value);
std::unique_ptr<Attribute> Build();
private:
DISALLOW_COPY_AND_ASSIGN(AttributeBuilder);
@@ -205,27 +121,11 @@ class AttributeBuilder {
class StyleBuilder {
public:
StyleBuilder() = default;
StyleBuilder& SetParent(const android::StringPiece& str) {
style_->parent = Reference(ParseNameOrDie(str));
return *this;
}
StyleBuilder& AddItem(const android::StringPiece& str, std::unique_ptr<Item> value) {
style_->entries.push_back(Style::Entry{Reference(ParseNameOrDie(str)), std::move(value)});
return *this;
}
StyleBuilder& SetParent(const android::StringPiece& str);
StyleBuilder& AddItem(const android::StringPiece& str, std::unique_ptr<Item> value);
StyleBuilder& AddItem(const android::StringPiece& str, const ResourceId& id,
std::unique_ptr<Item> value) {
AddItem(str, std::move(value));
style_->entries.back().key.id = id;
return *this;
}
std::unique_ptr<Style> Build() {
return std::move(style_);
}
std::unique_ptr<Item> value);
std::unique_ptr<Style> Build();
private:
DISALLOW_COPY_AND_ASSIGN(StyleBuilder);
@@ -236,14 +136,8 @@ class StyleBuilder {
class StyleableBuilder {
public:
StyleableBuilder() = default;
StyleableBuilder& AddItem(const android::StringPiece& str, const Maybe<ResourceId>& id = {}) {
styleable_->entries.push_back(Reference(ParseNameOrDie(str)));
styleable_->entries.back().id = id;
return *this;
}
std::unique_ptr<Styleable> Build() { return std::move(styleable_); }
StyleableBuilder& AddItem(const android::StringPiece& str, const Maybe<ResourceId>& id = {});
std::unique_ptr<Styleable> Build();
private:
DISALLOW_COPY_AND_ASSIGN(StyleableBuilder);
@@ -251,22 +145,9 @@ class StyleableBuilder {
std::unique_ptr<Styleable> styleable_ = util::make_unique<Styleable>();
};
inline std::unique_ptr<xml::XmlResource> BuildXmlDom(const android::StringPiece& str) {
std::stringstream in;
in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str;
StdErrDiagnostics diag;
std::unique_ptr<xml::XmlResource> doc =
xml::Inflate(&in, &diag, Source("test.xml"));
CHECK(doc != nullptr) << "failed to parse inline XML string";
return doc;
}
inline std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(
IAaptContext* context, const android::StringPiece& str) {
std::unique_ptr<xml::XmlResource> doc = BuildXmlDom(str);
doc->file.name.package = context->GetCompilationPackage();
return doc;
}
std::unique_ptr<xml::XmlResource> BuildXmlDom(const android::StringPiece& str);
std::unique_ptr<xml::XmlResource> BuildXmlDomForPackageName(IAaptContext* context,
const android::StringPiece& str);
} // namespace test
} // namespace aapt

View File

@@ -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)) {

View File

@@ -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;
}

View File

@@ -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 {};
}

View File

@@ -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:

View File

@@ -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;
@@ -250,9 +256,13 @@ class Tokenizer {
Tokenizer(android::StringPiece str, char sep);
iterator begin() { return begin_; }
iterator begin() const {
return begin_;
}
iterator end() { return end_; }
iterator end() const {
return end_;
}
private:
const iterator begin_;

View File

@@ -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");

View File

@@ -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);
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);
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;
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;
}
void Text::Accept(Visitor* visitor) {
visitor->Visit(this);
}
Visitor::Visit(ns);
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 {};

View File

@@ -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 {
// Visitor whose default implementation visits the children nodes of any node.
class Visitor {
public:
virtual ~RawVisitor() = default;
virtual ~Visitor() = default;
virtual void Visit(Namespace* node) {}
virtual void Visit(Element* node) {}
virtual void Visit(Text* text) {}
};
virtual void Visit(Element* el) {
VisitChildren(el);
}
/**
* Visitor whose default implementation visits the children nodes of any node.
*/
class Visitor : public RawVisitor {
public:
using RawVisitor::Visit;
virtual void Visit(Text* text) {
}
void Visit(Namespace* node) override { VisitChildren(node); }
protected:
Visitor() = default;
void Visit(Element* node) override { VisitChildren(node); }
void Visit(Text* text) override { VisitChildren(text); }
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;
}

View File

@@ -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="&quot;" />)");
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="&quot;" />)");
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

View File

@@ -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) {

View File

@@ -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:

View File

@@ -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));

View File

@@ -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);