/* * 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 #include #include #include namespace aapt { struct LinkOptions { std::string outputPath; std::string manifestPath; std::vector includePaths; std::vector overlayFiles; Maybe generateJavaClassPath; Maybe customJavaPackage; std::set extraJavaPackages; Maybe generateProguardRulesPath; bool noAutoVersion = false; bool staticLib = false; bool generateNonFinalIds = false; bool outputToDirectory = false; bool autoAddOverlay = false; bool doNotCompressAnything = false; std::vector extensionsToNotCompress; Maybe privateSymbols; ManifestFixerOptions manifestFixerOptions; std::unordered_set products; TableSplitterOptions tableSplitterOptions; }; struct LinkContext : public IAaptContext { StdErrDiagnostics mDiagnostics; std::unique_ptr mNameMangler; std::u16string mCompilationPackage; uint8_t mPackageId; std::unique_ptr mSymbols; bool mVerbose = false; IDiagnostics* getDiagnostics() override { return &mDiagnostics; } NameMangler* getNameMangler() override { return mNameMangler.get(); } StringPiece16 getCompilationPackage() override { return mCompilationPackage; } uint8_t getPackageId() override { return mPackageId; } ISymbolTable* getExternalSymbols() override { return mSymbols.get(); } bool verbose() override { return mVerbose; } }; static bool copyFileToArchive(io::IFile* file, const std::string& outPath, uint32_t compressionFlags, IArchiveWriter* writer, IAaptContext* context) { std::unique_ptr data = file->openAsData(); if (!data) { context->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file"); return false; } CompiledFileInputStream inputStream(data->data(), data->size()); if (!inputStream.CompiledFile()) { context->getDiagnostics()->error(DiagMessage(file->getSource()) << "invalid compiled file header"); return false; } if (context->verbose()) { context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive"); } if (writer->startEntry(outPath, compressionFlags)) { if (writer->writeEntry(reinterpret_cast(inputStream.data()), inputStream.size())) { 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 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(); } 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 loadTable(const Source& source, const void* data, size_t len, IDiagnostics* diag) { std::unique_ptr table = util::make_unique(); BinaryResourceParser parser(diag, table.get(), source, data, len); if (!parser.parse()) { return {}; } return table; }*/ static std::unique_ptr 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 table = deserializeTableFromPb(pbTable, source, diag); if (!table) { return {}; } return table; } /** * Inflates an XML file from the source path. */ static std::unique_ptr 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 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(inputStream.data()); const size_t xmlDataLen = inputStream.size(); std::unique_ptr xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source); if (!xmlRes) { return {}; } return xmlRes; } static std::unique_ptr 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 resFile = deserializeCompiledFileFromPb(*pbFile, source, diag); if (!resFile) { return {}; } return resFile; } struct ResourceFileFlattenerOptions { bool noAutoVersion = false; bool keepRawValues = false; bool doNotCompressAnything = false; std::vector 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 xmlToFlatten; std::string dstPath; }; uint32_t getCompressionFlags(const StringPiece& str); std::unique_ptr linkAndVersionXmlFile(const ResourceEntry* entry, const ResourceFile& fileDesc, io::IFile* file, ResourceTable* table); 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(str, extension)) { return 0; } } return ArchiveEntry::kCompress; } std::unique_ptr ResourceFileFlattener::linkAndVersionXmlFile( const ResourceEntry* entry, const ResourceFile& fileDesc, io::IFile* file, ResourceTable* table) { const StringPiece srcPath = file->getSource().path; if (mContext->verbose()) { mContext->getDiagnostics()->note(DiagMessage() << "linking " << srcPath); } std::unique_ptr data = file->openAsData(); if (!data) { mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file"); return {}; } std::unique_ptr xmlRes; if (util::stringEndsWith(srcPath, ".flat")) { xmlRes = loadBinaryXmlSkipFileExport(file->getSource(), data->data(), data->size(), mContext->getDiagnostics()); } else { xmlRes = xml::inflate(data->data(), data->size(), mContext->getDiagnostics(), file->getSource()); } if (!xmlRes) { return {}; } // Copy the the file description header. xmlRes->file = fileDesc; XmlReferenceLinker xmlLinker; if (!xmlLinker.consume(mContext, xmlRes.get())) { return {}; } if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(), mKeepSet)) { return {}; } if (!mOptions.noAutoVersion) { // 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 > xmlRes->file.config.sdkVersion) { if (!shouldGenerateVersionedResource(entry, xmlRes->file.config, sdkLevel)) { // If we shouldn't generate a versioned resource, stop checking. break; } ResourceFile versionedFileDesc = xmlRes->file; versionedFileDesc.config.sdkVersion = sdkLevel; if (mContext->verbose()) { mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source) << "auto-versioning resource from config '" << xmlRes->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 {}; } break; } } } return xmlRes; } /** * 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, 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(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(srcPath, ".xml.flat") || util::stringEndsWith(srcPath, ".xml"))) { ResourceFile fileDesc; fileDesc.config = configValue->config; fileDesc.name = ResourceName(pkg->name, type->type, entry->name); fileDesc.source = fileRef->getSource(); fileOp.xmlToFlatten = linkAndVersionXmlFile(entry.get(), fileDesc, file, table); if (!fileOp.xmlToFlatten) { 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 maxSdkLevel; if (!mOptions.noAutoVersion) { maxSdkLevel = std::max(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(nullptr) { std::unique_ptr fileCollection = util::make_unique(); // Get a pointer to the FileCollection for convenience, but it will be owned by the vector. mFileCollection = fileCollection.get(); // Move it to the collection. mCollections.push_back(std::move(fileCollection)); } /** * Creates a SymbolTable that loads symbols from the various APKs and caches the * results for faster lookup. */ std::unique_ptr createSymbolTableFromIncludePaths() { AssetManagerSymbolTableBuilder builder; for (const std::string& path : mOptions.includePaths) { if (mContext->verbose()) { mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path"); } std::unique_ptr assetManager = util::make_unique(); int32_t cookie = 0; if (!assetManager->addAssetPath(android::String8(path.data(), path.size()), &cookie)) { mContext->getDiagnostics()->error( DiagMessage(path) << "failed to load include path"); return {}; } builder.add(std::move(assetManager)); } return builder.build(); } Maybe extractAppInfoFromManifest(xml::XmlResource* xmlRes) { // Make sure the first element is 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& 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(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; } std::unique_ptr 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 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))); file::mkdirs(outPath); file::appendPath(&outPath, "R.java"); std::ofstream fout(outPath, std::ofstream::binary); if (!fout) { mContext->getDiagnostics()->error(DiagMessage() << strerror(errno)); return false; } JavaClassGenerator generator(table, javaOptions); if (!generator.generate(packageNameToGenerate, outPackage, &fout)) { mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError()); return false; } return true; } bool writeManifestJavaFile(xml::XmlResource* manifestXml) { if (!mOptions.generateJavaClassPath) { return true; } std::string outPath = mOptions.generateJavaClassPath.value(); file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(mContext->getCompilationPackage()))); file::mkdirs(outPath); file::appendPath(&outPath, "Manifest.java"); std::ofstream fout(outPath, std::ofstream::binary); if (!fout) { mContext->getDiagnostics()->error(DiagMessage() << strerror(errno)); return false; } ManifestClassGenerator generator; if (!generator.generate(mContext->getDiagnostics(), mContext->getCompilationPackage(), manifestXml, &fout)) { return false; } if (!fout) { mContext->getDiagnostics()->error(DiagMessage() << strerror(errno)); return false; } return true; } bool writeProguardFile(const proguard::KeepSet& keepSet) { if (!mOptions.generateProguardRulesPath) { return true; } std::ofstream fout(mOptions.generateProguardRulesPath.value(), std::ofstream::binary); if (!fout) { mContext->getDiagnostics()->error(DiagMessage() << strerror(errno)); return false; } proguard::writeKeepSet(&fout, keepSet); if (!fout) { mContext->getDiagnostics()->error(DiagMessage() << strerror(errno)); return false; } return true; } bool mergeStaticLibrary(const std::string& input) { // TODO(adamlesinski): Load resources from a static library APK and merge the table into // TableMerger. mContext->getDiagnostics()->warn(DiagMessage() << "linking static libraries not supported yet: " << input); return true; } bool mergeResourceTable(io::IFile* file, bool override) { if (mContext->verbose()) { mContext->getDiagnostics()->note(DiagMessage() << "linking " << file->getSource()); } std::unique_ptr data = file->openAsData(); if (!data) { mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file"); return false; } std::unique_ptr 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, std::unique_ptr fileDesc, bool overlay) { if (mContext->verbose()) { mContext->getDiagnostics()->note(DiagMessage() << "adding " << file->getSource()); } bool result = false; if (overlay) { 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().toString(); } ResourceNameRef resName = exportedSymbol.name; Maybe mangledName = mContext->getNameMangler()->mangleName( exportedSymbol.name); if (mangledName) { resName = mangledName.value(); } std::unique_ptr id = util::make_unique(); 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; } /** * Creates an io::IFileCollection from the ZIP archive and processes the files within. */ bool mergeArchive(const std::string& input, bool override) { std::string errorStr; std::unique_ptr 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 (!processFile(iter->next(), override)) { error = true; } } // Make sure to move the collection into the set of IFileCollections. mCollections.push_back(std::move(collection)); return !error; } bool processFile(const std::string& path, bool override) { if (util::stringEndsWith(path, ".flata") || util::stringEndsWith(path, ".jar") || util::stringEndsWith(path, ".jack") || util::stringEndsWith(path, ".zip")) { return mergeArchive(path, override); } io::IFile* file = mFileCollection->insertFile(path); return processFile(file, override); } bool processFile(io::IFile* file, bool override) { const Source& src = file->getSource(); if (util::stringEndsWith(src.path, ".arsc.flat")) { return mergeResourceTable(file, override); } else if (util::stringEndsWith(src.path, ".flat")){ // Try opening the file and looking for an Export header. std::unique_ptr data = file->openAsData(); if (!data) { mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open"); return false; } std::unique_ptr resourceFile = loadFileExportHeader( src, data->data(), data->size(), mContext->getDiagnostics()); if (resourceFile) { return mergeCompiledFile(file, std::move(resourceFile), 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& inputFiles) { // Load the AndroidManifest.xml std::unique_ptr manifestXml = loadXml(mOptions.manifestPath, mContext->getDiagnostics()); if (!manifestXml) { return 1; } if (Maybe maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) { mContext->mCompilationPackage = maybeAppInfo.value().package; } else { mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath) << "no package specified in tag"); return 1; } if (!util::isJavaPackageName(mContext->mCompilationPackage)) { mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath) << "invalid package name '" << mContext->mCompilationPackage << "'"); return 1; } mContext->mNameMangler = util::make_unique( NameManglerPolicy{ mContext->mCompilationPackage }); if (mContext->mCompilationPackage == u"android") { mContext->mPackageId = 0x01; } else { mContext->mPackageId = 0x7f; } mContext->mSymbols = createSymbolTableFromIncludePaths(); if (!mContext->mSymbols) { return 1; } TableMergerOptions tableMergerOptions; tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay; mTableMerger = util::make_unique(mContext, &mFinalTable, tableMergerOptions); if (mContext->verbose()) { mContext->getDiagnostics()->note( DiagMessage() << "linking package '" << mContext->mCompilationPackage << "' " << "with package ID " << std::hex << (int) mContext->mPackageId); } for (const std::string& input : inputFiles) { if (!processFile(input, false)) { mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input"); return 1; } } for (const std::string& input : mOptions.overlayFiles) { if (!processFile(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; } } { IdAssigner idAssigner; if (!idAssigner.consume(mContext, &mFinalTable)) { mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs"); return 1; } } mContext->mNameMangler = util::make_unique(NameManglerPolicy{ mContext->mCompilationPackage, mTableMerger->getMergedPackages() }); mContext->mSymbols = JoinedSymbolTableBuilder() .addSymbolTable(util::make_unique(&mFinalTable)) .addSymbolTable(std::move(mContext->mSymbols)) .build(); { ReferenceLinker linker; if (!linker.consume(mContext, &mFinalTable)) { mContext->getDiagnostics()->error(DiagMessage() << "failed linking references"); return 1; } 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 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().toString(); 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; 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 (!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; 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; ResourceTable mLocalFileTable; std::unique_ptr mTableMerger; // A pointer to the FileCollection representing the filesystem (not archives). io::FileCollection* mFileCollection; // A vector of IFileCollections. This is mainly here to keep ownership of the collections. std::vector> mCollections; }; int link(const std::vector& args) { LinkContext context; LinkOptions options; Maybe privateSymbolsPackage; Maybe minSdkVersion, targetSdkVersion; Maybe renameManifestPackage, renameInstrumentationTargetPackage; Maybe versionCode, versionName; Maybe customJavaPackage; std::vector extraJavaPackages; Maybe configs; Maybe preferredDensity; Maybe productList; bool legacyXFlag = false; bool requireLocalization = 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("-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("--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) .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in " "overlays without 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", &context.mVerbose); if (!flags.parse("aapt2 link", args, &std::cerr)) { return 1; } 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(productList.value(), ',')) { if (product != "" && product != "default") { options.products.insert(product.toString()); } } } AxisConfigFilter filter; if (configs) { for (const StringPiece& configStr : util::tokenize(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; } LinkCommand cmd(&context, options); return cmd.run(flags.getArgs()); } } // namespace aapt