blob: c2363943b5358cf0b6992f970c4509859ff2129b [file] [log] [blame]
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "AppInfo.h"
18#include "Debug.h"
19#include "Flags.h"
Adam Lesinski6a008172016-02-02 17:02:58 -080020#include "Locale.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070021#include "NameMangler.h"
Adam Lesinski59e04c62016-02-04 15:59:23 -080022#include "ResourceUtils.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070023#include "compile/IdAssigner.h"
Adam Lesinski6a008172016-02-02 17:02:58 -080024#include "filter/ConfigFilter.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070025#include "flatten/Archive.h"
26#include "flatten/TableFlattener.h"
27#include "flatten/XmlFlattener.h"
Adam Lesinskia40e9722015-11-24 19:11:46 -080028#include "io/FileSystem.h"
29#include "io/ZipArchive.h"
Adam Lesinskica5638f2015-10-21 14:42:43 -070030#include "java/JavaClassGenerator.h"
31#include "java/ManifestClassGenerator.h"
32#include "java/ProguardRules.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070033#include "link/Linkers.h"
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -080034#include "link/ProductFilter.h"
Adam Lesinski467f1712015-11-16 17:35:44 -080035#include "link/ReferenceLinker.h"
Adam Lesinski2ae4a872015-11-02 16:10:55 -080036#include "link/ManifestFixer.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070037#include "link/TableMerger.h"
38#include "process/IResourceTableConsumer.h"
39#include "process/SymbolTable.h"
Adam Lesinski59e04c62016-02-04 15:59:23 -080040#include "proto/ProtoSerialize.h"
Adam Lesinski355f2852016-02-13 20:26:45 -080041#include "split/TableSplitter.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070042#include "unflatten/BinaryResourceParser.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070043#include "util/Files.h"
44#include "util/StringPiece.h"
Adam Lesinski467f1712015-11-16 17:35:44 -080045#include "xml/XmlDom.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070046
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -070047#include <android-base/file.h>
Adam Lesinski59e04c62016-02-04 15:59:23 -080048#include <google/protobuf/io/coded_stream.h>
49
Adam Lesinski1ab598f2015-08-14 14:26:04 -070050#include <fstream>
Adam Lesinski5eeaadd2016-08-25 12:26:56 -070051#include <queue>
Adam Lesinski1ab598f2015-08-14 14:26:04 -070052#include <sys/stat.h>
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -070053#include <unordered_map>
Adam Lesinski1ab598f2015-08-14 14:26:04 -070054#include <vector>
55
Adam Lesinski5eeaadd2016-08-25 12:26:56 -070056using google::protobuf::io::CopyingOutputStreamAdaptor;
57
Adam Lesinski1ab598f2015-08-14 14:26:04 -070058namespace aapt {
59
60struct LinkOptions {
61 std::string outputPath;
62 std::string manifestPath;
63 std::vector<std::string> includePaths;
Adam Lesinskifb48d292015-11-07 15:52:13 -080064 std::vector<std::string> overlayFiles;
Adam Lesinski36c73a52016-08-11 13:39:24 -070065
66 // Java/Proguard options.
Adam Lesinski1ab598f2015-08-14 14:26:04 -070067 Maybe<std::string> generateJavaClassPath;
Adam Lesinskid0f116b2016-07-08 15:00:32 -070068 Maybe<std::string> customJavaPackage;
69 std::set<std::string> extraJavaPackages;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070070 Maybe<std::string> generateProguardRulesPath;
Rohit Agrawale49bb302016-04-22 12:27:55 -070071 Maybe<std::string> generateMainDexProguardRulesPath;
Adam Lesinski36c73a52016-08-11 13:39:24 -070072
Adam Lesinski1ab598f2015-08-14 14:26:04 -070073 bool noAutoVersion = false;
Adam Lesinski64587af2016-02-18 18:33:06 -080074 bool noVersionVectors = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070075 bool staticLib = false;
Adam Lesinski64587af2016-02-18 18:33:06 -080076 bool noStaticLibPackages = false;
Adam Lesinskief9c5012016-01-22 14:09:53 -080077 bool generateNonFinalIds = false;
Adam Lesinski3524a232016-04-01 19:19:24 -070078 std::vector<std::string> javadocAnnotations;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070079 bool outputToDirectory = false;
Alexandria Cornwalla7cc3f12016-08-16 13:33:32 -070080 bool noXmlNamespaces = false;
Adam Lesinskia6fe3452015-12-09 15:20:52 -080081 bool autoAddOverlay = false;
Adam Lesinski52364f72016-01-11 13:10:24 -080082 bool doNotCompressAnything = false;
Adam Lesinski9756dec2016-08-08 12:35:04 -070083 std::unordered_set<std::string> extensionsToNotCompress;
Adam Lesinskid0f116b2016-07-08 15:00:32 -070084 Maybe<std::string> privateSymbols;
Adam Lesinski52364f72016-01-11 13:10:24 -080085 ManifestFixerOptions manifestFixerOptions;
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -080086 std::unordered_set<std::string> products;
Adam Lesinski36c73a52016-08-11 13:39:24 -070087
88 // Split APK options.
Adam Lesinski355f2852016-02-13 20:26:45 -080089 TableSplitterOptions tableSplitterOptions;
Adam Lesinski36c73a52016-08-11 13:39:24 -070090 std::vector<SplitConstraints> splitConstraints;
91 std::vector<std::string> splitPaths;
92
93 // Stable ID options.
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -070094 std::unordered_map<ResourceName, ResourceId> stableIdMap;
95 Maybe<std::string> resourceIdMapPath;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070096};
97
Adam Lesinski64587af2016-02-18 18:33:06 -080098class LinkContext : public IAaptContext {
99public:
100 LinkContext() : mNameMangler({}) {
101 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700102
103 IDiagnostics* getDiagnostics() override {
104 return &mDiagnostics;
105 }
106
107 NameMangler* getNameMangler() override {
Adam Lesinski64587af2016-02-18 18:33:06 -0800108 return &mNameMangler;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700109 }
110
Adam Lesinski64587af2016-02-18 18:33:06 -0800111 void setNameManglerPolicy(const NameManglerPolicy& policy) {
112 mNameMangler = NameMangler(policy);
113 }
114
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700115 const std::string& getCompilationPackage() override {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700116 return mCompilationPackage;
117 }
118
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700119 void setCompilationPackage(const StringPiece& packageName) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800120 mCompilationPackage = packageName.toString();
121 }
122
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700123 uint8_t getPackageId() override {
124 return mPackageId;
125 }
126
Adam Lesinski64587af2016-02-18 18:33:06 -0800127 void setPackageId(uint8_t id) {
128 mPackageId = id;
129 }
130
131 SymbolTable* getExternalSymbols() override {
132 return &mSymbols;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700133 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800134
135 bool verbose() override {
136 return mVerbose;
137 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800138
139 void setVerbose(bool val) {
140 mVerbose = val;
141 }
142
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700143 int getMinSdkVersion() override {
144 return mMinSdkVersion;
145 }
146
147 void setMinSdkVersion(int minSdk) {
148 mMinSdkVersion = minSdk;
149 }
150
Adam Lesinski64587af2016-02-18 18:33:06 -0800151private:
152 StdErrDiagnostics mDiagnostics;
153 NameMangler mNameMangler;
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700154 std::string mCompilationPackage;
Adam Lesinski64587af2016-02-18 18:33:06 -0800155 uint8_t mPackageId = 0x0;
156 SymbolTable mSymbols;
157 bool mVerbose = false;
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700158 int mMinSdkVersion = 0;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700159};
160
Adam Lesinski355f2852016-02-13 20:26:45 -0800161static bool copyFileToArchive(io::IFile* file, const std::string& outPath,
162 uint32_t compressionFlags,
163 IArchiveWriter* writer, IAaptContext* context) {
164 std::unique_ptr<io::IData> data = file->openAsData();
165 if (!data) {
166 context->getDiagnostics()->error(DiagMessage(file->getSource())
167 << "failed to open file");
168 return false;
169 }
170
Adam Lesinski64587af2016-02-18 18:33:06 -0800171 const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data());
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700172 const size_t bufferSize = data->size();
Adam Lesinski355f2852016-02-13 20:26:45 -0800173
174 if (context->verbose()) {
175 context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive");
176 }
177
178 if (writer->startEntry(outPath, compressionFlags)) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800179 if (writer->writeEntry(buffer, bufferSize)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800180 if (writer->finishEntry()) {
181 return true;
182 }
183 }
184 }
185
186 context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath);
187 return false;
188}
189
190static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
191 bool keepRawValues, IArchiveWriter* writer, IAaptContext* context) {
192 BigBuffer buffer(1024);
193 XmlFlattenerOptions options = {};
194 options.keepRawValues = keepRawValues;
195 options.maxSdkLevel = maxSdkLevel;
196 XmlFlattener flattener(&buffer, options);
197 if (!flattener.consume(context, xmlRes)) {
198 return false;
199 }
200
201 if (context->verbose()) {
202 DiagMessage msg;
203 msg << "writing " << path << " to archive";
204 if (maxSdkLevel) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800205 msg << " maxSdkLevel=" << maxSdkLevel.value() << " keepRawValues=" << keepRawValues;
Adam Lesinski355f2852016-02-13 20:26:45 -0800206 }
207 context->getDiagnostics()->note(msg);
208 }
209
210 if (writer->startEntry(path, ArchiveEntry::kCompress)) {
211 if (writer->writeEntry(buffer)) {
212 if (writer->finishEntry()) {
213 return true;
214 }
215 }
216 }
217 context->getDiagnostics()->error(DiagMessage() << "failed to write " << path << " to archive");
218 return false;
219}
220
Adam Lesinski355f2852016-02-13 20:26:45 -0800221static std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
222 const void* data, size_t len,
223 IDiagnostics* diag) {
224 pb::ResourceTable pbTable;
225 if (!pbTable.ParseFromArray(data, len)) {
226 diag->error(DiagMessage(source) << "invalid compiled table");
227 return {};
228 }
229
230 std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source, diag);
231 if (!table) {
232 return {};
233 }
234 return table;
235}
236
237/**
238 * Inflates an XML file from the source path.
239 */
240static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
241 std::ifstream fin(path, std::ifstream::binary);
242 if (!fin) {
243 diag->error(DiagMessage(path) << strerror(errno));
244 return {};
245 }
246 return xml::inflate(&fin, diag, Source(path));
247}
248
Adam Lesinski355f2852016-02-13 20:26:45 -0800249struct ResourceFileFlattenerOptions {
250 bool noAutoVersion = false;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800251 bool noVersionVectors = false;
Alexandria Cornwalla7cc3f12016-08-16 13:33:32 -0700252 bool noXmlNamespaces = false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800253 bool keepRawValues = false;
254 bool doNotCompressAnything = false;
Rohit Agrawale49bb302016-04-22 12:27:55 -0700255 bool updateProguardSpec = false;
Adam Lesinski9756dec2016-08-08 12:35:04 -0700256 std::unordered_set<std::string> extensionsToNotCompress;
Adam Lesinski355f2852016-02-13 20:26:45 -0800257};
258
259class ResourceFileFlattener {
260public:
261 ResourceFileFlattener(const ResourceFileFlattenerOptions& options,
262 IAaptContext* context, proguard::KeepSet* keepSet) :
263 mOptions(options), mContext(context), mKeepSet(keepSet) {
264 }
265
266 bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter);
267
268private:
269 struct FileOperation {
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700270 ConfigDescription config;
271
272 // The entry this file came from.
273 const ResourceEntry* entry;
274
275 // The file to copy as-is.
Adam Lesinski355f2852016-02-13 20:26:45 -0800276 io::IFile* fileToCopy;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700277
278 // The XML to process and flatten.
Adam Lesinski355f2852016-02-13 20:26:45 -0800279 std::unique_ptr<xml::XmlResource> xmlToFlatten;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700280
281 // The destination to write this file to.
Adam Lesinski355f2852016-02-13 20:26:45 -0800282 std::string dstPath;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800283 bool skipVersion = false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800284 };
285
286 uint32_t getCompressionFlags(const StringPiece& str);
287
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700288 bool linkAndVersionXmlFile(ResourceTable* table, FileOperation* fileOp,
289 std::queue<FileOperation>* outFileOpQueue);
Adam Lesinski355f2852016-02-13 20:26:45 -0800290
291 ResourceFileFlattenerOptions mOptions;
292 IAaptContext* mContext;
293 proguard::KeepSet* mKeepSet;
294};
295
296uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) {
297 if (mOptions.doNotCompressAnything) {
298 return 0;
299 }
300
301 for (const std::string& extension : mOptions.extensionsToNotCompress) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700302 if (util::stringEndsWith(str, extension)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800303 return 0;
304 }
305 }
306 return ArchiveEntry::kCompress;
307}
308
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700309bool ResourceFileFlattener::linkAndVersionXmlFile(ResourceTable* table,
310 FileOperation* fileOp,
311 std::queue<FileOperation>* outFileOpQueue) {
312 xml::XmlResource* doc = fileOp->xmlToFlatten.get();
313 const Source& src = doc->file.source;
314
Adam Lesinski355f2852016-02-13 20:26:45 -0800315 if (mContext->verbose()) {
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700316 mContext->getDiagnostics()->note(DiagMessage() << "linking " << src.path);
Adam Lesinski355f2852016-02-13 20:26:45 -0800317 }
318
Adam Lesinski355f2852016-02-13 20:26:45 -0800319 XmlReferenceLinker xmlLinker;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700320 if (!xmlLinker.consume(mContext, doc)) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800321 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800322 }
323
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700324 if (mOptions.updateProguardSpec && !proguard::collectProguardRules(src, doc, mKeepSet)) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800325 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800326 }
327
Alexandria Cornwalla7cc3f12016-08-16 13:33:32 -0700328 if (mOptions.noXmlNamespaces) {
329 XmlNamespaceRemover namespaceRemover;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700330 if (!namespaceRemover.consume(mContext, doc)) {
Alexandria Cornwalla7cc3f12016-08-16 13:33:32 -0700331 return false;
332 }
333 }
334
Adam Lesinski355f2852016-02-13 20:26:45 -0800335 if (!mOptions.noAutoVersion) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800336 if (mOptions.noVersionVectors) {
337 // Skip this if it is a vector or animated-vector.
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700338 xml::Element* el = xml::findRootElement(doc);
Adam Lesinski626a69f2016-03-03 10:09:26 -0800339 if (el && el->namespaceUri.empty()) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700340 if (el->name == "vector" || el->name == "animated-vector") {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800341 // We are NOT going to version this file.
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700342 fileOp->skipVersion = true;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800343 return true;
344 }
345 }
346 }
347
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700348 const ConfigDescription& config = fileOp->config;
349
Adam Lesinski355f2852016-02-13 20:26:45 -0800350 // Find the first SDK level used that is higher than this defined config and
351 // not superseded by a lower or equal SDK level resource.
Alexandria Cornwallf6762fc2016-08-09 12:36:46 -0700352 const int minSdkVersion = mContext->getMinSdkVersion();
Adam Lesinski355f2852016-02-13 20:26:45 -0800353 for (int sdkLevel : xmlLinker.getSdkLevels()) {
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700354 if (sdkLevel > minSdkVersion && sdkLevel > config.sdkVersion) {
355 if (!shouldGenerateVersionedResource(fileOp->entry, config, sdkLevel)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800356 // If we shouldn't generate a versioned resource, stop checking.
357 break;
358 }
359
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700360 ResourceFile versionedFileDesc = doc->file;
Adam Lesinski64587af2016-02-18 18:33:06 -0800361 versionedFileDesc.config.sdkVersion = (uint16_t) sdkLevel;
Adam Lesinski355f2852016-02-13 20:26:45 -0800362
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700363 FileOperation newFileOp;
364 newFileOp.xmlToFlatten = util::make_unique<xml::XmlResource>(
365 versionedFileDesc, doc->root->clone());
366 newFileOp.config = versionedFileDesc.config;
367 newFileOp.entry = fileOp->entry;
368 newFileOp.dstPath = ResourceUtils::buildResourceFileName(
369 versionedFileDesc, mContext->getNameMangler());
370
Adam Lesinski355f2852016-02-13 20:26:45 -0800371 if (mContext->verbose()) {
372 mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source)
373 << "auto-versioning resource from config '"
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700374 << config
Adam Lesinski626a69f2016-03-03 10:09:26 -0800375 << "' -> '"
Adam Lesinski355f2852016-02-13 20:26:45 -0800376 << versionedFileDesc.config << "'");
377 }
378
Adam Lesinski355f2852016-02-13 20:26:45 -0800379 bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name,
380 versionedFileDesc.config,
381 versionedFileDesc.source,
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700382 newFileOp.dstPath,
383 nullptr,
Adam Lesinski355f2852016-02-13 20:26:45 -0800384 mContext->getDiagnostics());
385 if (!added) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800386 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800387 }
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700388
389 outFileOpQueue->push(std::move(newFileOp));
Adam Lesinski355f2852016-02-13 20:26:45 -0800390 break;
391 }
392 }
393 }
Adam Lesinski626a69f2016-03-03 10:09:26 -0800394 return true;
Adam Lesinski355f2852016-02-13 20:26:45 -0800395}
396
397/**
398 * Do not insert or remove any resources while executing in this function. It will
399 * corrupt the iteration order.
400 */
401bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiveWriter) {
402 bool error = false;
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700403 std::map<std::pair<ConfigDescription, StringPiece>, FileOperation> configSortedFiles;
Adam Lesinski355f2852016-02-13 20:26:45 -0800404
405 for (auto& pkg : table->packages) {
406 for (auto& type : pkg->types) {
407 // Sort by config and name, so that we get better locality in the zip file.
408 configSortedFiles.clear();
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700409 std::queue<FileOperation> fileOperations;
Adam Lesinski355f2852016-02-13 20:26:45 -0800410
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700411 // Populate the queue with all files in the ResourceTable.
412 for (auto& entry : type->entries) {
413 for (auto& configValue : entry->values) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800414 FileReference* fileRef = valueCast<FileReference>(configValue->value.get());
415 if (!fileRef) {
416 continue;
417 }
418
419 io::IFile* file = fileRef->file;
420 if (!file) {
421 mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource())
422 << "file not found");
423 return false;
424 }
425
426 FileOperation fileOp;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700427 fileOp.entry = entry.get();
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700428 fileOp.dstPath = *fileRef->path;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700429 fileOp.config = configValue->config;
Adam Lesinski355f2852016-02-13 20:26:45 -0800430
431 const StringPiece srcPath = file->getSource().path;
432 if (type->type != ResourceType::kRaw &&
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700433 (util::stringEndsWith(srcPath, ".xml.flat") ||
434 util::stringEndsWith(srcPath, ".xml"))) {
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700435 std::unique_ptr<io::IData> data = file->openAsData();
436 if (!data) {
437 mContext->getDiagnostics()->error(DiagMessage(file->getSource())
438 << "failed to open file");
439 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800440 }
441
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700442 fileOp.xmlToFlatten = xml::inflate(data->data(), data->size(),
443 mContext->getDiagnostics(),
444 file->getSource());
445
446 if (!fileOp.xmlToFlatten) {
447 return false;
448 }
449
450 fileOp.xmlToFlatten->file.config = configValue->config;
451 fileOp.xmlToFlatten->file.source = fileRef->getSource();
452 fileOp.xmlToFlatten->file.name =
453 ResourceName(pkg->name, type->type, entry->name);
454
455 // Enqueue the XML files to be processed.
456 fileOperations.push(std::move(fileOp));
Adam Lesinski355f2852016-02-13 20:26:45 -0800457 } else {
458 fileOp.fileToCopy = file;
Adam Lesinski355f2852016-02-13 20:26:45 -0800459
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700460 // NOTE(adamlesinski): Explicitly construct a StringPiece here, or else
461 // we end up copying the string in the std::make_pair() method, then
462 // creating a StringPiece from the copy, which would cause us to end up
463 // referencing garbage in the map.
464 const StringPiece entryName(entry->name);
465 configSortedFiles[std::make_pair(configValue->config, entryName)] =
466 std::move(fileOp);
467 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800468 }
469 }
470
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700471 // Now process the XML queue
472 for (; !fileOperations.empty(); fileOperations.pop()) {
473 FileOperation& fileOp = fileOperations.front();
474
475 if (!linkAndVersionXmlFile(table, &fileOp, &fileOperations)) {
476 error = true;
477 continue;
478 }
479
480 // NOTE(adamlesinski): Explicitly construct a StringPiece here, or else
481 // we end up copying the string in the std::make_pair() method, then creating
482 // a StringPiece from the copy, which would cause us to end up referencing
483 // garbage in the map.
484 const StringPiece entryName(fileOp.entry->name);
485 configSortedFiles[std::make_pair(fileOp.config, entryName)] = std::move(fileOp);
486 }
487
Adam Lesinski355f2852016-02-13 20:26:45 -0800488 if (error) {
489 return false;
490 }
491
492 // Now flatten the sorted values.
493 for (auto& mapEntry : configSortedFiles) {
494 const ConfigDescription& config = mapEntry.first.first;
495 const FileOperation& fileOp = mapEntry.second;
496
497 if (fileOp.xmlToFlatten) {
498 Maybe<size_t> maxSdkLevel;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800499 if (!mOptions.noAutoVersion && !fileOp.skipVersion) {
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700500 maxSdkLevel = std::max<size_t>(std::max<size_t>(config.sdkVersion, 1u),
501 mContext->getMinSdkVersion());
Adam Lesinski355f2852016-02-13 20:26:45 -0800502 }
503
504 bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel,
505 mOptions.keepRawValues,
506 archiveWriter, mContext);
507 if (!result) {
508 error = true;
509 }
510 } else {
511 bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath,
512 getCompressionFlags(fileOp.dstPath),
513 archiveWriter, mContext);
514 if (!result) {
515 error = true;
516 }
517 }
518 }
519 }
520 }
521 return !error;
522}
523
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700524static bool writeStableIdMapToPath(IDiagnostics* diag,
525 const std::unordered_map<ResourceName, ResourceId>& idMap,
Chih-Hung Hsieh9b8528f2016-08-10 14:15:30 -0700526 const std::string& idMapPath) {
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700527 std::ofstream fout(idMapPath, std::ofstream::binary);
528 if (!fout) {
529 diag->error(DiagMessage(idMapPath) << strerror(errno));
530 return false;
531 }
532
533 for (const auto& entry : idMap) {
534 const ResourceName& name = entry.first;
535 const ResourceId& id = entry.second;
536 fout << name << " = " << id << "\n";
537 }
538
539 if (!fout) {
540 diag->error(DiagMessage(idMapPath) << "failed writing to file: " << strerror(errno));
541 return false;
542 }
543
544 return true;
545}
546
547static bool loadStableIdMap(IDiagnostics* diag, const std::string& path,
548 std::unordered_map<ResourceName, ResourceId>* outIdMap) {
549 std::string content;
550 if (!android::base::ReadFileToString(path, &content)) {
551 diag->error(DiagMessage(path) << "failed reading stable ID file");
552 return false;
553 }
554
555 outIdMap->clear();
556 size_t lineNo = 0;
557 for (StringPiece line : util::tokenize(content, '\n')) {
558 lineNo++;
559 line = util::trimWhitespace(line);
560 if (line.empty()) {
561 continue;
562 }
563
564 auto iter = std::find(line.begin(), line.end(), '=');
565 if (iter == line.end()) {
566 diag->error(DiagMessage(Source(path, lineNo)) << "missing '='");
567 return false;
568 }
569
570 ResourceNameRef name;
571 StringPiece resNameStr = util::trimWhitespace(
572 line.substr(0, std::distance(line.begin(), iter)));
573 if (!ResourceUtils::parseResourceName(resNameStr, &name)) {
574 diag->error(DiagMessage(Source(path, lineNo))
575 << "invalid resource name '" << resNameStr << "'");
576 return false;
577 }
578
579 const size_t resIdStartIdx = std::distance(line.begin(), iter) + 1;
580 const size_t resIdStrLen = line.size() - resIdStartIdx;
581 StringPiece resIdStr = util::trimWhitespace(line.substr(resIdStartIdx, resIdStrLen));
582
Adam Lesinski36c73a52016-08-11 13:39:24 -0700583 Maybe<ResourceId> maybeId = ResourceUtils::parseResourceId(resIdStr);
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700584 if (!maybeId) {
585 diag->error(DiagMessage(Source(path, lineNo)) << "invalid resource ID '"
586 << resIdStr << "'");
587 return false;
588 }
589
590 (*outIdMap)[name.toResourceName()] = maybeId.value();
591 }
592 return true;
593}
594
Adam Lesinski36c73a52016-08-11 13:39:24 -0700595static bool parseSplitParameter(const StringPiece& arg, IDiagnostics* diag,
596 std::string* outPath, SplitConstraints* outSplit) {
597 std::vector<std::string> parts = util::split(arg, ':');
598 if (parts.size() != 2) {
599 diag->error(DiagMessage() << "invalid split parameter '" << arg << "'");
600 diag->note(DiagMessage() << "should be --split path/to/output.apk:<config>[,<config>...]");
601 return false;
602 }
603 *outPath = parts[0];
604 std::vector<ConfigDescription> configs;
605 for (const StringPiece& configStr : util::tokenize(parts[1], ',')) {
606 configs.push_back({});
607 if (!ConfigDescription::parse(configStr, &configs.back())) {
608 diag->error(DiagMessage() << "invalid config '" << configStr
609 << "' in split parameter '" << arg << "'");
610 return false;
611 }
612 }
613 outSplit->configs.insert(configs.begin(), configs.end());
614 return true;
615}
616
Adam Lesinskifb48d292015-11-07 15:52:13 -0800617class LinkCommand {
618public:
Adam Lesinski6a008172016-02-02 17:02:58 -0800619 LinkCommand(LinkContext* context, const LinkOptions& options) :
Adam Lesinski64587af2016-02-18 18:33:06 -0800620 mOptions(options), mContext(context), mFinalTable(),
621 mFileCollection(util::make_unique<io::FileCollection>()) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800622 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700623
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700624 /**
625 * Creates a SymbolTable that loads symbols from the various APKs and caches the
626 * results for faster lookup.
627 */
Adam Lesinski64587af2016-02-18 18:33:06 -0800628 bool loadSymbolsFromIncludePaths() {
629 std::unique_ptr<AssetManagerSymbolSource> assetSource =
630 util::make_unique<AssetManagerSymbolSource>();
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700631 for (const std::string& path : mOptions.includePaths) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800632 if (mContext->verbose()) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800633 mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700634 }
635
Adam Lesinski64587af2016-02-18 18:33:06 -0800636 // First try to load the file as a static lib.
637 std::string errorStr;
638 std::unique_ptr<ResourceTable> staticInclude = loadStaticLibrary(path, &errorStr);
639 if (staticInclude) {
640 if (!mOptions.staticLib) {
641 // Can't include static libraries when not building a static library.
642 mContext->getDiagnostics()->error(
643 DiagMessage(path) << "can't include static library when building app");
644 return false;
645 }
646
647 // If we are using --no-static-lib-packages, we need to rename the package of this
648 // table to our compilation package.
649 if (mOptions.noStaticLibPackages) {
650 if (ResourceTablePackage* pkg = staticInclude->findPackageById(0x7f)) {
651 pkg->name = mContext->getCompilationPackage();
652 }
653 }
654
655 mContext->getExternalSymbols()->appendSource(
656 util::make_unique<ResourceTableSymbolSource>(staticInclude.get()));
657
658 mStaticTableIncludes.push_back(std::move(staticInclude));
659
660 } else if (!errorStr.empty()) {
661 // We had an error with reading, so fail.
662 mContext->getDiagnostics()->error(DiagMessage(path) << errorStr);
663 return false;
664 }
665
666 if (!assetSource->addAssetPath(path)) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800667 mContext->getDiagnostics()->error(
Adam Lesinskifb48d292015-11-07 15:52:13 -0800668 DiagMessage(path) << "failed to load include path");
Adam Lesinski64587af2016-02-18 18:33:06 -0800669 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700670 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700671 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800672
673 mContext->getExternalSymbols()->appendSource(std::move(assetSource));
674 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700675 }
676
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700677 Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes, IDiagnostics* diag) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700678 // Make sure the first element is <manifest> with package attribute.
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800679 if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700680 AppInfo appInfo;
681
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700682 if (!manifestEl->namespaceUri.empty() || manifestEl->name != "manifest") {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700683 diag->error(DiagMessage(xmlRes->file.source) << "root tag must be <manifest>");
684 return {};
685 }
686
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700687 xml::Attribute* packageAttr = manifestEl->findAttribute({}, "package");
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700688 if (!packageAttr) {
689 diag->error(DiagMessage(xmlRes->file.source)
690 << "<manifest> must have a 'package' attribute");
691 return {};
692 }
693
694 appInfo.package = packageAttr->value;
695
Adam Lesinski36c73a52016-08-11 13:39:24 -0700696 if (xml::Attribute* versionCodeAttr =
697 manifestEl->findAttribute(xml::kSchemaAndroid, "versionCode")) {
698 Maybe<uint32_t> maybeCode = ResourceUtils::parseInt(versionCodeAttr->value);
699 if (!maybeCode) {
700 diag->error(DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber))
701 << "invalid android:versionCode '"
702 << versionCodeAttr->value << "'");
703 return {};
704 }
705 appInfo.versionCode = maybeCode.value();
706 }
707
708 if (xml::Attribute* revisionCodeAttr =
709 manifestEl->findAttribute(xml::kSchemaAndroid, "revisionCode")) {
710 Maybe<uint32_t> maybeCode = ResourceUtils::parseInt(revisionCodeAttr->value);
711 if (!maybeCode) {
712 diag->error(DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber))
713 << "invalid android:revisionCode '"
714 << revisionCodeAttr->value << "'");
715 return {};
716 }
717 appInfo.revisionCode = maybeCode.value();
718 }
719
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700720 if (xml::Element* usesSdkEl = manifestEl->findChild({}, "uses-sdk")) {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700721 if (xml::Attribute* minSdk =
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700722 usesSdkEl->findAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700723 appInfo.minSdkVersion = minSdk->value;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700724 }
725 }
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700726
727 return appInfo;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700728 }
729 return {};
730 }
731
Adam Lesinski979ccb22016-01-11 10:42:19 -0800732 /**
733 * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked.
734 * Postcondition: ResourceTable has only one package left. All others are stripped, or there
735 * is an error and false is returned.
736 */
Adam Lesinskifb48d292015-11-07 15:52:13 -0800737 bool verifyNoExternalPackages() {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800738 auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool {
Adam Lesinski6a008172016-02-02 17:02:58 -0800739 return mContext->getCompilationPackage() != pkg->name ||
Adam Lesinski979ccb22016-01-11 10:42:19 -0800740 !pkg->id ||
Adam Lesinski6a008172016-02-02 17:02:58 -0800741 pkg->id.value() != mContext->getPackageId();
Adam Lesinski979ccb22016-01-11 10:42:19 -0800742 };
743
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700744 bool error = false;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800745 for (const auto& package : mFinalTable.packages) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800746 if (isExtPackageFunc(package)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700747 // We have a package that is not related to the one we're building!
748 for (const auto& type : package->types) {
749 for (const auto& entry : type->entries) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800750 ResourceNameRef resName(package->name, type->type, entry->name);
751
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700752 for (const auto& configValue : entry->values) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800753 // Special case the occurrence of an ID that is being generated for the
754 // 'android' package. This is due to legacy reasons.
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800755 if (valueCast<Id>(configValue->value.get()) &&
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700756 package->name == "android") {
Adam Lesinski6a008172016-02-02 17:02:58 -0800757 mContext->getDiagnostics()->warn(
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800758 DiagMessage(configValue->value->getSource())
Adam Lesinski979ccb22016-01-11 10:42:19 -0800759 << "generated id '" << resName
760 << "' for external package '" << package->name
761 << "'");
762 } else {
Adam Lesinski6a008172016-02-02 17:02:58 -0800763 mContext->getDiagnostics()->error(
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800764 DiagMessage(configValue->value->getSource())
Adam Lesinski979ccb22016-01-11 10:42:19 -0800765 << "defined resource '" << resName
766 << "' for external package '" << package->name
767 << "'");
768 error = true;
769 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700770 }
771 }
772 }
773 }
774 }
Adam Lesinski979ccb22016-01-11 10:42:19 -0800775
776 auto newEndIter = std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(),
777 isExtPackageFunc);
778 mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700779 return !error;
780 }
781
Adam Lesinski64587af2016-02-18 18:33:06 -0800782 /**
783 * Returns true if no IDs have been set, false otherwise.
784 */
785 bool verifyNoIdsSet() {
786 for (const auto& package : mFinalTable.packages) {
787 for (const auto& type : package->types) {
788 if (type->id) {
789 mContext->getDiagnostics()->error(DiagMessage() << "type " << type->type
790 << " has ID " << std::hex
791 << (int) type->id.value()
792 << std::dec << " assigned");
793 return false;
794 }
795
796 for (const auto& entry : type->entries) {
797 if (entry->id) {
798 ResourceNameRef resName(package->name, type->type, entry->name);
799 mContext->getDiagnostics()->error(DiagMessage() << "entry " << resName
800 << " has ID " << std::hex
801 << (int) entry->id.value()
802 << std::dec << " assigned");
803 return false;
804 }
805 }
806 }
807 }
808 return true;
809 }
810
Adam Lesinski36c73a52016-08-11 13:39:24 -0700811 std::unique_ptr<IArchiveWriter> makeArchiveWriter(const StringPiece& out) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700812 if (mOptions.outputToDirectory) {
Adam Lesinski36c73a52016-08-11 13:39:24 -0700813 return createDirectoryArchiveWriter(mContext->getDiagnostics(), out);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700814 } else {
Adam Lesinski36c73a52016-08-11 13:39:24 -0700815 return createZipFileArchiveWriter(mContext->getDiagnostics(), out);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700816 }
817 }
818
819 bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
820 BigBuffer buffer(1024);
Adam Lesinski59e04c62016-02-04 15:59:23 -0800821 TableFlattener flattener(&buffer);
Adam Lesinski6a008172016-02-02 17:02:58 -0800822 if (!flattener.consume(mContext, table)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700823 return false;
824 }
825
Adam Lesinskia40e9722015-11-24 19:11:46 -0800826 if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
827 if (writer->writeEntry(buffer)) {
828 if (writer->finishEntry()) {
829 return true;
830 }
831 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700832 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800833
Adam Lesinski6a008172016-02-02 17:02:58 -0800834 mContext->getDiagnostics()->error(
Adam Lesinskia40e9722015-11-24 19:11:46 -0800835 DiagMessage() << "failed to write resources.arsc to archive");
836 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700837 }
838
Adam Lesinski64587af2016-02-18 18:33:06 -0800839 bool flattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
840 // Create the file/zip entry.
841 if (!writer->startEntry("resources.arsc.flat", 0)) {
842 mContext->getDiagnostics()->error(DiagMessage() << "failed to open");
843 return false;
844 }
845
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700846 // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
Adam Lesinski64587af2016-02-18 18:33:06 -0800847 {
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700848 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
849 // interface.
850 CopyingOutputStreamAdaptor adaptor(writer);
Adam Lesinski64587af2016-02-18 18:33:06 -0800851
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700852 std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table);
Adam Lesinski64587af2016-02-18 18:33:06 -0800853 if (!pbTable->SerializeToZeroCopyStream(&adaptor)) {
854 mContext->getDiagnostics()->error(DiagMessage() << "failed to write");
855 return false;
856 }
857 }
858
859 if (!writer->finishEntry()) {
860 mContext->getDiagnostics()->error(DiagMessage() << "failed to finish entry");
861 return false;
862 }
863 return true;
864 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700865
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700866 bool writeJavaFile(ResourceTable* table, const StringPiece& packageNameToGenerate,
Chih-Hung Hsieh9b8528f2016-08-10 14:15:30 -0700867 const StringPiece& outPackage, const JavaClassGeneratorOptions& javaOptions) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700868 if (!mOptions.generateJavaClassPath) {
869 return true;
870 }
871
872 std::string outPath = mOptions.generateJavaClassPath.value();
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700873 file::appendPath(&outPath, file::packageToPath(outPackage));
Adam Lesinski96917c22016-03-09 13:11:25 -0800874 if (!file::mkdirs(outPath)) {
875 mContext->getDiagnostics()->error(
876 DiagMessage() << "failed to create directory '" << outPath << "'");
877 return false;
878 }
879
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700880 file::appendPath(&outPath, "R.java");
881
882 std::ofstream fout(outPath, std::ofstream::binary);
883 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800884 mContext->getDiagnostics()->error(
885 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700886 return false;
887 }
888
Adam Lesinski76565542016-03-10 21:55:04 -0800889 JavaClassGenerator generator(mContext, table, javaOptions);
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700890 if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800891 mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700892 return false;
893 }
Adam Lesinski96917c22016-03-09 13:11:25 -0800894
895 if (!fout) {
896 mContext->getDiagnostics()->error(
897 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
898 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700899 return true;
900 }
901
Adam Lesinski467f1712015-11-16 17:35:44 -0800902 bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
Adam Lesinskica5638f2015-10-21 14:42:43 -0700903 if (!mOptions.generateJavaClassPath) {
904 return true;
905 }
906
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700907 std::unique_ptr<ClassDefinition> manifestClass = generateManifestClass(
908 mContext->getDiagnostics(), manifestXml);
909
910 if (!manifestClass) {
911 // Something bad happened, but we already logged it, so exit.
912 return false;
913 }
914
915 if (manifestClass->empty()) {
916 // Empty Manifest class, no need to generate it.
917 return true;
918 }
919
Adam Lesinski3524a232016-04-01 19:19:24 -0700920 // Add any JavaDoc annotations to the generated class.
921 for (const std::string& annotation : mOptions.javadocAnnotations) {
922 std::string properAnnotation = "@";
923 properAnnotation += annotation;
924 manifestClass->getCommentBuilder()->appendComment(properAnnotation);
925 }
926
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700927 const std::string& packageUtf8 = mContext->getCompilationPackage();
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700928
Adam Lesinskica5638f2015-10-21 14:42:43 -0700929 std::string outPath = mOptions.generateJavaClassPath.value();
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700930 file::appendPath(&outPath, file::packageToPath(packageUtf8));
931
Adam Lesinski96917c22016-03-09 13:11:25 -0800932 if (!file::mkdirs(outPath)) {
933 mContext->getDiagnostics()->error(
934 DiagMessage() << "failed to create directory '" << outPath << "'");
935 return false;
936 }
937
Adam Lesinskica5638f2015-10-21 14:42:43 -0700938 file::appendPath(&outPath, "Manifest.java");
939
940 std::ofstream fout(outPath, std::ofstream::binary);
941 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800942 mContext->getDiagnostics()->error(
943 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinskica5638f2015-10-21 14:42:43 -0700944 return false;
945 }
946
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700947 if (!ClassDefinition::writeJavaFile(manifestClass.get(), packageUtf8, true, &fout)) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800948 mContext->getDiagnostics()->error(
949 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinskica5638f2015-10-21 14:42:43 -0700950 return false;
951 }
952 return true;
953 }
954
Rohit Agrawale49bb302016-04-22 12:27:55 -0700955 bool writeProguardFile(const Maybe<std::string>& out, const proguard::KeepSet& keepSet) {
956 if (!out) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700957 return true;
958 }
959
Rohit Agrawale49bb302016-04-22 12:27:55 -0700960 const std::string& outPath = out.value();
Adam Lesinski96917c22016-03-09 13:11:25 -0800961 std::ofstream fout(outPath, std::ofstream::binary);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700962 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800963 mContext->getDiagnostics()->error(
964 DiagMessage() << "failed to open '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700965 return false;
966 }
967
968 proguard::writeKeepSet(&fout, keepSet);
969 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800970 mContext->getDiagnostics()->error(
971 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700972 return false;
973 }
974 return true;
975 }
976
Adam Lesinski64587af2016-02-18 18:33:06 -0800977 std::unique_ptr<ResourceTable> loadStaticLibrary(const std::string& input,
978 std::string* outError) {
979 std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
980 input, outError);
981 if (!collection) {
982 return {};
983 }
984 return loadTablePbFromCollection(collection.get());
985 }
986
987 std::unique_ptr<ResourceTable> loadTablePbFromCollection(io::IFileCollection* collection) {
988 io::IFile* file = collection->findFile("resources.arsc.flat");
989 if (!file) {
990 return {};
991 }
992
993 std::unique_ptr<io::IData> data = file->openAsData();
994 return loadTableFromPb(file->getSource(), data->data(), data->size(),
995 mContext->getDiagnostics());
996 }
997
998 bool mergeStaticLibrary(const std::string& input, bool override) {
999 if (mContext->verbose()) {
1000 mContext->getDiagnostics()->note(DiagMessage() << "merging static library " << input);
1001 }
1002
1003 std::string errorStr;
1004 std::unique_ptr<io::ZipFileCollection> collection =
1005 io::ZipFileCollection::create(input, &errorStr);
1006 if (!collection) {
1007 mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
1008 return false;
1009 }
1010
1011 std::unique_ptr<ResourceTable> table = loadTablePbFromCollection(collection.get());
1012 if (!table) {
1013 mContext->getDiagnostics()->error(DiagMessage(input) << "invalid static library");
1014 return false;
1015 }
1016
1017 ResourceTablePackage* pkg = table->findPackageById(0x7f);
1018 if (!pkg) {
1019 mContext->getDiagnostics()->error(DiagMessage(input)
1020 << "static library has no package");
1021 return false;
1022 }
1023
1024 bool result;
1025 if (mOptions.noStaticLibPackages) {
1026 // Merge all resources as if they were in the compilation package. This is the old
1027 // behaviour of aapt.
1028
1029 // Add the package to the set of --extra-packages so we emit an R.java for each
1030 // library package.
1031 if (!pkg->name.empty()) {
1032 mOptions.extraJavaPackages.insert(pkg->name);
1033 }
1034
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001035 pkg->name = "";
Adam Lesinski64587af2016-02-18 18:33:06 -08001036 if (override) {
1037 result = mTableMerger->mergeOverlay(Source(input), table.get(), collection.get());
1038 } else {
1039 result = mTableMerger->merge(Source(input), table.get(), collection.get());
1040 }
1041
1042 } else {
1043 // This is the proper way to merge libraries, where the package name is preserved
1044 // and resource names are mangled.
1045 result = mTableMerger->mergeAndMangle(Source(input), pkg->name, table.get(),
1046 collection.get());
1047 }
1048
1049 if (!result) {
1050 return false;
1051 }
1052
1053 // Make sure to move the collection into the set of IFileCollections.
1054 mCollections.push_back(std::move(collection));
Adam Lesinskifb48d292015-11-07 15:52:13 -08001055 return true;
1056 }
1057
Adam Lesinskia40e9722015-11-24 19:11:46 -08001058 bool mergeResourceTable(io::IFile* file, bool override) {
Adam Lesinski355f2852016-02-13 20:26:45 -08001059 if (mContext->verbose()) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001060 mContext->getDiagnostics()->note(DiagMessage() << "merging resource table "
1061 << file->getSource());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001062 }
1063
Adam Lesinskia40e9722015-11-24 19:11:46 -08001064 std::unique_ptr<io::IData> data = file->openAsData();
1065 if (!data) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001066 mContext->getDiagnostics()->error(DiagMessage(file->getSource())
Adam Lesinskia40e9722015-11-24 19:11:46 -08001067 << "failed to open file");
1068 return false;
1069 }
1070
Adam Lesinski355f2852016-02-13 20:26:45 -08001071 std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(),
1072 data->data(), data->size(),
1073 mContext->getDiagnostics());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001074 if (!table) {
1075 return false;
1076 }
1077
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001078 bool result = false;
1079 if (override) {
1080 result = mTableMerger->mergeOverlay(file->getSource(), table.get());
1081 } else {
1082 result = mTableMerger->merge(file->getSource(), table.get());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001083 }
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001084 return result;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001085 }
1086
Adam Lesinski64587af2016-02-18 18:33:06 -08001087 bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc, bool override) {
Adam Lesinski355f2852016-02-13 20:26:45 -08001088 if (mContext->verbose()) {
Adam Lesinski5eeaadd2016-08-25 12:26:56 -07001089 mContext->getDiagnostics()->note(DiagMessage()
1090 << "merging '" << fileDesc->name
1091 << "' from compiled file "
Adam Lesinski64587af2016-02-18 18:33:06 -08001092 << file->getSource());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001093 }
1094
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001095 bool result = false;
Adam Lesinski64587af2016-02-18 18:33:06 -08001096 if (override) {
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001097 result = mTableMerger->mergeFileOverlay(*fileDesc, file);
Adam Lesinskifb48d292015-11-07 15:52:13 -08001098 } else {
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001099 result = mTableMerger->mergeFile(*fileDesc, file);
Adam Lesinskifb48d292015-11-07 15:52:13 -08001100 }
1101
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001102 if (!result) {
Adam Lesinskifb48d292015-11-07 15:52:13 -08001103 return false;
1104 }
1105
1106 // Add the exports of this file to the table.
Adam Lesinskia40e9722015-11-24 19:11:46 -08001107 for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
Adam Lesinskifb48d292015-11-07 15:52:13 -08001108 if (exportedSymbol.name.package.empty()) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001109 exportedSymbol.name.package = mContext->getCompilationPackage();
Adam Lesinskifb48d292015-11-07 15:52:13 -08001110 }
1111
1112 ResourceNameRef resName = exportedSymbol.name;
1113
Adam Lesinski6a008172016-02-02 17:02:58 -08001114 Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
Adam Lesinskifb48d292015-11-07 15:52:13 -08001115 exportedSymbol.name);
1116 if (mangledName) {
1117 resName = mangledName.value();
1118 }
1119
1120 std::unique_ptr<Id> id = util::make_unique<Id>();
Adam Lesinskia40e9722015-11-24 19:11:46 -08001121 id->setSource(fileDesc->source.withLine(exportedSymbol.line));
Adam Lesinski64587af2016-02-18 18:33:06 -08001122 bool result = mFinalTable.addResourceAllowMangled(
1123 resName, ConfigDescription::defaultConfig(), std::string(), std::move(id),
1124 mContext->getDiagnostics());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001125 if (!result) {
1126 return false;
1127 }
1128 }
Adam Lesinskifb48d292015-11-07 15:52:13 -08001129 return true;
1130 }
1131
Adam Lesinskia40e9722015-11-24 19:11:46 -08001132 /**
Adam Lesinski64587af2016-02-18 18:33:06 -08001133 * Takes a path to load as a ZIP file and merges the files within into the master ResourceTable.
1134 * If override is true, conflicting resources are allowed to override each other, in order of
1135 * last seen.
1136 *
1137 * An io::IFileCollection is created from the ZIP file and added to the set of
1138 * io::IFileCollections that are open.
Adam Lesinskia40e9722015-11-24 19:11:46 -08001139 */
1140 bool mergeArchive(const std::string& input, bool override) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001141 if (mContext->verbose()) {
1142 mContext->getDiagnostics()->note(DiagMessage() << "merging archive " << input);
1143 }
1144
Adam Lesinskia40e9722015-11-24 19:11:46 -08001145 std::string errorStr;
Adam Lesinski64587af2016-02-18 18:33:06 -08001146 std::unique_ptr<io::ZipFileCollection> collection =
1147 io::ZipFileCollection::create(input, &errorStr);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001148 if (!collection) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001149 mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001150 return false;
1151 }
1152
1153 bool error = false;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001154 for (auto iter = collection->iterator(); iter->hasNext(); ) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001155 if (!mergeFile(iter->next(), override)) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001156 error = true;
1157 }
1158 }
1159
1160 // Make sure to move the collection into the set of IFileCollections.
1161 mCollections.push_back(std::move(collection));
1162 return !error;
1163 }
1164
Adam Lesinski64587af2016-02-18 18:33:06 -08001165 /**
1166 * Takes a path to load and merge into the master ResourceTable. If override is true,
1167 * conflicting resources are allowed to override each other, in order of last seen.
1168 *
1169 * If the file path ends with .flata, .jar, .jack, or .zip the file is treated as ZIP archive
1170 * and the files within are merged individually.
1171 *
1172 * Otherwise the files is processed on its own.
1173 */
1174 bool mergePath(const std::string& path, bool override) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001175 if (util::stringEndsWith(path, ".flata") ||
1176 util::stringEndsWith(path, ".jar") ||
1177 util::stringEndsWith(path, ".jack") ||
1178 util::stringEndsWith(path, ".zip")) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001179 return mergeArchive(path, override);
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001180 } else if (util::stringEndsWith(path, ".apk")) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001181 return mergeStaticLibrary(path, override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001182 }
1183
1184 io::IFile* file = mFileCollection->insertFile(path);
Adam Lesinski64587af2016-02-18 18:33:06 -08001185 return mergeFile(file, override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001186 }
1187
Adam Lesinski64587af2016-02-18 18:33:06 -08001188 /**
1189 * Takes a file to load and merge into the master ResourceTable. If override is true,
1190 * conflicting resources are allowed to override each other, in order of last seen.
1191 *
1192 * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and merged into the
1193 * master ResourceTable. If the file ends with .flat, then it is treated like a compiled file
1194 * and the header data is read and merged into the final ResourceTable.
1195 *
1196 * All other file types are ignored. This is because these files could be coming from a zip,
1197 * where we could have other files like classes.dex.
1198 */
1199 bool mergeFile(io::IFile* file, bool override) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001200 const Source& src = file->getSource();
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001201 if (util::stringEndsWith(src.path, ".arsc.flat")) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001202 return mergeResourceTable(file, override);
Adam Lesinski64587af2016-02-18 18:33:06 -08001203
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001204 } else if (util::stringEndsWith(src.path, ".flat")){
Adam Lesinskia40e9722015-11-24 19:11:46 -08001205 // Try opening the file and looking for an Export header.
1206 std::unique_ptr<io::IData> data = file->openAsData();
1207 if (!data) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001208 mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open");
Adam Lesinskia40e9722015-11-24 19:11:46 -08001209 return false;
1210 }
1211
Adam Lesinski5eeaadd2016-08-25 12:26:56 -07001212 CompiledFileInputStream inputStream(data->data(), data->size());
1213 uint32_t numFiles = 0;
1214 if (!inputStream.ReadLittleEndian32(&numFiles)) {
1215 mContext->getDiagnostics()->error(DiagMessage(src) << "failed read num files");
1216 return false;
Adam Lesinskia40e9722015-11-24 19:11:46 -08001217 }
Adam Lesinski5eeaadd2016-08-25 12:26:56 -07001218
1219 for (uint32_t i = 0; i < numFiles; i++) {
1220 pb::CompiledFile compiledFile;
1221 if (!inputStream.ReadCompiledFile(&compiledFile)) {
1222 mContext->getDiagnostics()->error(DiagMessage(src)
1223 << "failed to read compiled file header");
1224 return false;
1225 }
1226
1227 uint64_t offset, len;
1228 if (!inputStream.ReadDataMetaData(&offset, &len)) {
1229 mContext->getDiagnostics()->error(DiagMessage(src)
1230 << "failed to read data meta data");
1231 return false;
1232 }
1233
1234 std::unique_ptr<ResourceFile> resourceFile = deserializeCompiledFileFromPb(
1235 compiledFile, file->getSource(), mContext->getDiagnostics());
1236 if (!resourceFile) {
1237 return false;
1238 }
1239
1240 if (!mergeCompiledFile(file->createFileSegment(offset, len), resourceFile.get(),
1241 override)) {
1242 return false;
1243 }
1244 }
1245 return true;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001246 }
Adam Lesinski52364f72016-01-11 13:10:24 -08001247
Adam Lesinskic446a732016-01-21 11:04:46 -08001248 // Ignore non .flat files. This could be classes.dex or something else that happens
1249 // to be in an archive.
1250 return true;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001251 }
1252
Adam Lesinski36c73a52016-08-11 13:39:24 -07001253 std::unique_ptr<xml::XmlResource> generateSplitManifest(const AppInfo& appInfo,
1254 const SplitConstraints& constraints) {
1255 std::unique_ptr<xml::XmlResource> doc = util::make_unique<xml::XmlResource>();
1256
1257 std::unique_ptr<xml::Namespace> namespaceAndroid = util::make_unique<xml::Namespace>();
1258 namespaceAndroid->namespaceUri = xml::kSchemaAndroid;
1259 namespaceAndroid->namespacePrefix = "android";
1260
1261 std::unique_ptr<xml::Element> manifestEl = util::make_unique<xml::Element>();
1262 manifestEl->name = "manifest";
1263 manifestEl->attributes.push_back(
1264 xml::Attribute{ "", "package", appInfo.package });
1265
1266 if (appInfo.versionCode) {
1267 manifestEl->attributes.push_back(xml::Attribute{
1268 xml::kSchemaAndroid,
1269 "versionCode",
1270 std::to_string(appInfo.versionCode.value()) });
1271 }
1272
1273 if (appInfo.revisionCode) {
1274 manifestEl->attributes.push_back(xml::Attribute{
1275 xml::kSchemaAndroid,
1276 "revisionCode", std::to_string(appInfo.revisionCode.value()) });
1277 }
1278
1279 std::stringstream splitName;
1280 splitName << "config." << util::joiner(constraints.configs, "_");
1281
1282 manifestEl->attributes.push_back(
1283 xml::Attribute{ "", "split", splitName.str() });
1284
1285 std::unique_ptr<xml::Element> applicationEl = util::make_unique<xml::Element>();
1286 applicationEl->name = "application";
1287 applicationEl->attributes.push_back(
1288 xml::Attribute{ xml::kSchemaAndroid, "hasCode", "false" });
1289
1290 manifestEl->addChild(std::move(applicationEl));
1291 namespaceAndroid->addChild(std::move(manifestEl));
1292 doc->root = std::move(namespaceAndroid);
1293 return doc;
1294 }
1295
1296 /**
1297 * Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable
1298 * to the IArchiveWriter.
1299 */
1300 bool writeApk(IArchiveWriter* writer, proguard::KeepSet* keepSet, xml::XmlResource* manifest,
1301 ResourceTable* table) {
1302 const bool keepRawValues = mOptions.staticLib;
1303 bool result = flattenXml(manifest, "AndroidManifest.xml", {}, keepRawValues, writer,
1304 mContext);
1305 if (!result) {
1306 return false;
1307 }
1308
1309 ResourceFileFlattenerOptions fileFlattenerOptions;
1310 fileFlattenerOptions.keepRawValues = keepRawValues;
1311 fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything;
1312 fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress;
1313 fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion;
1314 fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors;
Alexandria Cornwalla7cc3f12016-08-16 13:33:32 -07001315 fileFlattenerOptions.noXmlNamespaces = mOptions.noXmlNamespaces;
Adam Lesinski36c73a52016-08-11 13:39:24 -07001316 fileFlattenerOptions.updateProguardSpec =
1317 static_cast<bool>(mOptions.generateProguardRulesPath);
1318
1319 ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, keepSet);
1320
1321 if (!fileFlattener.flatten(table, writer)) {
1322 mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources");
1323 return false;
1324 }
1325
1326 if (mOptions.staticLib) {
1327 if (!flattenTableToPb(table, writer)) {
1328 mContext->getDiagnostics()->error(DiagMessage()
1329 << "failed to write resources.arsc.flat");
1330 return false;
1331 }
1332 } else {
1333 if (!flattenTable(table, writer)) {
1334 mContext->getDiagnostics()->error(DiagMessage()
1335 << "failed to write resources.arsc");
1336 return false;
1337 }
1338 }
1339 return true;
1340 }
1341
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001342 int run(const std::vector<std::string>& inputFiles) {
1343 // Load the AndroidManifest.xml
Adam Lesinskia40e9722015-11-24 19:11:46 -08001344 std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
Adam Lesinski6a008172016-02-02 17:02:58 -08001345 mContext->getDiagnostics());
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001346 if (!manifestXml) {
1347 return 1;
1348 }
1349
Adam Lesinski36c73a52016-08-11 13:39:24 -07001350 // First extract the Package name without modifying it (via --rename-manifest-package).
Adam Lesinskifb6312f2016-06-28 14:40:32 -07001351 if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(),
1352 mContext->getDiagnostics())) {
Adam Lesinski36c73a52016-08-11 13:39:24 -07001353 const AppInfo& appInfo = maybeAppInfo.value();
Adam Lesinskifb6312f2016-06-28 14:40:32 -07001354 mContext->setCompilationPackage(appInfo.package);
Adam Lesinski36c73a52016-08-11 13:39:24 -07001355 }
1356
1357 ManifestFixer manifestFixer(mOptions.manifestFixerOptions);
1358 if (!manifestFixer.consume(mContext, manifestXml.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001359 return 1;
1360 }
1361
Adam Lesinski36c73a52016-08-11 13:39:24 -07001362 Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(),
1363 mContext->getDiagnostics());
1364 if (!maybeAppInfo) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001365 return 1;
1366 }
1367
Adam Lesinski36c73a52016-08-11 13:39:24 -07001368 const AppInfo& appInfo = maybeAppInfo.value();
1369 if (appInfo.minSdkVersion) {
1370 if (Maybe<int> maybeMinSdkVersion =
1371 ResourceUtils::parseSdkVersion(appInfo.minSdkVersion.value())) {
1372 mContext->setMinSdkVersion(maybeMinSdkVersion.value());
1373 }
1374 }
1375
Adam Lesinski64587af2016-02-18 18:33:06 -08001376 mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() });
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001377 if (mContext->getCompilationPackage() == "android") {
Adam Lesinski64587af2016-02-18 18:33:06 -08001378 mContext->setPackageId(0x01);
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001379 } else {
Adam Lesinski64587af2016-02-18 18:33:06 -08001380 mContext->setPackageId(0x7f);
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001381 }
1382
Adam Lesinski64587af2016-02-18 18:33:06 -08001383 if (!loadSymbolsFromIncludePaths()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001384 return 1;
1385 }
1386
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001387 TableMergerOptions tableMergerOptions;
1388 tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
Adam Lesinski6a008172016-02-02 17:02:58 -08001389 mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions);
Adam Lesinskifb48d292015-11-07 15:52:13 -08001390
Adam Lesinski355f2852016-02-13 20:26:45 -08001391 if (mContext->verbose()) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001392 mContext->getDiagnostics()->note(
Adam Lesinski64587af2016-02-18 18:33:06 -08001393 DiagMessage() << "linking package '" << mContext->getCompilationPackage()
1394 << "' with package ID " << std::hex
1395 << (int) mContext->getPackageId());
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001396 }
1397
Adam Lesinskifb48d292015-11-07 15:52:13 -08001398
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001399 for (const std::string& input : inputFiles) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001400 if (!mergePath(input, false)) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001401 mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input");
Adam Lesinski467f1712015-11-16 17:35:44 -08001402 return 1;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001403 }
1404 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001405
Adam Lesinskifb48d292015-11-07 15:52:13 -08001406 for (const std::string& input : mOptions.overlayFiles) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001407 if (!mergePath(input, true)) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001408 mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
Adam Lesinski467f1712015-11-16 17:35:44 -08001409 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001410 }
1411 }
1412
Adam Lesinskifb48d292015-11-07 15:52:13 -08001413 if (!verifyNoExternalPackages()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001414 return 1;
1415 }
1416
1417 if (!mOptions.staticLib) {
1418 PrivateAttributeMover mover;
Adam Lesinski6a008172016-02-02 17:02:58 -08001419 if (!mover.consume(mContext, &mFinalTable)) {
1420 mContext->getDiagnostics()->error(
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001421 DiagMessage() << "failed moving private attributes");
1422 return 1;
1423 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001424
Adam Lesinski64587af2016-02-18 18:33:06 -08001425 // Assign IDs if we are building a regular app.
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001426 IdAssigner idAssigner(&mOptions.stableIdMap);
Adam Lesinski6a008172016-02-02 17:02:58 -08001427 if (!idAssigner.consume(mContext, &mFinalTable)) {
1428 mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001429 return 1;
1430 }
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001431
1432 // Now grab each ID and emit it as a file.
1433 if (mOptions.resourceIdMapPath) {
1434 for (auto& package : mFinalTable.packages) {
1435 for (auto& type : package->types) {
1436 for (auto& entry : type->entries) {
1437 ResourceName name(package->name, type->type, entry->name);
1438 // The IDs are guaranteed to exist.
1439 mOptions.stableIdMap[std::move(name)] = ResourceId(package->id.value(),
1440 type->id.value(),
1441 entry->id.value());
1442 }
1443 }
1444 }
1445
1446 if (!writeStableIdMapToPath(mContext->getDiagnostics(),
1447 mOptions.stableIdMap,
1448 mOptions.resourceIdMapPath.value())) {
1449 return 1;
1450 }
1451 }
Adam Lesinski64587af2016-02-18 18:33:06 -08001452 } else {
1453 // Static libs are merged with other apps, and ID collisions are bad, so verify that
1454 // no IDs have been set.
1455 if (!verifyNoIdsSet()) {
1456 return 1;
1457 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001458 }
1459
Adam Lesinski64587af2016-02-18 18:33:06 -08001460 // Add the names to mangle based on our source merge earlier.
1461 mContext->setNameManglerPolicy(NameManglerPolicy{
1462 mContext->getCompilationPackage(), mTableMerger->getMergedPackages() });
1463
1464 // Add our table to the symbol table.
1465 mContext->getExternalSymbols()->prependSource(
1466 util::make_unique<ResourceTableSymbolSource>(&mFinalTable));
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001467
Adam Lesinski36c73a52016-08-11 13:39:24 -07001468 ReferenceLinker linker;
1469 if (!linker.consume(mContext, &mFinalTable)) {
1470 mContext->getDiagnostics()->error(DiagMessage() << "failed linking references");
1471 return 1;
1472 }
1473
1474 if (mOptions.staticLib) {
1475 if (!mOptions.products.empty()) {
1476 mContext->getDiagnostics()->warn(
1477 DiagMessage() << "can't select products when building static library");
1478 }
1479 } else {
1480 ProductFilter productFilter(mOptions.products);
1481 if (!productFilter.consume(mContext, &mFinalTable)) {
1482 mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001483 return 1;
1484 }
Adam Lesinski36c73a52016-08-11 13:39:24 -07001485 }
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001486
Adam Lesinski36c73a52016-08-11 13:39:24 -07001487 if (!mOptions.noAutoVersion) {
1488 AutoVersioner versioner;
1489 if (!versioner.consume(mContext, &mFinalTable)) {
1490 mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles");
1491 return 1;
1492 }
1493 }
Adam Lesinski355f2852016-02-13 20:26:45 -08001494
Adam Lesinski36c73a52016-08-11 13:39:24 -07001495 if (!mOptions.staticLib && mContext->getMinSdkVersion() > 0) {
1496 if (mContext->verbose()) {
1497 mContext->getDiagnostics()->note(
1498 DiagMessage() << "collapsing resource versions for minimum SDK "
1499 << mContext->getMinSdkVersion());
1500 }
Adam Lesinski355f2852016-02-13 20:26:45 -08001501
Adam Lesinski36c73a52016-08-11 13:39:24 -07001502 VersionCollapser collapser;
1503 if (!collapser.consume(mContext, &mFinalTable)) {
1504 return 1;
Adam Lesinski64587af2016-02-18 18:33:06 -08001505 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001506 }
1507
1508 proguard::KeepSet proguardKeepSet;
Rohit Agrawale49bb302016-04-22 12:27:55 -07001509 proguard::KeepSet proguardMainDexKeepSet;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001510
Adam Lesinski36c73a52016-08-11 13:39:24 -07001511 if (mOptions.staticLib) {
1512 if (mOptions.tableSplitterOptions.configFilter != nullptr ||
1513 mOptions.tableSplitterOptions.preferredDensity) {
1514 mContext->getDiagnostics()->warn(
1515 DiagMessage() << "can't strip resources when building static library");
1516 }
1517 } else {
1518 // Adjust the SplitConstraints so that their SDK version is stripped if it is less
1519 // than or equal to the minSdk. Otherwise the resources that have had their SDK version
1520 // stripped due to minSdk won't ever match.
1521 std::vector<SplitConstraints> adjustedConstraintsList;
1522 adjustedConstraintsList.reserve(mOptions.splitConstraints.size());
1523 for (const SplitConstraints& constraints : mOptions.splitConstraints) {
1524 SplitConstraints adjustedConstraints;
1525 for (const ConfigDescription& config : constraints.configs) {
1526 if (config.sdkVersion <= mContext->getMinSdkVersion()) {
1527 adjustedConstraints.configs.insert(config.copyWithoutSdkVersion());
1528 } else {
1529 adjustedConstraints.configs.insert(config);
1530 }
1531 }
1532 adjustedConstraintsList.push_back(std::move(adjustedConstraints));
1533 }
1534
1535 TableSplitter tableSplitter(adjustedConstraintsList, mOptions.tableSplitterOptions);
1536 if (!tableSplitter.verifySplitConstraints(mContext)) {
1537 return 1;
1538 }
1539 tableSplitter.splitTable(&mFinalTable);
1540
1541 // Now we need to write out the Split APKs.
1542 auto pathIter = mOptions.splitPaths.begin();
1543 auto splitConstraintsIter = adjustedConstraintsList.begin();
1544 for (std::unique_ptr<ResourceTable>& splitTable : tableSplitter.getSplits()) {
1545 if (mContext->verbose()) {
1546 mContext->getDiagnostics()->note(
1547 DiagMessage(*pathIter) << "generating split with configurations '"
1548 << util::joiner(splitConstraintsIter->configs, ", ") << "'");
1549 }
1550
1551 std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(*pathIter);
1552 if (!archiveWriter) {
1553 mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive");
1554 return 1;
1555 }
1556
1557 // Generate an AndroidManifest.xml for each split.
1558 std::unique_ptr<xml::XmlResource> splitManifest =
1559 generateSplitManifest(appInfo, *splitConstraintsIter);
1560
1561 XmlReferenceLinker linker;
1562 if (!linker.consume(mContext, splitManifest.get())) {
1563 mContext->getDiagnostics()->error(
1564 DiagMessage() << "failed to create Split AndroidManifest.xml");
1565 return 1;
1566 }
1567
1568 if (!writeApk(archiveWriter.get(), &proguardKeepSet, splitManifest.get(),
1569 splitTable.get())) {
1570 return 1;
1571 }
1572
1573 ++pathIter;
1574 ++splitConstraintsIter;
1575 }
1576 }
1577
1578 // Start writing the base APK.
1579 std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(mOptions.outputPath);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001580 if (!archiveWriter) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001581 mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001582 return 1;
1583 }
1584
Adam Lesinski467f1712015-11-16 17:35:44 -08001585 bool error = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001586 {
Adam Lesinski467f1712015-11-16 17:35:44 -08001587 // AndroidManifest.xml has no resource name, but the CallSite is built from the name
1588 // (aka, which package the AndroidManifest.xml is coming from).
1589 // So we give it a package name so it can see local resources.
Adam Lesinski64587af2016-02-18 18:33:06 -08001590 manifestXml->file.name.package = mContext->getCompilationPackage();
Adam Lesinski467f1712015-11-16 17:35:44 -08001591
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001592 XmlReferenceLinker manifestLinker;
Adam Lesinski6a008172016-02-02 17:02:58 -08001593 if (manifestLinker.consume(mContext, manifestXml.get())) {
Rohit Agrawale49bb302016-04-22 12:27:55 -07001594 if (mOptions.generateProguardRulesPath &&
1595 !proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
1596 manifestXml.get(),
1597 &proguardKeepSet)) {
1598 error = true;
1599 }
1600
1601 if (mOptions.generateMainDexProguardRulesPath &&
1602 !proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
1603 manifestXml.get(),
1604 &proguardMainDexKeepSet,
1605 true)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001606 error = true;
1607 }
1608
Adam Lesinskica5638f2015-10-21 14:42:43 -07001609 if (mOptions.generateJavaClassPath) {
1610 if (!writeManifestJavaFile(manifestXml.get())) {
1611 error = true;
1612 }
1613 }
Alexandria Cornwalla7cc3f12016-08-16 13:33:32 -07001614
1615 if (mOptions.noXmlNamespaces) {
1616 // PackageParser will fail if URIs are removed from AndroidManifest.xml.
1617 XmlNamespaceRemover namespaceRemover(true /* keepUris */);
1618 if (!namespaceRemover.consume(mContext, manifestXml.get())) {
1619 error = true;
1620 }
1621 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001622 } else {
1623 error = true;
1624 }
1625 }
1626
Adam Lesinski467f1712015-11-16 17:35:44 -08001627 if (error) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001628 mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest");
Adam Lesinski467f1712015-11-16 17:35:44 -08001629 return 1;
1630 }
1631
Adam Lesinski36c73a52016-08-11 13:39:24 -07001632 if (!writeApk(archiveWriter.get(), &proguardKeepSet, manifestXml.get(), &mFinalTable)) {
Alexandria Cornwall637b4822016-08-11 09:53:16 -07001633 return 1;
1634 }
1635
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001636 if (mOptions.generateJavaClassPath) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001637 JavaClassGeneratorOptions options;
Adam Lesinski52364f72016-01-11 13:10:24 -08001638 options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
Adam Lesinski3524a232016-04-01 19:19:24 -07001639 options.javadocAnnotations = mOptions.javadocAnnotations;
Adam Lesinski52364f72016-01-11 13:10:24 -08001640
Adam Lesinskief9c5012016-01-22 14:09:53 -08001641 if (mOptions.staticLib || mOptions.generateNonFinalIds) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001642 options.useFinal = false;
1643 }
1644
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001645 const StringPiece actualPackage = mContext->getCompilationPackage();
1646 StringPiece outputPackage = mContext->getCompilationPackage();
Adam Lesinski52364f72016-01-11 13:10:24 -08001647 if (mOptions.customJavaPackage) {
1648 // Override the output java package to the custom one.
1649 outputPackage = mOptions.customJavaPackage.value();
1650 }
Adam Lesinski83f22552015-11-07 11:51:23 -08001651
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001652 if (mOptions.privateSymbols) {
1653 // If we defined a private symbols package, we only emit Public symbols
1654 // to the original package, and private and public symbols to the private package.
1655
1656 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
Adam Lesinski6a008172016-02-02 17:02:58 -08001657 if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(),
Adam Lesinski52364f72016-01-11 13:10:24 -08001658 outputPackage, options)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001659 return 1;
1660 }
1661
1662 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
Adam Lesinski83f22552015-11-07 11:51:23 -08001663 outputPackage = mOptions.privateSymbols.value();
1664 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001665
Adam Lesinskifb48d292015-11-07 15:52:13 -08001666 if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
Adam Lesinski83f22552015-11-07 11:51:23 -08001667 return 1;
1668 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001669
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001670 for (const std::string& extraPackage : mOptions.extraJavaPackages) {
Adam Lesinski52364f72016-01-11 13:10:24 -08001671 if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001672 return 1;
1673 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001674 }
1675 }
1676
Rohit Agrawale49bb302016-04-22 12:27:55 -07001677 if (!writeProguardFile(mOptions.generateProguardRulesPath, proguardKeepSet)) {
1678 return 1;
1679 }
1680
1681 if (!writeProguardFile(mOptions.generateMainDexProguardRulesPath, proguardMainDexKeepSet)) {
1682 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001683 }
1684
Adam Lesinski355f2852016-02-13 20:26:45 -08001685 if (mContext->verbose()) {
1686 DebugPrintTableOptions debugPrintTableOptions;
1687 debugPrintTableOptions.showSources = true;
1688 Debug::printTable(&mFinalTable, debugPrintTableOptions);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001689 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001690 return 0;
1691 }
Adam Lesinskifb48d292015-11-07 15:52:13 -08001692
1693private:
1694 LinkOptions mOptions;
Adam Lesinski6a008172016-02-02 17:02:58 -08001695 LinkContext* mContext;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001696 ResourceTable mFinalTable;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001697
Adam Lesinskifb48d292015-11-07 15:52:13 -08001698 std::unique_ptr<TableMerger> mTableMerger;
1699
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001700 // A pointer to the FileCollection representing the filesystem (not archives).
Adam Lesinski64587af2016-02-18 18:33:06 -08001701 std::unique_ptr<io::FileCollection> mFileCollection;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001702
1703 // A vector of IFileCollections. This is mainly here to keep ownership of the collections.
Adam Lesinskia40e9722015-11-24 19:11:46 -08001704 std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
Adam Lesinski64587af2016-02-18 18:33:06 -08001705
1706 // A vector of ResourceTables. This is here to retain ownership, so that the SymbolTable
1707 // can use these.
1708 std::vector<std::unique_ptr<ResourceTable>> mStaticTableIncludes;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001709};
1710
1711int link(const std::vector<StringPiece>& args) {
Adam Lesinski355f2852016-02-13 20:26:45 -08001712 LinkContext context;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001713 LinkOptions options;
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001714 std::vector<std::string> overlayArgList;
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001715 std::vector<std::string> extraJavaPackages;
Adam Lesinski6a008172016-02-02 17:02:58 -08001716 Maybe<std::string> configs;
Adam Lesinski355f2852016-02-13 20:26:45 -08001717 Maybe<std::string> preferredDensity;
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001718 Maybe<std::string> productList;
Adam Lesinski8900aa82016-01-25 22:48:15 -08001719 bool legacyXFlag = false;
1720 bool requireLocalization = false;
Adam Lesinski64587af2016-02-18 18:33:06 -08001721 bool verbose = false;
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001722 Maybe<std::string> stableIdFilePath;
Adam Lesinski36c73a52016-08-11 13:39:24 -07001723 std::vector<std::string> splitArgs;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001724 Flags flags = Flags()
1725 .requiredFlag("-o", "Output path", &options.outputPath)
1726 .requiredFlag("--manifest", "Path to the Android manifest to build",
1727 &options.manifestPath)
1728 .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
Adam Lesinski52364f72016-01-11 13:10:24 -08001729 .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n"
Adam Lesinskifb48d292015-11-07 15:52:13 -08001730 "The last conflicting resource given takes precedence.",
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001731 &overlayArgList)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001732 .optionalFlag("--java", "Directory in which to generate R.java",
1733 &options.generateJavaClassPath)
1734 .optionalFlag("--proguard", "Output file for generated Proguard rules",
1735 &options.generateProguardRulesPath)
Rohit Agrawale49bb302016-04-22 12:27:55 -07001736 .optionalFlag("--proguard-main-dex",
1737 "Output file for generated Proguard rules for the main dex",
1738 &options.generateMainDexProguardRulesPath)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001739 .optionalSwitch("--no-auto-version",
1740 "Disables automatic style and layout SDK versioning",
1741 &options.noAutoVersion)
Adam Lesinski64587af2016-02-18 18:33:06 -08001742 .optionalSwitch("--no-version-vectors",
1743 "Disables automatic versioning of vector drawables. Use this only\n"
1744 "when building with vector drawable support library",
1745 &options.noVersionVectors)
Adam Lesinski8900aa82016-01-25 22:48:15 -08001746 .optionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01",
1747 &legacyXFlag)
1748 .optionalSwitch("-z", "Require localization of strings marked 'suggested'",
1749 &requireLocalization)
Adam Lesinski6a008172016-02-02 17:02:58 -08001750 .optionalFlag("-c", "Comma separated list of configurations to include. The default\n"
1751 "is all configurations", &configs)
Adam Lesinski355f2852016-02-13 20:26:45 -08001752 .optionalFlag("--preferred-density",
1753 "Selects the closest matching density and strips out all others.",
1754 &preferredDensity)
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001755 .optionalFlag("--product", "Comma separated list of product names to keep",
1756 &productList)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001757 .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
1758 "by -o",
1759 &options.outputToDirectory)
Alexandria Cornwalla7cc3f12016-08-16 13:33:32 -07001760 .optionalSwitch("--no-xml-namespaces", "Removes XML namespace prefix and URI "
1761 "information from AndroidManifest.xml\nand XML binaries in res/*.",
1762 &options.noXmlNamespaces)
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001763 .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001764 "AndroidManifest.xml",
1765 &options.manifestFixerOptions.minSdkVersionDefault)
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001766 .optionalFlag("--target-sdk-version", "Default target SDK version to use for "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001767 "AndroidManifest.xml",
1768 &options.manifestFixerOptions.targetSdkVersionDefault)
Adam Lesinski52364f72016-01-11 13:10:24 -08001769 .optionalFlag("--version-code", "Version code (integer) to inject into the "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001770 "AndroidManifest.xml if none is present",
1771 &options.manifestFixerOptions.versionCodeDefault)
Adam Lesinski52364f72016-01-11 13:10:24 -08001772 .optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001773 "if none is present",
1774 &options.manifestFixerOptions.versionNameDefault)
1775 .optionalSwitch("--static-lib", "Generate a static Android library",
1776 &options.staticLib)
Adam Lesinski64587af2016-02-18 18:33:06 -08001777 .optionalSwitch("--no-static-lib-packages",
1778 "Merge all library resources under the app's package",
1779 &options.noStaticLibPackages)
Adam Lesinskief9c5012016-01-22 14:09:53 -08001780 .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n"
1781 "This is implied when --static-lib is specified.",
1782 &options.generateNonFinalIds)
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001783 .optionalFlag("--stable-ids", "File containing a list of name to ID mapping.",
1784 &stableIdFilePath)
1785 .optionalFlag("--emit-ids", "Emit a file at the given path with a list of name to ID\n"
1786 "mappings, suitable for use with --stable-ids.",
1787 &options.resourceIdMapPath)
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001788 .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001789 "private symbols.\n"
1790 "If not specified, public and private symbols will use the application's "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001791 "package name",
1792 &options.privateSymbols)
Adam Lesinski52364f72016-01-11 13:10:24 -08001793 .optionalFlag("--custom-package", "Custom Java package under which to generate R.java",
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001794 &options.customJavaPackage)
Adam Lesinski83f22552015-11-07 11:51:23 -08001795 .optionalFlagList("--extra-packages", "Generate the same R.java but with different "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001796 "package names",
1797 &extraJavaPackages)
Adam Lesinski3524a232016-04-01 19:19:24 -07001798 .optionalFlagList("--add-javadoc-annotation", "Adds a JavaDoc annotation to all "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001799 "generated Java classes",
1800 &options.javadocAnnotations)
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001801 .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001802 "overlays without <add-resource> tags",
1803 &options.autoAddOverlay)
Adam Lesinski52364f72016-01-11 13:10:24 -08001804 .optionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml",
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001805 &options.manifestFixerOptions.renameManifestPackage)
Adam Lesinski52364f72016-01-11 13:10:24 -08001806 .optionalFlag("--rename-instrumentation-target-package",
1807 "Changes the name of the target package for instrumentation. Most useful "
1808 "when used\nin conjunction with --rename-manifest-package",
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001809 &options.manifestFixerOptions.renameInstrumentationTargetPackage)
Adam Lesinski52364f72016-01-11 13:10:24 -08001810 .optionalFlagList("-0", "File extensions not to compress",
1811 &options.extensionsToNotCompress)
Adam Lesinski36c73a52016-08-11 13:39:24 -07001812 .optionalFlagList("--split", "Split resources matching a set of configs out to a "
1813 "Split APK.\nSyntax: path/to/output.apk:<config>[,<config>[...]]",
1814 &splitArgs)
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001815 .optionalSwitch("-v", "Enables verbose logging",
1816 &verbose);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001817
1818 if (!flags.parse("aapt2 link", args, &std::cerr)) {
1819 return 1;
1820 }
1821
Adam Lesinskic51562c2016-04-28 11:12:38 -07001822 // Expand all argument-files passed into the command line. These start with '@'.
1823 std::vector<std::string> argList;
1824 for (const std::string& arg : flags.getArgs()) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001825 if (util::stringStartsWith(arg, "@")) {
Adam Lesinskic51562c2016-04-28 11:12:38 -07001826 const std::string path = arg.substr(1, arg.size() - 1);
1827 std::string error;
1828 if (!file::appendArgsFromFile(path, &argList, &error)) {
1829 context.getDiagnostics()->error(DiagMessage(path) << error);
1830 return 1;
1831 }
1832 } else {
1833 argList.push_back(arg);
1834 }
1835 }
1836
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001837 // Expand all argument-files passed to -R.
1838 for (const std::string& arg : overlayArgList) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001839 if (util::stringStartsWith(arg, "@")) {
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001840 const std::string path = arg.substr(1, arg.size() - 1);
1841 std::string error;
1842 if (!file::appendArgsFromFile(path, &options.overlayFiles, &error)) {
1843 context.getDiagnostics()->error(DiagMessage(path) << error);
1844 return 1;
1845 }
1846 } else {
1847 options.overlayFiles.push_back(arg);
1848 }
1849 }
1850
Adam Lesinski64587af2016-02-18 18:33:06 -08001851 if (verbose) {
1852 context.setVerbose(verbose);
1853 }
1854
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001855 // Populate the set of extra packages for which to generate R.java.
1856 for (std::string& extraPackage : extraJavaPackages) {
1857 // A given package can actually be a colon separated list of packages.
1858 for (StringPiece package : util::split(extraPackage, ':')) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001859 options.extraJavaPackages.insert(package.toString());
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001860 }
1861 }
1862
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001863 if (productList) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001864 for (StringPiece product : util::tokenize(productList.value(), ',')) {
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001865 if (product != "" && product != "default") {
1866 options.products.insert(product.toString());
1867 }
1868 }
1869 }
1870
Adam Lesinski6a008172016-02-02 17:02:58 -08001871 AxisConfigFilter filter;
1872 if (configs) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001873 for (const StringPiece& configStr : util::tokenize(configs.value(), ',')) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001874 ConfigDescription config;
1875 LocaleValue lv;
1876 if (lv.initFromFilterString(configStr)) {
1877 lv.writeTo(&config);
1878 } else if (!ConfigDescription::parse(configStr, &config)) {
1879 context.getDiagnostics()->error(
1880 DiagMessage() << "invalid config '" << configStr << "' for -c option");
1881 return 1;
1882 }
1883
1884 if (config.density != 0) {
1885 context.getDiagnostics()->warn(
1886 DiagMessage() << "ignoring density '" << config << "' for -c option");
1887 } else {
1888 filter.addConfig(config);
1889 }
1890 }
1891
Adam Lesinski355f2852016-02-13 20:26:45 -08001892 options.tableSplitterOptions.configFilter = &filter;
1893 }
1894
1895 if (preferredDensity) {
1896 ConfigDescription preferredDensityConfig;
1897 if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) {
1898 context.getDiagnostics()->error(DiagMessage() << "invalid density '"
1899 << preferredDensity.value()
1900 << "' for --preferred-density option");
1901 return 1;
1902 }
1903
1904 // Clear the version that can be automatically added.
1905 preferredDensityConfig.sdkVersion = 0;
1906
1907 if (preferredDensityConfig.diff(ConfigDescription::defaultConfig())
1908 != ConfigDescription::CONFIG_DENSITY) {
1909 context.getDiagnostics()->error(DiagMessage() << "invalid preferred density '"
1910 << preferredDensity.value() << "'. "
1911 << "Preferred density must only be a density value");
1912 return 1;
1913 }
1914 options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density;
Adam Lesinski6a008172016-02-02 17:02:58 -08001915 }
1916
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001917 if (!options.staticLib && stableIdFilePath) {
1918 if (!loadStableIdMap(context.getDiagnostics(), stableIdFilePath.value(),
1919 &options.stableIdMap)) {
1920 return 1;
1921 }
1922 }
1923
Adam Lesinski9756dec2016-08-08 12:35:04 -07001924 // Populate some default no-compress extensions that are already compressed.
1925 options.extensionsToNotCompress.insert({
1926 ".jpg", ".jpeg", ".png", ".gif",
1927 ".wav", ".mp2", ".mp3", ".ogg", ".aac",
1928 ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
1929 ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
1930 ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
1931 ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"});
1932
Adam Lesinski36c73a52016-08-11 13:39:24 -07001933 // Parse the split parameters.
1934 for (const std::string& splitArg : splitArgs) {
1935 options.splitPaths.push_back({});
1936 options.splitConstraints.push_back({});
1937 if (!parseSplitParameter(splitArg, context.getDiagnostics(), &options.splitPaths.back(),
1938 &options.splitConstraints.back())) {
1939 return 1;
1940 }
1941 }
1942
Adam Lesinski626a69f2016-03-03 10:09:26 -08001943 // Turn off auto versioning for static-libs.
1944 if (options.staticLib) {
1945 options.noAutoVersion = true;
1946 options.noVersionVectors = true;
1947 }
1948
Adam Lesinski6a008172016-02-02 17:02:58 -08001949 LinkCommand cmd(&context, options);
Adam Lesinskic51562c2016-04-28 11:12:38 -07001950 return cmd.run(argList);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001951}
1952
1953} // namespace aapt