diff options
Diffstat (limited to 'tools/aapt2/link/Link.cpp')
-rw-r--r-- | tools/aapt2/link/Link.cpp | 1022 |
1 files changed, 699 insertions, 323 deletions
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp index 49971201fb3c..b6b4b4732669 100644 --- a/tools/aapt2/link/Link.cpp +++ b/tools/aapt2/link/Link.cpp @@ -44,12 +44,17 @@ #include "util/StringPiece.h" #include "xml/XmlDom.h" +#include <android-base/file.h> #include <google/protobuf/io/coded_stream.h> #include <fstream> +#include <queue> #include <sys/stat.h> +#include <unordered_map> #include <vector> +using google::protobuf::io::CopyingOutputStreamAdaptor; + namespace aapt { struct LinkOptions { @@ -57,24 +62,38 @@ struct LinkOptions { std::string manifestPath; std::vector<std::string> includePaths; std::vector<std::string> overlayFiles; + + // Java/Proguard options. Maybe<std::string> generateJavaClassPath; - Maybe<std::u16string> customJavaPackage; - std::set<std::u16string> extraJavaPackages; + Maybe<std::string> customJavaPackage; + std::set<std::string> extraJavaPackages; Maybe<std::string> generateProguardRulesPath; + Maybe<std::string> generateMainDexProguardRulesPath; + bool noAutoVersion = false; bool noVersionVectors = false; + bool noResourceDeduping = false; bool staticLib = false; bool noStaticLibPackages = false; bool generateNonFinalIds = false; std::vector<std::string> javadocAnnotations; bool outputToDirectory = false; + bool noXmlNamespaces = false; bool autoAddOverlay = false; bool doNotCompressAnything = false; - std::vector<std::string> extensionsToNotCompress; - Maybe<std::u16string> privateSymbols; + std::unordered_set<std::string> extensionsToNotCompress; + Maybe<std::string> privateSymbols; ManifestFixerOptions manifestFixerOptions; std::unordered_set<std::string> products; + + // Split APK options. TableSplitterOptions tableSplitterOptions; + std::vector<SplitConstraints> splitConstraints; + std::vector<std::string> splitPaths; + + // Stable ID options. + std::unordered_map<ResourceName, ResourceId> stableIdMap; + Maybe<std::string> resourceIdMapPath; }; class LinkContext : public IAaptContext { @@ -94,11 +113,11 @@ public: mNameMangler = NameMangler(policy); } - const std::u16string& getCompilationPackage() override { + const std::string& getCompilationPackage() override { return mCompilationPackage; } - void setCompilationPackage(const StringPiece16& packageName) { + void setCompilationPackage(const StringPiece& packageName) { mCompilationPackage = packageName.toString(); } @@ -122,13 +141,22 @@ public: mVerbose = val; } + int getMinSdkVersion() override { + return mMinSdkVersion; + } + + void setMinSdkVersion(int minSdk) { + mMinSdkVersion = minSdk; + } + private: StdErrDiagnostics mDiagnostics; NameMangler mNameMangler; - std::u16string mCompilationPackage; + std::string mCompilationPackage; uint8_t mPackageId = 0x0; SymbolTable mSymbols; bool mVerbose = false; + int mMinSdkVersion = 0; }; static bool copyFileToArchive(io::IFile* file, const std::string& outPath, @@ -142,19 +170,7 @@ static bool copyFileToArchive(io::IFile* file, const std::string& outPath, } const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data()); - size_t bufferSize = data->size(); - - // If the file ends with .flat, we must strip off the CompiledFileHeader from it. - if (util::stringEndsWith<char>(file->getSource().path, ".flat")) { - CompiledFileInputStream inputStream(data->data(), data->size()); - if (!inputStream.CompiledFile()) { - context->getDiagnostics()->error(DiagMessage(file->getSource()) - << "invalid compiled file header"); - return false; - } - buffer = reinterpret_cast<const uint8_t*>(inputStream.data()); - bufferSize = inputStream.size(); - } + const size_t bufferSize = data->size(); if (context->verbose()) { context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive"); @@ -203,16 +219,6 @@ static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe< return false; } -/*static std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len, - IDiagnostics* diag) { - std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); - BinaryResourceParser parser(diag, table.get(), source, data, len); - if (!parser.parse()) { - return {}; - } - return table; -}*/ - static std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source, const void* data, size_t len, IDiagnostics* diag) { @@ -241,48 +247,14 @@ static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagn return xml::inflate(&fin, diag, Source(path)); } -static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const Source& source, - const void* data, size_t len, - IDiagnostics* diag) { - CompiledFileInputStream inputStream(data, len); - if (!inputStream.CompiledFile()) { - diag->error(DiagMessage(source) << "invalid compiled file header"); - return {}; - } - - const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data()); - const size_t xmlDataLen = inputStream.size(); - - std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source); - if (!xmlRes) { - return {}; - } - return xmlRes; -} - -static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source, - const void* data, size_t len, - IDiagnostics* diag) { - CompiledFileInputStream inputStream(data, len); - const pb::CompiledFile* pbFile = inputStream.CompiledFile(); - if (!pbFile) { - diag->error(DiagMessage(source) << "invalid compiled file header"); - return {}; - } - - std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source, diag); - if (!resFile) { - return {}; - } - return resFile; -} - struct ResourceFileFlattenerOptions { bool noAutoVersion = false; bool noVersionVectors = false; + bool noXmlNamespaces = false; bool keepRawValues = false; bool doNotCompressAnything = false; - std::vector<std::string> extensionsToNotCompress; + bool updateProguardSpec = false; + std::unordered_set<std::string> extensionsToNotCompress; }; class ResourceFileFlattener { @@ -296,16 +268,26 @@ public: private: struct FileOperation { + ConfigDescription config; + + // The entry this file came from. + const ResourceEntry* entry; + + // The file to copy as-is. io::IFile* fileToCopy; + + // The XML to process and flatten. std::unique_ptr<xml::XmlResource> xmlToFlatten; + + // The destination to write this file to. std::string dstPath; bool skipVersion = false; }; uint32_t getCompressionFlags(const StringPiece& str); - bool linkAndVersionXmlFile(const ResourceEntry* entry, const ResourceFile& fileDesc, - io::IFile* file, ResourceTable* table, FileOperation* outFileOp); + bool linkAndVersionXmlFile(ResourceTable* table, FileOperation* fileOp, + std::queue<FileOperation>* outFileOpQueue); ResourceFileFlattenerOptions mOptions; IAaptContext* mContext; @@ -318,102 +300,94 @@ uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) { } for (const std::string& extension : mOptions.extensionsToNotCompress) { - if (util::stringEndsWith<char>(str, extension)) { + if (util::stringEndsWith(str, extension)) { return 0; } } return ArchiveEntry::kCompress; } -bool ResourceFileFlattener::linkAndVersionXmlFile(const ResourceEntry* entry, - const ResourceFile& fileDesc, - io::IFile* file, - ResourceTable* table, - FileOperation* outFileOp) { - const StringPiece srcPath = file->getSource().path; - if (mContext->verbose()) { - mContext->getDiagnostics()->note(DiagMessage() << "linking " << srcPath); - } +bool ResourceFileFlattener::linkAndVersionXmlFile(ResourceTable* table, + FileOperation* fileOp, + std::queue<FileOperation>* outFileOpQueue) { + xml::XmlResource* doc = fileOp->xmlToFlatten.get(); + const Source& src = doc->file.source; - std::unique_ptr<io::IData> data = file->openAsData(); - if (!data) { - mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file"); - return false; - } - - if (util::stringEndsWith<char>(srcPath, ".flat")) { - outFileOp->xmlToFlatten = loadBinaryXmlSkipFileExport(file->getSource(), - data->data(), data->size(), - mContext->getDiagnostics()); - } else { - outFileOp->xmlToFlatten = xml::inflate(data->data(), data->size(), - mContext->getDiagnostics(), - file->getSource()); + if (mContext->verbose()) { + mContext->getDiagnostics()->note(DiagMessage() << "linking " << src.path); } - if (!outFileOp->xmlToFlatten) { + XmlReferenceLinker xmlLinker; + if (!xmlLinker.consume(mContext, doc)) { return false; } - // Copy the the file description header. - outFileOp->xmlToFlatten->file = fileDesc; - - XmlReferenceLinker xmlLinker; - if (!xmlLinker.consume(mContext, outFileOp->xmlToFlatten.get())) { + if (mOptions.updateProguardSpec && !proguard::collectProguardRules(src, doc, mKeepSet)) { return false; } - if (!proguard::collectProguardRules(outFileOp->xmlToFlatten->file.source, - outFileOp->xmlToFlatten.get(), mKeepSet)) { - return false; + if (mOptions.noXmlNamespaces) { + XmlNamespaceRemover namespaceRemover; + if (!namespaceRemover.consume(mContext, doc)) { + return false; + } } if (!mOptions.noAutoVersion) { if (mOptions.noVersionVectors) { // Skip this if it is a vector or animated-vector. - xml::Element* el = xml::findRootElement(outFileOp->xmlToFlatten.get()); + xml::Element* el = xml::findRootElement(doc); if (el && el->namespaceUri.empty()) { - if (el->name == u"vector" || el->name == u"animated-vector") { + if (el->name == "vector" || el->name == "animated-vector") { // We are NOT going to version this file. - outFileOp->skipVersion = true; + fileOp->skipVersion = true; return true; } } } + const ConfigDescription& config = fileOp->config; + // Find the first SDK level used that is higher than this defined config and // not superseded by a lower or equal SDK level resource. + const int minSdkVersion = mContext->getMinSdkVersion(); for (int sdkLevel : xmlLinker.getSdkLevels()) { - if (sdkLevel > outFileOp->xmlToFlatten->file.config.sdkVersion) { - if (!shouldGenerateVersionedResource(entry, outFileOp->xmlToFlatten->file.config, - sdkLevel)) { + if (sdkLevel > minSdkVersion && sdkLevel > config.sdkVersion) { + if (!shouldGenerateVersionedResource(fileOp->entry, config, sdkLevel)) { // If we shouldn't generate a versioned resource, stop checking. break; } - ResourceFile versionedFileDesc = outFileOp->xmlToFlatten->file; + ResourceFile versionedFileDesc = doc->file; versionedFileDesc.config.sdkVersion = (uint16_t) sdkLevel; + FileOperation newFileOp; + newFileOp.xmlToFlatten = util::make_unique<xml::XmlResource>( + versionedFileDesc, doc->root->clone()); + newFileOp.config = versionedFileDesc.config; + newFileOp.entry = fileOp->entry; + newFileOp.dstPath = ResourceUtils::buildResourceFileName( + versionedFileDesc, mContext->getNameMangler()); + if (mContext->verbose()) { mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source) << "auto-versioning resource from config '" - << outFileOp->xmlToFlatten->file.config + << config << "' -> '" << versionedFileDesc.config << "'"); } - std::u16string genPath = util::utf8ToUtf16(ResourceUtils::buildResourceFileName( - versionedFileDesc, mContext->getNameMangler())); - bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name, versionedFileDesc.config, versionedFileDesc.source, - genPath, - file, + newFileOp.dstPath, + nullptr, mContext->getDiagnostics()); if (!added) { return false; } + + outFileOpQueue->push(std::move(newFileOp)); break; } } @@ -427,18 +401,17 @@ bool ResourceFileFlattener::linkAndVersionXmlFile(const ResourceEntry* entry, */ bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiveWriter) { bool error = false; - std::map<std::pair<ConfigDescription, StringPiece16>, FileOperation> configSortedFiles; + std::map<std::pair<ConfigDescription, StringPiece>, FileOperation> configSortedFiles; for (auto& pkg : table->packages) { for (auto& type : pkg->types) { // Sort by config and name, so that we get better locality in the zip file. configSortedFiles.clear(); - for (auto& entry : type->entries) { - // Iterate via indices because auto generated values can be inserted ahead of - // the value being processed. - for (size_t i = 0; i < entry->values.size(); i++) { - ResourceConfigValue* configValue = entry->values[i].get(); + std::queue<FileOperation> fileOperations; + // Populate the queue with all files in the ResourceTable. + for (auto& entry : type->entries) { + for (auto& configValue : entry->values) { FileReference* fileRef = valueCast<FileReference>(configValue->value.get()); if (!fileRef) { continue; @@ -452,33 +425,65 @@ bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiv } FileOperation fileOp; - fileOp.dstPath = util::utf16ToUtf8(*fileRef->path); + fileOp.entry = entry.get(); + fileOp.dstPath = *fileRef->path; + fileOp.config = configValue->config; const StringPiece srcPath = file->getSource().path; if (type->type != ResourceType::kRaw && - (util::stringEndsWith<char>(srcPath, ".xml.flat") || - util::stringEndsWith<char>(srcPath, ".xml"))) { - ResourceFile fileDesc; - fileDesc.config = configValue->config; - fileDesc.name = ResourceName(pkg->name, type->type, entry->name); - fileDesc.source = fileRef->getSource(); - if (!linkAndVersionXmlFile(entry.get(), fileDesc, file, table, &fileOp)) { - error = true; - continue; + (util::stringEndsWith(srcPath, ".xml.flat") || + util::stringEndsWith(srcPath, ".xml"))) { + std::unique_ptr<io::IData> data = file->openAsData(); + if (!data) { + mContext->getDiagnostics()->error(DiagMessage(file->getSource()) + << "failed to open file"); + return false; } + fileOp.xmlToFlatten = xml::inflate(data->data(), data->size(), + mContext->getDiagnostics(), + file->getSource()); + + if (!fileOp.xmlToFlatten) { + return false; + } + + fileOp.xmlToFlatten->file.config = configValue->config; + fileOp.xmlToFlatten->file.source = fileRef->getSource(); + fileOp.xmlToFlatten->file.name = + ResourceName(pkg->name, type->type, entry->name); + + // Enqueue the XML files to be processed. + fileOperations.push(std::move(fileOp)); } else { fileOp.fileToCopy = file; + + // NOTE(adamlesinski): Explicitly construct a StringPiece here, or else + // we end up copying the string in the std::make_pair() method, then + // creating a StringPiece from the copy, which would cause us to end up + // referencing garbage in the map. + const StringPiece entryName(entry->name); + configSortedFiles[std::make_pair(configValue->config, entryName)] = + std::move(fileOp); } + } + } + + // Now process the XML queue + for (; !fileOperations.empty(); fileOperations.pop()) { + FileOperation& fileOp = fileOperations.front(); - // NOTE(adamlesinski): Explicitly construct a StringPiece16 here, or else - // we end up copying the string in the std::make_pair() method, then creating - // a StringPiece16 from the copy, which would cause us to end up referencing - // garbage in the map. - const StringPiece16 entryName(entry->name); - configSortedFiles[std::make_pair(configValue->config, entryName)] = - std::move(fileOp); + if (!linkAndVersionXmlFile(table, &fileOp, &fileOperations)) { + error = true; + continue; } + + // NOTE(adamlesinski): Explicitly construct a StringPiece here, or else + // we end up copying the string in the std::make_pair() method, then creating + // a StringPiece from the copy, which would cause us to end up referencing + // garbage in the map. + const StringPiece entryName(fileOp.entry->name); + configSortedFiles[std::make_pair(fileOp.config, entryName)] = std::move(fileOp); } if (error) { @@ -493,7 +498,8 @@ bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiv if (fileOp.xmlToFlatten) { Maybe<size_t> maxSdkLevel; if (!mOptions.noAutoVersion && !fileOp.skipVersion) { - maxSdkLevel = std::max<size_t>(config.sdkVersion, 1u); + maxSdkLevel = std::max<size_t>(std::max<size_t>(config.sdkVersion, 1u), + mContext->getMinSdkVersion()); } bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel, @@ -516,6 +522,99 @@ bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiv return !error; } +static bool writeStableIdMapToPath(IDiagnostics* diag, + const std::unordered_map<ResourceName, ResourceId>& idMap, + const std::string& idMapPath) { + std::ofstream fout(idMapPath, std::ofstream::binary); + if (!fout) { + diag->error(DiagMessage(idMapPath) << strerror(errno)); + return false; + } + + for (const auto& entry : idMap) { + const ResourceName& name = entry.first; + const ResourceId& id = entry.second; + fout << name << " = " << id << "\n"; + } + + if (!fout) { + diag->error(DiagMessage(idMapPath) << "failed writing to file: " << strerror(errno)); + return false; + } + + return true; +} + +static bool loadStableIdMap(IDiagnostics* diag, const std::string& path, + std::unordered_map<ResourceName, ResourceId>* outIdMap) { + std::string content; + if (!android::base::ReadFileToString(path, &content)) { + diag->error(DiagMessage(path) << "failed reading stable ID file"); + return false; + } + + outIdMap->clear(); + size_t lineNo = 0; + for (StringPiece line : util::tokenize(content, '\n')) { + lineNo++; + line = util::trimWhitespace(line); + if (line.empty()) { + continue; + } + + auto iter = std::find(line.begin(), line.end(), '='); + if (iter == line.end()) { + diag->error(DiagMessage(Source(path, lineNo)) << "missing '='"); + return false; + } + + ResourceNameRef name; + StringPiece resNameStr = util::trimWhitespace( + line.substr(0, std::distance(line.begin(), iter))); + if (!ResourceUtils::parseResourceName(resNameStr, &name)) { + diag->error(DiagMessage(Source(path, lineNo)) + << "invalid resource name '" << resNameStr << "'"); + return false; + } + + const size_t resIdStartIdx = std::distance(line.begin(), iter) + 1; + const size_t resIdStrLen = line.size() - resIdStartIdx; + StringPiece resIdStr = util::trimWhitespace(line.substr(resIdStartIdx, resIdStrLen)); + + Maybe<ResourceId> maybeId = ResourceUtils::parseResourceId(resIdStr); + if (!maybeId) { + diag->error(DiagMessage(Source(path, lineNo)) << "invalid resource ID '" + << resIdStr << "'"); + return false; + } + + (*outIdMap)[name.toResourceName()] = maybeId.value(); + } + return true; +} + +static bool parseSplitParameter(const StringPiece& arg, IDiagnostics* diag, + std::string* outPath, SplitConstraints* outSplit) { + std::vector<std::string> parts = util::split(arg, ':'); + if (parts.size() != 2) { + diag->error(DiagMessage() << "invalid split parameter '" << arg << "'"); + diag->note(DiagMessage() << "should be --split path/to/output.apk:<config>[,<config>...]"); + return false; + } + *outPath = parts[0]; + std::vector<ConfigDescription> configs; + for (const StringPiece& configStr : util::tokenize(parts[1], ',')) { + configs.push_back({}); + if (!ConfigDescription::parse(configStr, &configs.back())) { + diag->error(DiagMessage() << "invalid config '" << configStr + << "' in split parameter '" << arg << "'"); + return false; + } + } + outSplit->configs.insert(configs.begin(), configs.end()); + return true; +} + class LinkCommand { public: LinkCommand(LinkContext* context, const LinkOptions& options) : @@ -576,14 +675,57 @@ public: return true; } - Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) { + Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes, IDiagnostics* diag) { // Make sure the first element is <manifest> with package attribute. if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) { - if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") { - if (xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package")) { - return AppInfo{ packageAttr->value }; + AppInfo appInfo; + + if (!manifestEl->namespaceUri.empty() || manifestEl->name != "manifest") { + diag->error(DiagMessage(xmlRes->file.source) << "root tag must be <manifest>"); + return {}; + } + + xml::Attribute* packageAttr = manifestEl->findAttribute({}, "package"); + if (!packageAttr) { + diag->error(DiagMessage(xmlRes->file.source) + << "<manifest> must have a 'package' attribute"); + return {}; + } + + appInfo.package = packageAttr->value; + + if (xml::Attribute* versionCodeAttr = + manifestEl->findAttribute(xml::kSchemaAndroid, "versionCode")) { + Maybe<uint32_t> maybeCode = ResourceUtils::parseInt(versionCodeAttr->value); + if (!maybeCode) { + diag->error(DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber)) + << "invalid android:versionCode '" + << versionCodeAttr->value << "'"); + return {}; + } + appInfo.versionCode = maybeCode.value(); + } + + if (xml::Attribute* revisionCodeAttr = + manifestEl->findAttribute(xml::kSchemaAndroid, "revisionCode")) { + Maybe<uint32_t> maybeCode = ResourceUtils::parseInt(revisionCodeAttr->value); + if (!maybeCode) { + diag->error(DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber)) + << "invalid android:revisionCode '" + << revisionCodeAttr->value << "'"); + return {}; + } + appInfo.revisionCode = maybeCode.value(); + } + + if (xml::Element* usesSdkEl = manifestEl->findChild({}, "uses-sdk")) { + if (xml::Attribute* minSdk = + usesSdkEl->findAttribute(xml::kSchemaAndroid, "minSdkVersion")) { + appInfo.minSdkVersion = minSdk->value; } } + + return appInfo; } return {}; } @@ -612,7 +754,7 @@ public: // Special case the occurrence of an ID that is being generated for the // 'android' package. This is due to legacy reasons. if (valueCast<Id>(configValue->value.get()) && - package->name == u"android") { + package->name == "android") { mContext->getDiagnostics()->warn( DiagMessage(configValue->value->getSource()) << "generated id '" << resName @@ -667,11 +809,11 @@ public: return true; } - std::unique_ptr<IArchiveWriter> makeArchiveWriter() { + std::unique_ptr<IArchiveWriter> makeArchiveWriter(const StringPiece& out) { if (mOptions.outputToDirectory) { - return createDirectoryArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath); + return createDirectoryArchiveWriter(mContext->getDiagnostics(), out); } else { - return createZipFileArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath); + return createZipFileArchiveWriter(mContext->getDiagnostics(), out); } } @@ -702,13 +844,13 @@ public: return false; } - std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table); - - // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream - // interface. + // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry(). { - google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer); + // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream + // interface. + CopyingOutputStreamAdaptor adaptor(writer); + std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table); if (!pbTable->SerializeToZeroCopyStream(&adaptor)) { mContext->getDiagnostics()->error(DiagMessage() << "failed to write"); return false; @@ -722,14 +864,14 @@ public: return true; } - bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate, - const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) { + bool writeJavaFile(ResourceTable* table, const StringPiece& packageNameToGenerate, + const StringPiece& outPackage, const JavaClassGeneratorOptions& javaOptions) { if (!mOptions.generateJavaClassPath) { return true; } std::string outPath = mOptions.generateJavaClassPath.value(); - file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(outPackage))); + file::appendPath(&outPath, file::packageToPath(outPackage)); if (!file::mkdirs(outPath)) { mContext->getDiagnostics()->error( DiagMessage() << "failed to create directory '" << outPath << "'"); @@ -783,7 +925,7 @@ public: manifestClass->getCommentBuilder()->appendComment(properAnnotation); } - const std::string packageUtf8 = util::utf16ToUtf8(mContext->getCompilationPackage()); + const std::string& packageUtf8 = mContext->getCompilationPackage(); std::string outPath = mOptions.generateJavaClassPath.value(); file::appendPath(&outPath, file::packageToPath(packageUtf8)); @@ -811,12 +953,12 @@ public: return true; } - bool writeProguardFile(const proguard::KeepSet& keepSet) { - if (!mOptions.generateProguardRulesPath) { + bool writeProguardFile(const Maybe<std::string>& out, const proguard::KeepSet& keepSet) { + if (!out) { return true; } - const std::string& outPath = mOptions.generateProguardRulesPath.value(); + const std::string& outPath = out.value(); std::ofstream fout(outPath, std::ofstream::binary); if (!fout) { mContext->getDiagnostics()->error( @@ -891,7 +1033,7 @@ public: mOptions.extraJavaPackages.insert(pkg->name); } - pkg->name = u""; + pkg->name = ""; if (override) { result = mTableMerger->mergeOverlay(Source(input), table.get(), collection.get()); } else { @@ -945,7 +1087,9 @@ public: bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc, bool override) { if (mContext->verbose()) { - mContext->getDiagnostics()->note(DiagMessage() << "merging compiled file " + mContext->getDiagnostics()->note(DiagMessage() + << "merging '" << fileDesc->name + << "' from compiled file " << file->getSource()); } @@ -1029,12 +1173,12 @@ public: * Otherwise the files is processed on its own. */ bool mergePath(const std::string& path, bool override) { - if (util::stringEndsWith<char>(path, ".flata") || - util::stringEndsWith<char>(path, ".jar") || - util::stringEndsWith<char>(path, ".jack") || - util::stringEndsWith<char>(path, ".zip")) { + if (util::stringEndsWith(path, ".flata") || + util::stringEndsWith(path, ".jar") || + util::stringEndsWith(path, ".jack") || + util::stringEndsWith(path, ".zip")) { return mergeArchive(path, override); - } else if (util::stringEndsWith<char>(path, ".apk")) { + } else if (util::stringEndsWith(path, ".apk")) { return mergeStaticLibrary(path, override); } @@ -1055,10 +1199,10 @@ public: */ bool mergeFile(io::IFile* file, bool override) { const Source& src = file->getSource(); - if (util::stringEndsWith<char>(src.path, ".arsc.flat")) { + if (util::stringEndsWith(src.path, ".arsc.flat")) { return mergeResourceTable(file, override); - } else if (util::stringEndsWith<char>(src.path, ".flat")){ + } else if (util::stringEndsWith(src.path, ".flat")){ // Try opening the file and looking for an Export header. std::unique_ptr<io::IData> data = file->openAsData(); if (!data) { @@ -1066,12 +1210,40 @@ public: return false; } - std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader( - src, data->data(), data->size(), mContext->getDiagnostics()); - if (resourceFile) { - return mergeCompiledFile(file, resourceFile.get(), override); + CompiledFileInputStream inputStream(data->data(), data->size()); + uint32_t numFiles = 0; + if (!inputStream.ReadLittleEndian32(&numFiles)) { + mContext->getDiagnostics()->error(DiagMessage(src) << "failed read num files"); + return false; } - return false; + + for (uint32_t i = 0; i < numFiles; i++) { + pb::CompiledFile compiledFile; + if (!inputStream.ReadCompiledFile(&compiledFile)) { + mContext->getDiagnostics()->error(DiagMessage(src) + << "failed to read compiled file header"); + return false; + } + + uint64_t offset, len; + if (!inputStream.ReadDataMetaData(&offset, &len)) { + mContext->getDiagnostics()->error(DiagMessage(src) + << "failed to read data meta data"); + return false; + } + + std::unique_ptr<ResourceFile> resourceFile = deserializeCompiledFileFromPb( + compiledFile, file->getSource(), mContext->getDiagnostics()); + if (!resourceFile) { + return false; + } + + if (!mergeCompiledFile(file->createFileSegment(offset, len), resourceFile.get(), + override)) { + return false; + } + } + return true; } // Ignore non .flat files. This could be classes.dex or something else that happens @@ -1079,6 +1251,95 @@ public: return true; } + std::unique_ptr<xml::XmlResource> generateSplitManifest(const AppInfo& appInfo, + const SplitConstraints& constraints) { + std::unique_ptr<xml::XmlResource> doc = util::make_unique<xml::XmlResource>(); + + std::unique_ptr<xml::Namespace> namespaceAndroid = util::make_unique<xml::Namespace>(); + namespaceAndroid->namespaceUri = xml::kSchemaAndroid; + namespaceAndroid->namespacePrefix = "android"; + + std::unique_ptr<xml::Element> manifestEl = util::make_unique<xml::Element>(); + manifestEl->name = "manifest"; + manifestEl->attributes.push_back( + xml::Attribute{ "", "package", appInfo.package }); + + if (appInfo.versionCode) { + manifestEl->attributes.push_back(xml::Attribute{ + xml::kSchemaAndroid, + "versionCode", + std::to_string(appInfo.versionCode.value()) }); + } + + if (appInfo.revisionCode) { + manifestEl->attributes.push_back(xml::Attribute{ + xml::kSchemaAndroid, + "revisionCode", std::to_string(appInfo.revisionCode.value()) }); + } + + std::stringstream splitName; + splitName << "config." << util::joiner(constraints.configs, "_"); + + manifestEl->attributes.push_back( + xml::Attribute{ "", "split", splitName.str() }); + + std::unique_ptr<xml::Element> applicationEl = util::make_unique<xml::Element>(); + applicationEl->name = "application"; + applicationEl->attributes.push_back( + xml::Attribute{ xml::kSchemaAndroid, "hasCode", "false" }); + + manifestEl->addChild(std::move(applicationEl)); + namespaceAndroid->addChild(std::move(manifestEl)); + doc->root = std::move(namespaceAndroid); + return doc; + } + + /** + * Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable + * to the IArchiveWriter. + */ + bool writeApk(IArchiveWriter* writer, proguard::KeepSet* keepSet, xml::XmlResource* manifest, + ResourceTable* table) { + const bool keepRawValues = mOptions.staticLib; + bool result = flattenXml(manifest, "AndroidManifest.xml", {}, keepRawValues, writer, + mContext); + if (!result) { + return false; + } + + ResourceFileFlattenerOptions fileFlattenerOptions; + fileFlattenerOptions.keepRawValues = keepRawValues; + fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything; + fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress; + fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion; + fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors; + fileFlattenerOptions.noXmlNamespaces = mOptions.noXmlNamespaces; + fileFlattenerOptions.updateProguardSpec = + static_cast<bool>(mOptions.generateProguardRulesPath); + + ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, keepSet); + + if (!fileFlattener.flatten(table, writer)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources"); + return false; + } + + if (mOptions.staticLib) { + if (!flattenTableToPb(table, writer)) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed to write resources.arsc.flat"); + return false; + } + } else { + if (!flattenTable(table, writer)) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed to write resources.arsc"); + return false; + } + } + return true; + } + int run(const std::vector<std::string>& inputFiles) { // Load the AndroidManifest.xml std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath, @@ -1087,25 +1348,34 @@ public: return 1; } - if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) { - mContext->setCompilationPackage(maybeAppInfo.value().package); - } else { - mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath) - << "no package specified in <manifest> tag"); + // First extract the Package name without modifying it (via --rename-manifest-package). + if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(), + mContext->getDiagnostics())) { + const AppInfo& appInfo = maybeAppInfo.value(); + mContext->setCompilationPackage(appInfo.package); + } + + ManifestFixer manifestFixer(mOptions.manifestFixerOptions); + if (!manifestFixer.consume(mContext, manifestXml.get())) { return 1; } - if (!util::isJavaPackageName(mContext->getCompilationPackage())) { - mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath) - << "invalid package name '" - << mContext->getCompilationPackage() - << "'"); + Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(), + mContext->getDiagnostics()); + if (!maybeAppInfo) { return 1; } - mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() }); + const AppInfo& appInfo = maybeAppInfo.value(); + if (appInfo.minSdkVersion) { + if (Maybe<int> maybeMinSdkVersion = + ResourceUtils::parseSdkVersion(appInfo.minSdkVersion.value())) { + mContext->setMinSdkVersion(maybeMinSdkVersion.value()); + } + } - if (mContext->getCompilationPackage() == u"android") { + mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() }); + if (mContext->getCompilationPackage() == "android") { mContext->setPackageId(0x01); } else { mContext->setPackageId(0x7f); @@ -1152,15 +1422,34 @@ public: DiagMessage() << "failed moving private attributes"); return 1; } - } - if (!mOptions.staticLib) { // Assign IDs if we are building a regular app. - IdAssigner idAssigner; + IdAssigner idAssigner(&mOptions.stableIdMap); if (!idAssigner.consume(mContext, &mFinalTable)) { mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs"); return 1; } + + // Now grab each ID and emit it as a file. + if (mOptions.resourceIdMapPath) { + for (auto& package : mFinalTable.packages) { + for (auto& type : package->types) { + for (auto& entry : type->entries) { + ResourceName name(package->name, type->type, entry->name); + // The IDs are guaranteed to exist. + mOptions.stableIdMap[std::move(name)] = ResourceId(package->id.value(), + type->id.value(), + entry->id.value()); + } + } + } + + if (!writeStableIdMapToPath(mContext->getDiagnostics(), + mOptions.stableIdMap, + mOptions.resourceIdMapPath.value())) { + return 1; + } + } } else { // Static libs are merged with other apps, and ID collisions are bad, so verify that // no IDs have been set. @@ -1177,44 +1466,126 @@ public: mContext->getExternalSymbols()->prependSource( util::make_unique<ResourceTableSymbolSource>(&mFinalTable)); - { - ReferenceLinker linker; - if (!linker.consume(mContext, &mFinalTable)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed linking references"); + ReferenceLinker linker; + if (!linker.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed linking references"); + return 1; + } + + if (mOptions.staticLib) { + if (!mOptions.products.empty()) { + mContext->getDiagnostics()->warn( + DiagMessage() << "can't select products when building static library"); + } + } else { + ProductFilter productFilter(mOptions.products); + if (!productFilter.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products"); + return 1; + } + } + + if (!mOptions.noAutoVersion) { + AutoVersioner versioner; + if (!versioner.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles"); + return 1; + } + } + + if (!mOptions.staticLib && mContext->getMinSdkVersion() > 0) { + if (mContext->verbose()) { + mContext->getDiagnostics()->note( + DiagMessage() << "collapsing resource versions for minimum SDK " + << mContext->getMinSdkVersion()); + } + + VersionCollapser collapser; + if (!collapser.consume(mContext, &mFinalTable)) { + return 1; + } + } + + if (!mOptions.noResourceDeduping) { + ResourceDeduper deduper; + if (!deduper.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed deduping resources"); return 1; } + } + + proguard::KeepSet proguardKeepSet; + proguard::KeepSet proguardMainDexKeepSet; + + if (mOptions.staticLib) { + if (mOptions.tableSplitterOptions.configFilter != nullptr || + mOptions.tableSplitterOptions.preferredDensity) { + mContext->getDiagnostics()->warn( + DiagMessage() << "can't strip resources when building static library"); + } + } else { + // Adjust the SplitConstraints so that their SDK version is stripped if it is less + // than or equal to the minSdk. Otherwise the resources that have had their SDK version + // stripped due to minSdk won't ever match. + std::vector<SplitConstraints> adjustedConstraintsList; + adjustedConstraintsList.reserve(mOptions.splitConstraints.size()); + for (const SplitConstraints& constraints : mOptions.splitConstraints) { + SplitConstraints adjustedConstraints; + for (const ConfigDescription& config : constraints.configs) { + if (config.sdkVersion <= mContext->getMinSdkVersion()) { + adjustedConstraints.configs.insert(config.copyWithoutSdkVersion()); + } else { + adjustedConstraints.configs.insert(config); + } + } + adjustedConstraintsList.push_back(std::move(adjustedConstraints)); + } - if (mOptions.staticLib) { - if (!mOptions.products.empty()) { - mContext->getDiagnostics()->warn( - DiagMessage() << "can't select products when building static library"); + TableSplitter tableSplitter(adjustedConstraintsList, mOptions.tableSplitterOptions); + if (!tableSplitter.verifySplitConstraints(mContext)) { + return 1; + } + tableSplitter.splitTable(&mFinalTable); + + // Now we need to write out the Split APKs. + auto pathIter = mOptions.splitPaths.begin(); + auto splitConstraintsIter = adjustedConstraintsList.begin(); + for (std::unique_ptr<ResourceTable>& splitTable : tableSplitter.getSplits()) { + if (mContext->verbose()) { + mContext->getDiagnostics()->note( + DiagMessage(*pathIter) << "generating split with configurations '" + << util::joiner(splitConstraintsIter->configs, ", ") << "'"); } - if (mOptions.tableSplitterOptions.configFilter != nullptr || - mOptions.tableSplitterOptions.preferredDensity) { - mContext->getDiagnostics()->warn( - DiagMessage() << "can't strip resources when building static library"); + std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(*pathIter); + if (!archiveWriter) { + mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive"); + return 1; } - } else { - ProductFilter productFilter(mOptions.products); - if (!productFilter.consume(mContext, &mFinalTable)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products"); + + // Generate an AndroidManifest.xml for each split. + std::unique_ptr<xml::XmlResource> splitManifest = + generateSplitManifest(appInfo, *splitConstraintsIter); + + XmlReferenceLinker linker; + if (!linker.consume(mContext, splitManifest.get())) { + mContext->getDiagnostics()->error( + DiagMessage() << "failed to create Split AndroidManifest.xml"); return 1; } - // TODO(adamlesinski): Actually pass in split constraints and handle splits at the file - // level. - TableSplitter tableSplitter({}, mOptions.tableSplitterOptions); - if (!tableSplitter.verifySplitConstraints(mContext)) { + if (!writeApk(archiveWriter.get(), &proguardKeepSet, splitManifest.get(), + splitTable.get())) { return 1; } - tableSplitter.splitTable(&mFinalTable); + + ++pathIter; + ++splitConstraintsIter; } } - proguard::KeepSet proguardKeepSet; - - std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(); + // Start writing the base APK. + std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(mOptions.outputPath); if (!archiveWriter) { mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive"); return 1; @@ -1222,11 +1593,6 @@ public: bool error = false; { - ManifestFixer manifestFixer(mOptions.manifestFixerOptions); - if (!manifestFixer.consume(mContext, manifestXml.get())) { - error = true; - } - // AndroidManifest.xml has no resource name, but the CallSite is built from the name // (aka, which package the AndroidManifest.xml is coming from). // So we give it a package name so it can see local resources. @@ -1234,9 +1600,18 @@ public: XmlReferenceLinker manifestLinker; if (manifestLinker.consume(mContext, manifestXml.get())) { - if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath), - manifestXml.get(), - &proguardKeepSet)) { + if (mOptions.generateProguardRulesPath && + !proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath), + manifestXml.get(), + &proguardKeepSet)) { + error = true; + } + + if (mOptions.generateMainDexProguardRulesPath && + !proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath), + manifestXml.get(), + &proguardMainDexKeepSet, + true)) { error = true; } @@ -1246,11 +1621,12 @@ public: } } - const bool keepRawValues = mOptions.staticLib; - bool result = flattenXml(manifestXml.get(), "AndroidManifest.xml", {}, - keepRawValues, archiveWriter.get(), mContext); - if (!result) { - error = true; + if (mOptions.noXmlNamespaces) { + // PackageParser will fail if URIs are removed from AndroidManifest.xml. + XmlNamespaceRemover namespaceRemover(true /* keepUris */); + if (!namespaceRemover.consume(mContext, manifestXml.get())) { + error = true; + } } } else { error = true; @@ -1262,41 +1638,10 @@ public: return 1; } - ResourceFileFlattenerOptions fileFlattenerOptions; - fileFlattenerOptions.keepRawValues = mOptions.staticLib; - fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything; - fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress; - fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion; - fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors; - ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, &proguardKeepSet); - - if (!fileFlattener.flatten(&mFinalTable, archiveWriter.get())) { - mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources"); + if (!writeApk(archiveWriter.get(), &proguardKeepSet, manifestXml.get(), &mFinalTable)) { return 1; } - if (!mOptions.noAutoVersion) { - AutoVersioner versioner; - if (!versioner.consume(mContext, &mFinalTable)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles"); - return 1; - } - } - - if (mOptions.staticLib) { - if (!flattenTableToPb(&mFinalTable, archiveWriter.get())) { - mContext->getDiagnostics()->error(DiagMessage() - << "failed to write resources.arsc.flat"); - return 1; - } - } else { - if (!flattenTable(&mFinalTable, archiveWriter.get())) { - mContext->getDiagnostics()->error(DiagMessage() - << "failed to write resources.arsc"); - return 1; - } - } - if (mOptions.generateJavaClassPath) { JavaClassGeneratorOptions options; options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; @@ -1306,8 +1651,8 @@ public: options.useFinal = false; } - const StringPiece16 actualPackage = mContext->getCompilationPackage(); - StringPiece16 outputPackage = mContext->getCompilationPackage(); + const StringPiece actualPackage = mContext->getCompilationPackage(); + StringPiece outputPackage = mContext->getCompilationPackage(); if (mOptions.customJavaPackage) { // Override the output java package to the custom one. outputPackage = mOptions.customJavaPackage.value(); @@ -1331,17 +1676,19 @@ public: return 1; } - for (const std::u16string& extraPackage : mOptions.extraJavaPackages) { + for (const std::string& extraPackage : mOptions.extraJavaPackages) { if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) { return 1; } } } - if (mOptions.generateProguardRulesPath) { - if (!writeProguardFile(proguardKeepSet)) { - return 1; - } + if (!writeProguardFile(mOptions.generateProguardRulesPath, proguardKeepSet)) { + return 1; + } + + if (!writeProguardFile(mOptions.generateMainDexProguardRulesPath, proguardMainDexKeepSet)) { + return 1; } if (mContext->verbose()) { @@ -1373,11 +1720,7 @@ private: int link(const std::vector<StringPiece>& args) { LinkContext context; 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> overlayArgList; std::vector<std::string> extraJavaPackages; Maybe<std::string> configs; Maybe<std::string> preferredDensity; @@ -1385,6 +1728,8 @@ int link(const std::vector<StringPiece>& args) { bool legacyXFlag = false; bool requireLocalization = false; bool verbose = false; + Maybe<std::string> stableIdFilePath; + std::vector<std::string> splitArgs; Flags flags = Flags() .requiredFlag("-o", "Output path", &options.outputPath) .requiredFlag("--manifest", "Path to the Android manifest to build", @@ -1392,11 +1737,14 @@ int link(const std::vector<StringPiece>& args) { .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths) .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n" "The last conflicting resource given takes precedence.", - &options.overlayFiles) + &overlayArgList) .optionalFlag("--java", "Directory in which to generate R.java", &options.generateJavaClassPath) .optionalFlag("--proguard", "Output file for generated Proguard rules", &options.generateProguardRulesPath) + .optionalFlag("--proguard-main-dex", + "Output file for generated Proguard rules for the main dex", + &options.generateMainDexProguardRulesPath) .optionalSwitch("--no-auto-version", "Disables automatic style and layout SDK versioning", &options.noAutoVersion) @@ -1404,6 +1752,9 @@ int link(const std::vector<StringPiece>& args) { "Disables automatic versioning of vector drawables. Use this only\n" "when building with vector drawable support library", &options.noVersionVectors) + .optionalSwitch("--no-resource-deduping", "Disables automatic deduping of resources with\n" + "identical values across compatible configurations.", + &options.noResourceDeduping) .optionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01", &legacyXFlag) .optionalSwitch("-z", "Require localization of strings marked 'suggested'", @@ -1418,42 +1769,63 @@ int link(const std::vector<StringPiece>& args) { .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified " "by -o", &options.outputToDirectory) + .optionalSwitch("--no-xml-namespaces", "Removes XML namespace prefix and URI " + "information from AndroidManifest.xml\nand XML binaries in res/*.", + &options.noXmlNamespaces) .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for " - "AndroidManifest.xml", &minSdkVersion) + "AndroidManifest.xml", + &options.manifestFixerOptions.minSdkVersionDefault) .optionalFlag("--target-sdk-version", "Default target SDK version to use for " - "AndroidManifest.xml", &targetSdkVersion) + "AndroidManifest.xml", + &options.manifestFixerOptions.targetSdkVersionDefault) .optionalFlag("--version-code", "Version code (integer) to inject into the " - "AndroidManifest.xml if none is present", &versionCode) + "AndroidManifest.xml if none is present", + &options.manifestFixerOptions.versionCodeDefault) .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) + "if none is present", + &options.manifestFixerOptions.versionNameDefault) + .optionalSwitch("--static-lib", "Generate a static Android library", + &options.staticLib) .optionalSwitch("--no-static-lib-packages", "Merge all library resources under the app's package", &options.noStaticLibPackages) .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n" "This is implied when --static-lib is specified.", &options.generateNonFinalIds) + .optionalFlag("--stable-ids", "File containing a list of name to ID mapping.", + &stableIdFilePath) + .optionalFlag("--emit-ids", "Emit a file at the given path with a list of name to ID\n" + "mappings, suitable for use with --stable-ids.", + &options.resourceIdMapPath) .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) + "package name", + &options.privateSymbols) .optionalFlag("--custom-package", "Custom Java package under which to generate R.java", - &customJavaPackage) + &options.customJavaPackage) .optionalFlagList("--extra-packages", "Generate the same R.java but with different " - "package names", &extraJavaPackages) + "package names", + &extraJavaPackages) .optionalFlagList("--add-javadoc-annotation", "Adds a JavaDoc annotation to all " - "generated Java classes", &options.javadocAnnotations) + "generated Java classes", + &options.javadocAnnotations) .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in " - "overlays without <add-resource> tags", &options.autoAddOverlay) + "overlays without <add-resource> tags", + &options.autoAddOverlay) .optionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml", - &renameManifestPackage) + &options.manifestFixerOptions.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) + &options.manifestFixerOptions.renameInstrumentationTargetPackage) .optionalFlagList("-0", "File extensions not to compress", &options.extensionsToNotCompress) - .optionalSwitch("-v", "Enables verbose logging", &verbose); + .optionalFlagList("--split", "Split resources matching a set of configs out to a " + "Split APK.\nSyntax: path/to/output.apk:<config>[,<config>[...]]", + &splitArgs) + .optionalSwitch("-v", "Enables verbose logging", + &verbose); if (!flags.parse("aapt2 link", args, &std::cerr)) { return 1; @@ -1462,7 +1834,7 @@ int link(const std::vector<StringPiece>& args) { // Expand all argument-files passed into the command line. These start with '@'. std::vector<std::string> argList; for (const std::string& arg : flags.getArgs()) { - if (util::stringStartsWith<char>(arg, "@")) { + if (util::stringStartsWith(arg, "@")) { const std::string path = arg.substr(1, arg.size() - 1); std::string error; if (!file::appendArgsFromFile(path, &argList, &error)) { @@ -1474,56 +1846,34 @@ int link(const std::vector<StringPiece>& args) { } } - if (verbose) { - context.setVerbose(verbose); - } - - if (privateSymbolsPackage) { - options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value()); - } - - if (minSdkVersion) { - options.manifestFixerOptions.minSdkVersionDefault = - util::utf8ToUtf16(minSdkVersion.value()); - } - - if (targetSdkVersion) { - 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()); + // Expand all argument-files passed to -R. + for (const std::string& arg : overlayArgList) { + if (util::stringStartsWith(arg, "@")) { + const std::string path = arg.substr(1, arg.size() - 1); + std::string error; + if (!file::appendArgsFromFile(path, &options.overlayFiles, &error)) { + context.getDiagnostics()->error(DiagMessage(path) << error); + return 1; + } + } else { + options.overlayFiles.push_back(arg); + } } - if (customJavaPackage) { - options.customJavaPackage = util::utf8ToUtf16(customJavaPackage.value()); + if (verbose) { + context.setVerbose(verbose); } // 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(util::utf8ToUtf16(package)); + options.extraJavaPackages.insert(package.toString()); } } if (productList) { - for (StringPiece product : util::tokenize<char>(productList.value(), ',')) { + for (StringPiece product : util::tokenize(productList.value(), ',')) { if (product != "" && product != "default") { options.products.insert(product.toString()); } @@ -1532,7 +1882,7 @@ int link(const std::vector<StringPiece>& args) { AxisConfigFilter filter; if (configs) { - for (const StringPiece& configStr : util::tokenize<char>(configs.value(), ',')) { + for (const StringPiece& configStr : util::tokenize(configs.value(), ',')) { ConfigDescription config; LocaleValue lv; if (lv.initFromFilterString(configStr)) { @@ -1576,6 +1926,32 @@ int link(const std::vector<StringPiece>& args) { options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density; } + if (!options.staticLib && stableIdFilePath) { + if (!loadStableIdMap(context.getDiagnostics(), stableIdFilePath.value(), + &options.stableIdMap)) { + return 1; + } + } + + // Populate some default no-compress extensions that are already compressed. + options.extensionsToNotCompress.insert({ + ".jpg", ".jpeg", ".png", ".gif", + ".wav", ".mp2", ".mp3", ".ogg", ".aac", + ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet", + ".rtttl", ".imy", ".xmf", ".mp4", ".m4a", + ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2", + ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"}); + + // Parse the split parameters. + for (const std::string& splitArg : splitArgs) { + options.splitPaths.push_back({}); + options.splitConstraints.push_back({}); + if (!parseSplitParameter(splitArg, context.getDiagnostics(), &options.splitPaths.back(), + &options.splitConstraints.back())) { + return 1; + } + } + // Turn off auto versioning for static-libs. if (options.staticLib) { options.noAutoVersion = true; |