/* * 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 "AppInfo.h" #include "Debug.h" #include "Flags.h" #include "Locale.h" #include "NameMangler.h" #include "ResourceUtils.h" #include "compile/IdAssigner.h" #include "filter/ConfigFilter.h" #include "flatten/Archive.h" #include "flatten/TableFlattener.h" #include "flatten/XmlFlattener.h" #include "io/FileSystem.h" #include "io/ZipArchive.h" #include "java/JavaClassGenerator.h" #include "java/ManifestClassGenerator.h" #include "java/ProguardRules.h" #include "link/Linkers.h" #include "link/ProductFilter.h" #include "link/ReferenceLinker.h" #include "link/ManifestFixer.h" #include "link/TableMerger.h" #include "process/IResourceTableConsumer.h" #include "process/SymbolTable.h" #include "proto/ProtoSerialize.h" #include "split/TableSplitter.h" #include "unflatten/BinaryResourceParser.h" #include "util/Files.h" #include "util/StringPiece.h" #include "xml/XmlDom.h" #include <google/protobuf/io/coded_stream.h> #include <fstream> #include <sys/stat.h> #include <vector> namespace aapt { struct LinkOptions { std::string outputPath; std::string manifestPath; std::vector<std::string> includePaths; std::vector<std::string> overlayFiles; Maybe<std::string> generateJavaClassPath; Maybe<std::u16string> customJavaPackage; std::set<std::u16string> extraJavaPackages; Maybe<std::string> generateProguardRulesPath; bool noAutoVersion = false; bool noVersionVectors = false; bool staticLib = false; bool noStaticLibPackages = false; bool generateNonFinalIds = false; std::vector<std::string> javadocAnnotations; bool outputToDirectory = false; bool autoAddOverlay = false; bool doNotCompressAnything = false; std::vector<std::string> extensionsToNotCompress; Maybe<std::u16string> privateSymbols; ManifestFixerOptions manifestFixerOptions; std::unordered_set<std::string> products; TableSplitterOptions tableSplitterOptions; }; class LinkContext : public IAaptContext { public: LinkContext() : mNameMangler({}) { } IDiagnostics* getDiagnostics() override { return &mDiagnostics; } NameMangler* getNameMangler() override { return &mNameMangler; } void setNameManglerPolicy(const NameManglerPolicy& policy) { mNameMangler = NameMangler(policy); } const std::u16string& getCompilationPackage() override { return mCompilationPackage; } void setCompilationPackage(const StringPiece16& packageName) { mCompilationPackage = packageName.toString(); } uint8_t getPackageId() override { return mPackageId; } void setPackageId(uint8_t id) { mPackageId = id; } SymbolTable* getExternalSymbols() override { return &mSymbols; } bool verbose() override { return mVerbose; } void setVerbose(bool val) { mVerbose = val; } private: StdErrDiagnostics mDiagnostics; NameMangler mNameMangler; std::u16string mCompilationPackage; uint8_t mPackageId = 0x0; SymbolTable mSymbols; bool mVerbose = false; }; static bool copyFileToArchive(io::IFile* file, const std::string& outPath, uint32_t compressionFlags, IArchiveWriter* writer, IAaptContext* context) { std::unique_ptr<io::IData> data = file->openAsData(); if (!data) { context->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file"); return false; } 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(); } if (context->verbose()) { context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive"); } if (writer->startEntry(outPath, compressionFlags)) { if (writer->writeEntry(buffer, bufferSize)) { if (writer->finishEntry()) { return true; } } } context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath); return false; } static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel, bool keepRawValues, IArchiveWriter* writer, IAaptContext* context) { BigBuffer buffer(1024); XmlFlattenerOptions options = {}; options.keepRawValues = keepRawValues; options.maxSdkLevel = maxSdkLevel; XmlFlattener flattener(&buffer, options); if (!flattener.consume(context, xmlRes)) { return false; } if (context->verbose()) { DiagMessage msg; msg << "writing " << path << " to archive"; if (maxSdkLevel) { msg << " maxSdkLevel=" << maxSdkLevel.value() << " keepRawValues=" << keepRawValues; } context->getDiagnostics()->note(msg); } if (writer->startEntry(path, ArchiveEntry::kCompress)) { if (writer->writeEntry(buffer)) { if (writer->finishEntry()) { return true; } } } context->getDiagnostics()->error(DiagMessage() << "failed to write " << path << " to archive"); 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) { pb::ResourceTable pbTable; if (!pbTable.ParseFromArray(data, len)) { diag->error(DiagMessage(source) << "invalid compiled table"); return {}; } std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source, diag); if (!table) { return {}; } return table; } /** * Inflates an XML file from the source path. */ static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) { std::ifstream fin(path, std::ifstream::binary); if (!fin) { diag->error(DiagMessage(path) << strerror(errno)); return {}; } 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 keepRawValues = false; bool doNotCompressAnything = false; std::vector<std::string> extensionsToNotCompress; }; class ResourceFileFlattener { public: ResourceFileFlattener(const ResourceFileFlattenerOptions& options, IAaptContext* context, proguard::KeepSet* keepSet) : mOptions(options), mContext(context), mKeepSet(keepSet) { } bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter); private: struct FileOperation { io::IFile* fileToCopy; std::unique_ptr<xml::XmlResource> xmlToFlatten; 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); ResourceFileFlattenerOptions mOptions; IAaptContext* mContext; proguard::KeepSet* mKeepSet; }; uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) { if (mOptions.doNotCompressAnything) { return 0; } for (const std::string& extension : mOptions.extensionsToNotCompress) { if (util::stringEndsWith<char>(str, extension)) { return 0; } } return ArchiveEntry::kCompress; } bool 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); } 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 (!outFileOp->xmlToFlatten) { return false; } // Copy the the file description header. outFileOp->xmlToFlatten->file = fileDesc; XmlReferenceLinker xmlLinker; if (!xmlLinker.consume(mContext, outFileOp->xmlToFlatten.get())) { return false; } if (!proguard::collectProguardRules(outFileOp->xmlToFlatten->file.source, outFileOp->xmlToFlatten.get(), mKeepSet)) { 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()); if (el && el->namespaceUri.empty()) { if (el->name == u"vector" || el->name == u"animated-vector") { // We are NOT going to version this file. outFileOp->skipVersion = true; return true; } } } // Find the first SDK level used that is higher than this defined config and // not superseded by a lower or equal SDK level resource. for (int sdkLevel : xmlLinker.getSdkLevels()) { if (sdkLevel > outFileOp->xmlToFlatten->file.config.sdkVersion) { if (!shouldGenerateVersionedResource(entry, outFileOp->xmlToFlatten->file.config, sdkLevel)) { // If we shouldn't generate a versioned resource, stop checking. break; } ResourceFile versionedFileDesc = outFileOp->xmlToFlatten->file; versionedFileDesc.config.sdkVersion = (uint16_t) sdkLevel; if (mContext->verbose()) { mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source) << "auto-versioning resource from config '" << outFileOp->xmlToFlatten->file.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, mContext->getDiagnostics()); if (!added) { return false; } break; } } } return true; } /** * Do not insert or remove any resources while executing in this function. It will * corrupt the iteration order. */ bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiveWriter) { bool error = false; std::map<std::pair<ConfigDescription, StringPiece16>, 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(); FileReference* fileRef = valueCast<FileReference>(configValue->value.get()); if (!fileRef) { continue; } io::IFile* file = fileRef->file; if (!file) { mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource()) << "file not found"); return false; } FileOperation fileOp; fileOp.dstPath = util::utf16ToUtf8(*fileRef->path); 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; } } else { fileOp.fileToCopy = file; } // 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 (error) { return false; } // Now flatten the sorted values. for (auto& mapEntry : configSortedFiles) { const ConfigDescription& config = mapEntry.first.first; const FileOperation& fileOp = mapEntry.second; if (fileOp.xmlToFlatten) { Maybe<size_t> maxSdkLevel; if (!mOptions.noAutoVersion && !fileOp.skipVersion) { maxSdkLevel = std::max<size_t>(config.sdkVersion, 1u); } bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel, mOptions.keepRawValues, archiveWriter, mContext); if (!result) { error = true; } } else { bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath, getCompressionFlags(fileOp.dstPath), archiveWriter, mContext); if (!result) { error = true; } } } } } return !error; } class LinkCommand { public: LinkCommand(LinkContext* context, const LinkOptions& options) : mOptions(options), mContext(context), mFinalTable(), mFileCollection(util::make_unique<io::FileCollection>()) { } /** * Creates a SymbolTable that loads symbols from the various APKs and caches the * results for faster lookup. */ bool loadSymbolsFromIncludePaths() { std::unique_ptr<AssetManagerSymbolSource> assetSource = util::make_unique<AssetManagerSymbolSource>(); for (const std::string& path : mOptions.includePaths) { if (mContext->verbose()) { mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path"); } // First try to load the file as a static lib. std::string errorStr; std::unique_ptr<ResourceTable> staticInclude = loadStaticLibrary(path, &errorStr); if (staticInclude) { if (!mOptions.staticLib) { // Can't include static libraries when not building a static library. mContext->getDiagnostics()->error( DiagMessage(path) << "can't include static library when building app"); return false; } // If we are using --no-static-lib-packages, we need to rename the package of this // table to our compilation package. if (mOptions.noStaticLibPackages) { if (ResourceTablePackage* pkg = staticInclude->findPackageById(0x7f)) { pkg->name = mContext->getCompilationPackage(); } } mContext->getExternalSymbols()->appendSource( util::make_unique<ResourceTableSymbolSource>(staticInclude.get())); mStaticTableIncludes.push_back(std::move(staticInclude)); } else if (!errorStr.empty()) { // We had an error with reading, so fail. mContext->getDiagnostics()->error(DiagMessage(path) << errorStr); return false; } if (!assetSource->addAssetPath(path)) { mContext->getDiagnostics()->error( DiagMessage(path) << "failed to load include path"); return false; } } mContext->getExternalSymbols()->appendSource(std::move(assetSource)); return true; } Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) { // 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 }; } } } return {}; } /** * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked. * Postcondition: ResourceTable has only one package left. All others are stripped, or there * is an error and false is returned. */ bool verifyNoExternalPackages() { auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool { return mContext->getCompilationPackage() != pkg->name || !pkg->id || pkg->id.value() != mContext->getPackageId(); }; bool error = false; for (const auto& package : mFinalTable.packages) { if (isExtPackageFunc(package)) { // We have a package that is not related to the one we're building! for (const auto& type : package->types) { for (const auto& entry : type->entries) { ResourceNameRef resName(package->name, type->type, entry->name); for (const auto& configValue : entry->values) { // 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") { mContext->getDiagnostics()->warn( DiagMessage(configValue->value->getSource()) << "generated id '" << resName << "' for external package '" << package->name << "'"); } else { mContext->getDiagnostics()->error( DiagMessage(configValue->value->getSource()) << "defined resource '" << resName << "' for external package '" << package->name << "'"); error = true; } } } } } } auto newEndIter = std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(), isExtPackageFunc); mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end()); return !error; } /** * Returns true if no IDs have been set, false otherwise. */ bool verifyNoIdsSet() { for (const auto& package : mFinalTable.packages) { for (const auto& type : package->types) { if (type->id) { mContext->getDiagnostics()->error(DiagMessage() << "type " << type->type << " has ID " << std::hex << (int) type->id.value() << std::dec << " assigned"); return false; } for (const auto& entry : type->entries) { if (entry->id) { ResourceNameRef resName(package->name, type->type, entry->name); mContext->getDiagnostics()->error(DiagMessage() << "entry " << resName << " has ID " << std::hex << (int) entry->id.value() << std::dec << " assigned"); return false; } } } } return true; } std::unique_ptr<IArchiveWriter> makeArchiveWriter() { if (mOptions.outputToDirectory) { return createDirectoryArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath); } else { return createZipFileArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath); } } bool flattenTable(ResourceTable* table, IArchiveWriter* writer) { BigBuffer buffer(1024); TableFlattener flattener(&buffer); if (!flattener.consume(mContext, table)) { return false; } if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) { if (writer->writeEntry(buffer)) { if (writer->finishEntry()) { return true; } } } mContext->getDiagnostics()->error( DiagMessage() << "failed to write resources.arsc to archive"); return false; } bool flattenTableToPb(ResourceTable* table, IArchiveWriter* writer) { // Create the file/zip entry. if (!writer->startEntry("resources.arsc.flat", 0)) { mContext->getDiagnostics()->error(DiagMessage() << "failed to open"); return false; } std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table); // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream // interface. { google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer); if (!pbTable->SerializeToZeroCopyStream(&adaptor)) { mContext->getDiagnostics()->error(DiagMessage() << "failed to write"); return false; } } if (!writer->finishEntry()) { mContext->getDiagnostics()->error(DiagMessage() << "failed to finish entry"); return false; } return true; } bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate, const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) { if (!mOptions.generateJavaClassPath) { return true; } std::string outPath = mOptions.generateJavaClassPath.value(); file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(outPackage))); if (!file::mkdirs(outPath)) { mContext->getDiagnostics()->error( DiagMessage() << "failed to create directory '" << outPath << "'"); return false; } file::appendPath(&outPath, "R.java"); std::ofstream fout(outPath, std::ofstream::binary); if (!fout) { mContext->getDiagnostics()->error( DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); return false; } JavaClassGenerator generator(mContext, table, javaOptions); if (!generator.generate(packageNameToGenerate, outPackage, &fout)) { mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError()); return false; } if (!fout) { mContext->getDiagnostics()->error( DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); } return true; } bool writeManifestJavaFile(xml::XmlResource* manifestXml) { if (!mOptions.generateJavaClassPath) { return true; } std::unique_ptr<ClassDefinition> manifestClass = generateManifestClass( mContext->getDiagnostics(), manifestXml); if (!manifestClass) { // Something bad happened, but we already logged it, so exit. return false; } if (manifestClass->empty()) { // Empty Manifest class, no need to generate it. return true; } // Add any JavaDoc annotations to the generated class. for (const std::string& annotation : mOptions.javadocAnnotations) { std::string properAnnotation = "@"; properAnnotation += annotation; manifestClass->getCommentBuilder()->appendComment(properAnnotation); } const std::string packageUtf8 = util::utf16ToUtf8(mContext->getCompilationPackage()); std::string outPath = mOptions.generateJavaClassPath.value(); file::appendPath(&outPath, file::packageToPath(packageUtf8)); if (!file::mkdirs(outPath)) { mContext->getDiagnostics()->error( DiagMessage() << "failed to create directory '" << outPath << "'"); return false; } file::appendPath(&outPath, "Manifest.java"); std::ofstream fout(outPath, std::ofstream::binary); if (!fout) { mContext->getDiagnostics()->error( DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); return false; } if (!ClassDefinition::writeJavaFile(manifestClass.get(), packageUtf8, true, &fout)) { mContext->getDiagnostics()->error( DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); return false; } return true; } bool writeProguardFile(const proguard::KeepSet& keepSet) { if (!mOptions.generateProguardRulesPath) { return true; } const std::string& outPath = mOptions.generateProguardRulesPath.value(); std::ofstream fout(outPath, std::ofstream::binary); if (!fout) { mContext->getDiagnostics()->error( DiagMessage() << "failed to open '" << outPath << "': " << strerror(errno)); return false; } proguard::writeKeepSet(&fout, keepSet); if (!fout) { mContext->getDiagnostics()->error( DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); return false; } return true; } std::unique_ptr<ResourceTable> loadStaticLibrary(const std::string& input, std::string* outError) { std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create( input, outError); if (!collection) { return {}; } return loadTablePbFromCollection(collection.get()); } std::unique_ptr<ResourceTable> loadTablePbFromCollection(io::IFileCollection* collection) { io::IFile* file = collection->findFile("resources.arsc.flat"); if (!file) { return {}; } std::unique_ptr<io::IData> data = file->openAsData(); return loadTableFromPb(file->getSource(), data->data(), data->size(), mContext->getDiagnostics()); } bool mergeStaticLibrary(const std::string& input, bool override) { if (mContext->verbose()) { mContext->getDiagnostics()->note(DiagMessage() << "merging static library " << input); } std::string errorStr; std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(input, &errorStr); if (!collection) { mContext->getDiagnostics()->error(DiagMessage(input) << errorStr); return false; } std::unique_ptr<ResourceTable> table = loadTablePbFromCollection(collection.get()); if (!table) { mContext->getDiagnostics()->error(DiagMessage(input) << "invalid static library"); return false; } ResourceTablePackage* pkg = table->findPackageById(0x7f); if (!pkg) { mContext->getDiagnostics()->error(DiagMessage(input) << "static library has no package"); return false; } bool result; if (mOptions.noStaticLibPackages) { // Merge all resources as if they were in the compilation package. This is the old // behaviour of aapt. // Add the package to the set of --extra-packages so we emit an R.java for each // library package. if (!pkg->name.empty()) { mOptions.extraJavaPackages.insert(pkg->name); } pkg->name = u""; if (override) { result = mTableMerger->mergeOverlay(Source(input), table.get(), collection.get()); } else { result = mTableMerger->merge(Source(input), table.get(), collection.get()); } } else { // This is the proper way to merge libraries, where the package name is preserved // and resource names are mangled. result = mTableMerger->mergeAndMangle(Source(input), pkg->name, table.get(), collection.get()); } if (!result) { return false; } // Make sure to move the collection into the set of IFileCollections. mCollections.push_back(std::move(collection)); return true; } bool mergeResourceTable(io::IFile* file, bool override) { if (mContext->verbose()) { mContext->getDiagnostics()->note(DiagMessage() << "merging resource table " << file->getSource()); } std::unique_ptr<io::IData> data = file->openAsData(); if (!data) { mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file"); return false; } std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(), data->data(), data->size(), mContext->getDiagnostics()); if (!table) { return false; } bool result = false; if (override) { result = mTableMerger->mergeOverlay(file->getSource(), table.get()); } else { result = mTableMerger->merge(file->getSource(), table.get()); } return result; } bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc, bool override) { if (mContext->verbose()) { mContext->getDiagnostics()->note(DiagMessage() << "merging compiled file " << file->getSource()); } bool result = false; if (override) { result = mTableMerger->mergeFileOverlay(*fileDesc, file); } else { result = mTableMerger->mergeFile(*fileDesc, file); } if (!result) { return false; } // Add the exports of this file to the table. for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) { if (exportedSymbol.name.package.empty()) { exportedSymbol.name.package = mContext->getCompilationPackage(); } ResourceNameRef resName = exportedSymbol.name; Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName( exportedSymbol.name); if (mangledName) { resName = mangledName.value(); } std::unique_ptr<Id> id = util::make_unique<Id>(); id->setSource(fileDesc->source.withLine(exportedSymbol.line)); bool result = mFinalTable.addResourceAllowMangled( resName, ConfigDescription::defaultConfig(), std::string(), std::move(id), mContext->getDiagnostics()); if (!result) { return false; } } return true; } /** * Takes a path to load as a ZIP file and merges the files within into the master ResourceTable. * If override is true, conflicting resources are allowed to override each other, in order of * last seen. * * An io::IFileCollection is created from the ZIP file and added to the set of * io::IFileCollections that are open. */ bool mergeArchive(const std::string& input, bool override) { if (mContext->verbose()) { mContext->getDiagnostics()->note(DiagMessage() << "merging archive " << input); } std::string errorStr; std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(input, &errorStr); if (!collection) { mContext->getDiagnostics()->error(DiagMessage(input) << errorStr); return false; } bool error = false; for (auto iter = collection->iterator(); iter->hasNext(); ) { if (!mergeFile(iter->next(), override)) { error = true; } } // Make sure to move the collection into the set of IFileCollections. mCollections.push_back(std::move(collection)); return !error; } /** * Takes a path to load and merge into the master ResourceTable. If override is true, * conflicting resources are allowed to override each other, in order of last seen. * * If the file path ends with .flata, .jar, .jack, or .zip the file is treated as ZIP archive * and the files within are merged individually. * * 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")) { return mergeArchive(path, override); } else if (util::stringEndsWith<char>(path, ".apk")) { return mergeStaticLibrary(path, override); } io::IFile* file = mFileCollection->insertFile(path); return mergeFile(file, override); } /** * Takes a file to load and merge into the master ResourceTable. If override is true, * conflicting resources are allowed to override each other, in order of last seen. * * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and merged into the * master ResourceTable. If the file ends with .flat, then it is treated like a compiled file * and the header data is read and merged into the final ResourceTable. * * All other file types are ignored. This is because these files could be coming from a zip, * where we could have other files like classes.dex. */ bool mergeFile(io::IFile* file, bool override) { const Source& src = file->getSource(); if (util::stringEndsWith<char>(src.path, ".arsc.flat")) { return mergeResourceTable(file, override); } else if (util::stringEndsWith<char>(src.path, ".flat")){ // Try opening the file and looking for an Export header. std::unique_ptr<io::IData> data = file->openAsData(); if (!data) { mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open"); return false; } std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader( src, data->data(), data->size(), mContext->getDiagnostics()); if (resourceFile) { return mergeCompiledFile(file, resourceFile.get(), override); } return false; } // Ignore non .flat files. This could be classes.dex or something else that happens // to be in an archive. return true; } int run(const std::vector<std::string>& inputFiles) { // Load the AndroidManifest.xml std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath, mContext->getDiagnostics()); if (!manifestXml) { 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"); return 1; } if (!util::isJavaPackageName(mContext->getCompilationPackage())) { mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath) << "invalid package name '" << mContext->getCompilationPackage() << "'"); return 1; } mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() }); if (mContext->getCompilationPackage() == u"android") { mContext->setPackageId(0x01); } else { mContext->setPackageId(0x7f); } if (!loadSymbolsFromIncludePaths()) { return 1; } TableMergerOptions tableMergerOptions; tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay; mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions); if (mContext->verbose()) { mContext->getDiagnostics()->note( DiagMessage() << "linking package '" << mContext->getCompilationPackage() << "' with package ID " << std::hex << (int) mContext->getPackageId()); } for (const std::string& input : inputFiles) { if (!mergePath(input, false)) { mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input"); return 1; } } for (const std::string& input : mOptions.overlayFiles) { if (!mergePath(input, true)) { mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays"); return 1; } } if (!verifyNoExternalPackages()) { return 1; } if (!mOptions.staticLib) { PrivateAttributeMover mover; if (!mover.consume(mContext, &mFinalTable)) { mContext->getDiagnostics()->error( DiagMessage() << "failed moving private attributes"); return 1; } } if (!mOptions.staticLib) { // Assign IDs if we are building a regular app. IdAssigner idAssigner; if (!idAssigner.consume(mContext, &mFinalTable)) { mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs"); return 1; } } else { // Static libs are merged with other apps, and ID collisions are bad, so verify that // no IDs have been set. if (!verifyNoIdsSet()) { return 1; } } // Add the names to mangle based on our source merge earlier. mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage(), mTableMerger->getMergedPackages() }); // Add our table to the symbol table. mContext->getExternalSymbols()->prependSource( util::make_unique<ResourceTableSymbolSource>(&mFinalTable)); { 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"); } if (mOptions.tableSplitterOptions.configFilter != nullptr || mOptions.tableSplitterOptions.preferredDensity) { mContext->getDiagnostics()->warn( DiagMessage() << "can't strip resources when building static library"); } } else { ProductFilter productFilter(mOptions.products); if (!productFilter.consume(mContext, &mFinalTable)) { mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products"); return 1; } // TODO(adamlesinski): Actually pass in split constraints and handle splits at the file // level. TableSplitter tableSplitter({}, mOptions.tableSplitterOptions); if (!tableSplitter.verifySplitConstraints(mContext)) { return 1; } tableSplitter.splitTable(&mFinalTable); } } proguard::KeepSet proguardKeepSet; std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(); if (!archiveWriter) { mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive"); return 1; } 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. manifestXml->file.name.package = mContext->getCompilationPackage(); XmlReferenceLinker manifestLinker; if (manifestLinker.consume(mContext, manifestXml.get())) { if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath), manifestXml.get(), &proguardKeepSet)) { error = true; } if (mOptions.generateJavaClassPath) { if (!writeManifestJavaFile(manifestXml.get())) { error = true; } } const bool keepRawValues = mOptions.staticLib; bool result = flattenXml(manifestXml.get(), "AndroidManifest.xml", {}, keepRawValues, archiveWriter.get(), mContext); if (!result) { error = true; } } else { error = true; } } if (error) { mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest"); 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"); 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; options.javadocAnnotations = mOptions.javadocAnnotations; if (mOptions.staticLib || mOptions.generateNonFinalIds) { options.useFinal = false; } const StringPiece16 actualPackage = mContext->getCompilationPackage(); StringPiece16 outputPackage = mContext->getCompilationPackage(); if (mOptions.customJavaPackage) { // Override the output java package to the custom one. outputPackage = mOptions.customJavaPackage.value(); } if (mOptions.privateSymbols) { // If we defined a private symbols package, we only emit Public symbols // to the original package, and private and public symbols to the private package. options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic; if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(), outputPackage, options)) { return 1; } options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate; outputPackage = mOptions.privateSymbols.value(); } if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) { return 1; } for (const std::u16string& extraPackage : mOptions.extraJavaPackages) { if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) { return 1; } } } if (mOptions.generateProguardRulesPath) { if (!writeProguardFile(proguardKeepSet)) { return 1; } } if (mContext->verbose()) { DebugPrintTableOptions debugPrintTableOptions; debugPrintTableOptions.showSources = true; Debug::printTable(&mFinalTable, debugPrintTableOptions); } return 0; } private: LinkOptions mOptions; LinkContext* mContext; ResourceTable mFinalTable; std::unique_ptr<TableMerger> mTableMerger; // A pointer to the FileCollection representing the filesystem (not archives). std::unique_ptr<io::FileCollection> mFileCollection; // A vector of IFileCollections. This is mainly here to keep ownership of the collections. std::vector<std::unique_ptr<io::IFileCollection>> mCollections; // A vector of ResourceTables. This is here to retain ownership, so that the SymbolTable // can use these. std::vector<std::unique_ptr<ResourceTable>> mStaticTableIncludes; }; 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> extraJavaPackages; Maybe<std::string> configs; Maybe<std::string> preferredDensity; Maybe<std::string> productList; bool legacyXFlag = false; bool requireLocalization = false; bool verbose = false; Flags flags = Flags() .requiredFlag("-o", "Output path", &options.outputPath) .requiredFlag("--manifest", "Path to the Android manifest to build", &options.manifestPath) .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths) .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n" "The last conflicting resource given takes precedence.", &options.overlayFiles) .optionalFlag("--java", "Directory in which to generate R.java", &options.generateJavaClassPath) .optionalFlag("--proguard", "Output file for generated Proguard rules", &options.generateProguardRulesPath) .optionalSwitch("--no-auto-version", "Disables automatic style and layout SDK versioning", &options.noAutoVersion) .optionalSwitch("--no-version-vectors", "Disables automatic versioning of vector drawables. Use this only\n" "when building with vector drawable support library", &options.noVersionVectors) .optionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01", &legacyXFlag) .optionalSwitch("-z", "Require localization of strings marked 'suggested'", &requireLocalization) .optionalFlag("-c", "Comma separated list of configurations to include. The default\n" "is all configurations", &configs) .optionalFlag("--preferred-density", "Selects the closest matching density and strips out all others.", &preferredDensity) .optionalFlag("--product", "Comma separated list of product names to keep", &productList) .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified " "by -o", &options.outputToDirectory) .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for " "AndroidManifest.xml", &minSdkVersion) .optionalFlag("--target-sdk-version", "Default target SDK version to use for " "AndroidManifest.xml", &targetSdkVersion) .optionalFlag("--version-code", "Version code (integer) to inject into the " "AndroidManifest.xml if none is present", &versionCode) .optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml " "if none is present", &versionName) .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib) .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("--private-symbols", "Package name to use when generating R.java for " "private symbols.\n" "If not specified, public and private symbols will use the application's " "package name", &privateSymbolsPackage) .optionalFlag("--custom-package", "Custom Java package under which to generate R.java", &customJavaPackage) .optionalFlagList("--extra-packages", "Generate the same R.java but with different " "package names", &extraJavaPackages) .optionalFlagList("--add-javadoc-annotation", "Adds a JavaDoc annotation to all " "generated Java classes", &options.javadocAnnotations) .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in " "overlays without <add-resource> tags", &options.autoAddOverlay) .optionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml", &renameManifestPackage) .optionalFlag("--rename-instrumentation-target-package", "Changes the name of the target package for instrumentation. Most useful " "when used\nin conjunction with --rename-manifest-package", &renameInstrumentationTargetPackage) .optionalFlagList("-0", "File extensions not to compress", &options.extensionsToNotCompress) .optionalSwitch("-v", "Enables verbose logging", &verbose); if (!flags.parse("aapt2 link", args, &std::cerr)) { return 1; } // 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, "@")) { const std::string path = arg.substr(1, arg.size() - 1); std::string error; if (!file::appendArgsFromFile(path, &argList, &error)) { context.getDiagnostics()->error(DiagMessage(path) << error); return 1; } } else { argList.push_back(arg); } } 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()); } if (customJavaPackage) { options.customJavaPackage = util::utf8ToUtf16(customJavaPackage.value()); } // Populate the set of extra packages for which to generate R.java. for (std::string& extraPackage : extraJavaPackages) { // A given package can actually be a colon separated list of packages. for (StringPiece package : util::split(extraPackage, ':')) { options.extraJavaPackages.insert(util::utf8ToUtf16(package)); } } if (productList) { for (StringPiece product : util::tokenize<char>(productList.value(), ',')) { if (product != "" && product != "default") { options.products.insert(product.toString()); } } } AxisConfigFilter filter; if (configs) { for (const StringPiece& configStr : util::tokenize<char>(configs.value(), ',')) { ConfigDescription config; LocaleValue lv; if (lv.initFromFilterString(configStr)) { lv.writeTo(&config); } else if (!ConfigDescription::parse(configStr, &config)) { context.getDiagnostics()->error( DiagMessage() << "invalid config '" << configStr << "' for -c option"); return 1; } if (config.density != 0) { context.getDiagnostics()->warn( DiagMessage() << "ignoring density '" << config << "' for -c option"); } else { filter.addConfig(config); } } options.tableSplitterOptions.configFilter = &filter; } if (preferredDensity) { ConfigDescription preferredDensityConfig; if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) { context.getDiagnostics()->error(DiagMessage() << "invalid density '" << preferredDensity.value() << "' for --preferred-density option"); return 1; } // Clear the version that can be automatically added. preferredDensityConfig.sdkVersion = 0; if (preferredDensityConfig.diff(ConfigDescription::defaultConfig()) != ConfigDescription::CONFIG_DENSITY) { context.getDiagnostics()->error(DiagMessage() << "invalid preferred density '" << preferredDensity.value() << "'. " << "Preferred density must only be a density value"); return 1; } options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density; } // Turn off auto versioning for static-libs. if (options.staticLib) { options.noAutoVersion = true; options.noVersionVectors = true; } LinkCommand cmd(&context, options); return cmd.run(argList); } } // namespace aapt