Merge "AAPT2: Variety of small fixes to get the build working"
diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp
index 64353de..13f8b3b 100644
--- a/tools/aapt2/ConfigDescription.cpp
+++ b/tools/aapt2/ConfigDescription.cpp
@@ -30,6 +30,11 @@
static const char* kWildcardName = "any";
+const ConfigDescription& ConfigDescription::defaultConfig() {
+ static ConfigDescription config = {};
+ return config;
+}
+
static bool parseMcc(const char* name, ResTable_config* out) {
if (strcmp(name, kWildcardName) == 0) {
if (out) out->mcc = 0;
diff --git a/tools/aapt2/ConfigDescription.h b/tools/aapt2/ConfigDescription.h
index 4af089d..5749816 100644
--- a/tools/aapt2/ConfigDescription.h
+++ b/tools/aapt2/ConfigDescription.h
@@ -29,6 +29,11 @@
* initialization and comparison methods.
*/
struct ConfigDescription : public android::ResTable_config {
+ /**
+ * Returns an immutable default config.
+ */
+ static const ConfigDescription& defaultConfig();
+
/*
* Parse a string of the form 'fr-sw600dp-land' and fill in the
* given ResTable_config with resulting configuration parameters.
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 5fce2c1..b4e75f9 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -210,4 +210,19 @@
std::cout << "}" << std::endl;
}
+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] << " ";
+ if (i % 8 == 7) {
+ std::cerr << "\n";
+ }
+ }
+
+ if (len - 1 % 8 != 7) {
+ std::cerr << std::endl;
+ }
+}
+
+
} // namespace aapt
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index 5b0d7d6..ba05be9 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -20,12 +20,16 @@
#include "Resource.h"
#include "ResourceTable.h"
+// Include for printf-like debugging.
+#include <iostream>
+
namespace aapt {
struct Debug {
static void printTable(ResourceTable* table);
static void printStyleGraph(ResourceTable* table,
const ResourceName& targetStyle);
+ static void dumpHex(const void* data, size_t len);
};
} // namespace aapt
diff --git a/tools/aapt2/Flags.cpp b/tools/aapt2/Flags.cpp
index 9435396..666e8a8e 100644
--- a/tools/aapt2/Flags.cpp
+++ b/tools/aapt2/Flags.cpp
@@ -81,6 +81,8 @@
}
void Flags::usage(const StringPiece& command, std::ostream* out) {
+ constexpr size_t kWidth = 50;
+
*out << command << " [options]";
for (const Flag& flag : mFlags) {
if (flag.required) {
@@ -100,11 +102,11 @@
// the first line) followed by the description line. This will make sure that multiline
// descriptions are still right justified and aligned.
for (StringPiece line : util::tokenize<char>(flag.description, '\n')) {
- *out << " " << std::setw(30) << std::left << argLine << line << "\n";
+ *out << " " << std::setw(kWidth) << std::left << argLine << line << "\n";
argLine = " ";
}
}
- *out << " " << std::setw(30) << std::left << "-h" << "Displays this help menu\n";
+ *out << " " << std::setw(kWidth) << std::left << "-h" << "Displays this help menu\n";
out->flush();
}
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 5e7d3ec..b37d366 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -70,6 +70,7 @@
*/
struct ParsedResource {
ResourceName name;
+ ConfigDescription config;
Source source;
ResourceId id;
Maybe<SymbolState> symbolState;
@@ -108,8 +109,7 @@
}
// Recursively adds resources to the ResourceTable.
-static bool addResourcesToTable(ResourceTable* table, const ConfigDescription& config,
- IDiagnostics* diag, ParsedResource* res) {
+static bool addResourcesToTable(ResourceTable* table, IDiagnostics* diag, ParsedResource* res) {
if (res->symbolState) {
Symbol symbol;
symbol.state = res->symbolState.value();
@@ -125,14 +125,14 @@
res->value->setComment(std::move(res->comment));
res->value->setSource(std::move(res->source));
- if (!table->addResource(res->name, res->id, config, std::move(res->value), diag)) {
+ if (!table->addResource(res->name, res->id, res->config, std::move(res->value), diag)) {
return false;
}
}
bool error = false;
for (ParsedResource& child : res->childResources) {
- error |= !addResourcesToTable(table, config, diag, &child);
+ error |= !addResourcesToTable(table, diag, &child);
}
return !error;
}
@@ -290,6 +290,7 @@
}
ParsedResource parsedResource;
+ parsedResource.config = mConfig;
parsedResource.source = mSource.withLine(parser->getLineNumber());
parsedResource.comment = std::move(comment);
@@ -310,7 +311,7 @@
// Record that we stripped out this resource name.
// We will check that at least one variant of this resource was included.
strippedResources.insert(parsedResource.name);
- } else if (!addResourcesToTable(mTable, mConfig, mDiag, &parsedResource)) {
+ } else if (!addResourcesToTable(mTable, mDiag, &parsedResource)) {
error = true;
}
}
@@ -769,6 +770,13 @@
bool weak) {
outResource->name.type = ResourceType::kAttr;
+ // Attributes only end up in default configuration.
+ if (outResource->config != ConfigDescription::defaultConfig()) {
+ mDiag->warn(DiagMessage(outResource->source) << "ignoring configuration '"
+ << outResource->config << "' for attribute " << outResource->name);
+ outResource->config = ConfigDescription::defaultConfig();
+ }
+
uint32_t typeMask = 0;
Maybe<StringPiece16> maybeFormat = xml::findAttribute(parser, u"format");
@@ -940,8 +948,7 @@
}
return Attribute::Symbol{
- Reference(ResourceNameRef({}, ResourceType::kId, maybeName.value())),
- val.data };
+ Reference(ResourceNameRef({}, ResourceType::kId, maybeName.value())), val.data };
}
static Maybe<Reference> parseXmlAttributeName(StringPiece16 str) {
@@ -1190,12 +1197,21 @@
return true;
}
-bool ResourceParser::parseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseDeclareStyleable(xml::XmlPullParser* parser,
+ ParsedResource* outResource) {
outResource->name.type = ResourceType::kStyleable;
// Declare-styleable is kPrivate by default, because it technically only exists in R.java.
outResource->symbolState = SymbolState::kPublic;
+ // Declare-styleable only ends up in default config;
+ if (outResource->config != ConfigDescription::defaultConfig()) {
+ mDiag->warn(DiagMessage(outResource->source) << "ignoring configuration '"
+ << outResource->config << "' for styleable "
+ << outResource->name.entry);
+ outResource->config = ConfigDescription::defaultConfig();
+ }
+
std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
std::u16string comment;
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 8d10ba1..cf0fcd1 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -47,13 +47,26 @@
mContext = test::ContextBuilder().build();
}
+ ::testing::AssertionResult testParse(const StringPiece& str) {
+ return testParse(str, ConfigDescription{}, {});
+ }
+
+ ::testing::AssertionResult testParse(const StringPiece& str, const ConfigDescription& config) {
+ return testParse(str, config, {});
+ }
+
::testing::AssertionResult testParse(const StringPiece& str,
- std::initializer_list<std::u16string> products = {}) {
+ std::initializer_list<std::u16string> products) {
+ return testParse(str, {}, std::move(products));
+ }
+
+ ::testing::AssertionResult testParse(const StringPiece& str, const ConfigDescription& config,
+ std::initializer_list<std::u16string> products) {
std::stringstream input(kXmlPreamble);
input << "<resources>\n" << str << "\n</resources>" << std::endl;
ResourceParserOptions parserOptions;
parserOptions.products = products;
- ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, {},
+ ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, config,
parserOptions);
xml::XmlPullParser xmlParser(input);
if (parser.parse(&xmlParser)) {
@@ -138,6 +151,26 @@
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask);
}
+// Old AAPT allowed attributes to be defined under different configurations, but ultimately
+// stored them with the default configuration. Check that we have the same behavior.
+TEST_F(ResourceParserTest, ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoConfig) {
+ const ConfigDescription watchConfig = test::parseConfigOrDie("watch");
+ std::string input = R"EOF(
+ <attr name="foo" />
+ <declare-styleable name="bar">
+ <attr name="baz" />
+ </declare-styleable>)EOF";
+ ASSERT_TRUE(testParse(input, watchConfig));
+
+ EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, u"@attr/foo", watchConfig));
+ EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, u"@attr/baz", watchConfig));
+ EXPECT_EQ(nullptr, test::getValueForConfig<Styleable>(&mTable, u"@styleable/bar", watchConfig));
+
+ EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, u"@attr/foo"));
+ EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, u"@attr/baz"));
+ EXPECT_NE(nullptr, test::getValue<Styleable>(&mTable, u"@styleable/bar"));
+}
+
TEST_F(ResourceParserTest, ParseAttrWithMinMax) {
std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"integer\"/>";
ASSERT_TRUE(testParse(input));
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 1dc123e..07f62af 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -176,7 +176,7 @@
/*
* Style parent's are a bit different. We accept the following formats:
*
- * @[[*]package:]style/<entry>
+ * @[[*]package:][style/]<entry>
* ?[[*]package:]style/<entry>
* <[*]package>:[style/]<entry>
* [[*]package:style/]<entry>
@@ -216,14 +216,6 @@
*outError = err.str();
return {};
}
- } else {
- // No type was defined, this should not have a leading identifier.
- if (hasLeadingIdentifiers) {
- std::stringstream err;
- err << "invalid parent reference '" << str << "'";
- *outError = err.str();
- return {};
- }
}
if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) {
@@ -294,6 +286,12 @@
const StringPiece16& str) {
android::Res_value flags = { };
flags.dataType = android::Res_value::TYPE_INT_DEC;
+ flags.data = 0u;
+
+ if (util::trimWhitespace(str).empty()) {
+ // Empty string is a valid flag (0).
+ return util::make_unique<BinaryPrimitive>(flags);
+ }
for (StringPiece16 part : util::tokenize(str, u'|')) {
StringPiece16 trimmedPart = util::trimWhitespace(part);
@@ -386,12 +384,12 @@
bool tryParseBool(const StringPiece16& str, bool* outValue) {
StringPiece16 trimmedStr(util::trimWhitespace(str));
- if (trimmedStr == u"true" || trimmedStr == u"TRUE") {
+ if (trimmedStr == u"true" || trimmedStr == u"TRUE" || trimmedStr == u"True") {
if (outValue) {
*outValue = true;
}
return true;
- } else if (trimmedStr == u"false" || trimmedStr == u"FALSE") {
+ } else if (trimmedStr == u"false" || trimmedStr == u"FALSE" || trimmedStr == u"False") {
if (outValue) {
*outValue = false;
}
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index 88efa67..c9f93e1 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -16,12 +16,34 @@
#include "Resource.h"
#include "ResourceUtils.h"
+#include "test/Builders.h"
#include "test/Common.h"
#include <gtest/gtest.h>
namespace aapt {
+TEST(ResourceUtilsTest, ParseBool) {
+ bool val = false;
+ EXPECT_TRUE(ResourceUtils::tryParseBool(u"true", &val));
+ EXPECT_TRUE(val);
+
+ EXPECT_TRUE(ResourceUtils::tryParseBool(u"TRUE", &val));
+ EXPECT_TRUE(val);
+
+ EXPECT_TRUE(ResourceUtils::tryParseBool(u"True", &val));
+ EXPECT_TRUE(val);
+
+ EXPECT_TRUE(ResourceUtils::tryParseBool(u"false", &val));
+ EXPECT_FALSE(val);
+
+ EXPECT_TRUE(ResourceUtils::tryParseBool(u"FALSE", &val));
+ EXPECT_FALSE(val);
+
+ EXPECT_TRUE(ResourceUtils::tryParseBool(u"False", &val));
+ EXPECT_FALSE(val);
+}
+
TEST(ResourceUtilsTest, ParseResourceName) {
ResourceNameRef actual;
bool actualPriv = false;
@@ -154,6 +176,10 @@
AAPT_ASSERT_TRUE(ref);
EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+ ref = ResourceUtils::parseStyleParentReference(u"@android:foo", &errStr);
+ AAPT_ASSERT_TRUE(ref);
+ EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName);
+
ref = ResourceUtils::parseStyleParentReference(u"foo", &errStr);
AAPT_ASSERT_TRUE(ref);
EXPECT_EQ(ref.value().name.value(), kStyleFooName);
@@ -164,4 +190,16 @@
EXPECT_TRUE(ref.value().privateReference);
}
+TEST(ResourceUtilsTest, ParseEmptyFlag) {
+ std::unique_ptr<Attribute> attr = test::AttributeBuilder(false)
+ .setTypeMask(android::ResTable_map::TYPE_FLAGS)
+ .addItem(u"one", 0x01)
+ .addItem(u"two", 0x02)
+ .build();
+
+ std::unique_ptr<BinaryPrimitive> result = ResourceUtils::tryParseFlagSymbol(attr.get(), u"");
+ ASSERT_NE(nullptr, result);
+ EXPECT_EQ(0u, result->value.data);
+}
+
} // namespace aapt
diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp
index 8552f47..aadb00b 100644
--- a/tools/aapt2/StringPool.cpp
+++ b/tools/aapt2/StringPool.cpp
@@ -14,9 +14,9 @@
* limitations under the License.
*/
+#include "StringPool.h"
#include "util/BigBuffer.h"
#include "util/StringPiece.h"
-#include "StringPool.h"
#include "util/Util.h"
#include <algorithm>
@@ -342,7 +342,14 @@
// Encode the actual UTF16 string length.
data = encodeLength(data, entry->value.size());
- strncpy16(data, entry->value.data(), entry->value.size());
+ const size_t byteLength = entry->value.size() * sizeof(char16_t);
+
+ // NOTE: For some reason, strncpy16(data, entry->value.data(), entry->value.size())
+ // truncates the string.
+ memcpy(data, entry->value.data(), byteLength);
+
+ // The null-terminating character is already here due to the block of data being set
+ // to 0s on allocation.
}
}
diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp
index c722fbe..e93c2fba 100644
--- a/tools/aapt2/StringPool_test.cpp
+++ b/tools/aapt2/StringPool_test.cpp
@@ -180,6 +180,22 @@
ASSERT_EQ(test.setTo(data.get(), buffer.size()), android::NO_ERROR);
}
+TEST(StringPoolTest, FlattenOddCharactersUtf16) {
+ StringPool pool;
+ pool.makeRef(u"\u093f");
+ BigBuffer buffer(1024);
+ StringPool::flattenUtf16(&buffer, pool);
+
+ std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+ android::ResStringPool test;
+ ASSERT_EQ(test.setTo(data.get(), buffer.size()), android::NO_ERROR);
+ size_t len = 0;
+ const char16_t* str = test.stringAt(0, &len);
+ EXPECT_EQ(1u, len);
+ EXPECT_EQ(u'\u093f', *str);
+ EXPECT_EQ(0u, str[1]);
+}
+
constexpr const char16_t* sLongString = u"バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限します。メール、SMSや、同期を使 用するその他のアプリは、起動しても更新されないことがあります。バッテリーセーバーは端末の充電中は自動的にOFFになります。";
TEST(StringPoolTest, FlattenUtf8) {
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index c78670f..689ace6 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -117,7 +117,11 @@
if (!data.configStr.empty()) {
name << "-" << data.configStr;
}
- name << "_" << data.name << "." << data.extension << ".flat";
+ name << "_" << data.name;
+ if (!data.extension.empty()) {
+ name << "." << data.extension;
+ }
+ name << ".flat";
return name.str();
}
@@ -386,16 +390,26 @@
fileExportWriter.getChunkHeader()->size =
util::hostToDevice32(buffer.size() + f.value().getDataLength());
- if (writer->writeEntry(buffer)) {
- if (writer->writeEntry(f.value().getDataPtr(), f.value().getDataLength())) {
- if (writer->finishEntry()) {
- return true;
- }
+ if (!writer->writeEntry(buffer)) {
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
+ return false;
+ }
+
+ // Only write if we have something to write. This is because mmap fails with length of 0,
+ // but we still want to compile the file to get the resource ID.
+ if (f.value().getDataPtr() && f.value().getDataLength() > 0) {
+ if (!writer->writeEntry(f.value().getDataPtr(), f.value().getDataLength())) {
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
+ return false;
}
}
- context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
- return false;
+ if (!writer->finishEntry()) {
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
+ return false;
+ }
+
+ return true;
}
class CompileContext : public IAaptContext {
diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h
index 9081c55..467e604 100644
--- a/tools/aapt2/io/Data.h
+++ b/tools/aapt2/io/Data.h
@@ -79,6 +79,21 @@
size_t mSize;
};
+/**
+ * When mmap fails because the file has length 0, we use the EmptyData to simulate data of length 0.
+ */
+class EmptyData : public IData {
+public:
+ const void* data() const override {
+ static const uint8_t d = 0;
+ return &d;
+ }
+
+ size_t size() const override {
+ return 0u;
+ }
+};
+
} // namespace io
} // namespace aapt
diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp
index 76f87ae..e758d8a4 100644
--- a/tools/aapt2/io/FileSystem.cpp
+++ b/tools/aapt2/io/FileSystem.cpp
@@ -32,7 +32,10 @@
std::unique_ptr<IData> RegularFile::openAsData() {
android::FileMap map;
if (Maybe<android::FileMap> map = file::mmapPath(mSource.path, nullptr)) {
- return util::make_unique<MmappedData>(std::move(map.value()));
+ if (map.value().getDataPtr() && map.value().getDataLength() > 0) {
+ return util::make_unique<MmappedData>(std::move(map.value()));
+ }
+ return util::make_unique<EmptyData>();
}
return {};
}
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 8a87d96..2a4c020 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -51,16 +51,19 @@
std::vector<std::string> includePaths;
std::vector<std::string> overlayFiles;
Maybe<std::string> generateJavaClassPath;
- std::set<std::string> extraJavaPackages;
+ Maybe<std::u16string> customJavaPackage;
+ std::set<std::u16string> extraJavaPackages;
Maybe<std::string> generateProguardRulesPath;
bool noAutoVersion = false;
bool staticLib = false;
bool verbose = false;
bool outputToDirectory = false;
bool autoAddOverlay = false;
+ bool doNotCompressAnything = false;
+ std::vector<std::string> extensionsToNotCompress;
Maybe<std::u16string> privateSymbols;
- Maybe<std::u16string> minSdkVersionDefault;
- Maybe<std::u16string> targetSdkVersionDefault;
+ ManifestFixerOptions manifestFixerOptions;
+
};
struct LinkContext : public IAaptContext {
@@ -186,7 +189,20 @@
return resFile;
}
- bool copyFileToArchive(io::IFile* file, const std::string& outPath, uint32_t flags,
+ uint32_t getCompressionFlags(const StringPiece& str) {
+ if (mOptions.doNotCompressAnything) {
+ return 0;
+ }
+
+ for (const std::string& extension : mOptions.extensionsToNotCompress) {
+ if (util::stringEndsWith<char>(str, extension)) {
+ return 0;
+ }
+ }
+ return ArchiveEntry::kCompress;
+ }
+
+ bool copyFileToArchive(io::IFile* file, const std::string& outPath,
IArchiveWriter* writer) {
std::unique_ptr<io::IData> data = file->openAsData();
if (!data) {
@@ -202,7 +218,7 @@
return false;
}
- if (writer->startEntry(outPath, flags)) {
+ if (writer->startEntry(outPath, getCompressionFlags(outPath))) {
if (writer->writeEntry(reinterpret_cast<const uint8_t*>(data->data()) + offset,
data->size() - static_cast<size_t>(offset))) {
if (writer->finishEntry()) {
@@ -319,7 +335,6 @@
return false;
}
-
if (writer->startEntry(path, ArchiveEntry::kCompress)) {
if (writer->writeEntry(buffer)) {
if (writer->finishEntry()) {
@@ -520,7 +535,7 @@
const Source& src = file->getSource();
if (util::stringEndsWith<char>(src.path, ".arsc.flat")) {
return mergeResourceTable(file, override);
- } else {
+ } else if (util::stringEndsWith<char>(src.path, ".flat")){
// Try opening the file and looking for an Export header.
std::unique_ptr<io::IData> data = file->openAsData();
if (!data) {
@@ -533,7 +548,11 @@
if (resourceFile) {
return mergeCompiledFile(file, std::move(resourceFile), override);
}
+ } else {
+ // Ignore non .flat files. This could be classes.dex or something else that happens
+ // to be in an archive.
}
+
return false;
}
@@ -646,10 +665,7 @@
bool error = false;
{
- ManifestFixerOptions manifestFixerOptions;
- manifestFixerOptions.minSdkVersionDefault = mOptions.minSdkVersionDefault;
- manifestFixerOptions.targetSdkVersionDefault = mOptions.targetSdkVersionDefault;
- ManifestFixer manifestFixer(manifestFixerOptions);
+ ManifestFixer manifestFixer(mOptions.manifestFixerOptions);
if (!manifestFixer.consume(&mContext, manifestXml.get())) {
error = true;
}
@@ -786,7 +802,7 @@
mContext.getDiagnostics()->note(DiagMessage() << "copying " << path);
}
- if (!copyFileToArchive(fileToMerge.file, fileToMerge.dstPath, 0,
+ if (!copyFileToArchive(fileToMerge.file, fileToMerge.dstPath,
archiveWriter.get())) {
error = true;
}
@@ -813,12 +829,18 @@
if (mOptions.generateJavaClassPath) {
JavaClassGeneratorOptions options;
+ options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
+
if (mOptions.staticLib) {
options.useFinal = false;
}
- StringPiece16 actualPackage = mContext.getCompilationPackage();
+ const StringPiece16 actualPackage = mContext.getCompilationPackage();
StringPiece16 outputPackage = mContext.getCompilationPackage();
+ if (mOptions.customJavaPackage) {
+ // Override the output java package to the custom one.
+ outputPackage = mOptions.customJavaPackage.value();
+ }
if (mOptions.privateSymbols) {
// If we defined a private symbols package, we only emit Public symbols
@@ -826,7 +848,7 @@
options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
if (!writeJavaFile(&mFinalTable, mContext.getCompilationPackage(),
- mContext.getCompilationPackage(), options)) {
+ outputPackage, options)) {
return 1;
}
@@ -838,9 +860,8 @@
return 1;
}
- for (const std::string& extraPackage : mOptions.extraJavaPackages) {
- if (!writeJavaFile(&mFinalTable, actualPackage, util::utf8ToUtf16(extraPackage),
- options)) {
+ for (const std::u16string& extraPackage : mOptions.extraJavaPackages) {
+ if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) {
return 1;
}
}
@@ -877,13 +898,16 @@
LinkOptions options;
Maybe<std::string> privateSymbolsPackage;
Maybe<std::string> minSdkVersion, targetSdkVersion;
+ Maybe<std::string> renameManifestPackage, renameInstrumentationTargetPackage;
+ Maybe<std::string> versionCode, versionName;
+ Maybe<std::string> customJavaPackage;
std::vector<std::string> extraJavaPackages;
Flags flags = Flags()
.requiredFlag("-o", "Output path", &options.outputPath)
.requiredFlag("--manifest", "Path to the Android manifest to build",
&options.manifestPath)
.optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
- .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics. "
+ .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n"
"The last conflicting resource given takes precedence.",
&options.overlayFiles)
.optionalFlag("--java", "Directory in which to generate R.java",
@@ -900,15 +924,29 @@
"AndroidManifest.xml", &minSdkVersion)
.optionalFlag("--target-sdk-version", "Default target SDK version to use for "
"AndroidManifest.xml", &targetSdkVersion)
+ .optionalFlag("--version-code", "Version code (integer) to inject into the "
+ "AndroidManifest.xml if none is present", &versionCode)
+ .optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml "
+ "if none is present", &versionName)
.optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
.optionalFlag("--private-symbols", "Package name to use when generating R.java for "
"private symbols.\n"
"If not specified, public and private symbols will use the application's "
"package name", &privateSymbolsPackage)
+ .optionalFlag("--custom-package", "Custom Java package under which to generate R.java",
+ &customJavaPackage)
.optionalFlagList("--extra-packages", "Generate the same R.java but with different "
"package names", &extraJavaPackages)
.optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in "
"overlays without <add-resource> tags", &options.autoAddOverlay)
+ .optionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml",
+ &renameManifestPackage)
+ .optionalFlag("--rename-instrumentation-target-package",
+ "Changes the name of the target package for instrumentation. Most useful "
+ "when used\nin conjunction with --rename-manifest-package",
+ &renameInstrumentationTargetPackage)
+ .optionalFlagList("-0", "File extensions not to compress",
+ &options.extensionsToNotCompress)
.optionalSwitch("-v", "Enables verbose logging", &options.verbose);
if (!flags.parse("aapt2 link", args, &std::cerr)) {
@@ -920,18 +958,42 @@
}
if (minSdkVersion) {
- options.minSdkVersionDefault = util::utf8ToUtf16(minSdkVersion.value());
+ options.manifestFixerOptions.minSdkVersionDefault =
+ util::utf8ToUtf16(minSdkVersion.value());
}
if (targetSdkVersion) {
- options.targetSdkVersionDefault = util::utf8ToUtf16(targetSdkVersion.value());
+ options.manifestFixerOptions.targetSdkVersionDefault =
+ util::utf8ToUtf16(targetSdkVersion.value());
+ }
+
+ if (renameManifestPackage) {
+ options.manifestFixerOptions.renameManifestPackage =
+ util::utf8ToUtf16(renameManifestPackage.value());
+ }
+
+ if (renameInstrumentationTargetPackage) {
+ options.manifestFixerOptions.renameInstrumentationTargetPackage =
+ util::utf8ToUtf16(renameInstrumentationTargetPackage.value());
+ }
+
+ if (versionCode) {
+ options.manifestFixerOptions.versionCodeDefault = util::utf8ToUtf16(versionCode.value());
+ }
+
+ if (versionName) {
+ options.manifestFixerOptions.versionNameDefault = util::utf8ToUtf16(versionName.value());
+ }
+
+ if (customJavaPackage) {
+ options.customJavaPackage = util::utf8ToUtf16(customJavaPackage.value());
}
// Populate the set of extra packages for which to generate R.java.
for (std::string& extraPackage : extraJavaPackages) {
// A given package can actually be a colon separated list of packages.
for (StringPiece package : util::split(extraPackage, ':')) {
- options.extraJavaPackages.insert(package.toString());
+ options.extraJavaPackages.insert(util::utf8ToUtf16(package));
}
}
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 2034c57..9baf1d8 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -22,25 +22,43 @@
namespace aapt {
static bool verifyManifest(IAaptContext* context, const Source& source, xml::Element* manifestEl) {
- bool error = false;
-
xml::Attribute* attr = manifestEl->findAttribute({}, u"package");
if (!attr) {
context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber))
<< "missing 'package' attribute");
- error = true;
} else if (ResourceUtils::isReference(attr->value)) {
context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber))
<< "value for attribute 'package' must not be a "
"reference");
- error = true;
} else if (!util::isJavaPackageName(attr->value)) {
context->getDiagnostics()->error(DiagMessage(source.withLine(manifestEl->lineNumber))
<< "invalid package name '" << attr->value << "'");
- error = true;
+ } else {
+ return true;
+ }
+ return false;
+}
+
+static bool includeVersionName(IAaptContext* context, const Source& source,
+ const StringPiece16& versionName, xml::Element* manifestEl) {
+ if (manifestEl->findAttribute(xml::kSchemaAndroid, u"versionName")) {
+ return true;
}
- return !error;
+ manifestEl->attributes.push_back(xml::Attribute{
+ xml::kSchemaAndroid, u"versionName", versionName.toString() });
+ return true;
+}
+
+static bool includeVersionCode(IAaptContext* context, const Source& source,
+ const StringPiece16& versionCode, xml::Element* manifestEl) {
+ if (manifestEl->findAttribute(xml::kSchemaAndroid, u"versionCode")) {
+ return true;
+ }
+
+ manifestEl->attributes.push_back(xml::Attribute{
+ xml::kSchemaAndroid, u"versionCode", versionCode.toString() });
+ return true;
}
static bool fixUsesSdk(IAaptContext* context, const Source& source, xml::Element* el,
@@ -62,6 +80,76 @@
return true;
}
+class FullyQualifiedClassNameVisitor : public xml::Visitor {
+public:
+ using xml::Visitor::visit;
+
+ FullyQualifiedClassNameVisitor(const StringPiece16& package) : mPackage(package) {
+ }
+
+ void visit(xml::Element* el) override {
+ for (xml::Attribute& attr : el->attributes) {
+ if (Maybe<std::u16string> newValue =
+ util::getFullyQualifiedClassName(mPackage, attr.value)) {
+ attr.value = std::move(newValue.value());
+ }
+ }
+
+ // Super implementation to iterate over the children.
+ xml::Visitor::visit(el);
+ }
+
+private:
+ StringPiece16 mPackage;
+};
+
+static bool renameManifestPackage(IAaptContext* context, const Source& source,
+ const StringPiece16& packageOverride, xml::Element* manifestEl) {
+ if (!util::isJavaPackageName(packageOverride)) {
+ context->getDiagnostics()->error(DiagMessage() << "invalid manifest package override '"
+ << packageOverride << "'");
+ return false;
+ }
+
+ xml::Attribute* attr = manifestEl->findAttribute({}, u"package");
+
+ // We've already verified that the manifest element is present, with a package name specified.
+ assert(attr);
+
+ std::u16string originalPackage = std::move(attr->value);
+ attr->value = packageOverride.toString();
+
+ FullyQualifiedClassNameVisitor visitor(originalPackage);
+ manifestEl->accept(&visitor);
+ return true;
+}
+
+static bool renameInstrumentationTargetPackage(IAaptContext* context, const Source& source,
+ const StringPiece16& packageOverride,
+ xml::Element* manifestEl) {
+ if (!util::isJavaPackageName(packageOverride)) {
+ context->getDiagnostics()->error(DiagMessage()
+ << "invalid instrumentation target package override '"
+ << packageOverride << "'");
+ return false;
+ }
+
+ xml::Element* instrumentationEl = manifestEl->findChild({}, u"instrumentation");
+ if (!instrumentationEl) {
+ // No error if there is no work to be done.
+ return true;
+ }
+
+ xml::Attribute* attr = instrumentationEl->findAttribute(xml::kSchemaAndroid, u"targetPackage");
+ if (!attr) {
+ // No error if there is no work to be done.
+ return true;
+ }
+
+ attr->value = packageOverride.toString();
+ return true;
+}
+
bool ManifestFixer::consume(IAaptContext* context, xml::XmlResource* doc) {
xml::Element* root = xml::findRootElement(doc->root.get());
if (!root || !root->namespaceUri.empty() || root->name != u"manifest") {
@@ -74,6 +162,36 @@
return false;
}
+ if (mOptions.versionCodeDefault) {
+ if (!includeVersionCode(context, doc->file.source, mOptions.versionCodeDefault.value(),
+ root)) {
+ return false;
+ }
+ }
+
+ if (mOptions.versionNameDefault) {
+ if (!includeVersionName(context, doc->file.source, mOptions.versionNameDefault.value(),
+ root)) {
+ return false;
+ }
+ }
+
+ if (mOptions.renameManifestPackage) {
+ // Rename manifest package.
+ if (!renameManifestPackage(context, doc->file.source,
+ mOptions.renameManifestPackage.value(), root)) {
+ return false;
+ }
+ }
+
+ if (mOptions.renameInstrumentationTargetPackage) {
+ if (!renameInstrumentationTargetPackage(context, doc->file.source,
+ mOptions.renameInstrumentationTargetPackage.value(),
+ root)) {
+ return false;
+ }
+ }
+
bool foundUsesSdk = false;
for (xml::Element* el : root->getChildElements()) {
if (!el->namespaceUri.empty()) {
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index a77e6d5..b8d9c83 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -28,6 +28,10 @@
struct ManifestFixerOptions {
Maybe<std::u16string> minSdkVersionDefault;
Maybe<std::u16string> targetSdkVersionDefault;
+ Maybe<std::u16string> renameManifestPackage;
+ Maybe<std::u16string> renameInstrumentationTargetPackage;
+ Maybe<std::u16string> versionNameDefault;
+ Maybe<std::u16string> versionCodeDefault;
};
/**
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index f6bf895..f40fbfb 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -82,8 +82,6 @@
EXPECT_EQ(nullptr, verify("<manifest package=\"@string/str\" />"));
}
-
-
TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) {
ManifestFixerOptions options = { std::u16string(u"8"), std::u16string(u"22") };
@@ -97,7 +95,7 @@
xml::Element* el;
xml::Attribute* attr;
- el = xml::findRootElement(doc->root.get());
+ el = xml::findRootElement(doc.get());
ASSERT_NE(nullptr, el);
el = el->findChild({}, u"uses-sdk");
ASSERT_NE(nullptr, el);
@@ -115,7 +113,7 @@
</manifest>)EOF", options);
ASSERT_NE(nullptr, doc);
- el = xml::findRootElement(doc->root.get());
+ el = xml::findRootElement(doc.get());
ASSERT_NE(nullptr, el);
el = el->findChild({}, u"uses-sdk");
ASSERT_NE(nullptr, el);
@@ -133,7 +131,7 @@
</manifest>)EOF", options);
ASSERT_NE(nullptr, doc);
- el = xml::findRootElement(doc->root.get());
+ el = xml::findRootElement(doc.get());
ASSERT_NE(nullptr, el);
el = el->findChild({}, u"uses-sdk");
ASSERT_NE(nullptr, el);
@@ -149,7 +147,7 @@
package="android" />)EOF", options);
ASSERT_NE(nullptr, doc);
- el = xml::findRootElement(doc->root.get());
+ el = xml::findRootElement(doc.get());
ASSERT_NE(nullptr, el);
el = el->findChild({}, u"uses-sdk");
ASSERT_NE(nullptr, el);
@@ -161,4 +159,98 @@
EXPECT_EQ(u"22", attr->value);
}
+TEST_F(ManifestFixerTest, RenameManifestPackageAndFullyQualifyClasses) {
+ ManifestFixerOptions options;
+ options.renameManifestPackage = std::u16string(u"com.android");
+
+ std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <application name=".MainApplication" text="hello">
+ <activity name=".activity.Start" />
+ <receiver name="com.google.android.Receiver" />
+ </application>
+ </manifest>)EOF", options);
+ ASSERT_NE(nullptr, doc);
+
+ xml::Element* manifestEl = xml::findRootElement(doc.get());
+ ASSERT_NE(nullptr, manifestEl);
+
+ xml::Attribute* attr = nullptr;
+
+ attr = manifestEl->findAttribute({}, u"package");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::u16string(u"com.android"), attr->value);
+
+ xml::Element* applicationEl = manifestEl->findChild({}, u"application");
+ ASSERT_NE(nullptr, applicationEl);
+
+ attr = applicationEl->findAttribute({}, u"name");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::u16string(u"android.MainApplication"), attr->value);
+
+ attr = applicationEl->findAttribute({}, u"text");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::u16string(u"hello"), attr->value);
+
+ xml::Element* el;
+ el = applicationEl->findChild({}, u"activity");
+ ASSERT_NE(nullptr, el);
+
+ attr = el->findAttribute({}, u"name");
+ ASSERT_NE(nullptr, el);
+ EXPECT_EQ(std::u16string(u"android.activity.Start"), attr->value);
+
+ el = applicationEl->findChild({}, u"receiver");
+ ASSERT_NE(nullptr, el);
+
+ attr = el->findAttribute({}, u"name");
+ ASSERT_NE(nullptr, el);
+ EXPECT_EQ(std::u16string(u"com.google.android.Receiver"), attr->value);
+}
+
+TEST_F(ManifestFixerTest, RenameManifestInstrumentationPackageAndFullyQualifyTarget) {
+ ManifestFixerOptions options;
+ options.renameInstrumentationTargetPackage = std::u16string(u"com.android");
+
+ std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android">
+ <instrumentation android:targetPackage="android" />
+ </manifest>)EOF", options);
+ ASSERT_NE(nullptr, doc);
+
+ xml::Element* manifestEl = xml::findRootElement(doc.get());
+ ASSERT_NE(nullptr, manifestEl);
+
+ xml::Element* instrumentationEl = manifestEl->findChild({}, u"instrumentation");
+ ASSERT_NE(nullptr, instrumentationEl);
+
+ xml::Attribute* attr = instrumentationEl->findAttribute(xml::kSchemaAndroid, u"targetPackage");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::u16string(u"com.android"), attr->value);
+}
+
+TEST_F(ManifestFixerTest, UseDefaultVersionNameAndCode) {
+ ManifestFixerOptions options;
+ options.versionNameDefault = std::u16string(u"Beta");
+ options.versionCodeDefault = std::u16string(u"0x10000000");
+
+ std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android" />)EOF", options);
+ ASSERT_NE(nullptr, doc);
+
+ xml::Element* manifestEl = xml::findRootElement(doc.get());
+ ASSERT_NE(nullptr, manifestEl);
+
+ xml::Attribute* attr = manifestEl->findAttribute(xml::kSchemaAndroid, u"versionName");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::u16string(u"Beta"), attr->value);
+
+ attr = manifestEl->findAttribute(xml::kSchemaAndroid, u"versionCode");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(std::u16string(u"0x10000000"), attr->value);
+}
+
} // namespace aapt
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index a81dc7b..04e8199 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -163,6 +163,11 @@
}
android::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 (outError) *outError = strerror(errno);
return {};
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index 9ecc974..7b0c71d 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -175,10 +175,11 @@
return {};
}
- std::u16string result(package.data(), package.size());
if (className.data()[0] != u'.') {
- result += u'.';
+ return {};
}
+
+ std::u16string result(package.data(), package.size());
result.append(className.data(), className.size());
if (!isJavaClassName(result)) {
return {};
diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp
index 9208e07..1e0c7fa 100644
--- a/tools/aapt2/util/Util_test.cpp
+++ b/tools/aapt2/util/Util_test.cpp
@@ -144,8 +144,7 @@
TEST(UtilTest, FullyQualifiedClassName) {
Maybe<std::u16string> res = util::getFullyQualifiedClassName(u"android", u"asdf");
- AAPT_ASSERT_TRUE(res);
- EXPECT_EQ(res.value(), u"android.asdf");
+ AAPT_ASSERT_FALSE(res);
res = util::getFullyQualifiedClassName(u"android", u".asdf");
AAPT_ASSERT_TRUE(res);