diff options
author | 2016-01-14 02:51:02 +0000 | |
---|---|---|
committer | 2016-01-14 02:51:02 +0000 | |
commit | 44859db74bb1cc2341524c5484cc3e765ec42c42 (patch) | |
tree | 2ee50fa0fb92410de8867e7eec342e1e303ba54c | |
parent | e7d716d5e35a4e6b5fcc1ff012942e4ad4933d1a (diff) | |
parent | 52364f7ae31716d7827ea8f8566f4a28bd30a921 (diff) |
Merge "AAPT2: Variety of small fixes to get the build working"
-rw-r--r-- | tools/aapt2/ConfigDescription.cpp | 5 | ||||
-rw-r--r-- | tools/aapt2/ConfigDescription.h | 5 | ||||
-rw-r--r-- | tools/aapt2/Debug.cpp | 15 | ||||
-rw-r--r-- | tools/aapt2/Debug.h | 4 | ||||
-rw-r--r-- | tools/aapt2/Flags.cpp | 6 | ||||
-rw-r--r-- | tools/aapt2/ResourceParser.cpp | 32 | ||||
-rw-r--r-- | tools/aapt2/ResourceParser_test.cpp | 37 | ||||
-rw-r--r-- | tools/aapt2/ResourceUtils.cpp | 20 | ||||
-rw-r--r-- | tools/aapt2/ResourceUtils_test.cpp | 38 | ||||
-rw-r--r-- | tools/aapt2/StringPool.cpp | 11 | ||||
-rw-r--r-- | tools/aapt2/StringPool_test.cpp | 16 | ||||
-rw-r--r-- | tools/aapt2/compile/Compile.cpp | 30 | ||||
-rw-r--r-- | tools/aapt2/io/Data.h | 15 | ||||
-rw-r--r-- | tools/aapt2/io/FileSystem.cpp | 5 | ||||
-rw-r--r-- | tools/aapt2/link/Link.cpp | 104 | ||||
-rw-r--r-- | tools/aapt2/link/ManifestFixer.cpp | 130 | ||||
-rw-r--r-- | tools/aapt2/link/ManifestFixer.h | 4 | ||||
-rw-r--r-- | tools/aapt2/link/ManifestFixer_test.cpp | 104 | ||||
-rw-r--r-- | tools/aapt2/util/Files.cpp | 5 | ||||
-rw-r--r-- | tools/aapt2/util/Util.cpp | 5 | ||||
-rw-r--r-- | tools/aapt2/util/Util_test.cpp | 3 |
21 files changed, 523 insertions, 71 deletions
diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp index 64353deb204d..13f8b3b54f68 100644 --- a/tools/aapt2/ConfigDescription.cpp +++ b/tools/aapt2/ConfigDescription.cpp @@ -30,6 +30,11 @@ using android::ResTable_config; 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 4af089dc282a..5749816f5124 100644 --- a/tools/aapt2/ConfigDescription.h +++ b/tools/aapt2/ConfigDescription.h @@ -29,6 +29,11 @@ namespace aapt { * 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 5fce2c16f630..b4e75f9be3a9 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -210,4 +210,19 @@ void Debug::printStyleGraph(ResourceTable* table, const ResourceName& targetStyl 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 5b0d7d69e703..ba05be91e91d 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 9435396991df..666e8a8efff1 100644 --- a/tools/aapt2/Flags.cpp +++ b/tools/aapt2/Flags.cpp @@ -81,6 +81,8 @@ Flags& Flags::optionalSwitch(const StringPiece& name, const StringPiece& descrip } 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 @@ void Flags::usage(const StringPiece& command, std::ostream* out) { // 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 5e7d3ec4c1e0..b37d366a7887 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -70,6 +70,7 @@ static uint32_t parseFormatAttribute(const StringPiece16& str) { */ struct ParsedResource { ResourceName name; + ConfigDescription config; Source source; ResourceId id; Maybe<SymbolState> symbolState; @@ -108,8 +109,7 @@ bool ResourceParser::shouldStripResource(const ResourceNameRef& name, } // 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 @@ static bool addResourcesToTable(ResourceTable* table, const ConfigDescription& c 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 @@ bool ResourceParser::parseResources(xml::XmlPullParser* parser) { } ParsedResource parsedResource; + parsedResource.config = mConfig; parsedResource.source = mSource.withLine(parser->getLineNumber()); parsedResource.comment = std::move(comment); @@ -310,7 +311,7 @@ bool ResourceParser::parseResources(xml::XmlPullParser* parser) { // 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 ResourceParser::parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* o 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 @@ Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(xml::XmlPullParser* } 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 @@ bool ResourceParser::parsePlural(xml::XmlPullParser* parser, ParsedResource* out 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 8d10ba14924b..cf0fcd11b903 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -47,13 +47,26 @@ struct ResourceParserTest : public ::testing::Test { 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 @@ TEST_F(ResourceParserTest, ParseAttr) { 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 1dc123e45949..07f62afe05b9 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -176,7 +176,7 @@ bool isAttributeReference(const StringPiece16& str) { /* * 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 @@ Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string *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 @@ std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* flagAttr, 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 @@ std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str) { 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 88efa6779021..c9f93e1dd7c2 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 @@ TEST(ResourceUtilsTest, ParseStyleParentReference) { 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 @@ TEST(ResourceUtilsTest, ParseStyleParentReference) { 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 8552f470b123..aadb00b6be2a 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 @@ bool StringPool::flatten(BigBuffer* out, const StringPool& pool, bool utf8) { // 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 c722fbeca690..e93c2fba7f3c 100644 --- a/tools/aapt2/StringPool_test.cpp +++ b/tools/aapt2/StringPool_test.cpp @@ -180,6 +180,22 @@ TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) { 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 c78670fbc20d..689ace6e6aa1 100644 --- a/tools/aapt2/compile/Compile.cpp +++ b/tools/aapt2/compile/Compile.cpp @@ -117,7 +117,11 @@ static std::string buildIntermediateFilename(const ResourcePathData& data) { 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 @@ static bool compileFile(IAaptContext* context, const CompileOptions& options, 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 9081c55fc6e1..467e60464a68 100644 --- a/tools/aapt2/io/Data.h +++ b/tools/aapt2/io/Data.h @@ -79,6 +79,21 @@ private: 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 76f87aec6556..e758d8a421e1 100644 --- a/tools/aapt2/io/FileSystem.cpp +++ b/tools/aapt2/io/FileSystem.cpp @@ -32,7 +32,10 @@ RegularFile::RegularFile(const Source& source) : mSource(source) { 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 8a87d9691b5d..2a4c020c72fb 100644 --- a/tools/aapt2/link/Link.cpp +++ b/tools/aapt2/link/Link.cpp @@ -51,16 +51,19 @@ struct LinkOptions { 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 @@ public: 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 @@ public: 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 @@ public: return false; } - if (writer->startEntry(path, ArchiveEntry::kCompress)) { if (writer->writeEntry(buffer)) { if (writer->finishEntry()) { @@ -520,7 +535,7 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: 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 @@ public: options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic; if (!writeJavaFile(&mFinalTable, mContext.getCompilationPackage(), - mContext.getCompilationPackage(), options)) { + outputPackage, options)) { return 1; } @@ -838,9 +860,8 @@ public: 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 @@ int link(const std::vector<StringPiece>& args) { 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 @@ int link(const std::vector<StringPiece>& args) { "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 @@ int link(const std::vector<StringPiece>& args) { } 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 2034c5701492..9baf1d86795c 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 @@ static bool fixUsesSdk(IAaptContext* context, const Source& source, xml::Element 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 @@ bool ManifestFixer::consume(IAaptContext* context, xml::XmlResource* doc) { 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 a77e6d5f709c..b8d9c833ff05 100644 --- a/tools/aapt2/link/ManifestFixer.h +++ b/tools/aapt2/link/ManifestFixer.h @@ -28,6 +28,10 @@ namespace aapt { 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 f6bf895a7f57..f40fbfb2e81a 100644 --- a/tools/aapt2/link/ManifestFixer_test.cpp +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -82,8 +82,6 @@ TEST_F(ManifestFixerTest, EnsureManifestHasPackage) { 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 @@ TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) { 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 @@ TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) { </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 @@ TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) { </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 @@ TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) { 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 @@ TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) { 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 a81dc7b47926..04e8199d85b4 100644 --- a/tools/aapt2/util/Files.cpp +++ b/tools/aapt2/util/Files.cpp @@ -163,6 +163,11 @@ Maybe<android::FileMap> mmapPath(const StringPiece& path, std::string* outError) } 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 9ecc974d3a1c..7b0c71d93bb5 100644 --- a/tools/aapt2/util/Util.cpp +++ b/tools/aapt2/util/Util.cpp @@ -175,10 +175,11 @@ Maybe<std::u16string> getFullyQualifiedClassName(const StringPiece16& package, 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 9208e07e635b..1e0c7fa9152d 100644 --- a/tools/aapt2/util/Util_test.cpp +++ b/tools/aapt2/util/Util_test.cpp @@ -144,8 +144,7 @@ TEST(UtilTest, IsJavaPackageName) { 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); |