AAPT2: Expose split support to command line
Bug:30445078
Change-Id: If4b8530dba71b9059b8e62c04757da99c1119d22
diff --git a/tools/aapt2/AppInfo.h b/tools/aapt2/AppInfo.h
index 1d39b72..a9794a4 100644
--- a/tools/aapt2/AppInfo.h
+++ b/tools/aapt2/AppInfo.h
@@ -37,6 +37,16 @@
* 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 a74b5aa..ed55f85 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -25,7 +25,7 @@
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 a144c6a..bcdf401 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -500,8 +500,8 @@
};
// 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 @@
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 @@
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 @@
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 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 7dc88ded..f806d80 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -124,7 +124,7 @@
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 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 isAttributeReference(const StringPiece& str) {
- return tryParseAttributeReference(str, nullptr);
+ return parseAttributeReference(str, nullptr);
}
/*
@@ -271,13 +271,13 @@
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 @@
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<ResourceId> tryParseResourceId(const StringPiece& str) {
+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> parseResourceId(const StringPiece& str) {
StringPiece trimmedStr(util::trimWhitespace(str));
std::u16string str16 = util::utf8ToUtf16(trimmedStr);
@@ -452,7 +455,7 @@
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 @@
}
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 @@
};
}
-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 @@
* 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 31b8e89..00a1390 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 @@
* 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 @@
* 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 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.
*/
-bool tryParseBool(const StringPiece& str, bool* outValue);
+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.
+ */
+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 @@
* 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 fb76914..894cfcf 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 4a86579..73682ab 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -449,9 +449,7 @@
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 @@
*out << parent.value().name.value();
}
*out << " ["
- << util::joiner(entries.begin(), entries.end(), ", ")
+ << util::joiner(entries, ", ")
<< "]";
}
@@ -645,7 +643,7 @@
void Array::print(std::ostream* out) const {
*out << "(array) ["
- << util::joiner(items.begin(), items.end(), ", ")
+ << util::joiner(items, ", ")
<< "]";
}
@@ -730,7 +728,7 @@
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 9fc979c..3901419 100644
--- a/tools/aapt2/compile/XmlIdCollector.cpp
+++ b/tools/aapt2/compile/XmlIdCollector.cpp
@@ -42,7 +42,7 @@
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 acb0f38..c1c5ba2 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -59,11 +59,14 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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;
}
+ 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 @@
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 @@
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.staticLib) {
- if (!mOptions.products.empty()) {
- mContext->getDiagnostics()->warn(
- DiagMessage() << "can't select products when building static library");
- }
+ if (!mOptions.noAutoVersion) {
+ AutoVersioner versioner;
+ if (!versioner.consume(mContext, &mFinalTable)) {
+ mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles");
+ return 1;
+ }
+ }
- if (mOptions.tableSplitterOptions.configFilter != nullptr ||
- mOptions.tableSplitterOptions.preferredDensity) {
- mContext->getDiagnostics()->warn(
- DiagMessage() << "can't strip resources 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.staticLib && mContext->getMinSdkVersion() > 0) {
+ if (mContext->verbose()) {
+ mContext->getDiagnostics()->note(
+ DiagMessage() << "collapsing resource versions for minimum SDK "
+ << mContext->getMinSdkVersion());
+ }
- // TODO(adamlesinski): Actually pass in split constraints and handle splits at the file
- // level.
- TableSplitter tableSplitter({}, mOptions.tableSplitterOptions);
- if (!tableSplitter.verifySplitConstraints(mContext)) {
- return 1;
- }
- tableSplitter.splitTable(&mFinalTable);
+ VersionCollapser collapser;
+ if (!collapser.consume(mContext, &mFinalTable)) {
+ return 1;
}
}
proguard::KeepSet proguardKeepSet;
proguard::KeepSet proguardMainDexKeepSet;
- std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter();
+ 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));
+ }
+
+ 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, ", ") << "'");
+ }
+
+ 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;
+ }
+
+ if (!writeApk(archiveWriter.get(), &proguardKeepSet, splitManifest.get(),
+ splitTable.get())) {
+ return 1;
+ }
+
+ ++pathIter;
+ ++splitConstraintsIter;
+ }
+ }
+
+ // 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 @@
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 @@
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 @@
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 @@
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 @@
&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 @@
".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 6aa9c0e..be7aca3 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -138,7 +138,7 @@
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 a29d8dc..59ffe15 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -106,7 +106,7 @@
}
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 95c0173..a68d6f6 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 2dfe2a2..08b9ee9 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 @@
// 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 @@
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 @@
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 15e0764..2fa5c47 100644
--- a/tools/aapt2/split/TableSplitter.h
+++ b/tools/aapt2/split/TableSplitter.h
@@ -60,7 +60,7 @@
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 bad02a5..5150e82 100644
--- a/tools/aapt2/split/TableSplitter_test.cpp
+++ b/tools/aapt2/split/TableSplitter_test.cpp
@@ -52,6 +52,71 @@
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 @@
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 4a10987..09f9129 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -101,12 +101,16 @@
* 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;