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