blob: 8093e6a035d9a6ab8be2797664fcc88cc3f56fb5 [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 Lesinski59e04c62016-02-04 15:59:23 -080047#include <google/protobuf/io/coded_stream.h>
48
Adam Lesinski1ab598f2015-08-14 14:26:04 -070049#include <fstream>
50#include <sys/stat.h>
Adam Lesinski1ab598f2015-08-14 14:26:04 -070051#include <vector>
52
53namespace aapt {
54
55struct LinkOptions {
56 std::string outputPath;
57 std::string manifestPath;
58 std::vector<std::string> includePaths;
Adam Lesinskifb48d292015-11-07 15:52:13 -080059 std::vector<std::string> overlayFiles;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070060 Maybe<std::string> generateJavaClassPath;
Adam Lesinskid0f116b2016-07-08 15:00:32 -070061 Maybe<std::string> customJavaPackage;
62 std::set<std::string> extraJavaPackages;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070063 Maybe<std::string> generateProguardRulesPath;
Rohit Agrawale49bb302016-04-22 12:27:55 -070064 Maybe<std::string> generateMainDexProguardRulesPath;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070065 bool noAutoVersion = false;
Adam Lesinski64587af2016-02-18 18:33:06 -080066 bool noVersionVectors = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070067 bool staticLib = false;
Adam Lesinski64587af2016-02-18 18:33:06 -080068 bool noStaticLibPackages = false;
Adam Lesinskief9c5012016-01-22 14:09:53 -080069 bool generateNonFinalIds = false;
Adam Lesinski3524a232016-04-01 19:19:24 -070070 std::vector<std::string> javadocAnnotations;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070071 bool outputToDirectory = false;
Adam Lesinskia6fe3452015-12-09 15:20:52 -080072 bool autoAddOverlay = false;
Adam Lesinski52364f72016-01-11 13:10:24 -080073 bool doNotCompressAnything = false;
74 std::vector<std::string> extensionsToNotCompress;
Adam Lesinskid0f116b2016-07-08 15:00:32 -070075 Maybe<std::string> privateSymbols;
Adam Lesinski52364f72016-01-11 13:10:24 -080076 ManifestFixerOptions manifestFixerOptions;
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -080077 std::unordered_set<std::string> products;
Adam Lesinski355f2852016-02-13 20:26:45 -080078 TableSplitterOptions tableSplitterOptions;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070079};
80
Adam Lesinski64587af2016-02-18 18:33:06 -080081class LinkContext : public IAaptContext {
82public:
83 LinkContext() : mNameMangler({}) {
84 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -070085
86 IDiagnostics* getDiagnostics() override {
87 return &mDiagnostics;
88 }
89
90 NameMangler* getNameMangler() override {
Adam Lesinski64587af2016-02-18 18:33:06 -080091 return &mNameMangler;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070092 }
93
Adam Lesinski64587af2016-02-18 18:33:06 -080094 void setNameManglerPolicy(const NameManglerPolicy& policy) {
95 mNameMangler = NameMangler(policy);
96 }
97
Adam Lesinskid0f116b2016-07-08 15:00:32 -070098 const std::string& getCompilationPackage() override {
Adam Lesinski1ab598f2015-08-14 14:26:04 -070099 return mCompilationPackage;
100 }
101
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700102 void setCompilationPackage(const StringPiece& packageName) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800103 mCompilationPackage = packageName.toString();
104 }
105
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700106 uint8_t getPackageId() override {
107 return mPackageId;
108 }
109
Adam Lesinski64587af2016-02-18 18:33:06 -0800110 void setPackageId(uint8_t id) {
111 mPackageId = id;
112 }
113
114 SymbolTable* getExternalSymbols() override {
115 return &mSymbols;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700116 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800117
118 bool verbose() override {
119 return mVerbose;
120 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800121
122 void setVerbose(bool val) {
123 mVerbose = val;
124 }
125
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700126 int getMinSdkVersion() override {
127 return mMinSdkVersion;
128 }
129
130 void setMinSdkVersion(int minSdk) {
131 mMinSdkVersion = minSdk;
132 }
133
Adam Lesinski64587af2016-02-18 18:33:06 -0800134private:
135 StdErrDiagnostics mDiagnostics;
136 NameMangler mNameMangler;
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700137 std::string mCompilationPackage;
Adam Lesinski64587af2016-02-18 18:33:06 -0800138 uint8_t mPackageId = 0x0;
139 SymbolTable mSymbols;
140 bool mVerbose = false;
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700141 int mMinSdkVersion = 0;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700142};
143
Adam Lesinski355f2852016-02-13 20:26:45 -0800144static bool copyFileToArchive(io::IFile* file, const std::string& outPath,
145 uint32_t compressionFlags,
146 IArchiveWriter* writer, IAaptContext* context) {
147 std::unique_ptr<io::IData> data = file->openAsData();
148 if (!data) {
149 context->getDiagnostics()->error(DiagMessage(file->getSource())
150 << "failed to open file");
151 return false;
152 }
153
Adam Lesinski64587af2016-02-18 18:33:06 -0800154 const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data());
155 size_t bufferSize = data->size();
156
157 // If the file ends with .flat, we must strip off the CompiledFileHeader from it.
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700158 if (util::stringEndsWith(file->getSource().path, ".flat")) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800159 CompiledFileInputStream inputStream(data->data(), data->size());
160 if (!inputStream.CompiledFile()) {
161 context->getDiagnostics()->error(DiagMessage(file->getSource())
162 << "invalid compiled file header");
163 return false;
164 }
165 buffer = reinterpret_cast<const uint8_t*>(inputStream.data());
166 bufferSize = inputStream.size();
Adam Lesinski355f2852016-02-13 20:26:45 -0800167 }
168
169 if (context->verbose()) {
170 context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive");
171 }
172
173 if (writer->startEntry(outPath, compressionFlags)) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800174 if (writer->writeEntry(buffer, bufferSize)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800175 if (writer->finishEntry()) {
176 return true;
177 }
178 }
179 }
180
181 context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath);
182 return false;
183}
184
185static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
186 bool keepRawValues, IArchiveWriter* writer, IAaptContext* context) {
187 BigBuffer buffer(1024);
188 XmlFlattenerOptions options = {};
189 options.keepRawValues = keepRawValues;
190 options.maxSdkLevel = maxSdkLevel;
191 XmlFlattener flattener(&buffer, options);
192 if (!flattener.consume(context, xmlRes)) {
193 return false;
194 }
195
196 if (context->verbose()) {
197 DiagMessage msg;
198 msg << "writing " << path << " to archive";
199 if (maxSdkLevel) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800200 msg << " maxSdkLevel=" << maxSdkLevel.value() << " keepRawValues=" << keepRawValues;
Adam Lesinski355f2852016-02-13 20:26:45 -0800201 }
202 context->getDiagnostics()->note(msg);
203 }
204
205 if (writer->startEntry(path, ArchiveEntry::kCompress)) {
206 if (writer->writeEntry(buffer)) {
207 if (writer->finishEntry()) {
208 return true;
209 }
210 }
211 }
212 context->getDiagnostics()->error(DiagMessage() << "failed to write " << path << " to archive");
213 return false;
214}
215
Adam Lesinski355f2852016-02-13 20:26:45 -0800216static std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
217 const void* data, size_t len,
218 IDiagnostics* diag) {
219 pb::ResourceTable pbTable;
220 if (!pbTable.ParseFromArray(data, len)) {
221 diag->error(DiagMessage(source) << "invalid compiled table");
222 return {};
223 }
224
225 std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source, diag);
226 if (!table) {
227 return {};
228 }
229 return table;
230}
231
232/**
233 * Inflates an XML file from the source path.
234 */
235static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
236 std::ifstream fin(path, std::ifstream::binary);
237 if (!fin) {
238 diag->error(DiagMessage(path) << strerror(errno));
239 return {};
240 }
241 return xml::inflate(&fin, diag, Source(path));
242}
243
244static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const Source& source,
245 const void* data, size_t len,
246 IDiagnostics* diag) {
247 CompiledFileInputStream inputStream(data, len);
248 if (!inputStream.CompiledFile()) {
249 diag->error(DiagMessage(source) << "invalid compiled file header");
250 return {};
251 }
252
253 const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data());
254 const size_t xmlDataLen = inputStream.size();
255
256 std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source);
257 if (!xmlRes) {
258 return {};
259 }
260 return xmlRes;
261}
262
263static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
264 const void* data, size_t len,
265 IDiagnostics* diag) {
266 CompiledFileInputStream inputStream(data, len);
267 const pb::CompiledFile* pbFile = inputStream.CompiledFile();
268 if (!pbFile) {
269 diag->error(DiagMessage(source) << "invalid compiled file header");
270 return {};
271 }
272
273 std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source, diag);
274 if (!resFile) {
275 return {};
276 }
277 return resFile;
278}
279
280struct ResourceFileFlattenerOptions {
281 bool noAutoVersion = false;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800282 bool noVersionVectors = false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800283 bool keepRawValues = false;
284 bool doNotCompressAnything = false;
Rohit Agrawale49bb302016-04-22 12:27:55 -0700285 bool updateProguardSpec = false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800286 std::vector<std::string> extensionsToNotCompress;
287};
288
289class ResourceFileFlattener {
290public:
291 ResourceFileFlattener(const ResourceFileFlattenerOptions& options,
292 IAaptContext* context, proguard::KeepSet* keepSet) :
293 mOptions(options), mContext(context), mKeepSet(keepSet) {
294 }
295
296 bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter);
297
298private:
299 struct FileOperation {
300 io::IFile* fileToCopy;
301 std::unique_ptr<xml::XmlResource> xmlToFlatten;
302 std::string dstPath;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800303 bool skipVersion = false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800304 };
305
306 uint32_t getCompressionFlags(const StringPiece& str);
307
Adam Lesinski626a69f2016-03-03 10:09:26 -0800308 bool linkAndVersionXmlFile(const ResourceEntry* entry, const ResourceFile& fileDesc,
309 io::IFile* file, ResourceTable* table, FileOperation* outFileOp);
Adam Lesinski355f2852016-02-13 20:26:45 -0800310
311 ResourceFileFlattenerOptions mOptions;
312 IAaptContext* mContext;
313 proguard::KeepSet* mKeepSet;
314};
315
316uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) {
317 if (mOptions.doNotCompressAnything) {
318 return 0;
319 }
320
321 for (const std::string& extension : mOptions.extensionsToNotCompress) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700322 if (util::stringEndsWith(str, extension)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800323 return 0;
324 }
325 }
326 return ArchiveEntry::kCompress;
327}
328
Adam Lesinski626a69f2016-03-03 10:09:26 -0800329bool ResourceFileFlattener::linkAndVersionXmlFile(const ResourceEntry* entry,
330 const ResourceFile& fileDesc,
331 io::IFile* file,
332 ResourceTable* table,
333 FileOperation* outFileOp) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800334 const StringPiece srcPath = file->getSource().path;
335 if (mContext->verbose()) {
336 mContext->getDiagnostics()->note(DiagMessage() << "linking " << srcPath);
337 }
338
339 std::unique_ptr<io::IData> data = file->openAsData();
340 if (!data) {
341 mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file");
Adam Lesinski626a69f2016-03-03 10:09:26 -0800342 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800343 }
344
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700345 if (util::stringEndsWith(srcPath, ".flat")) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800346 outFileOp->xmlToFlatten = loadBinaryXmlSkipFileExport(file->getSource(),
347 data->data(), data->size(),
348 mContext->getDiagnostics());
Adam Lesinski355f2852016-02-13 20:26:45 -0800349 } else {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800350 outFileOp->xmlToFlatten = xml::inflate(data->data(), data->size(),
351 mContext->getDiagnostics(),
352 file->getSource());
Adam Lesinski355f2852016-02-13 20:26:45 -0800353 }
354
Adam Lesinski626a69f2016-03-03 10:09:26 -0800355 if (!outFileOp->xmlToFlatten) {
356 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800357 }
358
359 // Copy the the file description header.
Adam Lesinski626a69f2016-03-03 10:09:26 -0800360 outFileOp->xmlToFlatten->file = fileDesc;
Adam Lesinski355f2852016-02-13 20:26:45 -0800361
362 XmlReferenceLinker xmlLinker;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800363 if (!xmlLinker.consume(mContext, outFileOp->xmlToFlatten.get())) {
364 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800365 }
366
Rohit Agrawale49bb302016-04-22 12:27:55 -0700367 if (mOptions.updateProguardSpec && !proguard::collectProguardRules(
368 outFileOp->xmlToFlatten->file.source, outFileOp->xmlToFlatten.get(), mKeepSet)) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800369 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800370 }
371
372 if (!mOptions.noAutoVersion) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800373 if (mOptions.noVersionVectors) {
374 // Skip this if it is a vector or animated-vector.
375 xml::Element* el = xml::findRootElement(outFileOp->xmlToFlatten.get());
376 if (el && el->namespaceUri.empty()) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700377 if (el->name == "vector" || el->name == "animated-vector") {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800378 // We are NOT going to version this file.
379 outFileOp->skipVersion = true;
380 return true;
381 }
382 }
383 }
384
Adam Lesinski355f2852016-02-13 20:26:45 -0800385 // Find the first SDK level used that is higher than this defined config and
386 // not superseded by a lower or equal SDK level resource.
387 for (int sdkLevel : xmlLinker.getSdkLevels()) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800388 if (sdkLevel > outFileOp->xmlToFlatten->file.config.sdkVersion) {
389 if (!shouldGenerateVersionedResource(entry, outFileOp->xmlToFlatten->file.config,
390 sdkLevel)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800391 // If we shouldn't generate a versioned resource, stop checking.
392 break;
393 }
394
Adam Lesinski626a69f2016-03-03 10:09:26 -0800395 ResourceFile versionedFileDesc = outFileOp->xmlToFlatten->file;
Adam Lesinski64587af2016-02-18 18:33:06 -0800396 versionedFileDesc.config.sdkVersion = (uint16_t) sdkLevel;
Adam Lesinski355f2852016-02-13 20:26:45 -0800397
398 if (mContext->verbose()) {
399 mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source)
400 << "auto-versioning resource from config '"
Adam Lesinski626a69f2016-03-03 10:09:26 -0800401 << outFileOp->xmlToFlatten->file.config
402 << "' -> '"
Adam Lesinski355f2852016-02-13 20:26:45 -0800403 << versionedFileDesc.config << "'");
404 }
405
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700406 std::string genPath = ResourceUtils::buildResourceFileName(
407 versionedFileDesc, mContext->getNameMangler());
Adam Lesinski355f2852016-02-13 20:26:45 -0800408
409 bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name,
410 versionedFileDesc.config,
411 versionedFileDesc.source,
412 genPath,
413 file,
414 mContext->getDiagnostics());
415 if (!added) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800416 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800417 }
418 break;
419 }
420 }
421 }
Adam Lesinski626a69f2016-03-03 10:09:26 -0800422 return true;
Adam Lesinski355f2852016-02-13 20:26:45 -0800423}
424
425/**
426 * Do not insert or remove any resources while executing in this function. It will
427 * corrupt the iteration order.
428 */
429bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiveWriter) {
430 bool error = false;
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700431 std::map<std::pair<ConfigDescription, StringPiece>, FileOperation> configSortedFiles;
Adam Lesinski355f2852016-02-13 20:26:45 -0800432
433 for (auto& pkg : table->packages) {
434 for (auto& type : pkg->types) {
435 // Sort by config and name, so that we get better locality in the zip file.
436 configSortedFiles.clear();
437 for (auto& entry : type->entries) {
438 // Iterate via indices because auto generated values can be inserted ahead of
439 // the value being processed.
440 for (size_t i = 0; i < entry->values.size(); i++) {
441 ResourceConfigValue* configValue = entry->values[i].get();
442
443 FileReference* fileRef = valueCast<FileReference>(configValue->value.get());
444 if (!fileRef) {
445 continue;
446 }
447
448 io::IFile* file = fileRef->file;
449 if (!file) {
450 mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource())
451 << "file not found");
452 return false;
453 }
454
455 FileOperation fileOp;
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700456 fileOp.dstPath = *fileRef->path;
Adam Lesinski355f2852016-02-13 20:26:45 -0800457
458 const StringPiece srcPath = file->getSource().path;
459 if (type->type != ResourceType::kRaw &&
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700460 (util::stringEndsWith(srcPath, ".xml.flat") ||
461 util::stringEndsWith(srcPath, ".xml"))) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800462 ResourceFile fileDesc;
463 fileDesc.config = configValue->config;
464 fileDesc.name = ResourceName(pkg->name, type->type, entry->name);
465 fileDesc.source = fileRef->getSource();
Adam Lesinski626a69f2016-03-03 10:09:26 -0800466 if (!linkAndVersionXmlFile(entry.get(), fileDesc, file, table, &fileOp)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800467 error = true;
468 continue;
469 }
470
471 } else {
472 fileOp.fileToCopy = file;
473 }
474
475 // NOTE(adamlesinski): Explicitly construct a StringPiece16 here, or else
476 // we end up copying the string in the std::make_pair() method, then creating
477 // a StringPiece16 from the copy, which would cause us to end up referencing
478 // garbage in the map.
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700479 const StringPiece entryName(entry->name);
Adam Lesinski355f2852016-02-13 20:26:45 -0800480 configSortedFiles[std::make_pair(configValue->config, entryName)] =
481 std::move(fileOp);
482 }
483 }
484
485 if (error) {
486 return false;
487 }
488
489 // Now flatten the sorted values.
490 for (auto& mapEntry : configSortedFiles) {
491 const ConfigDescription& config = mapEntry.first.first;
492 const FileOperation& fileOp = mapEntry.second;
493
494 if (fileOp.xmlToFlatten) {
495 Maybe<size_t> maxSdkLevel;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800496 if (!mOptions.noAutoVersion && !fileOp.skipVersion) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800497 maxSdkLevel = std::max<size_t>(config.sdkVersion, 1u);
498 }
499
500 bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel,
501 mOptions.keepRawValues,
502 archiveWriter, mContext);
503 if (!result) {
504 error = true;
505 }
506 } else {
507 bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath,
508 getCompressionFlags(fileOp.dstPath),
509 archiveWriter, mContext);
510 if (!result) {
511 error = true;
512 }
513 }
514 }
515 }
516 }
517 return !error;
518}
519
Adam Lesinskifb48d292015-11-07 15:52:13 -0800520class LinkCommand {
521public:
Adam Lesinski6a008172016-02-02 17:02:58 -0800522 LinkCommand(LinkContext* context, const LinkOptions& options) :
Adam Lesinski64587af2016-02-18 18:33:06 -0800523 mOptions(options), mContext(context), mFinalTable(),
524 mFileCollection(util::make_unique<io::FileCollection>()) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800525 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700526
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700527 /**
528 * Creates a SymbolTable that loads symbols from the various APKs and caches the
529 * results for faster lookup.
530 */
Adam Lesinski64587af2016-02-18 18:33:06 -0800531 bool loadSymbolsFromIncludePaths() {
532 std::unique_ptr<AssetManagerSymbolSource> assetSource =
533 util::make_unique<AssetManagerSymbolSource>();
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700534 for (const std::string& path : mOptions.includePaths) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800535 if (mContext->verbose()) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800536 mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700537 }
538
Adam Lesinski64587af2016-02-18 18:33:06 -0800539 // First try to load the file as a static lib.
540 std::string errorStr;
541 std::unique_ptr<ResourceTable> staticInclude = loadStaticLibrary(path, &errorStr);
542 if (staticInclude) {
543 if (!mOptions.staticLib) {
544 // Can't include static libraries when not building a static library.
545 mContext->getDiagnostics()->error(
546 DiagMessage(path) << "can't include static library when building app");
547 return false;
548 }
549
550 // If we are using --no-static-lib-packages, we need to rename the package of this
551 // table to our compilation package.
552 if (mOptions.noStaticLibPackages) {
553 if (ResourceTablePackage* pkg = staticInclude->findPackageById(0x7f)) {
554 pkg->name = mContext->getCompilationPackage();
555 }
556 }
557
558 mContext->getExternalSymbols()->appendSource(
559 util::make_unique<ResourceTableSymbolSource>(staticInclude.get()));
560
561 mStaticTableIncludes.push_back(std::move(staticInclude));
562
563 } else if (!errorStr.empty()) {
564 // We had an error with reading, so fail.
565 mContext->getDiagnostics()->error(DiagMessage(path) << errorStr);
566 return false;
567 }
568
569 if (!assetSource->addAssetPath(path)) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800570 mContext->getDiagnostics()->error(
Adam Lesinskifb48d292015-11-07 15:52:13 -0800571 DiagMessage(path) << "failed to load include path");
Adam Lesinski64587af2016-02-18 18:33:06 -0800572 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700573 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700574 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800575
576 mContext->getExternalSymbols()->appendSource(std::move(assetSource));
577 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700578 }
579
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700580 Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes, IDiagnostics* diag) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700581 // Make sure the first element is <manifest> with package attribute.
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800582 if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700583 AppInfo appInfo;
584
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700585 if (!manifestEl->namespaceUri.empty() || manifestEl->name != "manifest") {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700586 diag->error(DiagMessage(xmlRes->file.source) << "root tag must be <manifest>");
587 return {};
588 }
589
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700590 xml::Attribute* packageAttr = manifestEl->findAttribute({}, "package");
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700591 if (!packageAttr) {
592 diag->error(DiagMessage(xmlRes->file.source)
593 << "<manifest> must have a 'package' attribute");
594 return {};
595 }
596
597 appInfo.package = packageAttr->value;
598
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700599 if (xml::Element* usesSdkEl = manifestEl->findChild({}, "uses-sdk")) {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700600 if (xml::Attribute* minSdk =
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700601 usesSdkEl->findAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700602 appInfo.minSdkVersion = minSdk->value;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700603 }
604 }
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700605
606 return appInfo;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700607 }
608 return {};
609 }
610
Adam Lesinski979ccb22016-01-11 10:42:19 -0800611 /**
612 * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked.
613 * Postcondition: ResourceTable has only one package left. All others are stripped, or there
614 * is an error and false is returned.
615 */
Adam Lesinskifb48d292015-11-07 15:52:13 -0800616 bool verifyNoExternalPackages() {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800617 auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool {
Adam Lesinski6a008172016-02-02 17:02:58 -0800618 return mContext->getCompilationPackage() != pkg->name ||
Adam Lesinski979ccb22016-01-11 10:42:19 -0800619 !pkg->id ||
Adam Lesinski6a008172016-02-02 17:02:58 -0800620 pkg->id.value() != mContext->getPackageId();
Adam Lesinski979ccb22016-01-11 10:42:19 -0800621 };
622
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700623 bool error = false;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800624 for (const auto& package : mFinalTable.packages) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800625 if (isExtPackageFunc(package)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700626 // We have a package that is not related to the one we're building!
627 for (const auto& type : package->types) {
628 for (const auto& entry : type->entries) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800629 ResourceNameRef resName(package->name, type->type, entry->name);
630
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700631 for (const auto& configValue : entry->values) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800632 // Special case the occurrence of an ID that is being generated for the
633 // 'android' package. This is due to legacy reasons.
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800634 if (valueCast<Id>(configValue->value.get()) &&
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700635 package->name == "android") {
Adam Lesinski6a008172016-02-02 17:02:58 -0800636 mContext->getDiagnostics()->warn(
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800637 DiagMessage(configValue->value->getSource())
Adam Lesinski979ccb22016-01-11 10:42:19 -0800638 << "generated id '" << resName
639 << "' for external package '" << package->name
640 << "'");
641 } else {
Adam Lesinski6a008172016-02-02 17:02:58 -0800642 mContext->getDiagnostics()->error(
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800643 DiagMessage(configValue->value->getSource())
Adam Lesinski979ccb22016-01-11 10:42:19 -0800644 << "defined resource '" << resName
645 << "' for external package '" << package->name
646 << "'");
647 error = true;
648 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700649 }
650 }
651 }
652 }
653 }
Adam Lesinski979ccb22016-01-11 10:42:19 -0800654
655 auto newEndIter = std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(),
656 isExtPackageFunc);
657 mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700658 return !error;
659 }
660
Adam Lesinski64587af2016-02-18 18:33:06 -0800661 /**
662 * Returns true if no IDs have been set, false otherwise.
663 */
664 bool verifyNoIdsSet() {
665 for (const auto& package : mFinalTable.packages) {
666 for (const auto& type : package->types) {
667 if (type->id) {
668 mContext->getDiagnostics()->error(DiagMessage() << "type " << type->type
669 << " has ID " << std::hex
670 << (int) type->id.value()
671 << std::dec << " assigned");
672 return false;
673 }
674
675 for (const auto& entry : type->entries) {
676 if (entry->id) {
677 ResourceNameRef resName(package->name, type->type, entry->name);
678 mContext->getDiagnostics()->error(DiagMessage() << "entry " << resName
679 << " has ID " << std::hex
680 << (int) entry->id.value()
681 << std::dec << " assigned");
682 return false;
683 }
684 }
685 }
686 }
687 return true;
688 }
689
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700690 std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
691 if (mOptions.outputToDirectory) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800692 return createDirectoryArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700693 } else {
Adam Lesinski6a008172016-02-02 17:02:58 -0800694 return createZipFileArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700695 }
696 }
697
698 bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
699 BigBuffer buffer(1024);
Adam Lesinski59e04c62016-02-04 15:59:23 -0800700 TableFlattener flattener(&buffer);
Adam Lesinski6a008172016-02-02 17:02:58 -0800701 if (!flattener.consume(mContext, table)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700702 return false;
703 }
704
Adam Lesinskia40e9722015-11-24 19:11:46 -0800705 if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
706 if (writer->writeEntry(buffer)) {
707 if (writer->finishEntry()) {
708 return true;
709 }
710 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700711 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800712
Adam Lesinski6a008172016-02-02 17:02:58 -0800713 mContext->getDiagnostics()->error(
Adam Lesinskia40e9722015-11-24 19:11:46 -0800714 DiagMessage() << "failed to write resources.arsc to archive");
715 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700716 }
717
Adam Lesinski64587af2016-02-18 18:33:06 -0800718 bool flattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
719 // Create the file/zip entry.
720 if (!writer->startEntry("resources.arsc.flat", 0)) {
721 mContext->getDiagnostics()->error(DiagMessage() << "failed to open");
722 return false;
723 }
724
725 std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table);
726
727 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
728 // interface.
729 {
730 google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
731
732 if (!pbTable->SerializeToZeroCopyStream(&adaptor)) {
733 mContext->getDiagnostics()->error(DiagMessage() << "failed to write");
734 return false;
735 }
736 }
737
738 if (!writer->finishEntry()) {
739 mContext->getDiagnostics()->error(DiagMessage() << "failed to finish entry");
740 return false;
741 }
742 return true;
743 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700744
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700745 bool writeJavaFile(ResourceTable* table, const StringPiece& packageNameToGenerate,
746 const StringPiece& outPackage, JavaClassGeneratorOptions javaOptions) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700747 if (!mOptions.generateJavaClassPath) {
748 return true;
749 }
750
751 std::string outPath = mOptions.generateJavaClassPath.value();
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700752 file::appendPath(&outPath, file::packageToPath(outPackage));
Adam Lesinski96917c22016-03-09 13:11:25 -0800753 if (!file::mkdirs(outPath)) {
754 mContext->getDiagnostics()->error(
755 DiagMessage() << "failed to create directory '" << outPath << "'");
756 return false;
757 }
758
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700759 file::appendPath(&outPath, "R.java");
760
761 std::ofstream fout(outPath, std::ofstream::binary);
762 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800763 mContext->getDiagnostics()->error(
764 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700765 return false;
766 }
767
Adam Lesinski76565542016-03-10 21:55:04 -0800768 JavaClassGenerator generator(mContext, table, javaOptions);
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700769 if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800770 mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700771 return false;
772 }
Adam Lesinski96917c22016-03-09 13:11:25 -0800773
774 if (!fout) {
775 mContext->getDiagnostics()->error(
776 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
777 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700778 return true;
779 }
780
Adam Lesinski467f1712015-11-16 17:35:44 -0800781 bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
Adam Lesinskica5638f2015-10-21 14:42:43 -0700782 if (!mOptions.generateJavaClassPath) {
783 return true;
784 }
785
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700786 std::unique_ptr<ClassDefinition> manifestClass = generateManifestClass(
787 mContext->getDiagnostics(), manifestXml);
788
789 if (!manifestClass) {
790 // Something bad happened, but we already logged it, so exit.
791 return false;
792 }
793
794 if (manifestClass->empty()) {
795 // Empty Manifest class, no need to generate it.
796 return true;
797 }
798
Adam Lesinski3524a232016-04-01 19:19:24 -0700799 // Add any JavaDoc annotations to the generated class.
800 for (const std::string& annotation : mOptions.javadocAnnotations) {
801 std::string properAnnotation = "@";
802 properAnnotation += annotation;
803 manifestClass->getCommentBuilder()->appendComment(properAnnotation);
804 }
805
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700806 const std::string& packageUtf8 = mContext->getCompilationPackage();
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700807
Adam Lesinskica5638f2015-10-21 14:42:43 -0700808 std::string outPath = mOptions.generateJavaClassPath.value();
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700809 file::appendPath(&outPath, file::packageToPath(packageUtf8));
810
Adam Lesinski96917c22016-03-09 13:11:25 -0800811 if (!file::mkdirs(outPath)) {
812 mContext->getDiagnostics()->error(
813 DiagMessage() << "failed to create directory '" << outPath << "'");
814 return false;
815 }
816
Adam Lesinskica5638f2015-10-21 14:42:43 -0700817 file::appendPath(&outPath, "Manifest.java");
818
819 std::ofstream fout(outPath, std::ofstream::binary);
820 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800821 mContext->getDiagnostics()->error(
822 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinskica5638f2015-10-21 14:42:43 -0700823 return false;
824 }
825
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700826 if (!ClassDefinition::writeJavaFile(manifestClass.get(), packageUtf8, true, &fout)) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800827 mContext->getDiagnostics()->error(
828 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinskica5638f2015-10-21 14:42:43 -0700829 return false;
830 }
831 return true;
832 }
833
Rohit Agrawale49bb302016-04-22 12:27:55 -0700834 bool writeProguardFile(const Maybe<std::string>& out, const proguard::KeepSet& keepSet) {
835 if (!out) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700836 return true;
837 }
838
Rohit Agrawale49bb302016-04-22 12:27:55 -0700839 const std::string& outPath = out.value();
Adam Lesinski96917c22016-03-09 13:11:25 -0800840 std::ofstream fout(outPath, std::ofstream::binary);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700841 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800842 mContext->getDiagnostics()->error(
843 DiagMessage() << "failed to open '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700844 return false;
845 }
846
847 proguard::writeKeepSet(&fout, keepSet);
848 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800849 mContext->getDiagnostics()->error(
850 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700851 return false;
852 }
853 return true;
854 }
855
Adam Lesinski64587af2016-02-18 18:33:06 -0800856 std::unique_ptr<ResourceTable> loadStaticLibrary(const std::string& input,
857 std::string* outError) {
858 std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
859 input, outError);
860 if (!collection) {
861 return {};
862 }
863 return loadTablePbFromCollection(collection.get());
864 }
865
866 std::unique_ptr<ResourceTable> loadTablePbFromCollection(io::IFileCollection* collection) {
867 io::IFile* file = collection->findFile("resources.arsc.flat");
868 if (!file) {
869 return {};
870 }
871
872 std::unique_ptr<io::IData> data = file->openAsData();
873 return loadTableFromPb(file->getSource(), data->data(), data->size(),
874 mContext->getDiagnostics());
875 }
876
877 bool mergeStaticLibrary(const std::string& input, bool override) {
878 if (mContext->verbose()) {
879 mContext->getDiagnostics()->note(DiagMessage() << "merging static library " << input);
880 }
881
882 std::string errorStr;
883 std::unique_ptr<io::ZipFileCollection> collection =
884 io::ZipFileCollection::create(input, &errorStr);
885 if (!collection) {
886 mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
887 return false;
888 }
889
890 std::unique_ptr<ResourceTable> table = loadTablePbFromCollection(collection.get());
891 if (!table) {
892 mContext->getDiagnostics()->error(DiagMessage(input) << "invalid static library");
893 return false;
894 }
895
896 ResourceTablePackage* pkg = table->findPackageById(0x7f);
897 if (!pkg) {
898 mContext->getDiagnostics()->error(DiagMessage(input)
899 << "static library has no package");
900 return false;
901 }
902
903 bool result;
904 if (mOptions.noStaticLibPackages) {
905 // Merge all resources as if they were in the compilation package. This is the old
906 // behaviour of aapt.
907
908 // Add the package to the set of --extra-packages so we emit an R.java for each
909 // library package.
910 if (!pkg->name.empty()) {
911 mOptions.extraJavaPackages.insert(pkg->name);
912 }
913
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700914 pkg->name = "";
Adam Lesinski64587af2016-02-18 18:33:06 -0800915 if (override) {
916 result = mTableMerger->mergeOverlay(Source(input), table.get(), collection.get());
917 } else {
918 result = mTableMerger->merge(Source(input), table.get(), collection.get());
919 }
920
921 } else {
922 // This is the proper way to merge libraries, where the package name is preserved
923 // and resource names are mangled.
924 result = mTableMerger->mergeAndMangle(Source(input), pkg->name, table.get(),
925 collection.get());
926 }
927
928 if (!result) {
929 return false;
930 }
931
932 // Make sure to move the collection into the set of IFileCollections.
933 mCollections.push_back(std::move(collection));
Adam Lesinskifb48d292015-11-07 15:52:13 -0800934 return true;
935 }
936
Adam Lesinskia40e9722015-11-24 19:11:46 -0800937 bool mergeResourceTable(io::IFile* file, bool override) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800938 if (mContext->verbose()) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800939 mContext->getDiagnostics()->note(DiagMessage() << "merging resource table "
940 << file->getSource());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800941 }
942
Adam Lesinskia40e9722015-11-24 19:11:46 -0800943 std::unique_ptr<io::IData> data = file->openAsData();
944 if (!data) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800945 mContext->getDiagnostics()->error(DiagMessage(file->getSource())
Adam Lesinskia40e9722015-11-24 19:11:46 -0800946 << "failed to open file");
947 return false;
948 }
949
Adam Lesinski355f2852016-02-13 20:26:45 -0800950 std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(),
951 data->data(), data->size(),
952 mContext->getDiagnostics());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800953 if (!table) {
954 return false;
955 }
956
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800957 bool result = false;
958 if (override) {
959 result = mTableMerger->mergeOverlay(file->getSource(), table.get());
960 } else {
961 result = mTableMerger->merge(file->getSource(), table.get());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800962 }
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800963 return result;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800964 }
965
Adam Lesinski64587af2016-02-18 18:33:06 -0800966 bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc, bool override) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800967 if (mContext->verbose()) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800968 mContext->getDiagnostics()->note(DiagMessage() << "merging compiled file "
969 << file->getSource());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800970 }
971
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800972 bool result = false;
Adam Lesinski64587af2016-02-18 18:33:06 -0800973 if (override) {
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800974 result = mTableMerger->mergeFileOverlay(*fileDesc, file);
Adam Lesinskifb48d292015-11-07 15:52:13 -0800975 } else {
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800976 result = mTableMerger->mergeFile(*fileDesc, file);
Adam Lesinskifb48d292015-11-07 15:52:13 -0800977 }
978
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800979 if (!result) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800980 return false;
981 }
982
983 // Add the exports of this file to the table.
Adam Lesinskia40e9722015-11-24 19:11:46 -0800984 for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800985 if (exportedSymbol.name.package.empty()) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800986 exportedSymbol.name.package = mContext->getCompilationPackage();
Adam Lesinskifb48d292015-11-07 15:52:13 -0800987 }
988
989 ResourceNameRef resName = exportedSymbol.name;
990
Adam Lesinski6a008172016-02-02 17:02:58 -0800991 Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
Adam Lesinskifb48d292015-11-07 15:52:13 -0800992 exportedSymbol.name);
993 if (mangledName) {
994 resName = mangledName.value();
995 }
996
997 std::unique_ptr<Id> id = util::make_unique<Id>();
Adam Lesinskia40e9722015-11-24 19:11:46 -0800998 id->setSource(fileDesc->source.withLine(exportedSymbol.line));
Adam Lesinski64587af2016-02-18 18:33:06 -0800999 bool result = mFinalTable.addResourceAllowMangled(
1000 resName, ConfigDescription::defaultConfig(), std::string(), std::move(id),
1001 mContext->getDiagnostics());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001002 if (!result) {
1003 return false;
1004 }
1005 }
Adam Lesinskifb48d292015-11-07 15:52:13 -08001006 return true;
1007 }
1008
Adam Lesinskia40e9722015-11-24 19:11:46 -08001009 /**
Adam Lesinski64587af2016-02-18 18:33:06 -08001010 * Takes a path to load as a ZIP file and merges the files within into the master ResourceTable.
1011 * If override is true, conflicting resources are allowed to override each other, in order of
1012 * last seen.
1013 *
1014 * An io::IFileCollection is created from the ZIP file and added to the set of
1015 * io::IFileCollections that are open.
Adam Lesinskia40e9722015-11-24 19:11:46 -08001016 */
1017 bool mergeArchive(const std::string& input, bool override) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001018 if (mContext->verbose()) {
1019 mContext->getDiagnostics()->note(DiagMessage() << "merging archive " << input);
1020 }
1021
Adam Lesinskia40e9722015-11-24 19:11:46 -08001022 std::string errorStr;
Adam Lesinski64587af2016-02-18 18:33:06 -08001023 std::unique_ptr<io::ZipFileCollection> collection =
1024 io::ZipFileCollection::create(input, &errorStr);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001025 if (!collection) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001026 mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001027 return false;
1028 }
1029
1030 bool error = false;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001031 for (auto iter = collection->iterator(); iter->hasNext(); ) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001032 if (!mergeFile(iter->next(), override)) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001033 error = true;
1034 }
1035 }
1036
1037 // Make sure to move the collection into the set of IFileCollections.
1038 mCollections.push_back(std::move(collection));
1039 return !error;
1040 }
1041
Adam Lesinski64587af2016-02-18 18:33:06 -08001042 /**
1043 * Takes a path to load and merge into the master ResourceTable. If override is true,
1044 * conflicting resources are allowed to override each other, in order of last seen.
1045 *
1046 * If the file path ends with .flata, .jar, .jack, or .zip the file is treated as ZIP archive
1047 * and the files within are merged individually.
1048 *
1049 * Otherwise the files is processed on its own.
1050 */
1051 bool mergePath(const std::string& path, bool override) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001052 if (util::stringEndsWith(path, ".flata") ||
1053 util::stringEndsWith(path, ".jar") ||
1054 util::stringEndsWith(path, ".jack") ||
1055 util::stringEndsWith(path, ".zip")) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001056 return mergeArchive(path, override);
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001057 } else if (util::stringEndsWith(path, ".apk")) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001058 return mergeStaticLibrary(path, override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001059 }
1060
1061 io::IFile* file = mFileCollection->insertFile(path);
Adam Lesinski64587af2016-02-18 18:33:06 -08001062 return mergeFile(file, override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001063 }
1064
Adam Lesinski64587af2016-02-18 18:33:06 -08001065 /**
1066 * Takes a file to load and merge into the master ResourceTable. If override is true,
1067 * conflicting resources are allowed to override each other, in order of last seen.
1068 *
1069 * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and merged into the
1070 * master ResourceTable. If the file ends with .flat, then it is treated like a compiled file
1071 * and the header data is read and merged into the final ResourceTable.
1072 *
1073 * All other file types are ignored. This is because these files could be coming from a zip,
1074 * where we could have other files like classes.dex.
1075 */
1076 bool mergeFile(io::IFile* file, bool override) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001077 const Source& src = file->getSource();
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001078 if (util::stringEndsWith(src.path, ".arsc.flat")) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001079 return mergeResourceTable(file, override);
Adam Lesinski64587af2016-02-18 18:33:06 -08001080
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001081 } else if (util::stringEndsWith(src.path, ".flat")){
Adam Lesinskia40e9722015-11-24 19:11:46 -08001082 // Try opening the file and looking for an Export header.
1083 std::unique_ptr<io::IData> data = file->openAsData();
1084 if (!data) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001085 mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open");
Adam Lesinskia40e9722015-11-24 19:11:46 -08001086 return false;
1087 }
1088
1089 std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader(
Adam Lesinski6a008172016-02-02 17:02:58 -08001090 src, data->data(), data->size(), mContext->getDiagnostics());
Adam Lesinskia40e9722015-11-24 19:11:46 -08001091 if (resourceFile) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001092 return mergeCompiledFile(file, resourceFile.get(), override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001093 }
Adam Lesinskic446a732016-01-21 11:04:46 -08001094 return false;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001095 }
Adam Lesinski52364f72016-01-11 13:10:24 -08001096
Adam Lesinskic446a732016-01-21 11:04:46 -08001097 // Ignore non .flat files. This could be classes.dex or something else that happens
1098 // to be in an archive.
1099 return true;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001100 }
1101
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001102 int run(const std::vector<std::string>& inputFiles) {
1103 // Load the AndroidManifest.xml
Adam Lesinskia40e9722015-11-24 19:11:46 -08001104 std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
Adam Lesinski6a008172016-02-02 17:02:58 -08001105 mContext->getDiagnostics());
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001106 if (!manifestXml) {
1107 return 1;
1108 }
1109
Adam Lesinskifb6312f2016-06-28 14:40:32 -07001110 if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(),
1111 mContext->getDiagnostics())) {
1112 AppInfo& appInfo = maybeAppInfo.value();
1113 mContext->setCompilationPackage(appInfo.package);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001114 } else {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001115 return 1;
1116 }
1117
Adam Lesinski64587af2016-02-18 18:33:06 -08001118 if (!util::isJavaPackageName(mContext->getCompilationPackage())) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001119 mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001120 << "invalid package name '"
Adam Lesinski64587af2016-02-18 18:33:06 -08001121 << mContext->getCompilationPackage()
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001122 << "'");
1123 return 1;
1124 }
1125
Adam Lesinski64587af2016-02-18 18:33:06 -08001126 mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() });
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001127
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001128 if (mContext->getCompilationPackage() == "android") {
Adam Lesinski64587af2016-02-18 18:33:06 -08001129 mContext->setPackageId(0x01);
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001130 } else {
Adam Lesinski64587af2016-02-18 18:33:06 -08001131 mContext->setPackageId(0x7f);
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001132 }
1133
Adam Lesinski64587af2016-02-18 18:33:06 -08001134 if (!loadSymbolsFromIncludePaths()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001135 return 1;
1136 }
1137
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001138 TableMergerOptions tableMergerOptions;
1139 tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
Adam Lesinski6a008172016-02-02 17:02:58 -08001140 mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions);
Adam Lesinskifb48d292015-11-07 15:52:13 -08001141
Adam Lesinski355f2852016-02-13 20:26:45 -08001142 if (mContext->verbose()) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001143 mContext->getDiagnostics()->note(
Adam Lesinski64587af2016-02-18 18:33:06 -08001144 DiagMessage() << "linking package '" << mContext->getCompilationPackage()
1145 << "' with package ID " << std::hex
1146 << (int) mContext->getPackageId());
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001147 }
1148
Adam Lesinskifb48d292015-11-07 15:52:13 -08001149
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001150 for (const std::string& input : inputFiles) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001151 if (!mergePath(input, false)) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001152 mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input");
Adam Lesinski467f1712015-11-16 17:35:44 -08001153 return 1;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001154 }
1155 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001156
Adam Lesinskifb48d292015-11-07 15:52:13 -08001157 for (const std::string& input : mOptions.overlayFiles) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001158 if (!mergePath(input, true)) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001159 mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
Adam Lesinski467f1712015-11-16 17:35:44 -08001160 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001161 }
1162 }
1163
Adam Lesinskifb48d292015-11-07 15:52:13 -08001164 if (!verifyNoExternalPackages()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001165 return 1;
1166 }
1167
1168 if (!mOptions.staticLib) {
1169 PrivateAttributeMover mover;
Adam Lesinski6a008172016-02-02 17:02:58 -08001170 if (!mover.consume(mContext, &mFinalTable)) {
1171 mContext->getDiagnostics()->error(
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001172 DiagMessage() << "failed moving private attributes");
1173 return 1;
1174 }
1175 }
1176
Adam Lesinski64587af2016-02-18 18:33:06 -08001177 if (!mOptions.staticLib) {
1178 // Assign IDs if we are building a regular app.
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001179 IdAssigner idAssigner;
Adam Lesinski6a008172016-02-02 17:02:58 -08001180 if (!idAssigner.consume(mContext, &mFinalTable)) {
1181 mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001182 return 1;
1183 }
Adam Lesinski64587af2016-02-18 18:33:06 -08001184 } else {
1185 // Static libs are merged with other apps, and ID collisions are bad, so verify that
1186 // no IDs have been set.
1187 if (!verifyNoIdsSet()) {
1188 return 1;
1189 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001190 }
1191
Adam Lesinski64587af2016-02-18 18:33:06 -08001192 // Add the names to mangle based on our source merge earlier.
1193 mContext->setNameManglerPolicy(NameManglerPolicy{
1194 mContext->getCompilationPackage(), mTableMerger->getMergedPackages() });
1195
1196 // Add our table to the symbol table.
1197 mContext->getExternalSymbols()->prependSource(
1198 util::make_unique<ResourceTableSymbolSource>(&mFinalTable));
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001199
1200 {
1201 ReferenceLinker linker;
Adam Lesinski6a008172016-02-02 17:02:58 -08001202 if (!linker.consume(mContext, &mFinalTable)) {
1203 mContext->getDiagnostics()->error(DiagMessage() << "failed linking references");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001204 return 1;
1205 }
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001206
Adam Lesinski64587af2016-02-18 18:33:06 -08001207 if (mOptions.staticLib) {
1208 if (!mOptions.products.empty()) {
1209 mContext->getDiagnostics()->warn(
1210 DiagMessage() << "can't select products when building static library");
1211 }
Adam Lesinski355f2852016-02-13 20:26:45 -08001212
Adam Lesinski64587af2016-02-18 18:33:06 -08001213 if (mOptions.tableSplitterOptions.configFilter != nullptr ||
1214 mOptions.tableSplitterOptions.preferredDensity) {
1215 mContext->getDiagnostics()->warn(
1216 DiagMessage() << "can't strip resources when building static library");
1217 }
1218 } else {
1219 ProductFilter productFilter(mOptions.products);
1220 if (!productFilter.consume(mContext, &mFinalTable)) {
1221 mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products");
1222 return 1;
1223 }
Adam Lesinski355f2852016-02-13 20:26:45 -08001224
Adam Lesinski64587af2016-02-18 18:33:06 -08001225 // TODO(adamlesinski): Actually pass in split constraints and handle splits at the file
1226 // level.
1227 TableSplitter tableSplitter({}, mOptions.tableSplitterOptions);
1228 if (!tableSplitter.verifySplitConstraints(mContext)) {
1229 return 1;
1230 }
1231 tableSplitter.splitTable(&mFinalTable);
1232 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001233 }
1234
1235 proguard::KeepSet proguardKeepSet;
Rohit Agrawale49bb302016-04-22 12:27:55 -07001236 proguard::KeepSet proguardMainDexKeepSet;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001237
1238 std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter();
1239 if (!archiveWriter) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001240 mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001241 return 1;
1242 }
1243
Adam Lesinski467f1712015-11-16 17:35:44 -08001244 bool error = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001245 {
Adam Lesinski52364f72016-01-11 13:10:24 -08001246 ManifestFixer manifestFixer(mOptions.manifestFixerOptions);
Adam Lesinski6a008172016-02-02 17:02:58 -08001247 if (!manifestFixer.consume(mContext, manifestXml.get())) {
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001248 error = true;
1249 }
1250
Adam Lesinski467f1712015-11-16 17:35:44 -08001251 // AndroidManifest.xml has no resource name, but the CallSite is built from the name
1252 // (aka, which package the AndroidManifest.xml is coming from).
1253 // So we give it a package name so it can see local resources.
Adam Lesinski64587af2016-02-18 18:33:06 -08001254 manifestXml->file.name.package = mContext->getCompilationPackage();
Adam Lesinski467f1712015-11-16 17:35:44 -08001255
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001256 XmlReferenceLinker manifestLinker;
Adam Lesinski6a008172016-02-02 17:02:58 -08001257 if (manifestLinker.consume(mContext, manifestXml.get())) {
Rohit Agrawale49bb302016-04-22 12:27:55 -07001258 if (mOptions.generateProguardRulesPath &&
1259 !proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
1260 manifestXml.get(),
1261 &proguardKeepSet)) {
1262 error = true;
1263 }
1264
1265 if (mOptions.generateMainDexProguardRulesPath &&
1266 !proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
1267 manifestXml.get(),
1268 &proguardMainDexKeepSet,
1269 true)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001270 error = true;
1271 }
1272
Adam Lesinskica5638f2015-10-21 14:42:43 -07001273 if (mOptions.generateJavaClassPath) {
1274 if (!writeManifestJavaFile(manifestXml.get())) {
1275 error = true;
1276 }
1277 }
1278
Adam Lesinski355f2852016-02-13 20:26:45 -08001279 const bool keepRawValues = mOptions.staticLib;
1280 bool result = flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
1281 keepRawValues, archiveWriter.get(), mContext);
1282 if (!result) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001283 error = true;
1284 }
1285 } else {
1286 error = true;
1287 }
1288 }
1289
Adam Lesinski467f1712015-11-16 17:35:44 -08001290 if (error) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001291 mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest");
Adam Lesinski467f1712015-11-16 17:35:44 -08001292 return 1;
1293 }
1294
Adam Lesinski355f2852016-02-13 20:26:45 -08001295 ResourceFileFlattenerOptions fileFlattenerOptions;
1296 fileFlattenerOptions.keepRawValues = mOptions.staticLib;
1297 fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything;
1298 fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress;
1299 fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion;
Adam Lesinski626a69f2016-03-03 10:09:26 -08001300 fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors;
Rohit Agrawale49bb302016-04-22 12:27:55 -07001301 fileFlattenerOptions.updateProguardSpec =
1302 static_cast<bool>(mOptions.generateProguardRulesPath);
Adam Lesinski355f2852016-02-13 20:26:45 -08001303 ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, &proguardKeepSet);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001304
Adam Lesinski355f2852016-02-13 20:26:45 -08001305 if (!fileFlattener.flatten(&mFinalTable, archiveWriter.get())) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001306 mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001307 return 1;
1308 }
1309
Adam Lesinski626a69f2016-03-03 10:09:26 -08001310 if (!mOptions.noAutoVersion) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001311 AutoVersioner versioner;
Adam Lesinski6a008172016-02-02 17:02:58 -08001312 if (!versioner.consume(mContext, &mFinalTable)) {
1313 mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001314 return 1;
1315 }
1316 }
1317
Adam Lesinskifb6312f2016-06-28 14:40:32 -07001318 Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(),
1319 mContext->getDiagnostics());
1320 if (maybeAppInfo && maybeAppInfo.value().minSdkVersion) {
1321 if (Maybe<int> maybeMinSdkVersion =
1322 ResourceUtils::tryParseSdkVersion(maybeAppInfo.value().minSdkVersion.value())) {
1323 mContext->setMinSdkVersion(maybeMinSdkVersion.value());
1324 }
1325 }
1326
1327 if (!mOptions.staticLib && mContext->getMinSdkVersion() > 0) {
1328 if (mContext->verbose()) {
1329 mContext->getDiagnostics()->note(
1330 DiagMessage() << "collapsing resource versions for minimum SDK "
1331 << mContext->getMinSdkVersion());
1332 }
1333
1334 VersionCollapser collapser;
1335 if (!collapser.consume(mContext, &mFinalTable)) {
1336 return 1;
1337 }
1338 }
1339
Adam Lesinski64587af2016-02-18 18:33:06 -08001340 if (mOptions.staticLib) {
1341 if (!flattenTableToPb(&mFinalTable, archiveWriter.get())) {
1342 mContext->getDiagnostics()->error(DiagMessage()
1343 << "failed to write resources.arsc.flat");
1344 return 1;
1345 }
1346 } else {
1347 if (!flattenTable(&mFinalTable, archiveWriter.get())) {
1348 mContext->getDiagnostics()->error(DiagMessage()
1349 << "failed to write resources.arsc");
1350 return 1;
1351 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001352 }
1353
1354 if (mOptions.generateJavaClassPath) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001355 JavaClassGeneratorOptions options;
Adam Lesinski52364f72016-01-11 13:10:24 -08001356 options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
Adam Lesinski3524a232016-04-01 19:19:24 -07001357 options.javadocAnnotations = mOptions.javadocAnnotations;
Adam Lesinski52364f72016-01-11 13:10:24 -08001358
Adam Lesinskief9c5012016-01-22 14:09:53 -08001359 if (mOptions.staticLib || mOptions.generateNonFinalIds) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001360 options.useFinal = false;
1361 }
1362
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001363 const StringPiece actualPackage = mContext->getCompilationPackage();
1364 StringPiece outputPackage = mContext->getCompilationPackage();
Adam Lesinski52364f72016-01-11 13:10:24 -08001365 if (mOptions.customJavaPackage) {
1366 // Override the output java package to the custom one.
1367 outputPackage = mOptions.customJavaPackage.value();
1368 }
Adam Lesinski83f22552015-11-07 11:51:23 -08001369
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001370 if (mOptions.privateSymbols) {
1371 // If we defined a private symbols package, we only emit Public symbols
1372 // to the original package, and private and public symbols to the private package.
1373
1374 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
Adam Lesinski6a008172016-02-02 17:02:58 -08001375 if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(),
Adam Lesinski52364f72016-01-11 13:10:24 -08001376 outputPackage, options)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001377 return 1;
1378 }
1379
1380 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
Adam Lesinski83f22552015-11-07 11:51:23 -08001381 outputPackage = mOptions.privateSymbols.value();
1382 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001383
Adam Lesinskifb48d292015-11-07 15:52:13 -08001384 if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
Adam Lesinski83f22552015-11-07 11:51:23 -08001385 return 1;
1386 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001387
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001388 for (const std::string& extraPackage : mOptions.extraJavaPackages) {
Adam Lesinski52364f72016-01-11 13:10:24 -08001389 if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001390 return 1;
1391 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001392 }
1393 }
1394
Rohit Agrawale49bb302016-04-22 12:27:55 -07001395 if (!writeProguardFile(mOptions.generateProguardRulesPath, proguardKeepSet)) {
1396 return 1;
1397 }
1398
1399 if (!writeProguardFile(mOptions.generateMainDexProguardRulesPath, proguardMainDexKeepSet)) {
1400 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001401 }
1402
Adam Lesinski355f2852016-02-13 20:26:45 -08001403 if (mContext->verbose()) {
1404 DebugPrintTableOptions debugPrintTableOptions;
1405 debugPrintTableOptions.showSources = true;
1406 Debug::printTable(&mFinalTable, debugPrintTableOptions);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001407 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001408 return 0;
1409 }
Adam Lesinskifb48d292015-11-07 15:52:13 -08001410
1411private:
1412 LinkOptions mOptions;
Adam Lesinski6a008172016-02-02 17:02:58 -08001413 LinkContext* mContext;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001414 ResourceTable mFinalTable;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001415
Adam Lesinskifb48d292015-11-07 15:52:13 -08001416 std::unique_ptr<TableMerger> mTableMerger;
1417
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001418 // A pointer to the FileCollection representing the filesystem (not archives).
Adam Lesinski64587af2016-02-18 18:33:06 -08001419 std::unique_ptr<io::FileCollection> mFileCollection;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001420
1421 // A vector of IFileCollections. This is mainly here to keep ownership of the collections.
Adam Lesinskia40e9722015-11-24 19:11:46 -08001422 std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
Adam Lesinski64587af2016-02-18 18:33:06 -08001423
1424 // A vector of ResourceTables. This is here to retain ownership, so that the SymbolTable
1425 // can use these.
1426 std::vector<std::unique_ptr<ResourceTable>> mStaticTableIncludes;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001427};
1428
1429int link(const std::vector<StringPiece>& args) {
Adam Lesinski355f2852016-02-13 20:26:45 -08001430 LinkContext context;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001431 LinkOptions options;
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001432 std::vector<std::string> overlayArgList;
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001433 std::vector<std::string> extraJavaPackages;
Adam Lesinski6a008172016-02-02 17:02:58 -08001434 Maybe<std::string> configs;
Adam Lesinski355f2852016-02-13 20:26:45 -08001435 Maybe<std::string> preferredDensity;
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001436 Maybe<std::string> productList;
Adam Lesinski8900aa82016-01-25 22:48:15 -08001437 bool legacyXFlag = false;
1438 bool requireLocalization = false;
Adam Lesinski64587af2016-02-18 18:33:06 -08001439 bool verbose = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001440 Flags flags = Flags()
1441 .requiredFlag("-o", "Output path", &options.outputPath)
1442 .requiredFlag("--manifest", "Path to the Android manifest to build",
1443 &options.manifestPath)
1444 .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
Adam Lesinski52364f72016-01-11 13:10:24 -08001445 .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n"
Adam Lesinskifb48d292015-11-07 15:52:13 -08001446 "The last conflicting resource given takes precedence.",
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001447 &overlayArgList)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001448 .optionalFlag("--java", "Directory in which to generate R.java",
1449 &options.generateJavaClassPath)
1450 .optionalFlag("--proguard", "Output file for generated Proguard rules",
1451 &options.generateProguardRulesPath)
Rohit Agrawale49bb302016-04-22 12:27:55 -07001452 .optionalFlag("--proguard-main-dex",
1453 "Output file for generated Proguard rules for the main dex",
1454 &options.generateMainDexProguardRulesPath)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001455 .optionalSwitch("--no-auto-version",
1456 "Disables automatic style and layout SDK versioning",
1457 &options.noAutoVersion)
Adam Lesinski64587af2016-02-18 18:33:06 -08001458 .optionalSwitch("--no-version-vectors",
1459 "Disables automatic versioning of vector drawables. Use this only\n"
1460 "when building with vector drawable support library",
1461 &options.noVersionVectors)
Adam Lesinski8900aa82016-01-25 22:48:15 -08001462 .optionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01",
1463 &legacyXFlag)
1464 .optionalSwitch("-z", "Require localization of strings marked 'suggested'",
1465 &requireLocalization)
Adam Lesinski6a008172016-02-02 17:02:58 -08001466 .optionalFlag("-c", "Comma separated list of configurations to include. The default\n"
1467 "is all configurations", &configs)
Adam Lesinski355f2852016-02-13 20:26:45 -08001468 .optionalFlag("--preferred-density",
1469 "Selects the closest matching density and strips out all others.",
1470 &preferredDensity)
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001471 .optionalFlag("--product", "Comma separated list of product names to keep",
1472 &productList)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001473 .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
1474 "by -o",
1475 &options.outputToDirectory)
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001476 .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001477 "AndroidManifest.xml",
1478 &options.manifestFixerOptions.minSdkVersionDefault)
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001479 .optionalFlag("--target-sdk-version", "Default target SDK version to use for "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001480 "AndroidManifest.xml",
1481 &options.manifestFixerOptions.targetSdkVersionDefault)
Adam Lesinski52364f72016-01-11 13:10:24 -08001482 .optionalFlag("--version-code", "Version code (integer) to inject into the "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001483 "AndroidManifest.xml if none is present",
1484 &options.manifestFixerOptions.versionCodeDefault)
Adam Lesinski52364f72016-01-11 13:10:24 -08001485 .optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001486 "if none is present",
1487 &options.manifestFixerOptions.versionNameDefault)
1488 .optionalSwitch("--static-lib", "Generate a static Android library",
1489 &options.staticLib)
Adam Lesinski64587af2016-02-18 18:33:06 -08001490 .optionalSwitch("--no-static-lib-packages",
1491 "Merge all library resources under the app's package",
1492 &options.noStaticLibPackages)
Adam Lesinskief9c5012016-01-22 14:09:53 -08001493 .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n"
1494 "This is implied when --static-lib is specified.",
1495 &options.generateNonFinalIds)
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001496 .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001497 "private symbols.\n"
1498 "If not specified, public and private symbols will use the application's "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001499 "package name",
1500 &options.privateSymbols)
Adam Lesinski52364f72016-01-11 13:10:24 -08001501 .optionalFlag("--custom-package", "Custom Java package under which to generate R.java",
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001502 &options.customJavaPackage)
Adam Lesinski83f22552015-11-07 11:51:23 -08001503 .optionalFlagList("--extra-packages", "Generate the same R.java but with different "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001504 "package names",
1505 &extraJavaPackages)
Adam Lesinski3524a232016-04-01 19:19:24 -07001506 .optionalFlagList("--add-javadoc-annotation", "Adds a JavaDoc annotation to all "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001507 "generated Java classes",
1508 &options.javadocAnnotations)
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001509 .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001510 "overlays without <add-resource> tags",
1511 &options.autoAddOverlay)
Adam Lesinski52364f72016-01-11 13:10:24 -08001512 .optionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml",
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001513 &options.manifestFixerOptions.renameManifestPackage)
Adam Lesinski52364f72016-01-11 13:10:24 -08001514 .optionalFlag("--rename-instrumentation-target-package",
1515 "Changes the name of the target package for instrumentation. Most useful "
1516 "when used\nin conjunction with --rename-manifest-package",
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001517 &options.manifestFixerOptions.renameInstrumentationTargetPackage)
Adam Lesinski52364f72016-01-11 13:10:24 -08001518 .optionalFlagList("-0", "File extensions not to compress",
1519 &options.extensionsToNotCompress)
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001520 .optionalSwitch("-v", "Enables verbose logging",
1521 &verbose);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001522
1523 if (!flags.parse("aapt2 link", args, &std::cerr)) {
1524 return 1;
1525 }
1526
Adam Lesinskic51562c2016-04-28 11:12:38 -07001527 // Expand all argument-files passed into the command line. These start with '@'.
1528 std::vector<std::string> argList;
1529 for (const std::string& arg : flags.getArgs()) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001530 if (util::stringStartsWith(arg, "@")) {
Adam Lesinskic51562c2016-04-28 11:12:38 -07001531 const std::string path = arg.substr(1, arg.size() - 1);
1532 std::string error;
1533 if (!file::appendArgsFromFile(path, &argList, &error)) {
1534 context.getDiagnostics()->error(DiagMessage(path) << error);
1535 return 1;
1536 }
1537 } else {
1538 argList.push_back(arg);
1539 }
1540 }
1541
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001542 // Expand all argument-files passed to -R.
1543 for (const std::string& arg : overlayArgList) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001544 if (util::stringStartsWith(arg, "@")) {
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001545 const std::string path = arg.substr(1, arg.size() - 1);
1546 std::string error;
1547 if (!file::appendArgsFromFile(path, &options.overlayFiles, &error)) {
1548 context.getDiagnostics()->error(DiagMessage(path) << error);
1549 return 1;
1550 }
1551 } else {
1552 options.overlayFiles.push_back(arg);
1553 }
1554 }
1555
Adam Lesinski64587af2016-02-18 18:33:06 -08001556 if (verbose) {
1557 context.setVerbose(verbose);
1558 }
1559
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001560 // Populate the set of extra packages for which to generate R.java.
1561 for (std::string& extraPackage : extraJavaPackages) {
1562 // A given package can actually be a colon separated list of packages.
1563 for (StringPiece package : util::split(extraPackage, ':')) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001564 options.extraJavaPackages.insert(package.toString());
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001565 }
1566 }
1567
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001568 if (productList) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001569 for (StringPiece product : util::tokenize(productList.value(), ',')) {
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001570 if (product != "" && product != "default") {
1571 options.products.insert(product.toString());
1572 }
1573 }
1574 }
1575
Adam Lesinski6a008172016-02-02 17:02:58 -08001576 AxisConfigFilter filter;
1577 if (configs) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001578 for (const StringPiece& configStr : util::tokenize(configs.value(), ',')) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001579 ConfigDescription config;
1580 LocaleValue lv;
1581 if (lv.initFromFilterString(configStr)) {
1582 lv.writeTo(&config);
1583 } else if (!ConfigDescription::parse(configStr, &config)) {
1584 context.getDiagnostics()->error(
1585 DiagMessage() << "invalid config '" << configStr << "' for -c option");
1586 return 1;
1587 }
1588
1589 if (config.density != 0) {
1590 context.getDiagnostics()->warn(
1591 DiagMessage() << "ignoring density '" << config << "' for -c option");
1592 } else {
1593 filter.addConfig(config);
1594 }
1595 }
1596
Adam Lesinski355f2852016-02-13 20:26:45 -08001597 options.tableSplitterOptions.configFilter = &filter;
1598 }
1599
1600 if (preferredDensity) {
1601 ConfigDescription preferredDensityConfig;
1602 if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) {
1603 context.getDiagnostics()->error(DiagMessage() << "invalid density '"
1604 << preferredDensity.value()
1605 << "' for --preferred-density option");
1606 return 1;
1607 }
1608
1609 // Clear the version that can be automatically added.
1610 preferredDensityConfig.sdkVersion = 0;
1611
1612 if (preferredDensityConfig.diff(ConfigDescription::defaultConfig())
1613 != ConfigDescription::CONFIG_DENSITY) {
1614 context.getDiagnostics()->error(DiagMessage() << "invalid preferred density '"
1615 << preferredDensity.value() << "'. "
1616 << "Preferred density must only be a density value");
1617 return 1;
1618 }
1619 options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density;
Adam Lesinski6a008172016-02-02 17:02:58 -08001620 }
1621
Adam Lesinski626a69f2016-03-03 10:09:26 -08001622 // Turn off auto versioning for static-libs.
1623 if (options.staticLib) {
1624 options.noAutoVersion = true;
1625 options.noVersionVectors = true;
1626 }
1627
Adam Lesinski6a008172016-02-02 17:02:58 -08001628 LinkCommand cmd(&context, options);
Adam Lesinskic51562c2016-04-28 11:12:38 -07001629 return cmd.run(argList);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001630}
1631
1632} // namespace aapt