blob: b84074d1cb58f559cf2e6591809d462bdf579c06 [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 Lesinski52364f72016-01-11 13:10:24 -080061 Maybe<std::u16string> customJavaPackage;
62 std::set<std::u16string> extraJavaPackages;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070063 Maybe<std::string> generateProguardRulesPath;
64 bool noAutoVersion = false;
Adam Lesinski64587af2016-02-18 18:33:06 -080065 bool noVersionVectors = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070066 bool staticLib = false;
Adam Lesinski64587af2016-02-18 18:33:06 -080067 bool noStaticLibPackages = false;
Adam Lesinskief9c5012016-01-22 14:09:53 -080068 bool generateNonFinalIds = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070069 bool outputToDirectory = false;
Adam Lesinskia6fe3452015-12-09 15:20:52 -080070 bool autoAddOverlay = false;
Adam Lesinski52364f72016-01-11 13:10:24 -080071 bool doNotCompressAnything = false;
72 std::vector<std::string> extensionsToNotCompress;
Adam Lesinski9e10ac72015-10-16 14:37:48 -070073 Maybe<std::u16string> privateSymbols;
Adam Lesinski52364f72016-01-11 13:10:24 -080074 ManifestFixerOptions manifestFixerOptions;
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -080075 std::unordered_set<std::string> products;
Adam Lesinski355f2852016-02-13 20:26:45 -080076 TableSplitterOptions tableSplitterOptions;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070077};
78
Adam Lesinski64587af2016-02-18 18:33:06 -080079class LinkContext : public IAaptContext {
80public:
81 LinkContext() : mNameMangler({}) {
82 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -070083
84 IDiagnostics* getDiagnostics() override {
85 return &mDiagnostics;
86 }
87
88 NameMangler* getNameMangler() override {
Adam Lesinski64587af2016-02-18 18:33:06 -080089 return &mNameMangler;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070090 }
91
Adam Lesinski64587af2016-02-18 18:33:06 -080092 void setNameManglerPolicy(const NameManglerPolicy& policy) {
93 mNameMangler = NameMangler(policy);
94 }
95
96 const std::u16string& getCompilationPackage() override {
Adam Lesinski1ab598f2015-08-14 14:26:04 -070097 return mCompilationPackage;
98 }
99
Adam Lesinski64587af2016-02-18 18:33:06 -0800100 void setCompilationPackage(const StringPiece16& packageName) {
101 mCompilationPackage = packageName.toString();
102 }
103
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700104 uint8_t getPackageId() override {
105 return mPackageId;
106 }
107
Adam Lesinski64587af2016-02-18 18:33:06 -0800108 void setPackageId(uint8_t id) {
109 mPackageId = id;
110 }
111
112 SymbolTable* getExternalSymbols() override {
113 return &mSymbols;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700114 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800115
116 bool verbose() override {
117 return mVerbose;
118 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800119
120 void setVerbose(bool val) {
121 mVerbose = val;
122 }
123
124private:
125 StdErrDiagnostics mDiagnostics;
126 NameMangler mNameMangler;
127 std::u16string mCompilationPackage;
128 uint8_t mPackageId = 0x0;
129 SymbolTable mSymbols;
130 bool mVerbose = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700131};
132
Adam Lesinski355f2852016-02-13 20:26:45 -0800133static bool copyFileToArchive(io::IFile* file, const std::string& outPath,
134 uint32_t compressionFlags,
135 IArchiveWriter* writer, IAaptContext* context) {
136 std::unique_ptr<io::IData> data = file->openAsData();
137 if (!data) {
138 context->getDiagnostics()->error(DiagMessage(file->getSource())
139 << "failed to open file");
140 return false;
141 }
142
Adam Lesinski64587af2016-02-18 18:33:06 -0800143 const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data());
144 size_t bufferSize = data->size();
145
146 // If the file ends with .flat, we must strip off the CompiledFileHeader from it.
147 if (util::stringEndsWith<char>(file->getSource().path, ".flat")) {
148 CompiledFileInputStream inputStream(data->data(), data->size());
149 if (!inputStream.CompiledFile()) {
150 context->getDiagnostics()->error(DiagMessage(file->getSource())
151 << "invalid compiled file header");
152 return false;
153 }
154 buffer = reinterpret_cast<const uint8_t*>(inputStream.data());
155 bufferSize = inputStream.size();
Adam Lesinski355f2852016-02-13 20:26:45 -0800156 }
157
158 if (context->verbose()) {
159 context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive");
160 }
161
162 if (writer->startEntry(outPath, compressionFlags)) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800163 if (writer->writeEntry(buffer, bufferSize)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800164 if (writer->finishEntry()) {
165 return true;
166 }
167 }
168 }
169
170 context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath);
171 return false;
172}
173
174static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
175 bool keepRawValues, IArchiveWriter* writer, IAaptContext* context) {
176 BigBuffer buffer(1024);
177 XmlFlattenerOptions options = {};
178 options.keepRawValues = keepRawValues;
179 options.maxSdkLevel = maxSdkLevel;
180 XmlFlattener flattener(&buffer, options);
181 if (!flattener.consume(context, xmlRes)) {
182 return false;
183 }
184
185 if (context->verbose()) {
186 DiagMessage msg;
187 msg << "writing " << path << " to archive";
188 if (maxSdkLevel) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800189 msg << " maxSdkLevel=" << maxSdkLevel.value() << " keepRawValues=" << keepRawValues;
Adam Lesinski355f2852016-02-13 20:26:45 -0800190 }
191 context->getDiagnostics()->note(msg);
192 }
193
194 if (writer->startEntry(path, ArchiveEntry::kCompress)) {
195 if (writer->writeEntry(buffer)) {
196 if (writer->finishEntry()) {
197 return true;
198 }
199 }
200 }
201 context->getDiagnostics()->error(DiagMessage() << "failed to write " << path << " to archive");
202 return false;
203}
204
205/*static std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len,
206 IDiagnostics* diag) {
207 std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
208 BinaryResourceParser parser(diag, table.get(), source, data, len);
209 if (!parser.parse()) {
210 return {};
211 }
212 return table;
213}*/
214
215static std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
216 const void* data, size_t len,
217 IDiagnostics* diag) {
218 pb::ResourceTable pbTable;
219 if (!pbTable.ParseFromArray(data, len)) {
220 diag->error(DiagMessage(source) << "invalid compiled table");
221 return {};
222 }
223
224 std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source, diag);
225 if (!table) {
226 return {};
227 }
228 return table;
229}
230
231/**
232 * Inflates an XML file from the source path.
233 */
234static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
235 std::ifstream fin(path, std::ifstream::binary);
236 if (!fin) {
237 diag->error(DiagMessage(path) << strerror(errno));
238 return {};
239 }
240 return xml::inflate(&fin, diag, Source(path));
241}
242
243static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const Source& source,
244 const void* data, size_t len,
245 IDiagnostics* diag) {
246 CompiledFileInputStream inputStream(data, len);
247 if (!inputStream.CompiledFile()) {
248 diag->error(DiagMessage(source) << "invalid compiled file header");
249 return {};
250 }
251
252 const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data());
253 const size_t xmlDataLen = inputStream.size();
254
255 std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source);
256 if (!xmlRes) {
257 return {};
258 }
259 return xmlRes;
260}
261
262static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
263 const void* data, size_t len,
264 IDiagnostics* diag) {
265 CompiledFileInputStream inputStream(data, len);
266 const pb::CompiledFile* pbFile = inputStream.CompiledFile();
267 if (!pbFile) {
268 diag->error(DiagMessage(source) << "invalid compiled file header");
269 return {};
270 }
271
272 std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source, diag);
273 if (!resFile) {
274 return {};
275 }
276 return resFile;
277}
278
279struct ResourceFileFlattenerOptions {
280 bool noAutoVersion = false;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800281 bool noVersionVectors = false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800282 bool keepRawValues = false;
283 bool doNotCompressAnything = false;
284 std::vector<std::string> extensionsToNotCompress;
285};
286
287class ResourceFileFlattener {
288public:
289 ResourceFileFlattener(const ResourceFileFlattenerOptions& options,
290 IAaptContext* context, proguard::KeepSet* keepSet) :
291 mOptions(options), mContext(context), mKeepSet(keepSet) {
292 }
293
294 bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter);
295
296private:
297 struct FileOperation {
298 io::IFile* fileToCopy;
299 std::unique_ptr<xml::XmlResource> xmlToFlatten;
300 std::string dstPath;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800301 bool skipVersion = false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800302 };
303
304 uint32_t getCompressionFlags(const StringPiece& str);
305
Adam Lesinski626a69f2016-03-03 10:09:26 -0800306 bool linkAndVersionXmlFile(const ResourceEntry* entry, const ResourceFile& fileDesc,
307 io::IFile* file, ResourceTable* table, FileOperation* outFileOp);
Adam Lesinski355f2852016-02-13 20:26:45 -0800308
309 ResourceFileFlattenerOptions mOptions;
310 IAaptContext* mContext;
311 proguard::KeepSet* mKeepSet;
312};
313
314uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) {
315 if (mOptions.doNotCompressAnything) {
316 return 0;
317 }
318
319 for (const std::string& extension : mOptions.extensionsToNotCompress) {
320 if (util::stringEndsWith<char>(str, extension)) {
321 return 0;
322 }
323 }
324 return ArchiveEntry::kCompress;
325}
326
Adam Lesinski626a69f2016-03-03 10:09:26 -0800327bool ResourceFileFlattener::linkAndVersionXmlFile(const ResourceEntry* entry,
328 const ResourceFile& fileDesc,
329 io::IFile* file,
330 ResourceTable* table,
331 FileOperation* outFileOp) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800332 const StringPiece srcPath = file->getSource().path;
333 if (mContext->verbose()) {
334 mContext->getDiagnostics()->note(DiagMessage() << "linking " << srcPath);
335 }
336
337 std::unique_ptr<io::IData> data = file->openAsData();
338 if (!data) {
339 mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file");
Adam Lesinski626a69f2016-03-03 10:09:26 -0800340 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800341 }
342
Adam Lesinski355f2852016-02-13 20:26:45 -0800343 if (util::stringEndsWith<char>(srcPath, ".flat")) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800344 outFileOp->xmlToFlatten = loadBinaryXmlSkipFileExport(file->getSource(),
345 data->data(), data->size(),
346 mContext->getDiagnostics());
Adam Lesinski355f2852016-02-13 20:26:45 -0800347 } else {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800348 outFileOp->xmlToFlatten = xml::inflate(data->data(), data->size(),
349 mContext->getDiagnostics(),
350 file->getSource());
Adam Lesinski355f2852016-02-13 20:26:45 -0800351 }
352
Adam Lesinski626a69f2016-03-03 10:09:26 -0800353 if (!outFileOp->xmlToFlatten) {
354 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800355 }
356
357 // Copy the the file description header.
Adam Lesinski626a69f2016-03-03 10:09:26 -0800358 outFileOp->xmlToFlatten->file = fileDesc;
Adam Lesinski355f2852016-02-13 20:26:45 -0800359
360 XmlReferenceLinker xmlLinker;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800361 if (!xmlLinker.consume(mContext, outFileOp->xmlToFlatten.get())) {
362 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800363 }
364
Adam Lesinski626a69f2016-03-03 10:09:26 -0800365 if (!proguard::collectProguardRules(outFileOp->xmlToFlatten->file.source,
366 outFileOp->xmlToFlatten.get(), mKeepSet)) {
367 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800368 }
369
370 if (!mOptions.noAutoVersion) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800371 if (mOptions.noVersionVectors) {
372 // Skip this if it is a vector or animated-vector.
373 xml::Element* el = xml::findRootElement(outFileOp->xmlToFlatten.get());
374 if (el && el->namespaceUri.empty()) {
375 if (el->name == u"vector" || el->name == u"animated-vector") {
376 // We are NOT going to version this file.
377 outFileOp->skipVersion = true;
378 return true;
379 }
380 }
381 }
382
Adam Lesinski355f2852016-02-13 20:26:45 -0800383 // Find the first SDK level used that is higher than this defined config and
384 // not superseded by a lower or equal SDK level resource.
385 for (int sdkLevel : xmlLinker.getSdkLevels()) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800386 if (sdkLevel > outFileOp->xmlToFlatten->file.config.sdkVersion) {
387 if (!shouldGenerateVersionedResource(entry, outFileOp->xmlToFlatten->file.config,
388 sdkLevel)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800389 // If we shouldn't generate a versioned resource, stop checking.
390 break;
391 }
392
Adam Lesinski626a69f2016-03-03 10:09:26 -0800393 ResourceFile versionedFileDesc = outFileOp->xmlToFlatten->file;
Adam Lesinski64587af2016-02-18 18:33:06 -0800394 versionedFileDesc.config.sdkVersion = (uint16_t) sdkLevel;
Adam Lesinski355f2852016-02-13 20:26:45 -0800395
396 if (mContext->verbose()) {
397 mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source)
398 << "auto-versioning resource from config '"
Adam Lesinski626a69f2016-03-03 10:09:26 -0800399 << outFileOp->xmlToFlatten->file.config
400 << "' -> '"
Adam Lesinski355f2852016-02-13 20:26:45 -0800401 << versionedFileDesc.config << "'");
402 }
403
404 std::u16string genPath = util::utf8ToUtf16(ResourceUtils::buildResourceFileName(
405 versionedFileDesc, mContext->getNameMangler()));
406
407 bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name,
408 versionedFileDesc.config,
409 versionedFileDesc.source,
410 genPath,
411 file,
412 mContext->getDiagnostics());
413 if (!added) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800414 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800415 }
416 break;
417 }
418 }
419 }
Adam Lesinski626a69f2016-03-03 10:09:26 -0800420 return true;
Adam Lesinski355f2852016-02-13 20:26:45 -0800421}
422
423/**
424 * Do not insert or remove any resources while executing in this function. It will
425 * corrupt the iteration order.
426 */
427bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiveWriter) {
428 bool error = false;
429 std::map<std::pair<ConfigDescription, StringPiece16>, FileOperation> configSortedFiles;
430
431 for (auto& pkg : table->packages) {
432 for (auto& type : pkg->types) {
433 // Sort by config and name, so that we get better locality in the zip file.
434 configSortedFiles.clear();
435 for (auto& entry : type->entries) {
436 // Iterate via indices because auto generated values can be inserted ahead of
437 // the value being processed.
438 for (size_t i = 0; i < entry->values.size(); i++) {
439 ResourceConfigValue* configValue = entry->values[i].get();
440
441 FileReference* fileRef = valueCast<FileReference>(configValue->value.get());
442 if (!fileRef) {
443 continue;
444 }
445
446 io::IFile* file = fileRef->file;
447 if (!file) {
448 mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource())
449 << "file not found");
450 return false;
451 }
452
453 FileOperation fileOp;
454 fileOp.dstPath = util::utf16ToUtf8(*fileRef->path);
455
456 const StringPiece srcPath = file->getSource().path;
457 if (type->type != ResourceType::kRaw &&
458 (util::stringEndsWith<char>(srcPath, ".xml.flat") ||
459 util::stringEndsWith<char>(srcPath, ".xml"))) {
460 ResourceFile fileDesc;
461 fileDesc.config = configValue->config;
462 fileDesc.name = ResourceName(pkg->name, type->type, entry->name);
463 fileDesc.source = fileRef->getSource();
Adam Lesinski626a69f2016-03-03 10:09:26 -0800464 if (!linkAndVersionXmlFile(entry.get(), fileDesc, file, table, &fileOp)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800465 error = true;
466 continue;
467 }
468
469 } else {
470 fileOp.fileToCopy = file;
471 }
472
473 // NOTE(adamlesinski): Explicitly construct a StringPiece16 here, or else
474 // we end up copying the string in the std::make_pair() method, then creating
475 // a StringPiece16 from the copy, which would cause us to end up referencing
476 // garbage in the map.
477 const StringPiece16 entryName(entry->name);
478 configSortedFiles[std::make_pair(configValue->config, entryName)] =
479 std::move(fileOp);
480 }
481 }
482
483 if (error) {
484 return false;
485 }
486
487 // Now flatten the sorted values.
488 for (auto& mapEntry : configSortedFiles) {
489 const ConfigDescription& config = mapEntry.first.first;
490 const FileOperation& fileOp = mapEntry.second;
491
492 if (fileOp.xmlToFlatten) {
493 Maybe<size_t> maxSdkLevel;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800494 if (!mOptions.noAutoVersion && !fileOp.skipVersion) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800495 maxSdkLevel = std::max<size_t>(config.sdkVersion, 1u);
496 }
497
498 bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel,
499 mOptions.keepRawValues,
500 archiveWriter, mContext);
501 if (!result) {
502 error = true;
503 }
504 } else {
505 bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath,
506 getCompressionFlags(fileOp.dstPath),
507 archiveWriter, mContext);
508 if (!result) {
509 error = true;
510 }
511 }
512 }
513 }
514 }
515 return !error;
516}
517
Adam Lesinskifb48d292015-11-07 15:52:13 -0800518class LinkCommand {
519public:
Adam Lesinski6a008172016-02-02 17:02:58 -0800520 LinkCommand(LinkContext* context, const LinkOptions& options) :
Adam Lesinski64587af2016-02-18 18:33:06 -0800521 mOptions(options), mContext(context), mFinalTable(),
522 mFileCollection(util::make_unique<io::FileCollection>()) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800523 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700524
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700525 /**
526 * Creates a SymbolTable that loads symbols from the various APKs and caches the
527 * results for faster lookup.
528 */
Adam Lesinski64587af2016-02-18 18:33:06 -0800529 bool loadSymbolsFromIncludePaths() {
530 std::unique_ptr<AssetManagerSymbolSource> assetSource =
531 util::make_unique<AssetManagerSymbolSource>();
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700532 for (const std::string& path : mOptions.includePaths) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800533 if (mContext->verbose()) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800534 mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700535 }
536
Adam Lesinski64587af2016-02-18 18:33:06 -0800537 // First try to load the file as a static lib.
538 std::string errorStr;
539 std::unique_ptr<ResourceTable> staticInclude = loadStaticLibrary(path, &errorStr);
540 if (staticInclude) {
541 if (!mOptions.staticLib) {
542 // Can't include static libraries when not building a static library.
543 mContext->getDiagnostics()->error(
544 DiagMessage(path) << "can't include static library when building app");
545 return false;
546 }
547
548 // If we are using --no-static-lib-packages, we need to rename the package of this
549 // table to our compilation package.
550 if (mOptions.noStaticLibPackages) {
551 if (ResourceTablePackage* pkg = staticInclude->findPackageById(0x7f)) {
552 pkg->name = mContext->getCompilationPackage();
553 }
554 }
555
556 mContext->getExternalSymbols()->appendSource(
557 util::make_unique<ResourceTableSymbolSource>(staticInclude.get()));
558
559 mStaticTableIncludes.push_back(std::move(staticInclude));
560
561 } else if (!errorStr.empty()) {
562 // We had an error with reading, so fail.
563 mContext->getDiagnostics()->error(DiagMessage(path) << errorStr);
564 return false;
565 }
566
567 if (!assetSource->addAssetPath(path)) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800568 mContext->getDiagnostics()->error(
Adam Lesinskifb48d292015-11-07 15:52:13 -0800569 DiagMessage(path) << "failed to load include path");
Adam Lesinski64587af2016-02-18 18:33:06 -0800570 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700571 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700572 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800573
574 mContext->getExternalSymbols()->appendSource(std::move(assetSource));
575 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700576 }
577
Adam Lesinski467f1712015-11-16 17:35:44 -0800578 Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700579 // Make sure the first element is <manifest> with package attribute.
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800580 if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700581 if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") {
582 if (xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package")) {
583 return AppInfo{ packageAttr->value };
584 }
585 }
586 }
587 return {};
588 }
589
Adam Lesinski979ccb22016-01-11 10:42:19 -0800590 /**
591 * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked.
592 * Postcondition: ResourceTable has only one package left. All others are stripped, or there
593 * is an error and false is returned.
594 */
Adam Lesinskifb48d292015-11-07 15:52:13 -0800595 bool verifyNoExternalPackages() {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800596 auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool {
Adam Lesinski6a008172016-02-02 17:02:58 -0800597 return mContext->getCompilationPackage() != pkg->name ||
Adam Lesinski979ccb22016-01-11 10:42:19 -0800598 !pkg->id ||
Adam Lesinski6a008172016-02-02 17:02:58 -0800599 pkg->id.value() != mContext->getPackageId();
Adam Lesinski979ccb22016-01-11 10:42:19 -0800600 };
601
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700602 bool error = false;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800603 for (const auto& package : mFinalTable.packages) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800604 if (isExtPackageFunc(package)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700605 // We have a package that is not related to the one we're building!
606 for (const auto& type : package->types) {
607 for (const auto& entry : type->entries) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800608 ResourceNameRef resName(package->name, type->type, entry->name);
609
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700610 for (const auto& configValue : entry->values) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800611 // Special case the occurrence of an ID that is being generated for the
612 // 'android' package. This is due to legacy reasons.
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800613 if (valueCast<Id>(configValue->value.get()) &&
Adam Lesinski979ccb22016-01-11 10:42:19 -0800614 package->name == u"android") {
Adam Lesinski6a008172016-02-02 17:02:58 -0800615 mContext->getDiagnostics()->warn(
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800616 DiagMessage(configValue->value->getSource())
Adam Lesinski979ccb22016-01-11 10:42:19 -0800617 << "generated id '" << resName
618 << "' for external package '" << package->name
619 << "'");
620 } else {
Adam Lesinski6a008172016-02-02 17:02:58 -0800621 mContext->getDiagnostics()->error(
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800622 DiagMessage(configValue->value->getSource())
Adam Lesinski979ccb22016-01-11 10:42:19 -0800623 << "defined resource '" << resName
624 << "' for external package '" << package->name
625 << "'");
626 error = true;
627 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700628 }
629 }
630 }
631 }
632 }
Adam Lesinski979ccb22016-01-11 10:42:19 -0800633
634 auto newEndIter = std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(),
635 isExtPackageFunc);
636 mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700637 return !error;
638 }
639
Adam Lesinski64587af2016-02-18 18:33:06 -0800640 /**
641 * Returns true if no IDs have been set, false otherwise.
642 */
643 bool verifyNoIdsSet() {
644 for (const auto& package : mFinalTable.packages) {
645 for (const auto& type : package->types) {
646 if (type->id) {
647 mContext->getDiagnostics()->error(DiagMessage() << "type " << type->type
648 << " has ID " << std::hex
649 << (int) type->id.value()
650 << std::dec << " assigned");
651 return false;
652 }
653
654 for (const auto& entry : type->entries) {
655 if (entry->id) {
656 ResourceNameRef resName(package->name, type->type, entry->name);
657 mContext->getDiagnostics()->error(DiagMessage() << "entry " << resName
658 << " has ID " << std::hex
659 << (int) entry->id.value()
660 << std::dec << " assigned");
661 return false;
662 }
663 }
664 }
665 }
666 return true;
667 }
668
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700669 std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
670 if (mOptions.outputToDirectory) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800671 return createDirectoryArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700672 } else {
Adam Lesinski6a008172016-02-02 17:02:58 -0800673 return createZipFileArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700674 }
675 }
676
677 bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
678 BigBuffer buffer(1024);
Adam Lesinski59e04c62016-02-04 15:59:23 -0800679 TableFlattener flattener(&buffer);
Adam Lesinski6a008172016-02-02 17:02:58 -0800680 if (!flattener.consume(mContext, table)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700681 return false;
682 }
683
Adam Lesinskia40e9722015-11-24 19:11:46 -0800684 if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
685 if (writer->writeEntry(buffer)) {
686 if (writer->finishEntry()) {
687 return true;
688 }
689 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700690 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800691
Adam Lesinski6a008172016-02-02 17:02:58 -0800692 mContext->getDiagnostics()->error(
Adam Lesinskia40e9722015-11-24 19:11:46 -0800693 DiagMessage() << "failed to write resources.arsc to archive");
694 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700695 }
696
Adam Lesinski64587af2016-02-18 18:33:06 -0800697 bool flattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
698 // Create the file/zip entry.
699 if (!writer->startEntry("resources.arsc.flat", 0)) {
700 mContext->getDiagnostics()->error(DiagMessage() << "failed to open");
701 return false;
702 }
703
704 std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table);
705
706 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
707 // interface.
708 {
709 google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
710
711 if (!pbTable->SerializeToZeroCopyStream(&adaptor)) {
712 mContext->getDiagnostics()->error(DiagMessage() << "failed to write");
713 return false;
714 }
715 }
716
717 if (!writer->finishEntry()) {
718 mContext->getDiagnostics()->error(DiagMessage() << "failed to finish entry");
719 return false;
720 }
721 return true;
722 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700723
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700724 bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
725 const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700726 if (!mOptions.generateJavaClassPath) {
727 return true;
728 }
729
730 std::string outPath = mOptions.generateJavaClassPath.value();
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700731 file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(outPackage)));
Adam Lesinski96917c22016-03-09 13:11:25 -0800732 if (!file::mkdirs(outPath)) {
733 mContext->getDiagnostics()->error(
734 DiagMessage() << "failed to create directory '" << outPath << "'");
735 return false;
736 }
737
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700738 file::appendPath(&outPath, "R.java");
739
740 std::ofstream fout(outPath, std::ofstream::binary);
741 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800742 mContext->getDiagnostics()->error(
743 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700744 return false;
745 }
746
Adam Lesinski76565542016-03-10 21:55:04 -0800747 JavaClassGenerator generator(mContext, table, javaOptions);
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700748 if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800749 mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700750 return false;
751 }
Adam Lesinski96917c22016-03-09 13:11:25 -0800752
753 if (!fout) {
754 mContext->getDiagnostics()->error(
755 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
756 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700757 return true;
758 }
759
Adam Lesinski467f1712015-11-16 17:35:44 -0800760 bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
Adam Lesinskica5638f2015-10-21 14:42:43 -0700761 if (!mOptions.generateJavaClassPath) {
762 return true;
763 }
764
765 std::string outPath = mOptions.generateJavaClassPath.value();
766 file::appendPath(&outPath,
Adam Lesinski6a008172016-02-02 17:02:58 -0800767 file::packageToPath(util::utf16ToUtf8(mContext->getCompilationPackage())));
Adam Lesinski96917c22016-03-09 13:11:25 -0800768 if (!file::mkdirs(outPath)) {
769 mContext->getDiagnostics()->error(
770 DiagMessage() << "failed to create directory '" << outPath << "'");
771 return false;
772 }
773
Adam Lesinskica5638f2015-10-21 14:42:43 -0700774 file::appendPath(&outPath, "Manifest.java");
775
776 std::ofstream fout(outPath, std::ofstream::binary);
777 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800778 mContext->getDiagnostics()->error(
779 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinskica5638f2015-10-21 14:42:43 -0700780 return false;
781 }
782
783 ManifestClassGenerator generator;
Adam Lesinski6a008172016-02-02 17:02:58 -0800784 if (!generator.generate(mContext->getDiagnostics(), mContext->getCompilationPackage(),
Adam Lesinskica5638f2015-10-21 14:42:43 -0700785 manifestXml, &fout)) {
786 return false;
787 }
788
789 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800790 mContext->getDiagnostics()->error(
791 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinskica5638f2015-10-21 14:42:43 -0700792 return false;
793 }
794 return true;
795 }
796
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700797 bool writeProguardFile(const proguard::KeepSet& keepSet) {
798 if (!mOptions.generateProguardRulesPath) {
799 return true;
800 }
801
Adam Lesinski96917c22016-03-09 13:11:25 -0800802 const std::string& outPath = mOptions.generateProguardRulesPath.value();
803 std::ofstream fout(outPath, std::ofstream::binary);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700804 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800805 mContext->getDiagnostics()->error(
806 DiagMessage() << "failed to open '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700807 return false;
808 }
809
810 proguard::writeKeepSet(&fout, keepSet);
811 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800812 mContext->getDiagnostics()->error(
813 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700814 return false;
815 }
816 return true;
817 }
818
Adam Lesinski64587af2016-02-18 18:33:06 -0800819 std::unique_ptr<ResourceTable> loadStaticLibrary(const std::string& input,
820 std::string* outError) {
821 std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
822 input, outError);
823 if (!collection) {
824 return {};
825 }
826 return loadTablePbFromCollection(collection.get());
827 }
828
829 std::unique_ptr<ResourceTable> loadTablePbFromCollection(io::IFileCollection* collection) {
830 io::IFile* file = collection->findFile("resources.arsc.flat");
831 if (!file) {
832 return {};
833 }
834
835 std::unique_ptr<io::IData> data = file->openAsData();
836 return loadTableFromPb(file->getSource(), data->data(), data->size(),
837 mContext->getDiagnostics());
838 }
839
840 bool mergeStaticLibrary(const std::string& input, bool override) {
841 if (mContext->verbose()) {
842 mContext->getDiagnostics()->note(DiagMessage() << "merging static library " << input);
843 }
844
845 std::string errorStr;
846 std::unique_ptr<io::ZipFileCollection> collection =
847 io::ZipFileCollection::create(input, &errorStr);
848 if (!collection) {
849 mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
850 return false;
851 }
852
853 std::unique_ptr<ResourceTable> table = loadTablePbFromCollection(collection.get());
854 if (!table) {
855 mContext->getDiagnostics()->error(DiagMessage(input) << "invalid static library");
856 return false;
857 }
858
859 ResourceTablePackage* pkg = table->findPackageById(0x7f);
860 if (!pkg) {
861 mContext->getDiagnostics()->error(DiagMessage(input)
862 << "static library has no package");
863 return false;
864 }
865
866 bool result;
867 if (mOptions.noStaticLibPackages) {
868 // Merge all resources as if they were in the compilation package. This is the old
869 // behaviour of aapt.
870
871 // Add the package to the set of --extra-packages so we emit an R.java for each
872 // library package.
873 if (!pkg->name.empty()) {
874 mOptions.extraJavaPackages.insert(pkg->name);
875 }
876
877 pkg->name = u"";
878 if (override) {
879 result = mTableMerger->mergeOverlay(Source(input), table.get(), collection.get());
880 } else {
881 result = mTableMerger->merge(Source(input), table.get(), collection.get());
882 }
883
884 } else {
885 // This is the proper way to merge libraries, where the package name is preserved
886 // and resource names are mangled.
887 result = mTableMerger->mergeAndMangle(Source(input), pkg->name, table.get(),
888 collection.get());
889 }
890
891 if (!result) {
892 return false;
893 }
894
895 // Make sure to move the collection into the set of IFileCollections.
896 mCollections.push_back(std::move(collection));
Adam Lesinskifb48d292015-11-07 15:52:13 -0800897 return true;
898 }
899
Adam Lesinskia40e9722015-11-24 19:11:46 -0800900 bool mergeResourceTable(io::IFile* file, bool override) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800901 if (mContext->verbose()) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800902 mContext->getDiagnostics()->note(DiagMessage() << "merging resource table "
903 << file->getSource());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800904 }
905
Adam Lesinskia40e9722015-11-24 19:11:46 -0800906 std::unique_ptr<io::IData> data = file->openAsData();
907 if (!data) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800908 mContext->getDiagnostics()->error(DiagMessage(file->getSource())
Adam Lesinskia40e9722015-11-24 19:11:46 -0800909 << "failed to open file");
910 return false;
911 }
912
Adam Lesinski355f2852016-02-13 20:26:45 -0800913 std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(),
914 data->data(), data->size(),
915 mContext->getDiagnostics());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800916 if (!table) {
917 return false;
918 }
919
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800920 bool result = false;
921 if (override) {
922 result = mTableMerger->mergeOverlay(file->getSource(), table.get());
923 } else {
924 result = mTableMerger->merge(file->getSource(), table.get());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800925 }
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800926 return result;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800927 }
928
Adam Lesinski64587af2016-02-18 18:33:06 -0800929 bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc, bool override) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800930 if (mContext->verbose()) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800931 mContext->getDiagnostics()->note(DiagMessage() << "merging compiled file "
932 << file->getSource());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800933 }
934
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800935 bool result = false;
Adam Lesinski64587af2016-02-18 18:33:06 -0800936 if (override) {
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800937 result = mTableMerger->mergeFileOverlay(*fileDesc, file);
Adam Lesinskifb48d292015-11-07 15:52:13 -0800938 } else {
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800939 result = mTableMerger->mergeFile(*fileDesc, file);
Adam Lesinskifb48d292015-11-07 15:52:13 -0800940 }
941
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800942 if (!result) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800943 return false;
944 }
945
946 // Add the exports of this file to the table.
Adam Lesinskia40e9722015-11-24 19:11:46 -0800947 for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800948 if (exportedSymbol.name.package.empty()) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800949 exportedSymbol.name.package = mContext->getCompilationPackage();
Adam Lesinskifb48d292015-11-07 15:52:13 -0800950 }
951
952 ResourceNameRef resName = exportedSymbol.name;
953
Adam Lesinski6a008172016-02-02 17:02:58 -0800954 Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
Adam Lesinskifb48d292015-11-07 15:52:13 -0800955 exportedSymbol.name);
956 if (mangledName) {
957 resName = mangledName.value();
958 }
959
960 std::unique_ptr<Id> id = util::make_unique<Id>();
Adam Lesinskia40e9722015-11-24 19:11:46 -0800961 id->setSource(fileDesc->source.withLine(exportedSymbol.line));
Adam Lesinski64587af2016-02-18 18:33:06 -0800962 bool result = mFinalTable.addResourceAllowMangled(
963 resName, ConfigDescription::defaultConfig(), std::string(), std::move(id),
964 mContext->getDiagnostics());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800965 if (!result) {
966 return false;
967 }
968 }
Adam Lesinskifb48d292015-11-07 15:52:13 -0800969 return true;
970 }
971
Adam Lesinskia40e9722015-11-24 19:11:46 -0800972 /**
Adam Lesinski64587af2016-02-18 18:33:06 -0800973 * Takes a path to load as a ZIP file and merges the files within into the master ResourceTable.
974 * If override is true, conflicting resources are allowed to override each other, in order of
975 * last seen.
976 *
977 * An io::IFileCollection is created from the ZIP file and added to the set of
978 * io::IFileCollections that are open.
Adam Lesinskia40e9722015-11-24 19:11:46 -0800979 */
980 bool mergeArchive(const std::string& input, bool override) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800981 if (mContext->verbose()) {
982 mContext->getDiagnostics()->note(DiagMessage() << "merging archive " << input);
983 }
984
Adam Lesinskia40e9722015-11-24 19:11:46 -0800985 std::string errorStr;
Adam Lesinski64587af2016-02-18 18:33:06 -0800986 std::unique_ptr<io::ZipFileCollection> collection =
987 io::ZipFileCollection::create(input, &errorStr);
Adam Lesinskia40e9722015-11-24 19:11:46 -0800988 if (!collection) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800989 mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
Adam Lesinskia40e9722015-11-24 19:11:46 -0800990 return false;
991 }
992
993 bool error = false;
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800994 for (auto iter = collection->iterator(); iter->hasNext(); ) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800995 if (!mergeFile(iter->next(), override)) {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800996 error = true;
997 }
998 }
999
1000 // Make sure to move the collection into the set of IFileCollections.
1001 mCollections.push_back(std::move(collection));
1002 return !error;
1003 }
1004
Adam Lesinski64587af2016-02-18 18:33:06 -08001005 /**
1006 * Takes a path to load and merge into the master ResourceTable. If override is true,
1007 * conflicting resources are allowed to override each other, in order of last seen.
1008 *
1009 * If the file path ends with .flata, .jar, .jack, or .zip the file is treated as ZIP archive
1010 * and the files within are merged individually.
1011 *
1012 * Otherwise the files is processed on its own.
1013 */
1014 bool mergePath(const std::string& path, bool override) {
Adam Lesinski656a5772016-01-14 15:17:41 -08001015 if (util::stringEndsWith<char>(path, ".flata") ||
1016 util::stringEndsWith<char>(path, ".jar") ||
1017 util::stringEndsWith<char>(path, ".jack") ||
1018 util::stringEndsWith<char>(path, ".zip")) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001019 return mergeArchive(path, override);
Adam Lesinski64587af2016-02-18 18:33:06 -08001020 } else if (util::stringEndsWith<char>(path, ".apk")) {
1021 return mergeStaticLibrary(path, override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001022 }
1023
1024 io::IFile* file = mFileCollection->insertFile(path);
Adam Lesinski64587af2016-02-18 18:33:06 -08001025 return mergeFile(file, override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001026 }
1027
Adam Lesinski64587af2016-02-18 18:33:06 -08001028 /**
1029 * Takes a file to load and merge into the master ResourceTable. If override is true,
1030 * conflicting resources are allowed to override each other, in order of last seen.
1031 *
1032 * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and merged into the
1033 * master ResourceTable. If the file ends with .flat, then it is treated like a compiled file
1034 * and the header data is read and merged into the final ResourceTable.
1035 *
1036 * All other file types are ignored. This is because these files could be coming from a zip,
1037 * where we could have other files like classes.dex.
1038 */
1039 bool mergeFile(io::IFile* file, bool override) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001040 const Source& src = file->getSource();
1041 if (util::stringEndsWith<char>(src.path, ".arsc.flat")) {
1042 return mergeResourceTable(file, override);
Adam Lesinski64587af2016-02-18 18:33:06 -08001043
Adam Lesinski52364f72016-01-11 13:10:24 -08001044 } else if (util::stringEndsWith<char>(src.path, ".flat")){
Adam Lesinskia40e9722015-11-24 19:11:46 -08001045 // Try opening the file and looking for an Export header.
1046 std::unique_ptr<io::IData> data = file->openAsData();
1047 if (!data) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001048 mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open");
Adam Lesinskia40e9722015-11-24 19:11:46 -08001049 return false;
1050 }
1051
1052 std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader(
Adam Lesinski6a008172016-02-02 17:02:58 -08001053 src, data->data(), data->size(), mContext->getDiagnostics());
Adam Lesinskia40e9722015-11-24 19:11:46 -08001054 if (resourceFile) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001055 return mergeCompiledFile(file, resourceFile.get(), override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001056 }
Adam Lesinskic446a732016-01-21 11:04:46 -08001057 return false;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001058 }
Adam Lesinski52364f72016-01-11 13:10:24 -08001059
Adam Lesinskic446a732016-01-21 11:04:46 -08001060 // Ignore non .flat files. This could be classes.dex or something else that happens
1061 // to be in an archive.
1062 return true;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001063 }
1064
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001065 int run(const std::vector<std::string>& inputFiles) {
1066 // Load the AndroidManifest.xml
Adam Lesinskia40e9722015-11-24 19:11:46 -08001067 std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
Adam Lesinski6a008172016-02-02 17:02:58 -08001068 mContext->getDiagnostics());
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001069 if (!manifestXml) {
1070 return 1;
1071 }
1072
1073 if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001074 mContext->setCompilationPackage(maybeAppInfo.value().package);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001075 } else {
Adam Lesinski6a008172016-02-02 17:02:58 -08001076 mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001077 << "no package specified in <manifest> tag");
1078 return 1;
1079 }
1080
Adam Lesinski64587af2016-02-18 18:33:06 -08001081 if (!util::isJavaPackageName(mContext->getCompilationPackage())) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001082 mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001083 << "invalid package name '"
Adam Lesinski64587af2016-02-18 18:33:06 -08001084 << mContext->getCompilationPackage()
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001085 << "'");
1086 return 1;
1087 }
1088
Adam Lesinski64587af2016-02-18 18:33:06 -08001089 mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() });
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001090
Adam Lesinski64587af2016-02-18 18:33:06 -08001091 if (mContext->getCompilationPackage() == u"android") {
1092 mContext->setPackageId(0x01);
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001093 } else {
Adam Lesinski64587af2016-02-18 18:33:06 -08001094 mContext->setPackageId(0x7f);
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001095 }
1096
Adam Lesinski64587af2016-02-18 18:33:06 -08001097 if (!loadSymbolsFromIncludePaths()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001098 return 1;
1099 }
1100
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001101 TableMergerOptions tableMergerOptions;
1102 tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
Adam Lesinski6a008172016-02-02 17:02:58 -08001103 mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions);
Adam Lesinskifb48d292015-11-07 15:52:13 -08001104
Adam Lesinski355f2852016-02-13 20:26:45 -08001105 if (mContext->verbose()) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001106 mContext->getDiagnostics()->note(
Adam Lesinski64587af2016-02-18 18:33:06 -08001107 DiagMessage() << "linking package '" << mContext->getCompilationPackage()
1108 << "' with package ID " << std::hex
1109 << (int) mContext->getPackageId());
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001110 }
1111
Adam Lesinskifb48d292015-11-07 15:52:13 -08001112
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001113 for (const std::string& input : inputFiles) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001114 if (!mergePath(input, false)) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001115 mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input");
Adam Lesinski467f1712015-11-16 17:35:44 -08001116 return 1;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001117 }
1118 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001119
Adam Lesinskifb48d292015-11-07 15:52:13 -08001120 for (const std::string& input : mOptions.overlayFiles) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001121 if (!mergePath(input, true)) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001122 mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
Adam Lesinski467f1712015-11-16 17:35:44 -08001123 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001124 }
1125 }
1126
Adam Lesinskifb48d292015-11-07 15:52:13 -08001127 if (!verifyNoExternalPackages()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001128 return 1;
1129 }
1130
1131 if (!mOptions.staticLib) {
1132 PrivateAttributeMover mover;
Adam Lesinski6a008172016-02-02 17:02:58 -08001133 if (!mover.consume(mContext, &mFinalTable)) {
1134 mContext->getDiagnostics()->error(
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001135 DiagMessage() << "failed moving private attributes");
1136 return 1;
1137 }
1138 }
1139
Adam Lesinski64587af2016-02-18 18:33:06 -08001140 if (!mOptions.staticLib) {
1141 // Assign IDs if we are building a regular app.
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001142 IdAssigner idAssigner;
Adam Lesinski6a008172016-02-02 17:02:58 -08001143 if (!idAssigner.consume(mContext, &mFinalTable)) {
1144 mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001145 return 1;
1146 }
Adam Lesinski64587af2016-02-18 18:33:06 -08001147 } else {
1148 // Static libs are merged with other apps, and ID collisions are bad, so verify that
1149 // no IDs have been set.
1150 if (!verifyNoIdsSet()) {
1151 return 1;
1152 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001153 }
1154
Adam Lesinski64587af2016-02-18 18:33:06 -08001155 // Add the names to mangle based on our source merge earlier.
1156 mContext->setNameManglerPolicy(NameManglerPolicy{
1157 mContext->getCompilationPackage(), mTableMerger->getMergedPackages() });
1158
1159 // Add our table to the symbol table.
1160 mContext->getExternalSymbols()->prependSource(
1161 util::make_unique<ResourceTableSymbolSource>(&mFinalTable));
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001162
1163 {
1164 ReferenceLinker linker;
Adam Lesinski6a008172016-02-02 17:02:58 -08001165 if (!linker.consume(mContext, &mFinalTable)) {
1166 mContext->getDiagnostics()->error(DiagMessage() << "failed linking references");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001167 return 1;
1168 }
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001169
Adam Lesinski64587af2016-02-18 18:33:06 -08001170 if (mOptions.staticLib) {
1171 if (!mOptions.products.empty()) {
1172 mContext->getDiagnostics()->warn(
1173 DiagMessage() << "can't select products when building static library");
1174 }
Adam Lesinski355f2852016-02-13 20:26:45 -08001175
Adam Lesinski64587af2016-02-18 18:33:06 -08001176 if (mOptions.tableSplitterOptions.configFilter != nullptr ||
1177 mOptions.tableSplitterOptions.preferredDensity) {
1178 mContext->getDiagnostics()->warn(
1179 DiagMessage() << "can't strip resources when building static library");
1180 }
1181 } else {
1182 ProductFilter productFilter(mOptions.products);
1183 if (!productFilter.consume(mContext, &mFinalTable)) {
1184 mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products");
1185 return 1;
1186 }
Adam Lesinski355f2852016-02-13 20:26:45 -08001187
Adam Lesinski64587af2016-02-18 18:33:06 -08001188 // TODO(adamlesinski): Actually pass in split constraints and handle splits at the file
1189 // level.
1190 TableSplitter tableSplitter({}, mOptions.tableSplitterOptions);
1191 if (!tableSplitter.verifySplitConstraints(mContext)) {
1192 return 1;
1193 }
1194 tableSplitter.splitTable(&mFinalTable);
1195 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001196 }
1197
1198 proguard::KeepSet proguardKeepSet;
1199
1200 std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter();
1201 if (!archiveWriter) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001202 mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001203 return 1;
1204 }
1205
Adam Lesinski467f1712015-11-16 17:35:44 -08001206 bool error = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001207 {
Adam Lesinski52364f72016-01-11 13:10:24 -08001208 ManifestFixer manifestFixer(mOptions.manifestFixerOptions);
Adam Lesinski6a008172016-02-02 17:02:58 -08001209 if (!manifestFixer.consume(mContext, manifestXml.get())) {
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001210 error = true;
1211 }
1212
Adam Lesinski467f1712015-11-16 17:35:44 -08001213 // AndroidManifest.xml has no resource name, but the CallSite is built from the name
1214 // (aka, which package the AndroidManifest.xml is coming from).
1215 // So we give it a package name so it can see local resources.
Adam Lesinski64587af2016-02-18 18:33:06 -08001216 manifestXml->file.name.package = mContext->getCompilationPackage();
Adam Lesinski467f1712015-11-16 17:35:44 -08001217
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001218 XmlReferenceLinker manifestLinker;
Adam Lesinski6a008172016-02-02 17:02:58 -08001219 if (manifestLinker.consume(mContext, manifestXml.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001220 if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
1221 manifestXml.get(),
1222 &proguardKeepSet)) {
1223 error = true;
1224 }
1225
Adam Lesinskica5638f2015-10-21 14:42:43 -07001226 if (mOptions.generateJavaClassPath) {
1227 if (!writeManifestJavaFile(manifestXml.get())) {
1228 error = true;
1229 }
1230 }
1231
Adam Lesinski355f2852016-02-13 20:26:45 -08001232 const bool keepRawValues = mOptions.staticLib;
1233 bool result = flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
1234 keepRawValues, archiveWriter.get(), mContext);
1235 if (!result) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001236 error = true;
1237 }
1238 } else {
1239 error = true;
1240 }
1241 }
1242
Adam Lesinski467f1712015-11-16 17:35:44 -08001243 if (error) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001244 mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest");
Adam Lesinski467f1712015-11-16 17:35:44 -08001245 return 1;
1246 }
1247
Adam Lesinski355f2852016-02-13 20:26:45 -08001248 ResourceFileFlattenerOptions fileFlattenerOptions;
1249 fileFlattenerOptions.keepRawValues = mOptions.staticLib;
1250 fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything;
1251 fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress;
1252 fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion;
Adam Lesinski626a69f2016-03-03 10:09:26 -08001253 fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors;
Adam Lesinski355f2852016-02-13 20:26:45 -08001254 ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, &proguardKeepSet);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001255
Adam Lesinski355f2852016-02-13 20:26:45 -08001256 if (!fileFlattener.flatten(&mFinalTable, archiveWriter.get())) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001257 mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001258 return 1;
1259 }
1260
Adam Lesinski626a69f2016-03-03 10:09:26 -08001261 if (!mOptions.noAutoVersion) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001262 AutoVersioner versioner;
Adam Lesinski6a008172016-02-02 17:02:58 -08001263 if (!versioner.consume(mContext, &mFinalTable)) {
1264 mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001265 return 1;
1266 }
1267 }
1268
Adam Lesinski64587af2016-02-18 18:33:06 -08001269 if (mOptions.staticLib) {
1270 if (!flattenTableToPb(&mFinalTable, archiveWriter.get())) {
1271 mContext->getDiagnostics()->error(DiagMessage()
1272 << "failed to write resources.arsc.flat");
1273 return 1;
1274 }
1275 } else {
1276 if (!flattenTable(&mFinalTable, archiveWriter.get())) {
1277 mContext->getDiagnostics()->error(DiagMessage()
1278 << "failed to write resources.arsc");
1279 return 1;
1280 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001281 }
1282
1283 if (mOptions.generateJavaClassPath) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001284 JavaClassGeneratorOptions options;
Adam Lesinski52364f72016-01-11 13:10:24 -08001285 options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
1286
Adam Lesinskief9c5012016-01-22 14:09:53 -08001287 if (mOptions.staticLib || mOptions.generateNonFinalIds) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001288 options.useFinal = false;
1289 }
1290
Adam Lesinski6a008172016-02-02 17:02:58 -08001291 const StringPiece16 actualPackage = mContext->getCompilationPackage();
1292 StringPiece16 outputPackage = mContext->getCompilationPackage();
Adam Lesinski52364f72016-01-11 13:10:24 -08001293 if (mOptions.customJavaPackage) {
1294 // Override the output java package to the custom one.
1295 outputPackage = mOptions.customJavaPackage.value();
1296 }
Adam Lesinski83f22552015-11-07 11:51:23 -08001297
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001298 if (mOptions.privateSymbols) {
1299 // If we defined a private symbols package, we only emit Public symbols
1300 // to the original package, and private and public symbols to the private package.
1301
1302 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
Adam Lesinski6a008172016-02-02 17:02:58 -08001303 if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(),
Adam Lesinski52364f72016-01-11 13:10:24 -08001304 outputPackage, options)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001305 return 1;
1306 }
1307
1308 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
Adam Lesinski83f22552015-11-07 11:51:23 -08001309 outputPackage = mOptions.privateSymbols.value();
1310 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001311
Adam Lesinskifb48d292015-11-07 15:52:13 -08001312 if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
Adam Lesinski83f22552015-11-07 11:51:23 -08001313 return 1;
1314 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001315
Adam Lesinski52364f72016-01-11 13:10:24 -08001316 for (const std::u16string& extraPackage : mOptions.extraJavaPackages) {
1317 if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001318 return 1;
1319 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001320 }
1321 }
1322
1323 if (mOptions.generateProguardRulesPath) {
1324 if (!writeProguardFile(proguardKeepSet)) {
1325 return 1;
1326 }
1327 }
1328
Adam Lesinski355f2852016-02-13 20:26:45 -08001329 if (mContext->verbose()) {
1330 DebugPrintTableOptions debugPrintTableOptions;
1331 debugPrintTableOptions.showSources = true;
1332 Debug::printTable(&mFinalTable, debugPrintTableOptions);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001333 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001334 return 0;
1335 }
Adam Lesinskifb48d292015-11-07 15:52:13 -08001336
1337private:
1338 LinkOptions mOptions;
Adam Lesinski6a008172016-02-02 17:02:58 -08001339 LinkContext* mContext;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001340 ResourceTable mFinalTable;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001341
Adam Lesinskifb48d292015-11-07 15:52:13 -08001342 std::unique_ptr<TableMerger> mTableMerger;
1343
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001344 // A pointer to the FileCollection representing the filesystem (not archives).
Adam Lesinski64587af2016-02-18 18:33:06 -08001345 std::unique_ptr<io::FileCollection> mFileCollection;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001346
1347 // A vector of IFileCollections. This is mainly here to keep ownership of the collections.
Adam Lesinskia40e9722015-11-24 19:11:46 -08001348 std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
Adam Lesinski64587af2016-02-18 18:33:06 -08001349
1350 // A vector of ResourceTables. This is here to retain ownership, so that the SymbolTable
1351 // can use these.
1352 std::vector<std::unique_ptr<ResourceTable>> mStaticTableIncludes;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001353};
1354
1355int link(const std::vector<StringPiece>& args) {
Adam Lesinski355f2852016-02-13 20:26:45 -08001356 LinkContext context;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001357 LinkOptions options;
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001358 Maybe<std::string> privateSymbolsPackage;
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001359 Maybe<std::string> minSdkVersion, targetSdkVersion;
Adam Lesinski52364f72016-01-11 13:10:24 -08001360 Maybe<std::string> renameManifestPackage, renameInstrumentationTargetPackage;
1361 Maybe<std::string> versionCode, versionName;
1362 Maybe<std::string> customJavaPackage;
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001363 std::vector<std::string> extraJavaPackages;
Adam Lesinski6a008172016-02-02 17:02:58 -08001364 Maybe<std::string> configs;
Adam Lesinski355f2852016-02-13 20:26:45 -08001365 Maybe<std::string> preferredDensity;
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001366 Maybe<std::string> productList;
Adam Lesinski8900aa82016-01-25 22:48:15 -08001367 bool legacyXFlag = false;
1368 bool requireLocalization = false;
Adam Lesinski64587af2016-02-18 18:33:06 -08001369 bool verbose = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001370 Flags flags = Flags()
1371 .requiredFlag("-o", "Output path", &options.outputPath)
1372 .requiredFlag("--manifest", "Path to the Android manifest to build",
1373 &options.manifestPath)
1374 .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
Adam Lesinski52364f72016-01-11 13:10:24 -08001375 .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n"
Adam Lesinskifb48d292015-11-07 15:52:13 -08001376 "The last conflicting resource given takes precedence.",
1377 &options.overlayFiles)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001378 .optionalFlag("--java", "Directory in which to generate R.java",
1379 &options.generateJavaClassPath)
1380 .optionalFlag("--proguard", "Output file for generated Proguard rules",
1381 &options.generateProguardRulesPath)
1382 .optionalSwitch("--no-auto-version",
1383 "Disables automatic style and layout SDK versioning",
1384 &options.noAutoVersion)
Adam Lesinski64587af2016-02-18 18:33:06 -08001385 .optionalSwitch("--no-version-vectors",
1386 "Disables automatic versioning of vector drawables. Use this only\n"
1387 "when building with vector drawable support library",
1388 &options.noVersionVectors)
Adam Lesinski8900aa82016-01-25 22:48:15 -08001389 .optionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01",
1390 &legacyXFlag)
1391 .optionalSwitch("-z", "Require localization of strings marked 'suggested'",
1392 &requireLocalization)
Adam Lesinski6a008172016-02-02 17:02:58 -08001393 .optionalFlag("-c", "Comma separated list of configurations to include. The default\n"
1394 "is all configurations", &configs)
Adam Lesinski355f2852016-02-13 20:26:45 -08001395 .optionalFlag("--preferred-density",
1396 "Selects the closest matching density and strips out all others.",
1397 &preferredDensity)
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001398 .optionalFlag("--product", "Comma separated list of product names to keep",
1399 &productList)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001400 .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
1401 "by -o",
1402 &options.outputToDirectory)
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001403 .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
1404 "AndroidManifest.xml", &minSdkVersion)
1405 .optionalFlag("--target-sdk-version", "Default target SDK version to use for "
1406 "AndroidManifest.xml", &targetSdkVersion)
Adam Lesinski52364f72016-01-11 13:10:24 -08001407 .optionalFlag("--version-code", "Version code (integer) to inject into the "
1408 "AndroidManifest.xml if none is present", &versionCode)
1409 .optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml "
1410 "if none is present", &versionName)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001411 .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
Adam Lesinski64587af2016-02-18 18:33:06 -08001412 .optionalSwitch("--no-static-lib-packages",
1413 "Merge all library resources under the app's package",
1414 &options.noStaticLibPackages)
Adam Lesinskief9c5012016-01-22 14:09:53 -08001415 .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n"
1416 "This is implied when --static-lib is specified.",
1417 &options.generateNonFinalIds)
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001418 .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001419 "private symbols.\n"
1420 "If not specified, public and private symbols will use the application's "
1421 "package name", &privateSymbolsPackage)
Adam Lesinski52364f72016-01-11 13:10:24 -08001422 .optionalFlag("--custom-package", "Custom Java package under which to generate R.java",
1423 &customJavaPackage)
Adam Lesinski83f22552015-11-07 11:51:23 -08001424 .optionalFlagList("--extra-packages", "Generate the same R.java but with different "
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001425 "package names", &extraJavaPackages)
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001426 .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in "
1427 "overlays without <add-resource> tags", &options.autoAddOverlay)
Adam Lesinski52364f72016-01-11 13:10:24 -08001428 .optionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml",
1429 &renameManifestPackage)
1430 .optionalFlag("--rename-instrumentation-target-package",
1431 "Changes the name of the target package for instrumentation. Most useful "
1432 "when used\nin conjunction with --rename-manifest-package",
1433 &renameInstrumentationTargetPackage)
1434 .optionalFlagList("-0", "File extensions not to compress",
1435 &options.extensionsToNotCompress)
Adam Lesinski64587af2016-02-18 18:33:06 -08001436 .optionalSwitch("-v", "Enables verbose logging", &verbose);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001437
1438 if (!flags.parse("aapt2 link", args, &std::cerr)) {
1439 return 1;
1440 }
1441
Adam Lesinski64587af2016-02-18 18:33:06 -08001442 if (verbose) {
1443 context.setVerbose(verbose);
1444 }
1445
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001446 if (privateSymbolsPackage) {
1447 options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
1448 }
1449
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001450 if (minSdkVersion) {
Adam Lesinski52364f72016-01-11 13:10:24 -08001451 options.manifestFixerOptions.minSdkVersionDefault =
1452 util::utf8ToUtf16(minSdkVersion.value());
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001453 }
1454
1455 if (targetSdkVersion) {
Adam Lesinski52364f72016-01-11 13:10:24 -08001456 options.manifestFixerOptions.targetSdkVersionDefault =
1457 util::utf8ToUtf16(targetSdkVersion.value());
1458 }
1459
1460 if (renameManifestPackage) {
1461 options.manifestFixerOptions.renameManifestPackage =
1462 util::utf8ToUtf16(renameManifestPackage.value());
1463 }
1464
1465 if (renameInstrumentationTargetPackage) {
1466 options.manifestFixerOptions.renameInstrumentationTargetPackage =
1467 util::utf8ToUtf16(renameInstrumentationTargetPackage.value());
1468 }
1469
1470 if (versionCode) {
1471 options.manifestFixerOptions.versionCodeDefault = util::utf8ToUtf16(versionCode.value());
1472 }
1473
1474 if (versionName) {
1475 options.manifestFixerOptions.versionNameDefault = util::utf8ToUtf16(versionName.value());
1476 }
1477
1478 if (customJavaPackage) {
1479 options.customJavaPackage = util::utf8ToUtf16(customJavaPackage.value());
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001480 }
1481
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001482 // Populate the set of extra packages for which to generate R.java.
1483 for (std::string& extraPackage : extraJavaPackages) {
1484 // A given package can actually be a colon separated list of packages.
1485 for (StringPiece package : util::split(extraPackage, ':')) {
Adam Lesinski52364f72016-01-11 13:10:24 -08001486 options.extraJavaPackages.insert(util::utf8ToUtf16(package));
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001487 }
1488 }
1489
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001490 if (productList) {
1491 for (StringPiece product : util::tokenize<char>(productList.value(), ',')) {
1492 if (product != "" && product != "default") {
1493 options.products.insert(product.toString());
1494 }
1495 }
1496 }
1497
Adam Lesinski6a008172016-02-02 17:02:58 -08001498 AxisConfigFilter filter;
1499 if (configs) {
1500 for (const StringPiece& configStr : util::tokenize<char>(configs.value(), ',')) {
1501 ConfigDescription config;
1502 LocaleValue lv;
1503 if (lv.initFromFilterString(configStr)) {
1504 lv.writeTo(&config);
1505 } else if (!ConfigDescription::parse(configStr, &config)) {
1506 context.getDiagnostics()->error(
1507 DiagMessage() << "invalid config '" << configStr << "' for -c option");
1508 return 1;
1509 }
1510
1511 if (config.density != 0) {
1512 context.getDiagnostics()->warn(
1513 DiagMessage() << "ignoring density '" << config << "' for -c option");
1514 } else {
1515 filter.addConfig(config);
1516 }
1517 }
1518
Adam Lesinski355f2852016-02-13 20:26:45 -08001519 options.tableSplitterOptions.configFilter = &filter;
1520 }
1521
1522 if (preferredDensity) {
1523 ConfigDescription preferredDensityConfig;
1524 if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) {
1525 context.getDiagnostics()->error(DiagMessage() << "invalid density '"
1526 << preferredDensity.value()
1527 << "' for --preferred-density option");
1528 return 1;
1529 }
1530
1531 // Clear the version that can be automatically added.
1532 preferredDensityConfig.sdkVersion = 0;
1533
1534 if (preferredDensityConfig.diff(ConfigDescription::defaultConfig())
1535 != ConfigDescription::CONFIG_DENSITY) {
1536 context.getDiagnostics()->error(DiagMessage() << "invalid preferred density '"
1537 << preferredDensity.value() << "'. "
1538 << "Preferred density must only be a density value");
1539 return 1;
1540 }
1541 options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density;
Adam Lesinski6a008172016-02-02 17:02:58 -08001542 }
1543
Adam Lesinski626a69f2016-03-03 10:09:26 -08001544 // Turn off auto versioning for static-libs.
1545 if (options.staticLib) {
1546 options.noAutoVersion = true;
1547 options.noVersionVectors = true;
1548 }
1549
Adam Lesinski6a008172016-02-02 17:02:58 -08001550 LinkCommand cmd(&context, options);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001551 return cmd.run(flags.getArgs());
1552}
1553
1554} // namespace aapt