diff options
author | 2016-08-11 13:39:24 -0700 | |
---|---|---|
committer | 2016-08-15 12:14:26 -0700 | |
commit | 36c73a595910e96f3552f938eeb81d46356067a1 (patch) | |
tree | 69632325f13ba59c48c9951b35abc3ef6e7ceda3 | |
parent | 534376f3e9e302946d6f5aae883748107d466c50 (diff) |
AAPT2: Expose split support to command line
Bug:30445078
Change-Id: If4b8530dba71b9059b8e62c04757da99c1119d22
-rw-r--r-- | tools/aapt2/AppInfo.h | 10 | ||||
-rw-r--r-- | tools/aapt2/Main.cpp | 2 | ||||
-rw-r--r-- | tools/aapt2/ResourceParser.cpp | 21 | ||||
-rw-r--r-- | tools/aapt2/ResourceUtils.cpp | 50 | ||||
-rw-r--r-- | tools/aapt2/ResourceUtils.h | 33 | ||||
-rw-r--r-- | tools/aapt2/ResourceUtils_test.cpp | 36 | ||||
-rw-r--r-- | tools/aapt2/ResourceValues.cpp | 10 | ||||
-rw-r--r-- | tools/aapt2/compile/XmlIdCollector.cpp | 2 | ||||
-rw-r--r-- | tools/aapt2/link/Link.cpp | 383 | ||||
-rw-r--r-- | tools/aapt2/link/ReferenceLinker.cpp | 2 | ||||
-rw-r--r-- | tools/aapt2/link/XmlReferenceLinker.cpp | 2 | ||||
-rw-r--r-- | tools/aapt2/readme.md | 9 | ||||
-rw-r--r-- | tools/aapt2/split/TableSplitter.cpp | 11 | ||||
-rw-r--r-- | tools/aapt2/split/TableSplitter.h | 2 | ||||
-rw-r--r-- | tools/aapt2/split/TableSplitter_test.cpp | 69 | ||||
-rw-r--r-- | tools/aapt2/util/Util.h | 16 |
16 files changed, 458 insertions, 200 deletions
diff --git a/tools/aapt2/AppInfo.h b/tools/aapt2/AppInfo.h index 1d39b722b053..a9794a419ca2 100644 --- a/tools/aapt2/AppInfo.h +++ b/tools/aapt2/AppInfo.h @@ -37,6 +37,16 @@ struct AppInfo { * The App's minimum SDK version. */ Maybe<std::string> minSdkVersion; + + /** + * The Version code of the app. + */ + Maybe<uint32_t> versionCode; + + /** + * The revision code of the app. + */ + Maybe<uint32_t> revisionCode; }; } // namespace aapt diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index a74b5aa2478e..ed55f852c24c 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -25,7 +25,7 @@ namespace aapt { static const char* sMajorVersion = "2"; // Update minor version whenever a feature or flag is added. -static const char* sMinorVersion = "0"; +static const char* sMinorVersion = "1"; int printVersion() { std::cerr << "Android Asset Packaging Tool (aapt) " diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index a144c6ab2be7..bcdf401077ee 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -500,8 +500,8 @@ std::unique_ptr<Item> ResourceParser::parseXml(xml::XmlPullParser* parser, const }; // Process the raw value. - std::unique_ptr<Item> processedItem = ResourceUtils::parseItemForAttribute(rawValue, typeMask, - onCreateReference); + std::unique_ptr<Item> processedItem = ResourceUtils::tryParseItemForAttribute( + rawValue, typeMask, onCreateReference); if (processedItem) { // Fix up the reference. if (Reference* ref = valueCast<Reference>(processedItem.get())) { @@ -528,20 +528,24 @@ std::unique_ptr<Item> ResourceParser::parseXml(xml::XmlPullParser* parser, const bool ResourceParser::parseString(xml::XmlPullParser* parser, ParsedResource* outResource) { bool formatted = true; if (Maybe<StringPiece> formattedAttr = xml::findAttribute(parser, "formatted")) { - if (!ResourceUtils::tryParseBool(formattedAttr.value(), &formatted)) { + Maybe<bool> maybeFormatted = ResourceUtils::parseBool(formattedAttr.value()); + if (!maybeFormatted) { mDiag->error(DiagMessage(outResource->source) << "invalid value for 'formatted'. Must be a boolean"); return false; } + formatted = maybeFormatted.value(); } bool translateable = mOptions.translatable; if (Maybe<StringPiece> translateableAttr = xml::findAttribute(parser, "translatable")) { - if (!ResourceUtils::tryParseBool(translateableAttr.value(), &translateable)) { + Maybe<bool> maybeTranslateable = ResourceUtils::parseBool(translateableAttr.value()); + if (!maybeTranslateable) { mDiag->error(DiagMessage(outResource->source) << "invalid value for 'translatable'. Must be a boolean"); return false; } + translateable = maybeTranslateable.value(); } outResource->value = parseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString); @@ -590,7 +594,7 @@ bool ResourceParser::parsePublic(xml::XmlPullParser* parser, ParsedResource* out outResource->name.type = *parsedType; if (Maybe<StringPiece> maybeIdStr = xml::findNonEmptyAttribute(parser, "id")) { - Maybe<ResourceId> maybeId = ResourceUtils::tryParseResourceId(maybeIdStr.value()); + Maybe<ResourceId> maybeId = ResourceUtils::parseResourceId(maybeIdStr.value()); if (!maybeId) { mDiag->error(DiagMessage(outResource->source) << "invalid resource ID '" << maybeId.value() << "' in <public>"); @@ -630,7 +634,7 @@ bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser, ParsedResource return false; } - Maybe<ResourceId> maybeId = ResourceUtils::tryParseResourceId(maybeIdStr.value()); + Maybe<ResourceId> maybeId = ResourceUtils::parseResourceId(maybeIdStr.value()); if (!maybeId) { mDiag->error(DiagMessage(outResource->source) << "invalid resource ID '" << maybeIdStr.value() << "' in <public-group>"); @@ -1058,14 +1062,17 @@ bool ResourceParser::parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* bool translateable = mOptions.translatable; if (Maybe<StringPiece> translateableAttr = xml::findAttribute(parser, "translatable")) { - if (!ResourceUtils::tryParseBool(translateableAttr.value(), &translateable)) { + Maybe<bool> maybeTranslateable = ResourceUtils::parseBool(translateableAttr.value()); + if (!maybeTranslateable) { mDiag->error(DiagMessage(outResource->source) << "invalid value for 'translatable'. Must be a boolean"); return false; } + translateable = maybeTranslateable.value(); } array->setTranslateable(translateable); + bool error = false; const size_t depth = parser->getDepth(); while (xml::XmlPullParser::nextChildNode(parser, depth)) { diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index 7dc88dedc96e..f806d8072aae 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -124,7 +124,7 @@ bool parseResourceName(const StringPiece& str, ResourceNameRef* outRef, bool* ou return true; } -bool tryParseReference(const StringPiece& str, ResourceNameRef* outRef, bool* outCreate, +bool parseReference(const StringPiece& str, ResourceNameRef* outRef, bool* outCreate, bool* outPrivate) { StringPiece trimmedStr(util::trimWhitespace(str)); if (trimmedStr.empty()) { @@ -171,10 +171,10 @@ bool tryParseReference(const StringPiece& str, ResourceNameRef* outRef, bool* ou } bool isReference(const StringPiece& str) { - return tryParseReference(str, nullptr, nullptr, nullptr); + return parseReference(str, nullptr, nullptr, nullptr); } -bool tryParseAttributeReference(const StringPiece& str, ResourceNameRef* outRef) { +bool parseAttributeReference(const StringPiece& str, ResourceNameRef* outRef) { StringPiece trimmedStr(util::trimWhitespace(str)); if (trimmedStr.empty()) { return false; @@ -208,7 +208,7 @@ bool tryParseAttributeReference(const StringPiece& str, ResourceNameRef* outRef) } bool isAttributeReference(const StringPiece& str) { - return tryParseAttributeReference(str, nullptr); + return parseAttributeReference(str, nullptr); } /* @@ -271,13 +271,13 @@ Maybe<Reference> parseStyleParentReference(const StringPiece& str, std::string* std::unique_ptr<Reference> tryParseReference(const StringPiece& str, bool* outCreate) { ResourceNameRef ref; bool privateRef = false; - if (tryParseReference(str, &ref, outCreate, &privateRef)) { + if (parseReference(str, &ref, outCreate, &privateRef)) { std::unique_ptr<Reference> value = util::make_unique<Reference>(ref); value->privateReference = privateRef; return value; } - if (tryParseAttributeReference(str, &ref)) { + if (parseAttributeReference(str, &ref)) { if (outCreate) { *outCreate = false; } @@ -420,23 +420,26 @@ std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece& str) { return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value); } -bool tryParseBool(const StringPiece& str, bool* outValue) { +Maybe<bool> parseBool(const StringPiece& str) { StringPiece trimmedStr(util::trimWhitespace(str)); if (trimmedStr == "true" || trimmedStr == "TRUE" || trimmedStr == "True") { - if (outValue) { - *outValue = true; - } - return true; + return Maybe<bool>(true); } else if (trimmedStr == "false" || trimmedStr == "FALSE" || trimmedStr == "False") { - if (outValue) { - *outValue = false; - } - return true; + return Maybe<bool>(false); } - return false; + return {}; +} + +Maybe<uint32_t> parseInt(const StringPiece& str) { + std::u16string str16 = util::utf8ToUtf16(str); + android::Res_value value; + if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { + return value.data; + } + return {}; } -Maybe<ResourceId> tryParseResourceId(const StringPiece& str) { +Maybe<ResourceId> parseResourceId(const StringPiece& str) { StringPiece trimmedStr(util::trimWhitespace(str)); std::u16string str16 = util::utf8ToUtf16(trimmedStr); @@ -452,7 +455,7 @@ Maybe<ResourceId> tryParseResourceId(const StringPiece& str) { return {}; } -Maybe<int> tryParseSdkVersion(const StringPiece& str) { +Maybe<int> parseSdkVersion(const StringPiece& str) { StringPiece trimmedStr(util::trimWhitespace(str)); std::u16string str16 = util::utf8ToUtf16(trimmedStr); @@ -470,12 +473,11 @@ Maybe<int> tryParseSdkVersion(const StringPiece& str) { } std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece& str) { - bool result = false; - if (tryParseBool(str, &result)) { + if (Maybe<bool> maybeResult = parseBool(str)) { android::Res_value value = {}; value.dataType = android::Res_value::TYPE_INT_BOOLEAN; - if (result) { + if (maybeResult.value()) { value.data = 0xffffffffu; } else { value.data = 0; @@ -542,7 +544,7 @@ uint32_t androidTypeToAttributeTypeMask(uint16_t type) { }; } -std::unique_ptr<Item> parseItemForAttribute( +std::unique_ptr<Item> tryParseItemForAttribute( const StringPiece& value, uint32_t typeMask, std::function<void(const ResourceName&)> onCreateReference) { @@ -602,11 +604,11 @@ std::unique_ptr<Item> parseItemForAttribute( * We successively try to parse the string as a resource type that the Attribute * allows. */ -std::unique_ptr<Item> parseItemForAttribute( +std::unique_ptr<Item> tryParseItemForAttribute( const StringPiece& str, const Attribute* attr, std::function<void(const ResourceName&)> onCreateReference) { const uint32_t typeMask = attr->typeMask; - std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, onCreateReference); + std::unique_ptr<Item> value = tryParseItemForAttribute(str, typeMask, onCreateReference); if (value) { return value; } diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h index 31b8e89850e5..00a13908379e 100644 --- a/tools/aapt2/ResourceUtils.h +++ b/tools/aapt2/ResourceUtils.h @@ -28,11 +28,6 @@ namespace aapt { namespace ResourceUtils { -/** - * Convert an android::ResTable::resource_name to an aapt::ResourceName struct. - */ -Maybe<ResourceName> toResourceName(const android::ResTable::resource_name& name); - /* * Extracts the package, type, and name from a string of the format: * @@ -60,8 +55,8 @@ bool parseResourceName(const StringPiece& str, ResourceNameRef* outResource, * If '+' was present in the reference, `outCreate` is set to true. * If '*' was present in the reference, `outPrivate` is set to true. */ -bool tryParseReference(const StringPiece& str, ResourceNameRef* outReference, - bool* outCreate = nullptr, bool* outPrivate = nullptr); +bool parseReference(const StringPiece& str, ResourceNameRef* outReference, + bool* outCreate = nullptr, bool* outPrivate = nullptr); /* * Returns true if the string is in the form of a resource reference (@[+][package:]type/name). @@ -72,7 +67,7 @@ bool isReference(const StringPiece& str); * Returns true if the string was parsed as an attribute reference (?[package:][type/]name), * with `outReference` set to the parsed reference. */ -bool tryParseAttributeReference(const StringPiece& str, ResourceNameRef* outReference); +bool parseAttributeReference(const StringPiece& str, ResourceNameRef* outReference); /** * Returns true if the string is in the form of an attribute reference(?[package:][type/]name). @@ -80,19 +75,29 @@ bool tryParseAttributeReference(const StringPiece& str, ResourceNameRef* outRefe bool isAttributeReference(const StringPiece& str); /** - * Returns true if the value is a boolean, putting the result in `outValue`. + * Convert an android::ResTable::resource_name to an aapt::ResourceName struct. + */ +Maybe<ResourceName> toResourceName(const android::ResTable::resource_name& name); + +/** + * Returns a boolean value if the string is equal to TRUE, true, True, FALSE, false, or False. + */ +Maybe<bool> parseBool(const StringPiece& str); + +/** + * Returns a uint32_t if the string is an integer. */ -bool tryParseBool(const StringPiece& str, bool* outValue); +Maybe<uint32_t> parseInt(const StringPiece& str); /** * Returns an ID if it the string represented a valid ID. */ -Maybe<ResourceId> tryParseResourceId(const StringPiece& str); +Maybe<ResourceId> parseResourceId(const StringPiece& str); /** * Parses an SDK version, which can be an integer, or a letter from A-Z. */ -Maybe<int> tryParseSdkVersion(const StringPiece& str); +Maybe<int> parseSdkVersion(const StringPiece& str); /* * Returns a Reference, or None Maybe instance if the string `str` was parsed as a @@ -161,11 +166,11 @@ std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* enumAttr, * The callback function onCreateReference is called when the parsed item is a * reference to an ID that must be created (@+id/foo). */ -std::unique_ptr<Item> parseItemForAttribute( +std::unique_ptr<Item> tryParseItemForAttribute( const StringPiece& value, const Attribute* attr, std::function<void(const ResourceName&)> onCreateReference = {}); -std::unique_ptr<Item> parseItemForAttribute( +std::unique_ptr<Item> tryParseItemForAttribute( const StringPiece& value, uint32_t typeMask, std::function<void(const ResourceName&)> onCreateReference = {}); diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp index fb76914cc495..894cfcf72144 100644 --- a/tools/aapt2/ResourceUtils_test.cpp +++ b/tools/aapt2/ResourceUtils_test.cpp @@ -21,24 +21,12 @@ namespace aapt { TEST(ResourceUtilsTest, ParseBool) { - bool val = false; - EXPECT_TRUE(ResourceUtils::tryParseBool("true", &val)); - EXPECT_TRUE(val); - - EXPECT_TRUE(ResourceUtils::tryParseBool("TRUE", &val)); - EXPECT_TRUE(val); - - EXPECT_TRUE(ResourceUtils::tryParseBool("True", &val)); - EXPECT_TRUE(val); - - EXPECT_TRUE(ResourceUtils::tryParseBool("false", &val)); - EXPECT_FALSE(val); - - EXPECT_TRUE(ResourceUtils::tryParseBool("FALSE", &val)); - EXPECT_FALSE(val); - - EXPECT_TRUE(ResourceUtils::tryParseBool("False", &val)); - EXPECT_FALSE(val); + EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("true")); + EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("TRUE")); + EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("True")); + EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("false")); + EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("FALSE")); + EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("False")); } TEST(ResourceUtilsTest, ParseResourceName) { @@ -64,7 +52,7 @@ TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) { ResourceNameRef actual; bool create = false; bool privateRef = false; - EXPECT_TRUE(ResourceUtils::tryParseReference("@color/foo", &actual, &create, &privateRef)); + EXPECT_TRUE(ResourceUtils::parseReference("@color/foo", &actual, &create, &privateRef)); EXPECT_EQ(expected, actual); EXPECT_FALSE(create); EXPECT_FALSE(privateRef); @@ -75,7 +63,7 @@ TEST(ResourceUtilsTest, ParseReferenceWithPackage) { ResourceNameRef actual; bool create = false; bool privateRef = false; - EXPECT_TRUE(ResourceUtils::tryParseReference("@android:color/foo", &actual, &create, + EXPECT_TRUE(ResourceUtils::parseReference("@android:color/foo", &actual, &create, &privateRef)); EXPECT_EQ(expected, actual); EXPECT_FALSE(create); @@ -87,7 +75,7 @@ TEST(ResourceUtilsTest, ParseReferenceWithSurroundingWhitespace) { ResourceNameRef actual; bool create = false; bool privateRef = false; - EXPECT_TRUE(ResourceUtils::tryParseReference("\t @android:color/foo\n \n\t", &actual, + EXPECT_TRUE(ResourceUtils::parseReference("\t @android:color/foo\n \n\t", &actual, &create, &privateRef)); EXPECT_EQ(expected, actual); EXPECT_FALSE(create); @@ -99,7 +87,7 @@ TEST(ResourceUtilsTest, ParseAutoCreateIdReference) { ResourceNameRef actual; bool create = false; bool privateRef = false; - EXPECT_TRUE(ResourceUtils::tryParseReference("@+android:id/foo", &actual, &create, + EXPECT_TRUE(ResourceUtils::parseReference("@+android:id/foo", &actual, &create, &privateRef)); EXPECT_EQ(expected, actual); EXPECT_TRUE(create); @@ -111,7 +99,7 @@ TEST(ResourceUtilsTest, ParsePrivateReference) { ResourceNameRef actual; bool create = false; bool privateRef = false; - EXPECT_TRUE(ResourceUtils::tryParseReference("@*android:id/foo", &actual, &create, + EXPECT_TRUE(ResourceUtils::parseReference("@*android:id/foo", &actual, &create, &privateRef)); EXPECT_EQ(expected, actual); EXPECT_FALSE(create); @@ -122,7 +110,7 @@ TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) { bool create = false; bool privateRef = false; ResourceNameRef actual; - EXPECT_FALSE(ResourceUtils::tryParseReference("@+android:color/foo", &actual, &create, + EXPECT_FALSE(ResourceUtils::parseReference("@+android:color/foo", &actual, &create, &privateRef)); } diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index 4a865799372d..73682ab110c7 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -449,9 +449,7 @@ void Attribute::print(std::ostream* out) const { printMask(out); if (!symbols.empty()) { - *out << " [" - << util::joiner(symbols.begin(), symbols.end(), ", ") - << "]"; + *out << " [" << util::joiner(symbols, ", ") << "]"; } if (minInt != std::numeric_limits<int32_t>::min()) { @@ -600,7 +598,7 @@ void Style::print(std::ostream* out) const { *out << parent.value().name.value(); } *out << " [" - << util::joiner(entries.begin(), entries.end(), ", ") + << util::joiner(entries, ", ") << "]"; } @@ -645,7 +643,7 @@ Array* Array::clone(StringPool* newPool) const { void Array::print(std::ostream* out) const { *out << "(array) [" - << util::joiner(items.begin(), items.end(), ", ") + << util::joiner(items, ", ") << "]"; } @@ -730,7 +728,7 @@ Styleable* Styleable::clone(StringPool* /*newPool*/) const { void Styleable::print(std::ostream* out) const { *out << "(styleable) " << " [" - << util::joiner(entries.begin(), entries.end(), ", ") + << util::joiner(entries, ", ") << "]"; } diff --git a/tools/aapt2/compile/XmlIdCollector.cpp b/tools/aapt2/compile/XmlIdCollector.cpp index 9fc979c34353..3901419636b4 100644 --- a/tools/aapt2/compile/XmlIdCollector.cpp +++ b/tools/aapt2/compile/XmlIdCollector.cpp @@ -42,7 +42,7 @@ struct IdCollector : public xml::Visitor { for (xml::Attribute& attr : element->attributes) { ResourceNameRef name; bool create = false; - if (ResourceUtils::tryParseReference(attr.value, &name, &create, nullptr)) { + if (ResourceUtils::parseReference(attr.value, &name, &create, nullptr)) { if (create && name.type == ResourceType::kId) { auto iter = std::lower_bound(mOutSymbols->begin(), mOutSymbols->end(), name, cmpName); diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp index acb0f38995cc..c1c5ba22026b 100644 --- a/tools/aapt2/link/Link.cpp +++ b/tools/aapt2/link/Link.cpp @@ -59,11 +59,14 @@ struct LinkOptions { std::string manifestPath; std::vector<std::string> includePaths; std::vector<std::string> overlayFiles; + + // Java/Proguard options. Maybe<std::string> generateJavaClassPath; Maybe<std::string> customJavaPackage; std::set<std::string> extraJavaPackages; Maybe<std::string> generateProguardRulesPath; Maybe<std::string> generateMainDexProguardRulesPath; + bool noAutoVersion = false; bool noVersionVectors = false; bool staticLib = false; @@ -77,7 +80,13 @@ struct LinkOptions { Maybe<std::string> privateSymbols; ManifestFixerOptions manifestFixerOptions; std::unordered_set<std::string> products; + + // Split APK options. TableSplitterOptions tableSplitterOptions; + std::vector<SplitConstraints> splitConstraints; + std::vector<std::string> splitPaths; + + // Stable ID options. std::unordered_map<ResourceName, ResourceId> stableIdMap; Maybe<std::string> resourceIdMapPath; }; @@ -585,7 +594,7 @@ static bool loadStableIdMap(IDiagnostics* diag, const std::string& path, const size_t resIdStrLen = line.size() - resIdStartIdx; StringPiece resIdStr = util::trimWhitespace(line.substr(resIdStartIdx, resIdStrLen)); - Maybe<ResourceId> maybeId = ResourceUtils::tryParseResourceId(resIdStr); + Maybe<ResourceId> maybeId = ResourceUtils::parseResourceId(resIdStr); if (!maybeId) { diag->error(DiagMessage(Source(path, lineNo)) << "invalid resource ID '" << resIdStr << "'"); @@ -597,6 +606,28 @@ static bool loadStableIdMap(IDiagnostics* diag, const std::string& path, return true; } +static bool parseSplitParameter(const StringPiece& arg, IDiagnostics* diag, + std::string* outPath, SplitConstraints* outSplit) { + std::vector<std::string> parts = util::split(arg, ':'); + if (parts.size() != 2) { + diag->error(DiagMessage() << "invalid split parameter '" << arg << "'"); + diag->note(DiagMessage() << "should be --split path/to/output.apk:<config>[,<config>...]"); + return false; + } + *outPath = parts[0]; + std::vector<ConfigDescription> configs; + for (const StringPiece& configStr : util::tokenize(parts[1], ',')) { + configs.push_back({}); + if (!ConfigDescription::parse(configStr, &configs.back())) { + diag->error(DiagMessage() << "invalid config '" << configStr + << "' in split parameter '" << arg << "'"); + return false; + } + } + outSplit->configs.insert(configs.begin(), configs.end()); + return true; +} + class LinkCommand { public: LinkCommand(LinkContext* context, const LinkOptions& options) : @@ -676,6 +707,30 @@ public: appInfo.package = packageAttr->value; + if (xml::Attribute* versionCodeAttr = + manifestEl->findAttribute(xml::kSchemaAndroid, "versionCode")) { + Maybe<uint32_t> maybeCode = ResourceUtils::parseInt(versionCodeAttr->value); + if (!maybeCode) { + diag->error(DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber)) + << "invalid android:versionCode '" + << versionCodeAttr->value << "'"); + return {}; + } + appInfo.versionCode = maybeCode.value(); + } + + if (xml::Attribute* revisionCodeAttr = + manifestEl->findAttribute(xml::kSchemaAndroid, "revisionCode")) { + Maybe<uint32_t> maybeCode = ResourceUtils::parseInt(revisionCodeAttr->value); + if (!maybeCode) { + diag->error(DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber)) + << "invalid android:revisionCode '" + << revisionCodeAttr->value << "'"); + return {}; + } + appInfo.revisionCode = maybeCode.value(); + } + if (xml::Element* usesSdkEl = manifestEl->findChild({}, "uses-sdk")) { if (xml::Attribute* minSdk = usesSdkEl->findAttribute(xml::kSchemaAndroid, "minSdkVersion")) { @@ -767,11 +822,11 @@ public: return true; } - std::unique_ptr<IArchiveWriter> makeArchiveWriter() { + std::unique_ptr<IArchiveWriter> makeArchiveWriter(const StringPiece& out) { if (mOptions.outputToDirectory) { - return createDirectoryArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath); + return createDirectoryArchiveWriter(mContext->getDiagnostics(), out); } else { - return createZipFileArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath); + return createZipFileArchiveWriter(mContext->getDiagnostics(), out); } } @@ -1179,6 +1234,94 @@ public: return true; } + std::unique_ptr<xml::XmlResource> generateSplitManifest(const AppInfo& appInfo, + const SplitConstraints& constraints) { + std::unique_ptr<xml::XmlResource> doc = util::make_unique<xml::XmlResource>(); + + std::unique_ptr<xml::Namespace> namespaceAndroid = util::make_unique<xml::Namespace>(); + namespaceAndroid->namespaceUri = xml::kSchemaAndroid; + namespaceAndroid->namespacePrefix = "android"; + + std::unique_ptr<xml::Element> manifestEl = util::make_unique<xml::Element>(); + manifestEl->name = "manifest"; + manifestEl->attributes.push_back( + xml::Attribute{ "", "package", appInfo.package }); + + if (appInfo.versionCode) { + manifestEl->attributes.push_back(xml::Attribute{ + xml::kSchemaAndroid, + "versionCode", + std::to_string(appInfo.versionCode.value()) }); + } + + if (appInfo.revisionCode) { + manifestEl->attributes.push_back(xml::Attribute{ + xml::kSchemaAndroid, + "revisionCode", std::to_string(appInfo.revisionCode.value()) }); + } + + std::stringstream splitName; + splitName << "config." << util::joiner(constraints.configs, "_"); + + manifestEl->attributes.push_back( + xml::Attribute{ "", "split", splitName.str() }); + + std::unique_ptr<xml::Element> applicationEl = util::make_unique<xml::Element>(); + applicationEl->name = "application"; + applicationEl->attributes.push_back( + xml::Attribute{ xml::kSchemaAndroid, "hasCode", "false" }); + + manifestEl->addChild(std::move(applicationEl)); + namespaceAndroid->addChild(std::move(manifestEl)); + doc->root = std::move(namespaceAndroid); + return doc; + } + + /** + * Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable + * to the IArchiveWriter. + */ + bool writeApk(IArchiveWriter* writer, proguard::KeepSet* keepSet, xml::XmlResource* manifest, + ResourceTable* table) { + const bool keepRawValues = mOptions.staticLib; + bool result = flattenXml(manifest, "AndroidManifest.xml", {}, keepRawValues, writer, + mContext); + if (!result) { + return false; + } + + ResourceFileFlattenerOptions fileFlattenerOptions; + fileFlattenerOptions.keepRawValues = keepRawValues; + fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything; + fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress; + fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion; + fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors; + fileFlattenerOptions.updateProguardSpec = + static_cast<bool>(mOptions.generateProguardRulesPath); + + ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, keepSet); + + if (!fileFlattener.flatten(table, writer)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources"); + return false; + } + + if (mOptions.staticLib) { + if (!flattenTableToPb(table, writer)) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed to write resources.arsc.flat"); + return false; + } + } else { + if (!flattenTable(table, writer)) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed to write resources.arsc"); + return false; + } + } + return true; + } + int run(const std::vector<std::string>& inputFiles) { // Load the AndroidManifest.xml std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath, @@ -1187,30 +1330,33 @@ public: return 1; } + // First extract the Package name without modifying it (via --rename-manifest-package). if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(), mContext->getDiagnostics())) { - AppInfo& appInfo = maybeAppInfo.value(); + const AppInfo& appInfo = maybeAppInfo.value(); mContext->setCompilationPackage(appInfo.package); - if (appInfo.minSdkVersion) { - if (Maybe<int> maybeMinSdkVersion = - ResourceUtils::tryParseSdkVersion(appInfo.minSdkVersion.value())) { - mContext->setMinSdkVersion(maybeMinSdkVersion.value()); - } - } - } else { + } + + ManifestFixer manifestFixer(mOptions.manifestFixerOptions); + if (!manifestFixer.consume(mContext, manifestXml.get())) { return 1; } - if (!util::isJavaPackageName(mContext->getCompilationPackage())) { - mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath) - << "invalid package name '" - << mContext->getCompilationPackage() - << "'"); + Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(), + mContext->getDiagnostics()); + if (!maybeAppInfo) { return 1; } - mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() }); + const AppInfo& appInfo = maybeAppInfo.value(); + if (appInfo.minSdkVersion) { + if (Maybe<int> maybeMinSdkVersion = + ResourceUtils::parseSdkVersion(appInfo.minSdkVersion.value())) { + mContext->setMinSdkVersion(maybeMinSdkVersion.value()); + } + } + mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() }); if (mContext->getCompilationPackage() == "android") { mContext->setPackageId(0x01); } else { @@ -1258,9 +1404,7 @@ public: DiagMessage() << "failed moving private attributes"); return 1; } - } - if (!mOptions.staticLib) { // Assign IDs if we are building a regular app. IdAssigner idAssigner(&mOptions.stableIdMap); if (!idAssigner.consume(mContext, &mFinalTable)) { @@ -1304,45 +1448,118 @@ public: mContext->getExternalSymbols()->prependSource( util::make_unique<ResourceTableSymbolSource>(&mFinalTable)); - { - ReferenceLinker linker; - if (!linker.consume(mContext, &mFinalTable)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed linking references"); + ReferenceLinker linker; + if (!linker.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed linking references"); + return 1; + } + + if (mOptions.staticLib) { + if (!mOptions.products.empty()) { + mContext->getDiagnostics()->warn( + DiagMessage() << "can't select products when building static library"); + } + } else { + ProductFilter productFilter(mOptions.products); + if (!productFilter.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products"); + return 1; + } + } + + if (!mOptions.noAutoVersion) { + AutoVersioner versioner; + if (!versioner.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles"); return 1; } + } + + if (!mOptions.staticLib && mContext->getMinSdkVersion() > 0) { + if (mContext->verbose()) { + mContext->getDiagnostics()->note( + DiagMessage() << "collapsing resource versions for minimum SDK " + << mContext->getMinSdkVersion()); + } - if (mOptions.staticLib) { - if (!mOptions.products.empty()) { - mContext->getDiagnostics()->warn( - DiagMessage() << "can't select products when building static library"); + VersionCollapser collapser; + if (!collapser.consume(mContext, &mFinalTable)) { + return 1; + } + } + + proguard::KeepSet proguardKeepSet; + proguard::KeepSet proguardMainDexKeepSet; + + if (mOptions.staticLib) { + if (mOptions.tableSplitterOptions.configFilter != nullptr || + mOptions.tableSplitterOptions.preferredDensity) { + mContext->getDiagnostics()->warn( + DiagMessage() << "can't strip resources when building static library"); + } + } else { + // Adjust the SplitConstraints so that their SDK version is stripped if it is less + // than or equal to the minSdk. Otherwise the resources that have had their SDK version + // stripped due to minSdk won't ever match. + std::vector<SplitConstraints> adjustedConstraintsList; + adjustedConstraintsList.reserve(mOptions.splitConstraints.size()); + for (const SplitConstraints& constraints : mOptions.splitConstraints) { + SplitConstraints adjustedConstraints; + for (const ConfigDescription& config : constraints.configs) { + if (config.sdkVersion <= mContext->getMinSdkVersion()) { + adjustedConstraints.configs.insert(config.copyWithoutSdkVersion()); + } else { + adjustedConstraints.configs.insert(config); + } } + adjustedConstraintsList.push_back(std::move(adjustedConstraints)); + } - if (mOptions.tableSplitterOptions.configFilter != nullptr || - mOptions.tableSplitterOptions.preferredDensity) { - mContext->getDiagnostics()->warn( - DiagMessage() << "can't strip resources when building static library"); + TableSplitter tableSplitter(adjustedConstraintsList, mOptions.tableSplitterOptions); + if (!tableSplitter.verifySplitConstraints(mContext)) { + return 1; + } + tableSplitter.splitTable(&mFinalTable); + + // Now we need to write out the Split APKs. + auto pathIter = mOptions.splitPaths.begin(); + auto splitConstraintsIter = adjustedConstraintsList.begin(); + for (std::unique_ptr<ResourceTable>& splitTable : tableSplitter.getSplits()) { + if (mContext->verbose()) { + mContext->getDiagnostics()->note( + DiagMessage(*pathIter) << "generating split with configurations '" + << util::joiner(splitConstraintsIter->configs, ", ") << "'"); } - } else { - ProductFilter productFilter(mOptions.products); - if (!productFilter.consume(mContext, &mFinalTable)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products"); + + std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(*pathIter); + if (!archiveWriter) { + mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive"); + return 1; + } + + // Generate an AndroidManifest.xml for each split. + std::unique_ptr<xml::XmlResource> splitManifest = + generateSplitManifest(appInfo, *splitConstraintsIter); + + XmlReferenceLinker linker; + if (!linker.consume(mContext, splitManifest.get())) { + mContext->getDiagnostics()->error( + DiagMessage() << "failed to create Split AndroidManifest.xml"); return 1; } - // TODO(adamlesinski): Actually pass in split constraints and handle splits at the file - // level. - TableSplitter tableSplitter({}, mOptions.tableSplitterOptions); - if (!tableSplitter.verifySplitConstraints(mContext)) { + if (!writeApk(archiveWriter.get(), &proguardKeepSet, splitManifest.get(), + splitTable.get())) { return 1; } - tableSplitter.splitTable(&mFinalTable); + + ++pathIter; + ++splitConstraintsIter; } } - proguard::KeepSet proguardKeepSet; - proguard::KeepSet proguardMainDexKeepSet; - - std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(); + // Start writing the base APK. + std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(mOptions.outputPath); if (!archiveWriter) { mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive"); return 1; @@ -1350,11 +1567,6 @@ public: bool error = false; { - ManifestFixer manifestFixer(mOptions.manifestFixerOptions); - if (!manifestFixer.consume(mContext, manifestXml.get())) { - error = true; - } - // AndroidManifest.xml has no resource name, but the CallSite is built from the name // (aka, which package the AndroidManifest.xml is coming from). // So we give it a package name so it can see local resources. @@ -1382,13 +1594,6 @@ public: error = true; } } - - const bool keepRawValues = mOptions.staticLib; - bool result = flattenXml(manifestXml.get(), "AndroidManifest.xml", {}, - keepRawValues, archiveWriter.get(), mContext); - if (!result) { - error = true; - } } else { error = true; } @@ -1399,58 +1604,10 @@ public: return 1; } - if (!mOptions.noAutoVersion) { - AutoVersioner versioner; - if (!versioner.consume(mContext, &mFinalTable)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles"); - return 1; - } - } - - if (!mOptions.staticLib && mContext->getMinSdkVersion() > 0) { - if (mContext->verbose()) { - mContext->getDiagnostics()->note( - DiagMessage() << "collapsing resource versions for minimum SDK " - << mContext->getMinSdkVersion()); - } - - VersionCollapser collapser; - if (!collapser.consume(mContext, &mFinalTable)) { - return 1; - } - } - - // Write out the table to an archive. Optimizations to the table should come before this - // step. - ResourceFileFlattenerOptions fileFlattenerOptions; - fileFlattenerOptions.keepRawValues = mOptions.staticLib; - fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything; - fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress; - fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion; - fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors; - fileFlattenerOptions.updateProguardSpec = - static_cast<bool>(mOptions.generateProguardRulesPath); - ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, &proguardKeepSet); - - if (!fileFlattener.flatten(&mFinalTable, archiveWriter.get())) { - mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources"); + if (!writeApk(archiveWriter.get(), &proguardKeepSet, manifestXml.get(), &mFinalTable)) { return 1; } - if (mOptions.staticLib) { - if (!flattenTableToPb(&mFinalTable, archiveWriter.get())) { - mContext->getDiagnostics()->error(DiagMessage() - << "failed to write resources.arsc.flat"); - return 1; - } - } else { - if (!flattenTable(&mFinalTable, archiveWriter.get())) { - mContext->getDiagnostics()->error(DiagMessage() - << "failed to write resources.arsc"); - return 1; - } - } - if (mOptions.generateJavaClassPath) { JavaClassGeneratorOptions options; options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; @@ -1538,6 +1695,7 @@ int link(const std::vector<StringPiece>& args) { bool requireLocalization = false; bool verbose = false; Maybe<std::string> stableIdFilePath; + std::vector<std::string> splitArgs; Flags flags = Flags() .requiredFlag("-o", "Output path", &options.outputPath) .requiredFlag("--manifest", "Path to the Android manifest to build", @@ -1623,6 +1781,9 @@ int link(const std::vector<StringPiece>& args) { &options.manifestFixerOptions.renameInstrumentationTargetPackage) .optionalFlagList("-0", "File extensions not to compress", &options.extensionsToNotCompress) + .optionalFlagList("--split", "Split resources matching a set of configs out to a " + "Split APK.\nSyntax: path/to/output.apk:<config>[,<config>[...]]", + &splitArgs) .optionalSwitch("-v", "Enables verbose logging", &verbose); @@ -1741,6 +1902,16 @@ int link(const std::vector<StringPiece>& args) { ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2", ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"}); + // Parse the split parameters. + for (const std::string& splitArg : splitArgs) { + options.splitPaths.push_back({}); + options.splitConstraints.push_back({}); + if (!parseSplitParameter(splitArg, context.getDiagnostics(), &options.splitPaths.back(), + &options.splitConstraints.back())) { + return 1; + } + } + // Turn off auto versioning for static-libs. if (options.staticLib) { options.noAutoVersion = true; diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp index 6aa9c0edd059..be7aca3ca49f 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -138,7 +138,7 @@ private: const Attribute* attr) { if (RawString* rawString = valueCast<RawString>(value.get())) { std::unique_ptr<Item> transformed = - ResourceUtils::parseItemForAttribute(*rawString->value, attr); + ResourceUtils::tryParseItemForAttribute(*rawString->value, attr); // If we could not parse as any specific type, try a basic STRING. if (!transformed && (attr->typeMask & android::ResTable_map::TYPE_STRING)) { diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp index a29d8dcfe3c6..59ffe15fd4a4 100644 --- a/tools/aapt2/link/XmlReferenceLinker.cpp +++ b/tools/aapt2/link/XmlReferenceLinker.cpp @@ -106,7 +106,7 @@ public: } const Attribute* attribute = &attr.compiledAttribute.value().attribute; - attr.compiledValue = ResourceUtils::parseItemForAttribute(attr.value, + attr.compiledValue = ResourceUtils::tryParseItemForAttribute(attr.value, attribute); if (!attr.compiledValue && !(attribute->typeMask & android::ResTable_map::TYPE_STRING)) { diff --git a/tools/aapt2/readme.md b/tools/aapt2/readme.md index 95c0173f921a..a68d6f67324f 100644 --- a/tools/aapt2/readme.md +++ b/tools/aapt2/readme.md @@ -1,5 +1,14 @@ # Android Asset Packaging Tool 2.0 (AAPT2) release notes +## Version 2.1 +### `aapt2 link ...` +- Configuration Split APK support: supports splitting resources that match a set of + configurations to a separate APK which can be loaded alongside the base APK on + API 21+ devices. This is done using the flag + `--split path/to/split.apk:<config1>[,<config2>,...]`. +- SDK version resource filtering: Resources with an SDK version qualifier that is unreachable + at runtime due to the minimum SDK level declared by the AndroidManifest.xml are stripped. + ## Version 2.0 ### `aapt2 compile ...` - Pseudo-localization: generates pseudolocalized versions of default strings when the diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp index 2dfe2a239900..08b9ee9cbe1b 100644 --- a/tools/aapt2/split/TableSplitter.cpp +++ b/tools/aapt2/split/TableSplitter.cpp @@ -17,6 +17,7 @@ #include "ConfigDescription.h" #include "ResourceTable.h" #include "split/TableSplitter.h" +#include "util/Util.h" #include <algorithm> #include <map> @@ -76,7 +77,6 @@ public: // in multiple splits. const ConfigDescription& config = entry.first; const std::vector<ResourceConfigValue*>& relatedValues = entry.second; - auto densityValueIter = mDensityDependentConfigToDensityMap.find(config); if (densityValueIter != mDensityDependentConfigToDensityMap.end()) { // Select the best one! @@ -89,12 +89,12 @@ public: thisValue->config.isBetterThan(bestValue->config, &targetDensity)) { bestValue = thisValue; } - - // When we select one of these, they are all claimed such that the base - // doesn't include any anymore. - (*claimedValues)[thisValue] = true; } assert(bestValue); + + // When we select one of these, they are all claimed such that the base + // doesn't include any anymore. + (*claimedValues)[bestValue] = true; selected.push_back(bestValue); } } @@ -135,7 +135,6 @@ static void markNonPreferredDensitiesAsClaimed(uint16_t preferredDensity, assert(bestValue); } } - bool TableSplitter::verifySplitConstraints(IAaptContext* context) { bool error = false; for (size_t i = 0; i < mSplitConstraints.size(); i++) { diff --git a/tools/aapt2/split/TableSplitter.h b/tools/aapt2/split/TableSplitter.h index 15e0764c4259..2fa5c47d5bd6 100644 --- a/tools/aapt2/split/TableSplitter.h +++ b/tools/aapt2/split/TableSplitter.h @@ -60,7 +60,7 @@ public: void splitTable(ResourceTable* originalTable); - const std::vector<std::unique_ptr<ResourceTable>>& getSplits() { + std::vector<std::unique_ptr<ResourceTable>>& getSplits() { return mSplits; } diff --git a/tools/aapt2/split/TableSplitter_test.cpp b/tools/aapt2/split/TableSplitter_test.cpp index bad02a5cb332..5150e82b6d93 100644 --- a/tools/aapt2/split/TableSplitter_test.cpp +++ b/tools/aapt2/split/TableSplitter_test.cpp @@ -52,6 +52,71 @@ TEST(TableSplitterTest, NoSplitPreferredDensity) { EXPECT_NE(nullptr, test::getValue<Id>(table.get(), "android:string/one")); } +TEST(TableSplitterTest, SplitTableByDensity) { + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .addFileReference("android:drawable/foo", "res/drawable-mdpi/foo.png", + test::parseConfigOrDie("mdpi")) + .addFileReference("android:drawable/foo", "res/drawable-hdpi/foo.png", + test::parseConfigOrDie("hdpi")) + .addFileReference("android:drawable/foo", "res/drawable-xhdpi/foo.png", + test::parseConfigOrDie("xhdpi")) + .addFileReference("android:drawable/foo", "res/drawable-xxhdpi/foo.png", + test::parseConfigOrDie("xxhdpi")) + .build(); + + std::vector<SplitConstraints> constraints; + constraints.push_back(SplitConstraints{ { test::parseConfigOrDie("mdpi") } }); + constraints.push_back(SplitConstraints{ { test::parseConfigOrDie("hdpi") } }); + constraints.push_back(SplitConstraints{ { test::parseConfigOrDie("xhdpi") } }); + + TableSplitter splitter(constraints, TableSplitterOptions{}); + splitter.splitTable(table.get()); + + ASSERT_EQ(3u, splitter.getSplits().size()); + + ResourceTable* splitOne = splitter.getSplits()[0].get(); + ResourceTable* splitTwo = splitter.getSplits()[1].get(); + ResourceTable* splitThree = splitter.getSplits()[2].get(); + + // Just xxhdpi should be in the base. + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(), "android:drawable/foo", + test::parseConfigOrDie("mdpi"))); + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(), "android:drawable/foo", + test::parseConfigOrDie("hdpi"))); + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(table.get(), "android:drawable/foo", + test::parseConfigOrDie("xhdpi"))); + EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(table.get(), "android:drawable/foo", + test::parseConfigOrDie("xxhdpi"))); + + // Each split should have one and only one drawable. + EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(splitOne, "android:drawable/foo", + test::parseConfigOrDie("mdpi"))); + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitOne, "android:drawable/foo", + test::parseConfigOrDie("hdpi"))); + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitOne, "android:drawable/foo", + test::parseConfigOrDie("xhdpi"))); + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitOne, "android:drawable/foo", + test::parseConfigOrDie("xxhdpi"))); + + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitTwo, "android:drawable/foo", + test::parseConfigOrDie("mdpi"))); + EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(splitTwo, "android:drawable/foo", + test::parseConfigOrDie("hdpi"))); + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitTwo, "android:drawable/foo", + test::parseConfigOrDie("xhdpi"))); + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitTwo, "android:drawable/foo", + test::parseConfigOrDie("xxhdpi"))); + + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitThree, "android:drawable/foo", + test::parseConfigOrDie("mdpi"))); + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitThree, "android:drawable/foo", + test::parseConfigOrDie("hdpi"))); + EXPECT_NE(nullptr, test::getValueForConfig<FileReference>(splitThree, "android:drawable/foo", + test::parseConfigOrDie("xhdpi"))); + EXPECT_EQ(nullptr, test::getValueForConfig<FileReference>(splitThree, "android:drawable/foo", + test::parseConfigOrDie("xxhdpi"))); +} + TEST(TableSplitterTest, SplitTableByConfigAndDensity) { ResourceTable table; @@ -78,12 +143,12 @@ TEST(TableSplitterTest, SplitTableByConfigAndDensity) { ResourceTable* splitOne = splitter.getSplits()[0].get(); ResourceTable* splitTwo = splitter.getSplits()[1].get(); - // Since a split was defined, all densities should be gone from base. + // All but the xxhdpi resource should be gone, since there were closer matches in land-xhdpi. EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, "android:string/foo", test::parseConfigOrDie("land-hdpi"))); EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, "android:string/foo", test::parseConfigOrDie("land-xhdpi"))); - EXPECT_EQ(nullptr, test::getValueForConfig<Id>(&table, "android:string/foo", + EXPECT_NE(nullptr, test::getValueForConfig<Id>(&table, "android:string/foo", test::parseConfigOrDie("land-xxhdpi"))); EXPECT_NE(nullptr, test::getValueForConfig<Id>(splitOne, "android:string/foo", diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h index 4a10987b4400..09f9129ec17c 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -101,12 +101,16 @@ std::unique_ptr<T> make_unique(Args&&... args) { * Writes a set of items to the std::ostream, joining the times with the provided * separator. */ -template <typename Iterator> -::std::function<::std::ostream&(::std::ostream&)> joiner(Iterator begin, Iterator end, - const char* sep) { - return [begin, end, sep](::std::ostream& out) -> ::std::ostream& { - for (auto iter = begin; iter != end; ++iter) { - if (iter != begin) { +template <typename Container> +::std::function<::std::ostream&(::std::ostream&)> joiner(const Container& container, + const char* sep) { + using std::begin; + using std::end; + const auto beginIter = begin(container); + const auto endIter = end(container); + return [beginIter, endIter, sep](::std::ostream& out) -> ::std::ostream& { + for (auto iter = beginIter; iter != endIter; ++iter) { + if (iter != beginIter) { out << sep; } out << *iter; |