1624 lines
65 KiB
C++
1624 lines
65 KiB
C++
/*
|
|
* 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;
|
|
Maybe<std::string> generateMainDexProguardRulesPath;
|
|
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;
|
|
bool updateProguardSpec = 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 (mOptions.updateProguardSpec && !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 Maybe<std::string>& out, const proguard::KeepSet& keepSet) {
|
|
if (!out) {
|
|
return true;
|
|
}
|
|
|
|
const std::string& outPath = out.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;
|
|
proguard::KeepSet proguardMainDexKeepSet;
|
|
|
|
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 (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;
|
|
}
|
|
|
|
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;
|
|
fileFlattenerOptions.updateProguardSpec =
|
|
static_cast<bool>(mOptions.generateProguardRulesPath);
|
|
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 (!writeProguardFile(mOptions.generateProguardRulesPath, proguardKeepSet)) {
|
|
return 1;
|
|
}
|
|
|
|
if (!writeProguardFile(mOptions.generateMainDexProguardRulesPath, proguardMainDexKeepSet)) {
|
|
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;
|
|
std::vector<std::string> overlayArgList;
|
|
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.",
|
|
&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)
|
|
.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);
|
|
}
|
|
}
|
|
|
|
// Expand all argument-files passed to -R.
|
|
for (const std::string& arg : overlayArgList) {
|
|
if (util::stringStartsWith<char>(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 (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
|