diff options
author | Adam Lesinski <adamlesinski@google.com> | 2015-12-09 15:20:52 -0800 |
---|---|---|
committer | Adam Lesinski <adamlesinski@google.com> | 2015-12-10 16:24:15 -0800 |
commit | a6fe345be955368a13aea76aefb4db821aad11df (patch) | |
tree | c5385f798a6e1fb674f6f13c0f323726258618ca | |
parent | 01655232371d7c7ea5b58ccf66ad99917072018a (diff) |
AAPT2: Fix overlay support
Supports the <add-resource> tag and mimics old AAPT behavior of
not allowing new resources defined unless <add-resource> was used
or --auto-add-overlay was specified.
Change-Id: I9b461137357617ade37fd7045b418b8e6450b9c4
24 files changed, 838 insertions, 307 deletions
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk index d8e0aac678f0..0f839801ff81 100644 --- a/tools/aapt2/Android.mk +++ b/tools/aapt2/Android.mk @@ -31,6 +31,8 @@ sources := \ flatten/Archive.cpp \ flatten/TableFlattener.cpp \ flatten/XmlFlattener.cpp \ + io/FileSystem.cpp \ + io/ZipArchive.cpp \ link/AutoVersioner.cpp \ link/ManifestFixer.cpp \ link/PrivateAttributeMover.cpp \ diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h index 6d752bb38d2a..054b9ee116f4 100644 --- a/tools/aapt2/NameMangler.h +++ b/tools/aapt2/NameMangler.h @@ -60,7 +60,7 @@ public: }; } - bool shouldMangle(const std::u16string& package) { + bool shouldMangle(const std::u16string& package) const { if (package.empty() || mPolicy.targetPackageName == package) { return false; } diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp index 34dc1d5508e3..9328b697719d 100644 --- a/tools/aapt2/Resource.cpp +++ b/tools/aapt2/Resource.cpp @@ -85,4 +85,12 @@ const ResourceType* parseResourceType(const StringPiece16& str) { return &iter->second; } +bool operator<(const ResourceKey& a, const ResourceKey& b) { + return std::tie(a.name, a.config) < std::tie(b.name, b.config); +} + +bool operator<(const ResourceKeyRef& a, const ResourceKeyRef& b) { + return std::tie(a.name, a.config) < std::tie(b.name, b.config); +} + } // namespace aapt diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index a7afbb5e4018..c71e249d71f2 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -165,6 +165,36 @@ struct ResourceFile { std::vector<SourcedResourceName> exportedSymbols; }; +/** + * Useful struct used as a key to represent a unique resource in associative containers. + */ +struct ResourceKey { + ResourceName name; + ConfigDescription config; +}; + +bool operator<(const ResourceKey& a, const ResourceKey& b); + +/** + * Useful struct used as a key to represent a unique resource in associative containers. + * Holds a reference to the name, so that name better live longer than this key! + */ +struct ResourceKeyRef { + ResourceNameRef name; + ConfigDescription config; + + ResourceKeyRef() = default; + ResourceKeyRef(const ResourceNameRef& n, const ConfigDescription& c) : name(n), config(c) { + } + + /** + * Prevent taking a reference to a temporary. This is bad. + */ + ResourceKeyRef(ResourceName&& n, const ConfigDescription& c) = delete; +}; + +bool operator<(const ResourceKeyRef& a, const ResourceKeyRef& b); + // // ResourceId implementation. // diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index c2ddb5c233c1..d4c536f61c45 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -173,7 +173,7 @@ struct ParsedResource { ResourceName name; Source source; ResourceId id; - SymbolState symbolState = SymbolState::kUndefined; + Maybe<SymbolState> symbolState; std::u16string comment; std::unique_ptr<Value> value; std::list<ParsedResource> childResources; @@ -182,9 +182,9 @@ struct ParsedResource { // Recursively adds resources to the ResourceTable. static bool addResourcesToTable(ResourceTable* table, const ConfigDescription& config, IDiagnostics* diag, ParsedResource* res) { - if (res->symbolState != SymbolState::kUndefined) { + if (res->symbolState) { Symbol symbol; - symbol.state = res->symbolState; + symbol.state = res->symbolState.value(); symbol.source = res->source; symbol.comment = res->comment; if (!table->setSymbolState(res->name, res->id, symbol, diag)) { @@ -325,6 +325,8 @@ bool ResourceParser::parseResources(xml::XmlPullParser* parser) { result = parseSymbol(parser, &parsedResource); } else if (elementName == u"public-group") { result = parsePublicGroup(parser, &parsedResource); + } else if (elementName == u"add-resource") { + result = parseAddResource(parser, &parsedResource); } else { // Try parsing the elementName (or type) as a resource. These shall only be // resources like 'layout' or 'xml' and they can only be references. @@ -643,7 +645,7 @@ bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser, ParsedResource return !error; } -bool ResourceParser::parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource) { +bool ResourceParser::parseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* outResource) { const Source source = mSource.withLine(parser->getLineNumber()); Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type"); @@ -661,10 +663,25 @@ bool ResourceParser::parseSymbol(xml::XmlPullParser* parser, ParsedResource* out } outResource->name.type = *parsedType; - outResource->symbolState = SymbolState::kPrivate; return true; } +bool ResourceParser::parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource) { + if (parseSymbolImpl(parser, outResource)) { + outResource->symbolState = SymbolState::kPrivate; + return true; + } + return false; +} + +bool ResourceParser::parseAddResource(xml::XmlPullParser* parser, ParsedResource* outResource) { + if (parseSymbolImpl(parser, outResource)) { + outResource->symbolState = SymbolState::kUndefined; + return true; + } + return false; +} + static uint32_t parseFormatType(const StringPiece16& piece) { if (piece == u"reference") return android::ResTable_map::TYPE_REFERENCE; else if (piece == u"string") return android::ResTable_map::TYPE_STRING; @@ -870,7 +887,7 @@ Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(xml::XmlPullParser* } return Attribute::Symbol{ - Reference(ResourceName({}, ResourceType::kId, maybeName.value().toString())), + Reference(ResourceNameRef({}, ResourceType::kId, maybeName.value())), val.data }; } diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index 1150758b930e..04db5778a456 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -83,7 +83,9 @@ private: bool parsePrimitive(xml::XmlPullParser* parser, ParsedResource* outResource); bool parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource); bool parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource); + bool parseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* outResource); bool parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource); + bool parseAddResource(xml::XmlPullParser* parser, ParsedResource* outResource); bool parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource); bool parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource, bool weak); Maybe<Attribute::Symbol> parseEnumOrFlagItem(xml::XmlPullParser* parser, diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 6e0812bf86e5..84f67c6be005 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -563,4 +563,16 @@ TEST_F(ResourceParserTest, ExternalTypesShouldOnlyBeReferences) { ASSERT_FALSE(testParse(input)); } +TEST_F(ResourceParserTest, AddResourcesElementShouldAddEntryWithUndefinedSymbol) { + std::string input = R"EOF(<add-resource name="bar" type="string" />)EOF"; + ASSERT_TRUE(testParse(input)); + + Maybe<ResourceTable::SearchResult> result = mTable.findResource( + test::parseNameOrDie(u"@string/bar")); + AAPT_ASSERT_TRUE(result); + const ResourceEntry* entry = result.value().entry; + ASSERT_NE(nullptr, entry); + EXPECT_EQ(SymbolState::kUndefined, entry->symbolStatus.state); +} + } // namespace aapt diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index 73d85852b2b0..8a3d047f6e8d 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -101,7 +101,7 @@ ResourceTableType* ResourceTablePackage::findOrCreateType(ResourceType type) { if (iter != last && (*iter)->type == type) { return iter->get(); } - return types.emplace(iter, new ResourceTableType{ type })->get(); + return types.emplace(iter, new ResourceTableType(type))->get(); } ResourceEntry* ResourceTableType::findEntry(const StringPiece16& name) { @@ -121,7 +121,7 @@ ResourceEntry* ResourceTableType::findOrCreateEntry(const StringPiece16& name) { if (iter != last && name == (*iter)->name) { return iter->get(); } - return entries.emplace(iter, new ResourceEntry{ name })->get(); + return entries.emplace(iter, new ResourceEntry(name))->get(); } /** @@ -342,11 +342,6 @@ bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name, const Resour IDiagnostics* diag) { assert(diag && "diagnostics can't be nullptr"); - if (symbol.state == SymbolState::kUndefined) { - // Nothing to do. - return true; - } - auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars); if (badCharIter != name.entry.end()) { diag->error(DiagMessage(symbol.source) @@ -400,23 +395,30 @@ bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name, const Resour return false; } + if (resId.isValid()) { + package->id = resId.packageId(); + type->id = resId.typeId(); + entry->id = resId.entryId(); + } + // Only mark the type state as public, it doesn't care about being private. if (symbol.state == SymbolState::kPublic) { type->symbolStatus.state = SymbolState::kPublic; } - // Downgrading to a private symbol from a public one is not allowed. - if (entry->symbolStatus.state != SymbolState::kPublic) { - if (entry->symbolStatus.state != symbol.state) { - entry->symbolStatus = std::move(symbol); - } + if (symbol.state == SymbolState::kUndefined && + entry->symbolStatus.state != SymbolState::kUndefined) { + // We can't undefine a symbol (remove its visibility). Ignore. + return true; } - if (resId.isValid()) { - package->id = resId.packageId(); - type->id = resId.typeId(); - entry->id = resId.entryId(); + if (symbol.state == SymbolState::kPrivate && + entry->symbolStatus.state == SymbolState::kPublic) { + // We can't downgrade public to private. Ignore. + return true; } + + entry->symbolStatus = std::move(symbol); return true; } diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index ffe6595cf0b6..36c3e702574e 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -14,8 +14,10 @@ * limitations under the License. */ +#include "NameMangler.h" #include "ResourceUtils.h" #include "flatten/ResourceTypeExtensions.h" +#include "util/Files.h" #include "util/Util.h" #include <androidfw/ResourceTypes.h> @@ -554,5 +556,22 @@ std::unique_ptr<Item> parseItemForAttribute( return {}; } +std::string buildResourceFileName(const ResourceFile& resFile, const NameMangler* mangler) { + std::stringstream out; + out << "res/" << resFile.name.type; + if (resFile.config != ConfigDescription{}) { + out << "-" << resFile.config; + } + out << "/"; + + if (mangler && mangler->shouldMangle(resFile.name.package)) { + out << NameMangler::mangleEntry(resFile.name.package, resFile.name.entry); + } else { + out << resFile.name.entry; + } + out << file::getExtension(resFile.source.path); + return out.str(); +} + } // namespace ResourceUtils } // namespace aapt diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h index f93a4c762706..64ca97185153 100644 --- a/tools/aapt2/ResourceUtils.h +++ b/tools/aapt2/ResourceUtils.h @@ -17,6 +17,7 @@ #ifndef AAPT_RESOURCEUTILS_H #define AAPT_RESOURCEUTILS_H +#include "NameMangler.h" #include "Resource.h" #include "ResourceValues.h" #include "util/StringPiece.h" @@ -154,6 +155,16 @@ std::unique_ptr<Item> parseItemForAttribute( uint32_t androidTypeToAttributeTypeMask(uint16_t type); +/** + * Returns a string path suitable for use within an APK. The path will look like: + * + * res/type[-config]/<name>.<ext> + * + * Then name may be mangled if a NameMangler is supplied (can be nullptr) and the package + * requires mangling. + */ +std::string buildResourceFileName(const ResourceFile& resFile, const NameMangler* mangler); + } // namespace ResourceUtils } // namespace aapt diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp index 46ee914c150a..a2f53e15df69 100644 --- a/tools/aapt2/flatten/TableFlattener.cpp +++ b/tools/aapt2/flatten/TableFlattener.cpp @@ -583,8 +583,8 @@ private: publicEntry->state = Public_entry::kPublic; break; - default: - assert(false && "should not serialize any other state"); + case SymbolState::kUndefined: + publicEntry->state = Public_entry::kUndefined; break; } diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h index 9fca3980d964..b4d49719aa3e 100644 --- a/tools/aapt2/io/File.h +++ b/tools/aapt2/io/File.h @@ -52,6 +52,14 @@ public: virtual const Source& getSource() const = 0; }; +class IFileCollectionIterator { +public: + virtual ~IFileCollectionIterator() = default; + + virtual bool hasNext() = 0; + virtual IFile* next() = 0; +}; + /** * Interface for a collection of files, all of which share a common source. That source may * simply be the filesystem, or a ZIP archive. @@ -60,10 +68,8 @@ class IFileCollection { public: virtual ~IFileCollection() = default; - using const_iterator = std::vector<std::unique_ptr<IFile>>::const_iterator; - - virtual const_iterator begin() const = 0; - virtual const_iterator end() const = 0; + virtual IFile* findFile(const StringPiece& path) = 0; + virtual std::unique_ptr<IFileCollectionIterator> iterator() = 0; }; } // namespace io diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp new file mode 100644 index 000000000000..76f87aec6556 --- /dev/null +++ b/tools/aapt2/io/FileSystem.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Source.h" +#include "io/FileSystem.h" +#include "util/Files.h" +#include "util/Maybe.h" +#include "util/StringPiece.h" +#include "util/Util.h" + +#include <utils/FileMap.h> + +namespace aapt { +namespace io { + +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())); + } + return {}; +} + +const Source& RegularFile::getSource() const { + return mSource; +} + +FileCollectionIterator::FileCollectionIterator(FileCollection* collection) : + mCurrent(collection->mFiles.begin()), mEnd(collection->mFiles.end()) { +} + +bool FileCollectionIterator::hasNext() { + return mCurrent != mEnd; +} + +IFile* FileCollectionIterator::next() { + IFile* result = mCurrent->second.get(); + ++mCurrent; + return result; +} + +IFile* FileCollection::insertFile(const StringPiece& path) { + return (mFiles[path.toString()] = util::make_unique<RegularFile>(Source(path))).get(); +} + +IFile* FileCollection::findFile(const StringPiece& path) { + auto iter = mFiles.find(path.toString()); + if (iter != mFiles.end()) { + return iter->second.get(); + } + return nullptr; +} + +std::unique_ptr<IFileCollectionIterator> FileCollection::iterator() { + return util::make_unique<FileCollectionIterator>(this); +} + +} // namespace io +} // namespace aapt diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h index 5dbefcc0f687..f0559c03a8b8 100644 --- a/tools/aapt2/io/FileSystem.h +++ b/tools/aapt2/io/FileSystem.h @@ -18,7 +18,8 @@ #define AAPT_IO_FILESYSTEM_H #include "io/File.h" -#include "util/Files.h" + +#include <map> namespace aapt { namespace io { @@ -28,25 +29,28 @@ namespace io { */ class RegularFile : public IFile { public: - RegularFile(const Source& source) : mSource(source) { - } - - std::unique_ptr<IData> openAsData() override { - android::FileMap map; - if (Maybe<android::FileMap> map = file::mmapPath(mSource.path, nullptr)) { - return util::make_unique<MmappedData>(std::move(map.value())); - } - return {}; - } + RegularFile(const Source& source); - const Source& getSource() const override { - return mSource; - } + std::unique_ptr<IData> openAsData() override; + const Source& getSource() const override; private: Source mSource; }; +class FileCollection; + +class FileCollectionIterator : public IFileCollectionIterator { +public: + FileCollectionIterator(FileCollection* collection); + + bool hasNext() override; + io::IFile* next() override; + +private: + std::map<std::string, std::unique_ptr<IFile>>::const_iterator mCurrent, mEnd; +}; + /** * An IFileCollection representing the file system. */ @@ -55,21 +59,13 @@ public: /** * Adds a file located at path. Returns the IFile representation of that file. */ - IFile* insertFile(const StringPiece& path) { - mFiles.push_back(util::make_unique<RegularFile>(Source(path))); - return mFiles.back().get(); - } - - const_iterator begin() const override { - return mFiles.begin(); - } - - const_iterator end() const override { - return mFiles.end(); - } + IFile* insertFile(const StringPiece& path); + IFile* findFile(const StringPiece& path) override; + std::unique_ptr<IFileCollectionIterator> iterator() override; private: - std::vector<std::unique_ptr<IFile>> mFiles; + friend class FileCollectionIterator; + std::map<std::string, std::unique_ptr<IFile>> mFiles; }; } // namespace io diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp new file mode 100644 index 000000000000..bf0f4aa5b338 --- /dev/null +++ b/tools/aapt2/io/ZipArchive.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Source.h" +#include "io/ZipArchive.h" +#include "util/Util.h" + +#include <utils/FileMap.h> +#include <ziparchive/zip_archive.h> + +namespace aapt { +namespace io { + +ZipFile::ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source) : + mZipHandle(handle), mZipEntry(entry), mSource(source) { +} + +std::unique_ptr<IData> ZipFile::openAsData() { + if (mZipEntry.method == kCompressStored) { + int fd = GetFileDescriptor(mZipHandle); + + android::FileMap fileMap; + bool result = fileMap.create(nullptr, fd, mZipEntry.offset, + mZipEntry.uncompressed_length, true); + if (!result) { + return {}; + } + return util::make_unique<MmappedData>(std::move(fileMap)); + + } else { + std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>( + new uint8_t[mZipEntry.uncompressed_length]); + int32_t result = ExtractToMemory(mZipHandle, &mZipEntry, data.get(), + static_cast<uint32_t>(mZipEntry.uncompressed_length)); + if (result != 0) { + return {}; + } + return util::make_unique<MallocData>(std::move(data), mZipEntry.uncompressed_length); + } +} + +const Source& ZipFile::getSource() const { + return mSource; +} + +ZipFileCollectionIterator::ZipFileCollectionIterator(ZipFileCollection* collection) : + mCurrent(collection->mFiles.begin()), mEnd(collection->mFiles.end()) { +} + +bool ZipFileCollectionIterator::hasNext() { + return mCurrent != mEnd; +} + +IFile* ZipFileCollectionIterator::next() { + IFile* result = mCurrent->second.get(); + ++mCurrent; + return result; +} + +ZipFileCollection::ZipFileCollection() : mHandle(nullptr) { +} + +std::unique_ptr<ZipFileCollection> ZipFileCollection::create(const StringPiece& path, + std::string* outError) { + std::unique_ptr<ZipFileCollection> collection = std::unique_ptr<ZipFileCollection>( + new ZipFileCollection()); + + int32_t result = OpenArchive(path.data(), &collection->mHandle); + if (result != 0) { + if (outError) *outError = ErrorCodeString(result); + return {}; + } + + ZipString suffix(".flat"); + void* cookie = nullptr; + result = StartIteration(collection->mHandle, &cookie, nullptr, &suffix); + if (result != 0) { + if (outError) *outError = ErrorCodeString(result); + return {}; + } + + using IterationEnder = std::unique_ptr<void, decltype(EndIteration)*>; + IterationEnder iterationEnder(cookie, EndIteration); + + ZipString zipEntryName; + ZipEntry zipData; + while ((result = Next(cookie, &zipData, &zipEntryName)) == 0) { + std::string zipEntryPath = std::string(reinterpret_cast<const char*>(zipEntryName.name), + zipEntryName.name_length); + std::string nestedPath = path.toString() + "@" + zipEntryPath; + collection->mFiles[zipEntryPath] = util::make_unique<ZipFile>(collection->mHandle, + zipData, + Source(nestedPath)); + } + + if (result != -1) { + if (outError) *outError = ErrorCodeString(result); + return {}; + } + return collection; +} + +IFile* ZipFileCollection::findFile(const StringPiece& path) { + auto iter = mFiles.find(path.toString()); + if (iter != mFiles.end()) { + return iter->second.get(); + } + return nullptr; +} + +std::unique_ptr<IFileCollectionIterator> ZipFileCollection::iterator() { + return util::make_unique<ZipFileCollectionIterator>(this); +} + +ZipFileCollection::~ZipFileCollection() { + if (mHandle) { + CloseArchive(mHandle); + } +} + +} // namespace io +} // namespace aapt diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h index 98afc498708f..5ad119d1d6d4 100644 --- a/tools/aapt2/io/ZipArchive.h +++ b/tools/aapt2/io/ZipArchive.h @@ -20,7 +20,7 @@ #include "io/File.h" #include "util/StringPiece.h" -#include <utils/FileMap.h> +#include <map> #include <ziparchive/zip_archive.h> namespace aapt { @@ -32,37 +32,10 @@ namespace io { */ class ZipFile : public IFile { public: - ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source) : - mZipHandle(handle), mZipEntry(entry), mSource(source) { - } - - std::unique_ptr<IData> openAsData() override { - if (mZipEntry.method == kCompressStored) { - int fd = GetFileDescriptor(mZipHandle); - - android::FileMap fileMap; - bool result = fileMap.create(nullptr, fd, mZipEntry.offset, - mZipEntry.uncompressed_length, true); - if (!result) { - return {}; - } - return util::make_unique<MmappedData>(std::move(fileMap)); - - } else { - std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>( - new uint8_t[mZipEntry.uncompressed_length]); - int32_t result = ExtractToMemory(mZipHandle, &mZipEntry, data.get(), - static_cast<uint32_t>(mZipEntry.uncompressed_length)); - if (result != 0) { - return {}; - } - return util::make_unique<MallocData>(std::move(data), mZipEntry.uncompressed_length); - } - } - - const Source& getSource() const override { - return mSource; - } + ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source); + + std::unique_ptr<IData> openAsData() override; + const Source& getSource() const override; private: ZipArchiveHandle mZipHandle; @@ -70,71 +43,38 @@ private: Source mSource; }; +class ZipFileCollection; + +class ZipFileCollectionIterator : public IFileCollectionIterator { +public: + ZipFileCollectionIterator(ZipFileCollection* collection); + + bool hasNext() override; + io::IFile* next() override; + +private: + std::map<std::string, std::unique_ptr<IFile>>::const_iterator mCurrent, mEnd; +}; + /** * An IFileCollection that represents a ZIP archive and the entries within it. */ class ZipFileCollection : public IFileCollection { public: static std::unique_ptr<ZipFileCollection> create(const StringPiece& path, - std::string* outError) { - std::unique_ptr<ZipFileCollection> collection = std::unique_ptr<ZipFileCollection>( - new ZipFileCollection()); - - int32_t result = OpenArchive(path.data(), &collection->mHandle); - if (result != 0) { - if (outError) *outError = ErrorCodeString(result); - return {}; - } - - ZipString suffix(".flat"); - void* cookie = nullptr; - result = StartIteration(collection->mHandle, &cookie, nullptr, &suffix); - if (result != 0) { - if (outError) *outError = ErrorCodeString(result); - return {}; - } - - using IterationEnder = std::unique_ptr<void, decltype(EndIteration)*>; - IterationEnder iterationEnder(cookie, EndIteration); - - ZipString zipEntryName; - ZipEntry zipData; - while ((result = Next(cookie, &zipData, &zipEntryName)) == 0) { - std::string nestedPath = path.toString(); - nestedPath += "@" + std::string(reinterpret_cast<const char*>(zipEntryName.name), - zipEntryName.name_length); - collection->mFiles.push_back(util::make_unique<ZipFile>(collection->mHandle, - zipData, - Source(nestedPath))); - } - - if (result != -1) { - if (outError) *outError = ErrorCodeString(result); - return {}; - } - return collection; - } - - const_iterator begin() const override { - return mFiles.begin(); - } - - const_iterator end() const override { - return mFiles.end(); - } - - ~ZipFileCollection() override { - if (mHandle) { - CloseArchive(mHandle); - } - } + std::string* outError); + + io::IFile* findFile(const StringPiece& path) override; + std::unique_ptr<IFileCollectionIterator> iterator() override; + + ~ZipFileCollection() override; private: - ZipFileCollection() : mHandle(nullptr) { - } + friend class ZipFileCollectionIterator; + ZipFileCollection(); ZipArchiveHandle mHandle; - std::vector<std::unique_ptr<IFile>> mFiles; + std::map<std::string, std::unique_ptr<IFile>> mFiles; }; } // namespace io diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp index 33d9272b39fd..652e52fa0a67 100644 --- a/tools/aapt2/link/Link.cpp +++ b/tools/aapt2/link/Link.cpp @@ -57,6 +57,7 @@ struct LinkOptions { bool staticLib = false; bool verbose = false; bool outputToDirectory = false; + bool autoAddOverlay = false; Maybe<std::u16string> privateSymbols; Maybe<std::u16string> minSdkVersionDefault; Maybe<std::u16string> targetSdkVersionDefault; @@ -104,23 +105,6 @@ public: mCollections.push_back(std::move(fileCollection)); } - std::string buildResourceFileName(const ResourceFile& resFile) { - std::stringstream out; - out << "res/" << resFile.name.type; - if (resFile.config != ConfigDescription{}) { - out << "-" << resFile.config; - } - out << "/"; - - if (mContext.getNameMangler()->shouldMangle(resFile.name.package)) { - out << NameMangler::mangleEntry(resFile.name.package, resFile.name.entry); - } else { - out << resFile.name.entry; - } - out << file::getExtension(resFile.source.path); - return out.str(); - } - /** * Creates a SymbolTable that loads symbols from the various APKs and caches the * results for faster lookup. @@ -425,45 +409,28 @@ public: return false; } - if (!mTableMerger->merge(file->getSource(), table.get(), override)) { - return false; + bool result = false; + if (override) { + result = mTableMerger->mergeOverlay(file->getSource(), table.get()); + } else { + result = mTableMerger->merge(file->getSource(), table.get()); } - return true; + return result; } - bool mergeCompiledFile(io::IFile* file, std::unique_ptr<ResourceFile> fileDesc, bool override) { - // Apply the package name used for this compilation phase if none was specified. - if (fileDesc->name.package.empty()) { - fileDesc->name.package = mContext.getCompilationPackage().toString(); - } - - // Mangle the name if necessary. - ResourceNameRef resName = fileDesc->name; - Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(fileDesc->name); - if (mangledName) { - resName = mangledName.value(); + bool mergeCompiledFile(io::IFile* file, std::unique_ptr<ResourceFile> fileDesc, bool overlay) { + if (mOptions.verbose) { + mContext.getDiagnostics()->note(DiagMessage() << "adding " << file->getSource()); } - // If we are overriding resources, we supply a custom resolver function. - std::function<int(Value*,Value*)> resolver; - if (override) { - resolver = [](Value* a, Value* b) -> int { - int result = ResourceTable::resolveValueCollision(a, b); - if (result == 0) { - // Always accept the new value if it would normally conflict (override). - result = 1; - } - return result; - }; + bool result = false; + if (overlay) { + result = mTableMerger->mergeFileOverlay(*fileDesc, file); } else { - // Otherwise use the default resolution. - resolver = ResourceTable::resolveValueCollision; + result = mTableMerger->mergeFile(*fileDesc, file); } - // Add this file to the table. - if (!mFinalTable.addFileReference(resName, fileDesc->config, fileDesc->source, - util::utf8ToUtf16(buildResourceFileName(*fileDesc)), - resolver, mContext.getDiagnostics())) { + if (!result) { return false; } @@ -489,10 +456,6 @@ public: return false; } } - - // Now add this file for later processing. Once the table is assigned IDs, we can compile - // this file. - mFilesToProcess.insert(FileToProcess{ std::move(fileDesc), file }); return true; } @@ -509,8 +472,8 @@ public: } bool error = false; - for (const std::unique_ptr<io::IFile>& file : *collection) { - if (!processFile(file.get(), override)) { + for (auto iter = collection->iterator(); iter->hasNext(); ) { + if (!processFile(iter->next(), override)) { error = true; } } @@ -588,7 +551,9 @@ public: return 1; } - mTableMerger = util::make_unique<TableMerger>(&mContext, &mFinalTable); + TableMergerOptions tableMergerOptions; + tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay; + mTableMerger = util::make_unique<TableMerger>(&mContext, &mFinalTable, tableMergerOptions); if (mOptions.verbose) { mContext.getDiagnostics()->note( @@ -698,31 +663,47 @@ public: return 1; } - for (const FileToProcess& file : mFilesToProcess) { - const StringPiece path = file.file->getSource().path; + for (auto& mergeEntry : mTableMerger->getFilesToMerge()) { + const ResourceKeyRef& key = mergeEntry.first; + const FileToMerge& fileToMerge = mergeEntry.second; + + const StringPiece path = fileToMerge.file->getSource().path; - if (file.fileExport->name.type != ResourceType::kRaw && - util::stringEndsWith<char>(path, ".xml.flat")) { + if (key.name.type != ResourceType::kRaw && + (util::stringEndsWith<char>(path, ".xml.flat") || + util::stringEndsWith<char>(path, ".xml"))) { if (mOptions.verbose) { mContext.getDiagnostics()->note(DiagMessage() << "linking " << path); } - std::unique_ptr<io::IData> data = file.file->openAsData(); + io::IFile* file = fileToMerge.file; + std::unique_ptr<io::IData> data = file->openAsData(); if (!data) { - mContext.getDiagnostics()->error(DiagMessage(file.file->getSource()) + mContext.getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file"); return 1; } - std::unique_ptr<xml::XmlResource> xmlRes = loadBinaryXmlSkipFileExport( - file.file->getSource(), data->data(), data->size(), - mContext.getDiagnostics()); + std::unique_ptr<xml::XmlResource> xmlRes; + if (util::stringEndsWith<char>(path, ".flat")) { + xmlRes = loadBinaryXmlSkipFileExport(file->getSource(), + data->data(), data->size(), + mContext.getDiagnostics()); + } else { + xmlRes = xml::inflate(data->data(), data->size(), mContext.getDiagnostics(), + file->getSource()); + } + if (!xmlRes) { return 1; } - // Move the file description over. - xmlRes->file = std::move(*file.fileExport); + // Create the file description header. + xmlRes->file = ResourceFile{ + key.name.toResourceName(), + key.config, + fileToMerge.originalSource, + }; XmlReferenceLinker xmlLinker; if (xmlLinker.consume(&mContext, xmlRes.get())) { @@ -736,7 +717,7 @@ public: maxSdkLevel = std::max<size_t>(xmlRes->file.config.sdkVersion, 1u); } - if (!flattenXml(xmlRes.get(), buildResourceFileName(xmlRes->file), maxSdkLevel, + if (!flattenXml(xmlRes.get(), fileToMerge.dstPath, maxSdkLevel, archiveWriter.get())) { error = true; } @@ -750,19 +731,23 @@ public: xmlRes->file.config, sdkLevel)) { xmlRes->file.config.sdkVersion = sdkLevel; + + std::string genResourcePath = ResourceUtils::buildResourceFileName( + xmlRes->file, mContext.getNameMangler()); + bool added = mFinalTable.addFileReference( xmlRes->file.name, xmlRes->file.config, xmlRes->file.source, - util::utf8ToUtf16(buildResourceFileName(xmlRes->file)), + util::utf8ToUtf16(genResourcePath), mContext.getDiagnostics()); if (!added) { error = true; continue; } - if (!flattenXml(xmlRes.get(), buildResourceFileName(xmlRes->file), - sdkLevel, archiveWriter.get())) { + if (!flattenXml(xmlRes.get(), genResourcePath, sdkLevel, + archiveWriter.get())) { error = true; } } @@ -777,7 +762,7 @@ public: mContext.getDiagnostics()->note(DiagMessage() << "copying " << path); } - if (!copyFileToArchive(file.file, buildResourceFileName(*file.fileExport), 0, + if (!copyFileToArchive(fileToMerge.file, fileToMerge.dstPath, 0, archiveWriter.get())) { error = true; } @@ -845,15 +830,7 @@ public: if (mOptions.verbose) { Debug::printTable(&mFinalTable); - for (; !mTableMerger->getFileMergeQueue()->empty(); - mTableMerger->getFileMergeQueue()->pop()) { - const FileToMerge& f = mTableMerger->getFileMergeQueue()->front(); - mContext.getDiagnostics()->note( - DiagMessage() << f.srcPath << " -> " << f.dstPath << " from (0x" - << std::hex << (uintptr_t) f.srcTable << std::dec); - } } - return 0; } @@ -861,24 +838,15 @@ private: LinkOptions mOptions; LinkContext mContext; ResourceTable mFinalTable; + + ResourceTable mLocalFileTable; std::unique_ptr<TableMerger> mTableMerger; + // A pointer to the FileCollection representing the filesystem (not archives). io::FileCollection* mFileCollection; - std::vector<std::unique_ptr<io::IFileCollection>> mCollections; - - struct FileToProcess { - std::unique_ptr<ResourceFile> fileExport; - io::IFile* file; - }; - struct FileToProcessComparator { - bool operator()(const FileToProcess& a, const FileToProcess& b) { - return std::tie(a.fileExport->name, a.fileExport->config) < - std::tie(b.fileExport->name, b.fileExport->config); - } - }; - - std::set<FileToProcess, FileToProcessComparator> mFilesToProcess; + // A vector of IFileCollections. This is mainly here to keep ownership of the collections. + std::vector<std::unique_ptr<io::IFileCollection>> mCollections; }; int link(const std::vector<StringPiece>& args) { @@ -915,6 +883,8 @@ int link(const std::vector<StringPiece>& args) { "package name", &privateSymbolsPackage) .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) .optionalSwitch("-v", "Enables verbose logging", &options.verbose); if (!flags.parse("aapt2 link", args, &std::cerr)) { diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp index a06a1bfe21a5..27a23bd65103 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -15,6 +15,7 @@ */ #include "ResourceTable.h" +#include "ResourceUtils.h" #include "ResourceValues.h" #include "ValueVisitor.h" @@ -26,8 +27,9 @@ namespace aapt { -TableMerger::TableMerger(IAaptContext* context, ResourceTable* outTable) : - mContext(context), mMasterTable(outTable) { +TableMerger::TableMerger(IAaptContext* context, ResourceTable* outTable, + const TableMergerOptions& options) : + mContext(context), mMasterTable(outTable), mOptions(options) { // Create the desired package that all tables will be merged into. mMasterPackage = mMasterTable->createPackage( mContext->getCompilationPackage(), mContext->getPackageId()); @@ -37,7 +39,8 @@ TableMerger::TableMerger(IAaptContext* context, ResourceTable* outTable) : /** * This will merge packages with the same package name (or no package name). */ -bool TableMerger::merge(const Source& src, ResourceTable* table, bool overrideExisting) { +bool TableMerger::mergeImpl(const Source& src, ResourceTable* table, + bool overlay, bool allowNew) { const uint8_t desiredPackageId = mContext->getPackageId(); bool error = false; @@ -55,19 +58,26 @@ bool TableMerger::merge(const Source& src, ResourceTable* table, bool overrideEx // mangled, then looked up at resolution time. // Also, when linking, we convert references with no package name to use // the compilation package name. - if (!doMerge(src, table, package.get(), false, overrideExisting)) { - error = true; - } + error |= !doMerge(src, table, package.get(), + false /* mangle */, overlay, allowNew, {}); } } return !error; } +bool TableMerger::merge(const Source& src, ResourceTable* table) { + return mergeImpl(src, table, false /* overlay */, true /* allow new */); +} + +bool TableMerger::mergeOverlay(const Source& src, ResourceTable* table) { + return mergeImpl(src, table, true /* overlay */, mOptions.autoAddOverlay); +} + /** * This will merge and mangle resources from a static library. */ bool TableMerger::mergeAndMangle(const Source& src, const StringPiece16& packageName, - ResourceTable* table) { + ResourceTable* table, io::IFileCollection* collection) { bool error = false; for (auto& package : table->packages) { // Warn of packages with an unrelated ID. @@ -79,16 +89,35 @@ bool TableMerger::mergeAndMangle(const Source& src, const StringPiece16& package bool mangle = packageName != mContext->getCompilationPackage(); mMergedPackages.insert(package->name); - if (!doMerge(src, table, package.get(), mangle, false)) { - error = true; - } + + auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config, + FileReference* newFile, FileReference* oldFile) -> bool { + // The old file's path points inside the APK, so we can use it as is. + io::IFile* f = collection->findFile(util::utf16ToUtf8(*oldFile->path)); + if (!f) { + mContext->getDiagnostics()->error(DiagMessage(src) << "file '" << *oldFile->path + << "' not found"); + return false; + } + + mFilesToMerge[ResourceKeyRef{ name, config }] = FileToMerge{ + f, oldFile->getSource(), util::utf16ToUtf8(*newFile->path) }; + return true; + }; + + error |= !doMerge(src, table, package.get(), + mangle, false /* overlay */, true /* allow new */, callback); } return !error; } -bool TableMerger::doMerge(const Source& src, ResourceTable* srcTable, - ResourceTablePackage* srcPackage, const bool manglePackage, - const bool overrideExisting) { +bool TableMerger::doMerge(const Source& src, + ResourceTable* srcTable, + ResourceTablePackage* srcPackage, + const bool manglePackage, + const bool overlay, + const bool allowNewResources, + FileMergeCallback callback) { bool error = false; for (auto& srcType : srcPackage->types) { @@ -112,10 +141,33 @@ bool TableMerger::doMerge(const Source& src, ResourceTable* srcTable, for (auto& srcEntry : srcType->entries) { ResourceEntry* dstEntry; if (manglePackage) { - dstEntry = dstType->findOrCreateEntry(NameMangler::mangleEntry( - srcPackage->name, srcEntry->name)); + std::u16string mangledName = NameMangler::mangleEntry(srcPackage->name, + srcEntry->name); + if (allowNewResources) { + dstEntry = dstType->findOrCreateEntry(mangledName); + } else { + dstEntry = dstType->findEntry(mangledName); + } } else { - dstEntry = dstType->findOrCreateEntry(srcEntry->name); + if (allowNewResources) { + dstEntry = dstType->findOrCreateEntry(srcEntry->name); + } else { + dstEntry = dstType->findEntry(srcEntry->name); + } + } + + if (!dstEntry) { + mContext->getDiagnostics()->error(DiagMessage(src) + << "resource " + << ResourceNameRef(srcPackage->name, + srcType->type, + srcEntry->name) + << " does not override an existing resource"); + mContext->getDiagnostics()->note(DiagMessage(src) + << "define an <add-resource> tag or use " + "--auto-add-overlay"); + error = true; + continue; } if (srcEntry->symbolStatus.state != SymbolState::kUndefined) { @@ -143,6 +195,8 @@ bool TableMerger::doMerge(const Source& src, ResourceTable* srcTable, } } + ResourceNameRef resName(mMasterPackage->name, dstType->type, dstEntry->name); + for (ResourceConfigValue& srcValue : srcEntry->values) { auto iter = std::lower_bound(dstEntry->values.begin(), dstEntry->values.end(), srcValue.config, cmp::lessThanConfig); @@ -150,7 +204,7 @@ bool TableMerger::doMerge(const Source& src, ResourceTable* srcTable, if (iter != dstEntry->values.end() && iter->config == srcValue.config) { const int collisionResult = ResourceTable::resolveValueCollision( iter->value.get(), srcValue.value.get()); - if (collisionResult == 0 && !overrideExisting) { + if (collisionResult == 0 && !overlay) { // Error! ResourceNameRef resourceName(srcPackage->name, srcType->type, @@ -175,10 +229,26 @@ bool TableMerger::doMerge(const Source& src, ResourceTable* srcTable, iter = dstEntry->values.insert(iter, ResourceConfigValue{ srcValue.config }); } - if (manglePackage) { - iter->value = cloneAndMangle(srcTable, srcPackage->name, srcValue.value.get()); + if (FileReference* f = valueCast<FileReference>(srcValue.value.get())) { + std::unique_ptr<FileReference> newFileRef; + if (manglePackage) { + newFileRef = cloneAndMangleFile(srcPackage->name, *f); + } else { + newFileRef = std::unique_ptr<FileReference>(f->clone( + &mMasterTable->stringPool)); + } + + if (callback) { + if (!callback(resName, iter->config, newFileRef.get(), f)) { + error = true; + continue; + } + } + iter->value = std::move(newFileRef); + } else { - iter->value = clone(srcValue.value.get()); + iter->value = std::unique_ptr<Value>(srcValue.value->clone( + &mMasterTable->stringPool)); } } } @@ -186,28 +256,52 @@ bool TableMerger::doMerge(const Source& src, ResourceTable* srcTable, return !error; } -std::unique_ptr<Value> TableMerger::cloneAndMangle(ResourceTable* table, - const std::u16string& package, - Value* value) { - if (FileReference* f = valueCast<FileReference>(value)) { - // Mangle the path. - StringPiece16 prefix, entry, suffix; - if (util::extractResFilePathParts(*f->path, &prefix, &entry, &suffix)) { - std::u16string mangledEntry = NameMangler::mangleEntry(package, entry.toString()); - std::u16string newPath = prefix.toString() + mangledEntry + suffix.toString(); - mFilesToMerge.push(FileToMerge{ table, *f->path, newPath }); - std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>( - mMasterTable->stringPool.makeRef(newPath)); - fileRef->setComment(f->getComment()); - fileRef->setSource(f->getSource()); - return std::move(fileRef); - } +std::unique_ptr<FileReference> TableMerger::cloneAndMangleFile(const std::u16string& package, + const FileReference& fileRef) { + + StringPiece16 prefix, entry, suffix; + if (util::extractResFilePathParts(*fileRef.path, &prefix, &entry, &suffix)) { + std::u16string mangledEntry = NameMangler::mangleEntry(package, entry.toString()); + std::u16string newPath = prefix.toString() + mangledEntry + suffix.toString(); + std::unique_ptr<FileReference> newFileRef = util::make_unique<FileReference>( + mMasterTable->stringPool.makeRef(newPath)); + newFileRef->setComment(fileRef.getComment()); + newFileRef->setSource(fileRef.getSource()); + return newFileRef; } - return clone(value); + return std::unique_ptr<FileReference>(fileRef.clone(&mMasterTable->stringPool)); +} + +bool TableMerger::mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, bool overlay) { + ResourceTable table; + std::u16string path = util::utf8ToUtf16(ResourceUtils::buildResourceFileName(fileDesc, + nullptr)); + std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>( + table.stringPool.makeRef(path)); + fileRef->setSource(fileDesc.source); + + ResourceTablePackage* pkg = table.createPackage(fileDesc.name.package, 0x0); + pkg->findOrCreateType(fileDesc.name.type) + ->findOrCreateEntry(fileDesc.name.entry) + ->values.push_back(ResourceConfigValue{ fileDesc.config, std::move(fileRef) }); + + auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config, + FileReference* newFile, FileReference* oldFile) -> bool { + mFilesToMerge[ResourceKeyRef{ name, config }] = FileToMerge{ + file, oldFile->getSource(), util::utf16ToUtf8(*newFile->path) }; + return true; + }; + + return doMerge(file->getSource(), &table, pkg, + false /* mangle */, overlay /* overlay */, true /* allow new */, callback); +} + +bool TableMerger::mergeFile(const ResourceFile& fileDesc, io::IFile* file) { + return mergeFileImpl(fileDesc, file, false /* overlay */); } -std::unique_ptr<Value> TableMerger::clone(Value* value) { - return std::unique_ptr<Value>(value->clone(&mMasterTable->stringPool)); +bool TableMerger::mergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file) { + return mergeFileImpl(fileDesc, file, true /* overlay */); } } // namespace aapt diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h index a2c9dbf31fe1..e1be5d52e9cf 100644 --- a/tools/aapt2/link/TableMerger.h +++ b/tools/aapt2/link/TableMerger.h @@ -17,21 +17,40 @@ #ifndef AAPT_TABLEMERGER_H #define AAPT_TABLEMERGER_H +#include "Resource.h" #include "ResourceTable.h" #include "ResourceValues.h" -#include "util/Util.h" - +#include "io/File.h" #include "process/IResourceTableConsumer.h" +#include "util/Util.h" -#include <queue> -#include <set> +#include <functional> +#include <map> namespace aapt { struct FileToMerge { - ResourceTable* srcTable; - std::u16string srcPath; - std::u16string dstPath; + /** + * The compiled file from which to read the data. + */ + io::IFile* file; + + /** + * Where the original, uncompiled file came from. + */ + Source originalSource; + + /** + * The destination path within the APK/archive. + */ + std::string dstPath; +}; + +struct TableMergerOptions { + /** + * If true, resources in overlays can be added without previously having existed. + */ + bool autoAddOverlay = false; }; /** @@ -50,40 +69,74 @@ struct FileToMerge { */ class TableMerger { public: - TableMerger(IAaptContext* context, ResourceTable* outTable); + /** + * Note: The outTable ResourceTable must live longer than this TableMerger. References + * are made to this ResourceTable for efficiency reasons. + */ + TableMerger(IAaptContext* context, ResourceTable* outTable, const TableMergerOptions& options); - inline std::queue<FileToMerge>* getFileMergeQueue() { - return &mFilesToMerge; + const std::map<ResourceKeyRef, FileToMerge>& getFilesToMerge() { + return mFilesToMerge; } - inline const std::set<std::u16string>& getMergedPackages() const { + const std::set<std::u16string>& getMergedPackages() const { return mMergedPackages; } /** * Merges resources from the same or empty package. This is for local sources. */ - bool merge(const Source& src, ResourceTable* table, bool overrideExisting); + bool merge(const Source& src, ResourceTable* table); + + /** + * Merges resources from an overlay ResourceTable. + */ + bool mergeOverlay(const Source& src, ResourceTable* table); /** * Merges resources from the given package, mangling the name. This is for static libraries. + * An io::IFileCollection is needed in order to find the referenced Files and process them. + */ + bool mergeAndMangle(const Source& src, const StringPiece16& package, ResourceTable* table, + io::IFileCollection* collection); + + /** + * Merges a compiled file that belongs to this same or empty package. This is for local sources. + */ + bool mergeFile(const ResourceFile& fileDesc, io::IFile* file); + + /** + * Merges a compiled file from an overlay, overriding an existing definition. */ - bool mergeAndMangle(const Source& src, const StringPiece16& package, ResourceTable* table); + bool mergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file); private: + using FileMergeCallback = std::function<bool(const ResourceNameRef&, + const ConfigDescription& config, + FileReference*, + FileReference*)>; + IAaptContext* mContext; ResourceTable* mMasterTable; + TableMergerOptions mOptions; ResourceTablePackage* mMasterPackage; std::set<std::u16string> mMergedPackages; - std::queue<FileToMerge> mFilesToMerge; + std::map<ResourceKeyRef, FileToMerge> mFilesToMerge; + + bool mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, bool overlay); + + bool mergeImpl(const Source& src, ResourceTable* srcTable, + bool overlay, bool allowNew); bool doMerge(const Source& src, ResourceTable* srcTable, ResourceTablePackage* srcPackage, - const bool manglePackage, const bool overrideExisting); + const bool manglePackage, + const bool overlay, + const bool allowNewResources, + FileMergeCallback callback); - std::unique_ptr<Value> cloneAndMangle(ResourceTable* table, const std::u16string& package, - Value* value); - std::unique_ptr<Value> clone(Value* value); + std::unique_ptr<FileReference> cloneAndMangleFile(const std::u16string& package, + const FileReference& value); }; } // namespace aapt diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp index b7ffba7d2157..fa4afd3d852a 100644 --- a/tools/aapt2/link/TableMerger_test.cpp +++ b/tools/aapt2/link/TableMerger_test.cpp @@ -14,8 +14,8 @@ * limitations under the License. */ +#include "io/FileSystem.h" #include "link/TableMerger.h" - #include "test/Builders.h" #include "test/Context.h" @@ -57,10 +57,11 @@ TEST_F(TableMergerTest, SimpleMerge) { .build(); ResourceTable finalTable; - TableMerger merger(mContext.get(), &finalTable); + TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{}); + io::FileCollection collection; - ASSERT_TRUE(merger.merge({}, tableA.get(), false)); - ASSERT_TRUE(merger.mergeAndMangle({}, u"com.app.b", tableB.get())); + ASSERT_TRUE(merger.merge({}, tableA.get())); + ASSERT_TRUE(merger.mergeAndMangle({}, u"com.app.b", tableB.get(), &collection)); EXPECT_TRUE(merger.getMergedPackages().count(u"com.app.b") != 0); @@ -76,6 +77,60 @@ TEST_F(TableMergerTest, SimpleMerge) { AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie(u"@com.app.a:id/com.app.b$foo"))); } +TEST_F(TableMergerTest, MergeFile) { + ResourceTable finalTable; + TableMergerOptions options; + options.autoAddOverlay = false; + TableMerger merger(mContext.get(), &finalTable, options); + + ResourceFile fileDesc; + fileDesc.config = test::parseConfigOrDie("hdpi-v4"); + fileDesc.name = test::parseNameOrDie(u"@layout/main"); + fileDesc.source = Source("res/layout-hdpi/main.xml"); + test::TestFile testFile("path/to/res/layout-hdpi/main.xml.flat"); + + ASSERT_TRUE(merger.mergeFile(fileDesc, &testFile)); + + FileReference* file = test::getValueForConfig<FileReference>(&finalTable, + u"@com.app.a:layout/main", + test::parseConfigOrDie("hdpi-v4")); + ASSERT_NE(nullptr, file); + EXPECT_EQ(std::u16string(u"res/layout-hdpi-v4/main.xml"), *file->path); + + ResourceName name = test::parseNameOrDie(u"@com.app.a:layout/main"); + ResourceKeyRef key = { name, test::parseConfigOrDie("hdpi-v4") }; + + auto iter = merger.getFilesToMerge().find(key); + ASSERT_NE(merger.getFilesToMerge().end(), iter); + + const FileToMerge& actualFileToMerge = iter->second; + EXPECT_EQ(&testFile, actualFileToMerge.file); + EXPECT_EQ(std::string("res/layout-hdpi-v4/main.xml"), actualFileToMerge.dstPath); +} + +TEST_F(TableMergerTest, MergeFileOverlay) { + ResourceTable finalTable; + TableMergerOptions tableMergerOptions; + tableMergerOptions.autoAddOverlay = false; + TableMerger merger(mContext.get(), &finalTable, tableMergerOptions); + + ResourceFile fileDesc; + fileDesc.name = test::parseNameOrDie(u"@xml/foo"); + test::TestFile fileA("path/to/fileA.xml.flat"); + test::TestFile fileB("path/to/fileB.xml.flat"); + + ASSERT_TRUE(merger.mergeFile(fileDesc, &fileA)); + ASSERT_TRUE(merger.mergeFileOverlay(fileDesc, &fileB)); + + ResourceName name = test::parseNameOrDie(u"@com.app.a:xml/foo"); + ResourceKeyRef key = { name, ConfigDescription{} }; + auto iter = merger.getFilesToMerge().find(key); + ASSERT_NE(merger.getFilesToMerge().end(), iter); + + const FileToMerge& actualFileToMerge = iter->second; + EXPECT_EQ(&fileB, actualFileToMerge.file); +} + TEST_F(TableMergerTest, MergeFileReferences) { std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder() .setPackageId(u"com.app.a", 0x7f) @@ -87,10 +142,12 @@ TEST_F(TableMergerTest, MergeFileReferences) { .build(); ResourceTable finalTable; - TableMerger merger(mContext.get(), &finalTable); + TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{}); + io::FileCollection collection; + collection.insertFile("res/xml/file.xml"); - ASSERT_TRUE(merger.merge({}, tableA.get(), false)); - ASSERT_TRUE(merger.mergeAndMangle({}, u"com.app.b", tableB.get())); + ASSERT_TRUE(merger.merge({}, tableA.get())); + ASSERT_TRUE(merger.mergeAndMangle({}, u"com.app.b", tableB.get(), &collection)); FileReference* f = test::getValue<FileReference>(&finalTable, u"@com.app.a:xml/file"); ASSERT_NE(f, nullptr); @@ -100,13 +157,90 @@ TEST_F(TableMergerTest, MergeFileReferences) { ASSERT_NE(f, nullptr); EXPECT_EQ(std::u16string(u"res/xml/com.app.b$file.xml"), *f->path); - std::queue<FileToMerge>* filesToMerge = merger.getFileMergeQueue(); - ASSERT_FALSE(filesToMerge->empty()); + ResourceName name = test::parseNameOrDie(u"@com.app.a:xml/com.app.b$file"); + ResourceKeyRef key = { name, ConfigDescription{} }; + auto iter = merger.getFilesToMerge().find(key); + ASSERT_NE(merger.getFilesToMerge().end(), iter); + + const FileToMerge& actualFileToMerge = iter->second; + EXPECT_EQ(Source("res/xml/file.xml"), actualFileToMerge.file->getSource()); + EXPECT_EQ(std::string("res/xml/com.app.b$file.xml"), actualFileToMerge.dstPath); +} + +TEST_F(TableMergerTest, OverrideResourceWithOverlay) { + std::unique_ptr<ResourceTable> base = test::ResourceTableBuilder() + .setPackageId(u"", 0x00) + .addValue(u"@bool/foo", ResourceUtils::tryParseBool(u"true")) + .build(); + std::unique_ptr<ResourceTable> overlay = test::ResourceTableBuilder() + .setPackageId(u"", 0x00) + .addValue(u"@bool/foo", ResourceUtils::tryParseBool(u"false")) + .build(); + + ResourceTable finalTable; + TableMergerOptions tableMergerOptions; + tableMergerOptions.autoAddOverlay = false; + TableMerger merger(mContext.get(), &finalTable, tableMergerOptions); + + ASSERT_TRUE(merger.merge({}, base.get())); + ASSERT_TRUE(merger.mergeOverlay({}, overlay.get())); + + BinaryPrimitive* foo = test::getValue<BinaryPrimitive>(&finalTable, u"@com.app.a:bool/foo"); + ASSERT_NE(nullptr, foo); + EXPECT_EQ(0x0u, foo->value.data); +} + +TEST_F(TableMergerTest, MergeAddResourceFromOverlay) { + std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder() + .setPackageId(u"", 0x7f) + .setSymbolState(u"@bool/foo", {}, SymbolState::kUndefined) + .build(); + std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder() + .setPackageId(u"", 0x7f) + .addValue(u"@bool/foo", ResourceUtils::tryParseBool(u"true")) + .build(); + + ResourceTable finalTable; + TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{}); + + ASSERT_TRUE(merger.merge({}, tableA.get())); + ASSERT_TRUE(merger.mergeOverlay({}, tableB.get())); +} + +TEST_F(TableMergerTest, MergeAddResourceFromOverlayWithAutoAddOverlay) { + std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder() + .setPackageId(u"", 0x7f) + .build(); + std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder() + .setPackageId(u"", 0x7f) + .addValue(u"@bool/foo", ResourceUtils::tryParseBool(u"true")) + .build(); + + ResourceTable finalTable; + TableMergerOptions options; + options.autoAddOverlay = true; + TableMerger merger(mContext.get(), &finalTable, options); + + ASSERT_TRUE(merger.merge({}, tableA.get())); + ASSERT_TRUE(merger.mergeOverlay({}, tableB.get())); +} + +TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) { + std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder() + .setPackageId(u"", 0x7f) + .build(); + std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder() + .setPackageId(u"", 0x7f) + .addValue(u"@bool/foo", ResourceUtils::tryParseBool(u"true")) + .build(); + + ResourceTable finalTable; + TableMergerOptions options; + options.autoAddOverlay = false; + TableMerger merger(mContext.get(), &finalTable, options); - FileToMerge& fileToMerge = filesToMerge->front(); - EXPECT_EQ(fileToMerge.srcTable, tableB.get()); - EXPECT_EQ(fileToMerge.srcPath, u"res/xml/file.xml"); - EXPECT_EQ(fileToMerge.dstPath, u"res/xml/com.app.b$file.xml"); + ASSERT_TRUE(merger.merge({}, tableA.get())); + ASSERT_FALSE(merger.mergeOverlay({}, tableB.get())); } } // namespace aapt diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h index 6fdaebba6897..51e2dd44e521 100644 --- a/tools/aapt2/test/Common.h +++ b/tools/aapt2/test/Common.h @@ -22,7 +22,7 @@ #include "ResourceTable.h" #include "ResourceUtils.h" #include "ValueVisitor.h" - +#include "io/File.h" #include "process/IResourceTableConsumer.h" #include "util/StringPiece.h" @@ -87,6 +87,22 @@ template <typename T> T* getValue(ResourceTable* table, const StringPiece16& res return getValueForConfig<T>(table, resName, {}); } +class TestFile : public io::IFile { +private: + Source mSource; + +public: + TestFile(const StringPiece& path) : mSource(path) {} + + std::unique_ptr<io::IData> openAsData() override { + return {}; + } + + const Source& getSource() const override { + return mSource; + } +}; + } // namespace test } // namespace aapt diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp index 46b520592f25..21e476fc9c29 100644 --- a/tools/aapt2/unflatten/BinaryResourceParser.cpp +++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp @@ -447,6 +447,10 @@ bool BinaryResourceParser::parsePublic(const ResourceTablePackage* package, case Public_entry::kPublic: symbol.state = SymbolState::kPublic; break; + + case Public_entry::kUndefined: + symbol.state = SymbolState::kUndefined; + break; } if (!mTable->setSymbolStateAllowMangled(name, resId, symbol, mContext->getDiagnostics())) { diff --git a/tools/aapt2/util/Maybe.h b/tools/aapt2/util/Maybe.h index aa409ea62ade..10a280347141 100644 --- a/tools/aapt2/util/Maybe.h +++ b/tools/aapt2/util/Maybe.h @@ -285,6 +285,8 @@ auto operator==(const Maybe<T>& a, const Maybe<U>& b) -> decltype(std::declval<T> == std::declval<U>) { if (a && b) { return a.value() == b.value(); + } else if (!a && !b) { + return true; } return false; } diff --git a/tools/aapt2/util/Maybe_test.cpp b/tools/aapt2/util/Maybe_test.cpp index 9cca40ea631f..5d42dc3ac3ab 100644 --- a/tools/aapt2/util/Maybe_test.cpp +++ b/tools/aapt2/util/Maybe_test.cpp @@ -124,9 +124,12 @@ TEST(MaybeTest, Equality) { Maybe<int> b = 1; Maybe<int> c; + Maybe<int> emptyA, emptyB; + EXPECT_EQ(a, b); EXPECT_EQ(b, a); EXPECT_NE(a, c); + EXPECT_EQ(emptyA, emptyB); } } // namespace aapt |