blob: 49971201fb3c10c2ae359c1468a396df0bac4ecd [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 Lesinski3524a232016-04-01 19:19:24 -070069 std::vector<std::string> javadocAnnotations;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070070 bool outputToDirectory = false;
Adam Lesinskia6fe3452015-12-09 15:20:52 -080071 bool autoAddOverlay = false;
Adam Lesinski52364f72016-01-11 13:10:24 -080072 bool doNotCompressAnything = false;
73 std::vector<std::string> extensionsToNotCompress;
Adam Lesinski9e10ac72015-10-16 14:37:48 -070074 Maybe<std::u16string> privateSymbols;
Adam Lesinski52364f72016-01-11 13:10:24 -080075 ManifestFixerOptions manifestFixerOptions;
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -080076 std::unordered_set<std::string> products;
Adam Lesinski355f2852016-02-13 20:26:45 -080077 TableSplitterOptions tableSplitterOptions;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070078};
79
Adam Lesinski64587af2016-02-18 18:33:06 -080080class LinkContext : public IAaptContext {
81public:
82 LinkContext() : mNameMangler({}) {
83 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -070084
85 IDiagnostics* getDiagnostics() override {
86 return &mDiagnostics;
87 }
88
89 NameMangler* getNameMangler() override {
Adam Lesinski64587af2016-02-18 18:33:06 -080090 return &mNameMangler;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070091 }
92
Adam Lesinski64587af2016-02-18 18:33:06 -080093 void setNameManglerPolicy(const NameManglerPolicy& policy) {
94 mNameMangler = NameMangler(policy);
95 }
96
97 const std::u16string& getCompilationPackage() override {
Adam Lesinski1ab598f2015-08-14 14:26:04 -070098 return mCompilationPackage;
99 }
100
Adam Lesinski64587af2016-02-18 18:33:06 -0800101 void setCompilationPackage(const StringPiece16& packageName) {
102 mCompilationPackage = packageName.toString();
103 }
104
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700105 uint8_t getPackageId() override {
106 return mPackageId;
107 }
108
Adam Lesinski64587af2016-02-18 18:33:06 -0800109 void setPackageId(uint8_t id) {
110 mPackageId = id;
111 }
112
113 SymbolTable* getExternalSymbols() override {
114 return &mSymbols;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700115 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800116
117 bool verbose() override {
118 return mVerbose;
119 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800120
121 void setVerbose(bool val) {
122 mVerbose = val;
123 }
124
125private:
126 StdErrDiagnostics mDiagnostics;
127 NameMangler mNameMangler;
128 std::u16string mCompilationPackage;
129 uint8_t mPackageId = 0x0;
130 SymbolTable mSymbols;
131 bool mVerbose = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700132};
133
Adam Lesinski355f2852016-02-13 20:26:45 -0800134static bool copyFileToArchive(io::IFile* file, const std::string& outPath,
135 uint32_t compressionFlags,
136 IArchiveWriter* writer, IAaptContext* context) {
137 std::unique_ptr<io::IData> data = file->openAsData();
138 if (!data) {
139 context->getDiagnostics()->error(DiagMessage(file->getSource())
140 << "failed to open file");
141 return false;
142 }
143
Adam Lesinski64587af2016-02-18 18:33:06 -0800144 const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data());
145 size_t bufferSize = data->size();
146
147 // If the file ends with .flat, we must strip off the CompiledFileHeader from it.
148 if (util::stringEndsWith<char>(file->getSource().path, ".flat")) {
149 CompiledFileInputStream inputStream(data->data(), data->size());
150 if (!inputStream.CompiledFile()) {
151 context->getDiagnostics()->error(DiagMessage(file->getSource())
152 << "invalid compiled file header");
153 return false;
154 }
155 buffer = reinterpret_cast<const uint8_t*>(inputStream.data());
156 bufferSize = inputStream.size();
Adam Lesinski355f2852016-02-13 20:26:45 -0800157 }
158
159 if (context->verbose()) {
160 context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive");
161 }
162
163 if (writer->startEntry(outPath, compressionFlags)) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800164 if (writer->writeEntry(buffer, bufferSize)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800165 if (writer->finishEntry()) {
166 return true;
167 }
168 }
169 }
170
171 context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath);
172 return false;
173}
174
175static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
176 bool keepRawValues, IArchiveWriter* writer, IAaptContext* context) {
177 BigBuffer buffer(1024);
178 XmlFlattenerOptions options = {};
179 options.keepRawValues = keepRawValues;
180 options.maxSdkLevel = maxSdkLevel;
181 XmlFlattener flattener(&buffer, options);
182 if (!flattener.consume(context, xmlRes)) {
183 return false;
184 }
185
186 if (context->verbose()) {
187 DiagMessage msg;
188 msg << "writing " << path << " to archive";
189 if (maxSdkLevel) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800190 msg << " maxSdkLevel=" << maxSdkLevel.value() << " keepRawValues=" << keepRawValues;
Adam Lesinski355f2852016-02-13 20:26:45 -0800191 }
192 context->getDiagnostics()->note(msg);
193 }
194
195 if (writer->startEntry(path, ArchiveEntry::kCompress)) {
196 if (writer->writeEntry(buffer)) {
197 if (writer->finishEntry()) {
198 return true;
199 }
200 }
201 }
202 context->getDiagnostics()->error(DiagMessage() << "failed to write " << path << " to archive");
203 return false;
204}
205
206/*static std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len,
207 IDiagnostics* diag) {
208 std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
209 BinaryResourceParser parser(diag, table.get(), source, data, len);
210 if (!parser.parse()) {
211 return {};
212 }
213 return table;
214}*/
215
216static 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;
285 std::vector<std::string> extensionsToNotCompress;
286};
287
288class ResourceFileFlattener {
289public:
290 ResourceFileFlattener(const ResourceFileFlattenerOptions& options,
291 IAaptContext* context, proguard::KeepSet* keepSet) :
292 mOptions(options), mContext(context), mKeepSet(keepSet) {
293 }
294
295 bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter);
296
297private:
298 struct FileOperation {
299 io::IFile* fileToCopy;
300 std::unique_ptr<xml::XmlResource> xmlToFlatten;
301 std::string dstPath;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800302 bool skipVersion = false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800303 };
304
305 uint32_t getCompressionFlags(const StringPiece& str);
306
Adam Lesinski626a69f2016-03-03 10:09:26 -0800307 bool linkAndVersionXmlFile(const ResourceEntry* entry, const ResourceFile& fileDesc,
308 io::IFile* file, ResourceTable* table, FileOperation* outFileOp);
Adam Lesinski355f2852016-02-13 20:26:45 -0800309
310 ResourceFileFlattenerOptions mOptions;
311 IAaptContext* mContext;
312 proguard::KeepSet* mKeepSet;
313};
314
315uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) {
316 if (mOptions.doNotCompressAnything) {
317 return 0;
318 }
319
320 for (const std::string& extension : mOptions.extensionsToNotCompress) {
321 if (util::stringEndsWith<char>(str, extension)) {
322 return 0;
323 }
324 }
325 return ArchiveEntry::kCompress;
326}
327
Adam Lesinski626a69f2016-03-03 10:09:26 -0800328bool ResourceFileFlattener::linkAndVersionXmlFile(const ResourceEntry* entry,
329 const ResourceFile& fileDesc,
330 io::IFile* file,
331 ResourceTable* table,
332 FileOperation* outFileOp) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800333 const StringPiece srcPath = file->getSource().path;
334 if (mContext->verbose()) {
335 mContext->getDiagnostics()->note(DiagMessage() << "linking " << srcPath);
336 }
337
338 std::unique_ptr<io::IData> data = file->openAsData();
339 if (!data) {
340 mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file");
Adam Lesinski626a69f2016-03-03 10:09:26 -0800341 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800342 }
343
Adam Lesinski355f2852016-02-13 20:26:45 -0800344 if (util::stringEndsWith<char>(srcPath, ".flat")) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800345 outFileOp->xmlToFlatten = loadBinaryXmlSkipFileExport(file->getSource(),
346 data->data(), data->size(),
347 mContext->getDiagnostics());
Adam Lesinski355f2852016-02-13 20:26:45 -0800348 } else {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800349 outFileOp->xmlToFlatten = xml::inflate(data->data(), data->size(),
350 mContext->getDiagnostics(),
351 file->getSource());
Adam Lesinski355f2852016-02-13 20:26:45 -0800352 }
353
Adam Lesinski626a69f2016-03-03 10:09:26 -0800354 if (!outFileOp->xmlToFlatten) {
355 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800356 }
357
358 // Copy the the file description header.
Adam Lesinski626a69f2016-03-03 10:09:26 -0800359 outFileOp->xmlToFlatten->file = fileDesc;
Adam Lesinski355f2852016-02-13 20:26:45 -0800360
361 XmlReferenceLinker xmlLinker;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800362 if (!xmlLinker.consume(mContext, outFileOp->xmlToFlatten.get())) {
363 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800364 }
365
Adam Lesinski626a69f2016-03-03 10:09:26 -0800366 if (!proguard::collectProguardRules(outFileOp->xmlToFlatten->file.source,
367 outFileOp->xmlToFlatten.get(), mKeepSet)) {
368 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800369 }
370
371 if (!mOptions.noAutoVersion) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800372 if (mOptions.noVersionVectors) {
373 // Skip this if it is a vector or animated-vector.
374 xml::Element* el = xml::findRootElement(outFileOp->xmlToFlatten.get());
375 if (el && el->namespaceUri.empty()) {
376 if (el->name == u"vector" || el->name == u"animated-vector") {
377 // We are NOT going to version this file.
378 outFileOp->skipVersion = true;
379 return true;
380 }
381 }
382 }
383
Adam Lesinski355f2852016-02-13 20:26:45 -0800384 // Find the first SDK level used that is higher than this defined config and
385 // not superseded by a lower or equal SDK level resource.
386 for (int sdkLevel : xmlLinker.getSdkLevels()) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800387 if (sdkLevel > outFileOp->xmlToFlatten->file.config.sdkVersion) {
388 if (!shouldGenerateVersionedResource(entry, outFileOp->xmlToFlatten->file.config,
389 sdkLevel)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800390 // If we shouldn't generate a versioned resource, stop checking.
391 break;
392 }
393
Adam Lesinski626a69f2016-03-03 10:09:26 -0800394 ResourceFile versionedFileDesc = outFileOp->xmlToFlatten->file;
Adam Lesinski64587af2016-02-18 18:33:06 -0800395 versionedFileDesc.config.sdkVersion = (uint16_t) sdkLevel;
Adam Lesinski355f2852016-02-13 20:26:45 -0800396
397 if (mContext->verbose()) {
398 mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source)
399 << "auto-versioning resource from config '"
Adam Lesinski626a69f2016-03-03 10:09:26 -0800400 << outFileOp->xmlToFlatten->file.config
401 << "' -> '"
Adam Lesinski355f2852016-02-13 20:26:45 -0800402 << versionedFileDesc.config << "'");
403 }
404
405 std::u16string genPath = util::utf8ToUtf16(ResourceUtils::buildResourceFileName(
406 versionedFileDesc, mContext->getNameMangler()));
407
408 bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name,
409 versionedFileDesc.config,
410 versionedFileDesc.source,
411 genPath,
412 file,
413 mContext->getDiagnostics());
414 if (!added) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800415 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800416 }
417 break;
418 }
419 }
420 }
Adam Lesinski626a69f2016-03-03 10:09:26 -0800421 return true;
Adam Lesinski355f2852016-02-13 20:26:45 -0800422}
423
424/**
425 * Do not insert or remove any resources while executing in this function. It will
426 * corrupt the iteration order.
427 */
428bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiveWriter) {
429 bool error = false;
430 std::map<std::pair<ConfigDescription, StringPiece16>, FileOperation> configSortedFiles;
431
432 for (auto& pkg : table->packages) {
433 for (auto& type : pkg->types) {
434 // Sort by config and name, so that we get better locality in the zip file.
435 configSortedFiles.clear();
436 for (auto& entry : type->entries) {
437 // Iterate via indices because auto generated values can be inserted ahead of
438 // the value being processed.
439 for (size_t i = 0; i < entry->values.size(); i++) {
440 ResourceConfigValue* configValue = entry->values[i].get();
441
442 FileReference* fileRef = valueCast<FileReference>(configValue->value.get());
443 if (!fileRef) {
444 continue;
445 }
446
447 io::IFile* file = fileRef->file;
448 if (!file) {
449 mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource())
450 << "file not found");
451 return false;
452 }
453
454 FileOperation fileOp;
455 fileOp.dstPath = util::utf16ToUtf8(*fileRef->path);
456
457 const StringPiece srcPath = file->getSource().path;
458 if (type->type != ResourceType::kRaw &&
459 (util::stringEndsWith<char>(srcPath, ".xml.flat") ||
460 util::stringEndsWith<char>(srcPath, ".xml"))) {
461 ResourceFile fileDesc;
462 fileDesc.config = configValue->config;
463 fileDesc.name = ResourceName(pkg->name, type->type, entry->name);
464 fileDesc.source = fileRef->getSource();
Adam Lesinski626a69f2016-03-03 10:09:26 -0800465 if (!linkAndVersionXmlFile(entry.get(), fileDesc, file, table, &fileOp)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800466 error = true;
467 continue;
468 }
469
470 } else {
471 fileOp.fileToCopy = file;
472 }
473
474 // NOTE(adamlesinski): Explicitly construct a StringPiece16 here, or else
475 // we end up copying the string in the std::make_pair() method, then creating
476 // a StringPiece16 from the copy, which would cause us to end up referencing
477 // garbage in the map.
478 const StringPiece16 entryName(entry->name);
479 configSortedFiles[std::make_pair(configValue->config, entryName)] =
480 std::move(fileOp);
481 }
482 }
483
484 if (error) {
485 return false;
486 }
487
488 // Now flatten the sorted values.
489 for (auto& mapEntry : configSortedFiles) {
490 const ConfigDescription& config = mapEntry.first.first;
491 const FileOperation& fileOp = mapEntry.second;
492
493 if (fileOp.xmlToFlatten) {
494 Maybe<size_t> maxSdkLevel;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800495 if (!mOptions.noAutoVersion && !fileOp.skipVersion) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800496 maxSdkLevel = std::max<size_t>(config.sdkVersion, 1u);
497 }
498
499 bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel,
500 mOptions.keepRawValues,
501 archiveWriter, mContext);
502 if (!result) {
503 error = true;
504 }
505 } else {
506 bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath,
507 getCompressionFlags(fileOp.dstPath),
508 archiveWriter, mContext);
509 if (!result) {
510 error = true;
511 }
512 }
513 }
514 }
515 }
516 return !error;
517}
518
Adam Lesinskifb48d292015-11-07 15:52:13 -0800519class LinkCommand {
520public:
Adam Lesinski6a008172016-02-02 17:02:58 -0800521 LinkCommand(LinkContext* context, const LinkOptions& options) :
Adam Lesinski64587af2016-02-18 18:33:06 -0800522 mOptions(options), mContext(context), mFinalTable(),
523 mFileCollection(util::make_unique<io::FileCollection>()) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800524 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700525
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700526 /**
527 * Creates a SymbolTable that loads symbols from the various APKs and caches the
528 * results for faster lookup.
529 */
Adam Lesinski64587af2016-02-18 18:33:06 -0800530 bool loadSymbolsFromIncludePaths() {
531 std::unique_ptr<AssetManagerSymbolSource> assetSource =
532 util::make_unique<AssetManagerSymbolSource>();
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700533 for (const std::string& path : mOptions.includePaths) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800534 if (mContext->verbose()) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800535 mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700536 }
537
Adam Lesinski64587af2016-02-18 18:33:06 -0800538 // First try to load the file as a static lib.
539 std::string errorStr;
540 std::unique_ptr<ResourceTable> staticInclude = loadStaticLibrary(path, &errorStr);
541 if (staticInclude) {
542 if (!mOptions.staticLib) {
543 // Can't include static libraries when not building a static library.
544 mContext->getDiagnostics()->error(
545 DiagMessage(path) << "can't include static library when building app");
546 return false;
547 }
548
549 // If we are using --no-static-lib-packages, we need to rename the package of this
550 // table to our compilation package.
551 if (mOptions.noStaticLibPackages) {
552 if (ResourceTablePackage* pkg = staticInclude->findPackageById(0x7f)) {
553 pkg->name = mContext->getCompilationPackage();
554 }
555 }
556
557 mContext->getExternalSymbols()->appendSource(
558 util::make_unique<ResourceTableSymbolSource>(staticInclude.get()));
559
560 mStaticTableIncludes.push_back(std::move(staticInclude));
561
562 } else if (!errorStr.empty()) {
563 // We had an error with reading, so fail.
564 mContext->getDiagnostics()->error(DiagMessage(path) << errorStr);
565 return false;
566 }
567
568 if (!assetSource->addAssetPath(path)) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800569 mContext->getDiagnostics()->error(
Adam Lesinskifb48d292015-11-07 15:52:13 -0800570 DiagMessage(path) << "failed to load include path");
Adam Lesinski64587af2016-02-18 18:33:06 -0800571 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700572 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700573 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800574
575 mContext->getExternalSymbols()->appendSource(std::move(assetSource));
576 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700577 }
578
Adam Lesinski467f1712015-11-16 17:35:44 -0800579 Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700580 // Make sure the first element is <manifest> with package attribute.
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800581 if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700582 if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") {
583 if (xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package")) {
584 return AppInfo{ packageAttr->value };
585 }
586 }
587 }
588 return {};
589 }
590
Adam Lesinski979ccb22016-01-11 10:42:19 -0800591 /**
592 * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked.
593 * Postcondition: ResourceTable has only one package left. All others are stripped, or there
594 * is an error and false is returned.
595 */
Adam Lesinskifb48d292015-11-07 15:52:13 -0800596 bool verifyNoExternalPackages() {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800597 auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool {
Adam Lesinski6a008172016-02-02 17:02:58 -0800598 return mContext->getCompilationPackage() != pkg->name ||
Adam Lesinski979ccb22016-01-11 10:42:19 -0800599 !pkg->id ||
Adam Lesinski6a008172016-02-02 17:02:58 -0800600 pkg->id.value() != mContext->getPackageId();
Adam Lesinski979ccb22016-01-11 10:42:19 -0800601 };
602
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700603 bool error = false;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800604 for (const auto& package : mFinalTable.packages) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800605 if (isExtPackageFunc(package)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700606 // We have a package that is not related to the one we're building!
607 for (const auto& type : package->types) {
608 for (const auto& entry : type->entries) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800609 ResourceNameRef resName(package->name, type->type, entry->name);
610
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700611 for (const auto& configValue : entry->values) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800612 // Special case the occurrence of an ID that is being generated for the
613 // 'android' package. This is due to legacy reasons.
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800614 if (valueCast<Id>(configValue->value.get()) &&
Adam Lesinski979ccb22016-01-11 10:42:19 -0800615 package->name == u"android") {
Adam Lesinski6a008172016-02-02 17:02:58 -0800616 mContext->getDiagnostics()->warn(
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800617 DiagMessage(configValue->value->getSource())
Adam Lesinski979ccb22016-01-11 10:42:19 -0800618 << "generated id '" << resName
619 << "' for external package '" << package->name
620 << "'");
621 } else {
Adam Lesinski6a008172016-02-02 17:02:58 -0800622 mContext->getDiagnostics()->error(
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800623 DiagMessage(configValue->value->getSource())
Adam Lesinski979ccb22016-01-11 10:42:19 -0800624 << "defined resource '" << resName
625 << "' for external package '" << package->name
626 << "'");
627 error = true;
628 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700629 }
630 }
631 }
632 }
633 }
Adam Lesinski979ccb22016-01-11 10:42:19 -0800634
635 auto newEndIter = std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(),
636 isExtPackageFunc);
637 mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700638 return !error;
639 }
640
Adam Lesinski64587af2016-02-18 18:33:06 -0800641 /**
642 * Returns true if no IDs have been set, false otherwise.
643 */
644 bool verifyNoIdsSet() {
645 for (const auto& package : mFinalTable.packages) {
646 for (const auto& type : package->types) {
647 if (type->id) {
648 mContext->getDiagnostics()->error(DiagMessage() << "type " << type->type
649 << " has ID " << std::hex
650 << (int) type->id.value()
651 << std::dec << " assigned");
652 return false;
653 }
654
655 for (const auto& entry : type->entries) {
656 if (entry->id) {
657 ResourceNameRef resName(package->name, type->type, entry->name);
658 mContext->getDiagnostics()->error(DiagMessage() << "entry " << resName
659 << " has ID " << std::hex
660 << (int) entry->id.value()
661 << std::dec << " assigned");
662 return false;
663 }
664 }
665 }
666 }
667 return true;
668 }
669
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700670 std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
671 if (mOptions.outputToDirectory) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800672 return createDirectoryArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700673 } else {
Adam Lesinski6a008172016-02-02 17:02:58 -0800674 return createZipFileArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700675 }
676 }
677
678 bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
679 BigBuffer buffer(1024);
Adam Lesinski59e04c62016-02-04 15:59:23 -0800680 TableFlattener flattener(&buffer);
Adam Lesinski6a008172016-02-02 17:02:58 -0800681 if (!flattener.consume(mContext, table)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700682 return false;
683 }
684
Adam Lesinskia40e9722015-11-24 19:11:46 -0800685 if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
686 if (writer->writeEntry(buffer)) {
687 if (writer->finishEntry()) {
688 return true;
689 }
690 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700691 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800692
Adam Lesinski6a008172016-02-02 17:02:58 -0800693 mContext->getDiagnostics()->error(
Adam Lesinskia40e9722015-11-24 19:11:46 -0800694 DiagMessage() << "failed to write resources.arsc to archive");
695 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700696 }
697
Adam Lesinski64587af2016-02-18 18:33:06 -0800698 bool flattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
699 // Create the file/zip entry.
700 if (!writer->startEntry("resources.arsc.flat", 0)) {
701 mContext->getDiagnostics()->error(DiagMessage() << "failed to open");
702 return false;
703 }
704
705 std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table);
706
707 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
708 // interface.
709 {
710 google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
711
712 if (!pbTable->SerializeToZeroCopyStream(&adaptor)) {
713 mContext->getDiagnostics()->error(DiagMessage() << "failed to write");
714 return false;
715 }
716 }
717
718 if (!writer->finishEntry()) {
719 mContext->getDiagnostics()->error(DiagMessage() << "failed to finish entry");
720 return false;
721 }
722 return true;
723 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700724
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700725 bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
726 const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700727 if (!mOptions.generateJavaClassPath) {
728 return true;
729 }
730
731 std::string outPath = mOptions.generateJavaClassPath.value();
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700732 file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(outPackage)));
Adam Lesinski96917c22016-03-09 13:11:25 -0800733 if (!file::mkdirs(outPath)) {
734 mContext->getDiagnostics()->error(
735 DiagMessage() << "failed to create directory '" << outPath << "'");
736 return false;
737 }
738
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700739 file::appendPath(&outPath, "R.java");
740
741 std::ofstream fout(outPath, std::ofstream::binary);
742 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800743 mContext->getDiagnostics()->error(
744 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700745 return false;
746 }
747
Adam Lesinski76565542016-03-10 21:55:04 -0800748 JavaClassGenerator generator(mContext, table, javaOptions);
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700749 if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800750 mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700751 return false;
752 }
Adam Lesinski96917c22016-03-09 13:11:25 -0800753
754 if (!fout) {
755 mContext->getDiagnostics()->error(
756 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
757 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700758 return true;
759 }
760
Adam Lesinski467f1712015-11-16 17:35:44 -0800761 bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
Adam Lesinskica5638f2015-10-21 14:42:43 -0700762 if (!mOptions.generateJavaClassPath) {
763 return true;
764 }
765
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700766 std::unique_ptr<ClassDefinition> manifestClass = generateManifestClass(
767 mContext->getDiagnostics(), manifestXml);
768
769 if (!manifestClass) {
770 // Something bad happened, but we already logged it, so exit.
771 return false;
772 }
773
774 if (manifestClass->empty()) {
775 // Empty Manifest class, no need to generate it.
776 return true;
777 }
778
Adam Lesinski3524a232016-04-01 19:19:24 -0700779 // Add any JavaDoc annotations to the generated class.
780 for (const std::string& annotation : mOptions.javadocAnnotations) {
781 std::string properAnnotation = "@";
782 properAnnotation += annotation;
783 manifestClass->getCommentBuilder()->appendComment(properAnnotation);
784 }
785
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700786 const std::string packageUtf8 = util::utf16ToUtf8(mContext->getCompilationPackage());
787
Adam Lesinskica5638f2015-10-21 14:42:43 -0700788 std::string outPath = mOptions.generateJavaClassPath.value();
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700789 file::appendPath(&outPath, file::packageToPath(packageUtf8));
790
Adam Lesinski96917c22016-03-09 13:11:25 -0800791 if (!file::mkdirs(outPath)) {
792 mContext->getDiagnostics()->error(
793 DiagMessage() << "failed to create directory '" << outPath << "'");
794 return false;
795 }
796
Adam Lesinskica5638f2015-10-21 14:42:43 -0700797 file::appendPath(&outPath, "Manifest.java");
798
799 std::ofstream fout(outPath, std::ofstream::binary);
800 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800801 mContext->getDiagnostics()->error(
802 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinskica5638f2015-10-21 14:42:43 -0700803 return false;
804 }
805
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700806 if (!ClassDefinition::writeJavaFile(manifestClass.get(), packageUtf8, true, &fout)) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800807 mContext->getDiagnostics()->error(
808 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinskica5638f2015-10-21 14:42:43 -0700809 return false;
810 }
811 return true;
812 }
813
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700814 bool writeProguardFile(const proguard::KeepSet& keepSet) {
815 if (!mOptions.generateProguardRulesPath) {
816 return true;
817 }
818
Adam Lesinski96917c22016-03-09 13:11:25 -0800819 const std::string& outPath = mOptions.generateProguardRulesPath.value();
820 std::ofstream fout(outPath, std::ofstream::binary);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700821 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800822 mContext->getDiagnostics()->error(
823 DiagMessage() << "failed to open '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700824 return false;
825 }
826
827 proguard::writeKeepSet(&fout, keepSet);
828 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800829 mContext->getDiagnostics()->error(
830 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700831 return false;
832 }
833 return true;
834 }
835
Adam Lesinski64587af2016-02-18 18:33:06 -0800836 std::unique_ptr<ResourceTable> loadStaticLibrary(const std::string& input,
837 std::string* outError) {
838 std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
839 input, outError);
840 if (!collection) {
841 return {};
842 }
843 return loadTablePbFromCollection(collection.get());
844 }
845
846 std::unique_ptr<ResourceTable> loadTablePbFromCollection(io::IFileCollection* collection) {
847 io::IFile* file = collection->findFile("resources.arsc.flat");
848 if (!file) {
849 return {};
850 }
851
852 std::unique_ptr<io::IData> data = file->openAsData();
853 return loadTableFromPb(file->getSource(), data->data(), data->size(),
854 mContext->getDiagnostics());
855 }
856
857 bool mergeStaticLibrary(const std::string& input, bool override) {
858 if (mContext->verbose()) {
859 mContext->getDiagnostics()->note(DiagMessage() << "merging static library " << input);
860 }
861
862 std::string errorStr;
863 std::unique_ptr<io::ZipFileCollection> collection =
864 io::ZipFileCollection::create(input, &errorStr);
865 if (!collection) {
866 mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
867 return false;
868 }
869
870 std::unique_ptr<ResourceTable> table = loadTablePbFromCollection(collection.get());
871 if (!table) {
872 mContext->getDiagnostics()->error(DiagMessage(input) << "invalid static library");
873 return false;
874 }
875
876 ResourceTablePackage* pkg = table->findPackageById(0x7f);
877 if (!pkg) {
878 mContext->getDiagnostics()->error(DiagMessage(input)
879 << "static library has no package");
880 return false;
881 }
882
883 bool result;
884 if (mOptions.noStaticLibPackages) {
885 // Merge all resources as if they were in the compilation package. This is the old
886 // behaviour of aapt.
887
888 // Add the package to the set of --extra-packages so we emit an R.java for each
889 // library package.
890 if (!pkg->name.empty()) {
891 mOptions.extraJavaPackages.insert(pkg->name);
892 }
893
894 pkg->name = u"";
895 if (override) {
896 result = mTableMerger->mergeOverlay(Source(input), table.get(), collection.get());
897 } else {
898 result = mTableMerger->merge(Source(input), table.get(), collection.get());
899 }
900
901 } else {
902 // This is the proper way to merge libraries, where the package name is preserved
903 // and resource names are mangled.
904 result = mTableMerger->mergeAndMangle(Source(input), pkg->name, table.get(),
905 collection.get());
906 }
907
908 if (!result) {
909 return false;
910 }
911
912 // Make sure to move the collection into the set of IFileCollections.
913 mCollections.push_back(std::move(collection));
Adam Lesinskifb48d292015-11-07 15:52:13 -0800914 return true;
915 }
916
Adam Lesinskia40e9722015-11-24 19:11:46 -0800917 bool mergeResourceTable(io::IFile* file, bool override) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800918 if (mContext->verbose()) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800919 mContext->getDiagnostics()->note(DiagMessage() << "merging resource table "
920 << file->getSource());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800921 }
922
Adam Lesinskia40e9722015-11-24 19:11:46 -0800923 std::unique_ptr<io::IData> data = file->openAsData();
924 if (!data) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800925 mContext->getDiagnostics()->error(DiagMessage(file->getSource())
Adam Lesinskia40e9722015-11-24 19:11:46 -0800926 << "failed to open file");
927 return false;
928 }
929
Adam Lesinski355f2852016-02-13 20:26:45 -0800930 std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(),
931 data->data(), data->size(),
932 mContext->getDiagnostics());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800933 if (!table) {
934 return false;
935 }
936
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800937 bool result = false;
938 if (override) {
939 result = mTableMerger->mergeOverlay(file->getSource(), table.get());
940 } else {
941 result = mTableMerger->merge(file->getSource(), table.get());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800942 }
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800943 return result;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800944 }
945
Adam Lesinski64587af2016-02-18 18:33:06 -0800946 bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc, bool override) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800947 if (mContext->verbose()) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800948 mContext->getDiagnostics()->note(DiagMessage() << "merging compiled file "
949 << file->getSource());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800950 }
951
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800952 bool result = false;
Adam Lesinski64587af2016-02-18 18:33:06 -0800953 if (override) {
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800954 result = mTableMerger->mergeFileOverlay(*fileDesc, file);
Adam Lesinskifb48d292015-11-07 15:52:13 -0800955 } else {
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800956 result = mTableMerger->mergeFile(*fileDesc, file);
Adam Lesinskifb48d292015-11-07 15:52:13 -0800957 }
958
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800959 if (!result) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800960 return false;
961 }
962
963 // Add the exports of this file to the table.
Adam Lesinskia40e9722015-11-24 19:11:46 -0800964 for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800965 if (exportedSymbol.name.package.empty()) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800966 exportedSymbol.name.package = mContext->getCompilationPackage();
Adam Lesinskifb48d292015-11-07 15:52:13 -0800967 }
968
969 ResourceNameRef resName = exportedSymbol.name;
970
Adam Lesinski6a008172016-02-02 17:02:58 -0800971 Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
Adam Lesinskifb48d292015-11-07 15:52:13 -0800972 exportedSymbol.name);
973 if (mangledName) {
974 resName = mangledName.value();
975 }
976
977 std::unique_ptr<Id> id = util::make_unique<Id>();
Adam Lesinskia40e9722015-11-24 19:11:46 -0800978 id->setSource(fileDesc->source.withLine(exportedSymbol.line));
Adam Lesinski64587af2016-02-18 18:33:06 -0800979 bool result = mFinalTable.addResourceAllowMangled(
980 resName, ConfigDescription::defaultConfig(), std::string(), std::move(id),
981 mContext->getDiagnostics());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800982 if (!result) {
983 return false;
984 }
985 }
Adam Lesinskifb48d292015-11-07 15:52:13 -0800986 return true;
987 }
988
Adam Lesinskia40e9722015-11-24 19:11:46 -0800989 /**
Adam Lesinski64587af2016-02-18 18:33:06 -0800990 * Takes a path to load as a ZIP file and merges the files within into the master ResourceTable.
991 * If override is true, conflicting resources are allowed to override each other, in order of
992 * last seen.
993 *
994 * An io::IFileCollection is created from the ZIP file and added to the set of
995 * io::IFileCollections that are open.
Adam Lesinskia40e9722015-11-24 19:11:46 -0800996 */
997 bool mergeArchive(const std::string& input, bool override) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800998 if (mContext->verbose()) {
999 mContext->getDiagnostics()->note(DiagMessage() << "merging archive " << input);
1000 }
1001
Adam Lesinskia40e9722015-11-24 19:11:46 -08001002 std::string errorStr;
Adam Lesinski64587af2016-02-18 18:33:06 -08001003 std::unique_ptr<io::ZipFileCollection> collection =
1004 io::ZipFileCollection::create(input, &errorStr);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001005 if (!collection) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001006 mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001007 return false;
1008 }
1009
1010 bool error = false;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001011 for (auto iter = collection->iterator(); iter->hasNext(); ) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001012 if (!mergeFile(iter->next(), override)) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001013 error = true;
1014 }
1015 }
1016
1017 // Make sure to move the collection into the set of IFileCollections.
1018 mCollections.push_back(std::move(collection));
1019 return !error;
1020 }
1021
Adam Lesinski64587af2016-02-18 18:33:06 -08001022 /**
1023 * Takes a path to load and merge into the master ResourceTable. If override is true,
1024 * conflicting resources are allowed to override each other, in order of last seen.
1025 *
1026 * If the file path ends with .flata, .jar, .jack, or .zip the file is treated as ZIP archive
1027 * and the files within are merged individually.
1028 *
1029 * Otherwise the files is processed on its own.
1030 */
1031 bool mergePath(const std::string& path, bool override) {
Adam Lesinski656a5772016-01-14 15:17:41 -08001032 if (util::stringEndsWith<char>(path, ".flata") ||
1033 util::stringEndsWith<char>(path, ".jar") ||
1034 util::stringEndsWith<char>(path, ".jack") ||
1035 util::stringEndsWith<char>(path, ".zip")) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001036 return mergeArchive(path, override);
Adam Lesinski64587af2016-02-18 18:33:06 -08001037 } else if (util::stringEndsWith<char>(path, ".apk")) {
1038 return mergeStaticLibrary(path, override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001039 }
1040
1041 io::IFile* file = mFileCollection->insertFile(path);
Adam Lesinski64587af2016-02-18 18:33:06 -08001042 return mergeFile(file, override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001043 }
1044
Adam Lesinski64587af2016-02-18 18:33:06 -08001045 /**
1046 * Takes a file to load and merge into the master ResourceTable. If override is true,
1047 * conflicting resources are allowed to override each other, in order of last seen.
1048 *
1049 * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and merged into the
1050 * master ResourceTable. If the file ends with .flat, then it is treated like a compiled file
1051 * and the header data is read and merged into the final ResourceTable.
1052 *
1053 * All other file types are ignored. This is because these files could be coming from a zip,
1054 * where we could have other files like classes.dex.
1055 */
1056 bool mergeFile(io::IFile* file, bool override) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001057 const Source& src = file->getSource();
1058 if (util::stringEndsWith<char>(src.path, ".arsc.flat")) {
1059 return mergeResourceTable(file, override);
Adam Lesinski64587af2016-02-18 18:33:06 -08001060
Adam Lesinski52364f72016-01-11 13:10:24 -08001061 } else if (util::stringEndsWith<char>(src.path, ".flat")){
Adam Lesinskia40e9722015-11-24 19:11:46 -08001062 // Try opening the file and looking for an Export header.
1063 std::unique_ptr<io::IData> data = file->openAsData();
1064 if (!data) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001065 mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open");
Adam Lesinskia40e9722015-11-24 19:11:46 -08001066 return false;
1067 }
1068
1069 std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader(
Adam Lesinski6a008172016-02-02 17:02:58 -08001070 src, data->data(), data->size(), mContext->getDiagnostics());
Adam Lesinskia40e9722015-11-24 19:11:46 -08001071 if (resourceFile) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001072 return mergeCompiledFile(file, resourceFile.get(), override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001073 }
Adam Lesinskic446a732016-01-21 11:04:46 -08001074 return false;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001075 }
Adam Lesinski52364f72016-01-11 13:10:24 -08001076
Adam Lesinskic446a732016-01-21 11:04:46 -08001077 // Ignore non .flat files. This could be classes.dex or something else that happens
1078 // to be in an archive.
1079 return true;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001080 }
1081
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001082 int run(const std::vector<std::string>& inputFiles) {
1083 // Load the AndroidManifest.xml
Adam Lesinskia40e9722015-11-24 19:11:46 -08001084 std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
Adam Lesinski6a008172016-02-02 17:02:58 -08001085 mContext->getDiagnostics());
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001086 if (!manifestXml) {
1087 return 1;
1088 }
1089
1090 if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001091 mContext->setCompilationPackage(maybeAppInfo.value().package);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001092 } else {
Adam Lesinski6a008172016-02-02 17:02:58 -08001093 mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001094 << "no package specified in <manifest> tag");
1095 return 1;
1096 }
1097
Adam Lesinski64587af2016-02-18 18:33:06 -08001098 if (!util::isJavaPackageName(mContext->getCompilationPackage())) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001099 mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001100 << "invalid package name '"
Adam Lesinski64587af2016-02-18 18:33:06 -08001101 << mContext->getCompilationPackage()
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001102 << "'");
1103 return 1;
1104 }
1105
Adam Lesinski64587af2016-02-18 18:33:06 -08001106 mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() });
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001107
Adam Lesinski64587af2016-02-18 18:33:06 -08001108 if (mContext->getCompilationPackage() == u"android") {
1109 mContext->setPackageId(0x01);
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001110 } else {
Adam Lesinski64587af2016-02-18 18:33:06 -08001111 mContext->setPackageId(0x7f);
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001112 }
1113
Adam Lesinski64587af2016-02-18 18:33:06 -08001114 if (!loadSymbolsFromIncludePaths()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001115 return 1;
1116 }
1117
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001118 TableMergerOptions tableMergerOptions;
1119 tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
Adam Lesinski6a008172016-02-02 17:02:58 -08001120 mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions);
Adam Lesinskifb48d292015-11-07 15:52:13 -08001121
Adam Lesinski355f2852016-02-13 20:26:45 -08001122 if (mContext->verbose()) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001123 mContext->getDiagnostics()->note(
Adam Lesinski64587af2016-02-18 18:33:06 -08001124 DiagMessage() << "linking package '" << mContext->getCompilationPackage()
1125 << "' with package ID " << std::hex
1126 << (int) mContext->getPackageId());
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001127 }
1128
Adam Lesinskifb48d292015-11-07 15:52:13 -08001129
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001130 for (const std::string& input : inputFiles) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001131 if (!mergePath(input, false)) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001132 mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input");
Adam Lesinski467f1712015-11-16 17:35:44 -08001133 return 1;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001134 }
1135 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001136
Adam Lesinskifb48d292015-11-07 15:52:13 -08001137 for (const std::string& input : mOptions.overlayFiles) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001138 if (!mergePath(input, true)) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001139 mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
Adam Lesinski467f1712015-11-16 17:35:44 -08001140 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001141 }
1142 }
1143
Adam Lesinskifb48d292015-11-07 15:52:13 -08001144 if (!verifyNoExternalPackages()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001145 return 1;
1146 }
1147
1148 if (!mOptions.staticLib) {
1149 PrivateAttributeMover mover;
Adam Lesinski6a008172016-02-02 17:02:58 -08001150 if (!mover.consume(mContext, &mFinalTable)) {
1151 mContext->getDiagnostics()->error(
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001152 DiagMessage() << "failed moving private attributes");
1153 return 1;
1154 }
1155 }
1156
Adam Lesinski64587af2016-02-18 18:33:06 -08001157 if (!mOptions.staticLib) {
1158 // Assign IDs if we are building a regular app.
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001159 IdAssigner idAssigner;
Adam Lesinski6a008172016-02-02 17:02:58 -08001160 if (!idAssigner.consume(mContext, &mFinalTable)) {
1161 mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001162 return 1;
1163 }
Adam Lesinski64587af2016-02-18 18:33:06 -08001164 } else {
1165 // Static libs are merged with other apps, and ID collisions are bad, so verify that
1166 // no IDs have been set.
1167 if (!verifyNoIdsSet()) {
1168 return 1;
1169 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001170 }
1171
Adam Lesinski64587af2016-02-18 18:33:06 -08001172 // Add the names to mangle based on our source merge earlier.
1173 mContext->setNameManglerPolicy(NameManglerPolicy{
1174 mContext->getCompilationPackage(), mTableMerger->getMergedPackages() });
1175
1176 // Add our table to the symbol table.
1177 mContext->getExternalSymbols()->prependSource(
1178 util::make_unique<ResourceTableSymbolSource>(&mFinalTable));
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001179
1180 {
1181 ReferenceLinker linker;
Adam Lesinski6a008172016-02-02 17:02:58 -08001182 if (!linker.consume(mContext, &mFinalTable)) {
1183 mContext->getDiagnostics()->error(DiagMessage() << "failed linking references");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001184 return 1;
1185 }
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001186
Adam Lesinski64587af2016-02-18 18:33:06 -08001187 if (mOptions.staticLib) {
1188 if (!mOptions.products.empty()) {
1189 mContext->getDiagnostics()->warn(
1190 DiagMessage() << "can't select products when building static library");
1191 }
Adam Lesinski355f2852016-02-13 20:26:45 -08001192
Adam Lesinski64587af2016-02-18 18:33:06 -08001193 if (mOptions.tableSplitterOptions.configFilter != nullptr ||
1194 mOptions.tableSplitterOptions.preferredDensity) {
1195 mContext->getDiagnostics()->warn(
1196 DiagMessage() << "can't strip resources when building static library");
1197 }
1198 } else {
1199 ProductFilter productFilter(mOptions.products);
1200 if (!productFilter.consume(mContext, &mFinalTable)) {
1201 mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products");
1202 return 1;
1203 }
Adam Lesinski355f2852016-02-13 20:26:45 -08001204
Adam Lesinski64587af2016-02-18 18:33:06 -08001205 // TODO(adamlesinski): Actually pass in split constraints and handle splits at the file
1206 // level.
1207 TableSplitter tableSplitter({}, mOptions.tableSplitterOptions);
1208 if (!tableSplitter.verifySplitConstraints(mContext)) {
1209 return 1;
1210 }
1211 tableSplitter.splitTable(&mFinalTable);
1212 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001213 }
1214
1215 proguard::KeepSet proguardKeepSet;
1216
1217 std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter();
1218 if (!archiveWriter) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001219 mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001220 return 1;
1221 }
1222
Adam Lesinski467f1712015-11-16 17:35:44 -08001223 bool error = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001224 {
Adam Lesinski52364f72016-01-11 13:10:24 -08001225 ManifestFixer manifestFixer(mOptions.manifestFixerOptions);
Adam Lesinski6a008172016-02-02 17:02:58 -08001226 if (!manifestFixer.consume(mContext, manifestXml.get())) {
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001227 error = true;
1228 }
1229
Adam Lesinski467f1712015-11-16 17:35:44 -08001230 // AndroidManifest.xml has no resource name, but the CallSite is built from the name
1231 // (aka, which package the AndroidManifest.xml is coming from).
1232 // So we give it a package name so it can see local resources.
Adam Lesinski64587af2016-02-18 18:33:06 -08001233 manifestXml->file.name.package = mContext->getCompilationPackage();
Adam Lesinski467f1712015-11-16 17:35:44 -08001234
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001235 XmlReferenceLinker manifestLinker;
Adam Lesinski6a008172016-02-02 17:02:58 -08001236 if (manifestLinker.consume(mContext, manifestXml.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001237 if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
1238 manifestXml.get(),
1239 &proguardKeepSet)) {
1240 error = true;
1241 }
1242
Adam Lesinskica5638f2015-10-21 14:42:43 -07001243 if (mOptions.generateJavaClassPath) {
1244 if (!writeManifestJavaFile(manifestXml.get())) {
1245 error = true;
1246 }
1247 }
1248
Adam Lesinski355f2852016-02-13 20:26:45 -08001249 const bool keepRawValues = mOptions.staticLib;
1250 bool result = flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
1251 keepRawValues, archiveWriter.get(), mContext);
1252 if (!result) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001253 error = true;
1254 }
1255 } else {
1256 error = true;
1257 }
1258 }
1259
Adam Lesinski467f1712015-11-16 17:35:44 -08001260 if (error) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001261 mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest");
Adam Lesinski467f1712015-11-16 17:35:44 -08001262 return 1;
1263 }
1264
Adam Lesinski355f2852016-02-13 20:26:45 -08001265 ResourceFileFlattenerOptions fileFlattenerOptions;
1266 fileFlattenerOptions.keepRawValues = mOptions.staticLib;
1267 fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything;
1268 fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress;
1269 fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion;
Adam Lesinski626a69f2016-03-03 10:09:26 -08001270 fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors;
Adam Lesinski355f2852016-02-13 20:26:45 -08001271 ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, &proguardKeepSet);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001272
Adam Lesinski355f2852016-02-13 20:26:45 -08001273 if (!fileFlattener.flatten(&mFinalTable, archiveWriter.get())) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001274 mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001275 return 1;
1276 }
1277
Adam Lesinski626a69f2016-03-03 10:09:26 -08001278 if (!mOptions.noAutoVersion) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001279 AutoVersioner versioner;
Adam Lesinski6a008172016-02-02 17:02:58 -08001280 if (!versioner.consume(mContext, &mFinalTable)) {
1281 mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001282 return 1;
1283 }
1284 }
1285
Adam Lesinski64587af2016-02-18 18:33:06 -08001286 if (mOptions.staticLib) {
1287 if (!flattenTableToPb(&mFinalTable, archiveWriter.get())) {
1288 mContext->getDiagnostics()->error(DiagMessage()
1289 << "failed to write resources.arsc.flat");
1290 return 1;
1291 }
1292 } else {
1293 if (!flattenTable(&mFinalTable, archiveWriter.get())) {
1294 mContext->getDiagnostics()->error(DiagMessage()
1295 << "failed to write resources.arsc");
1296 return 1;
1297 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001298 }
1299
1300 if (mOptions.generateJavaClassPath) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001301 JavaClassGeneratorOptions options;
Adam Lesinski52364f72016-01-11 13:10:24 -08001302 options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
Adam Lesinski3524a232016-04-01 19:19:24 -07001303 options.javadocAnnotations = mOptions.javadocAnnotations;
Adam Lesinski52364f72016-01-11 13:10:24 -08001304
Adam Lesinskief9c5012016-01-22 14:09:53 -08001305 if (mOptions.staticLib || mOptions.generateNonFinalIds) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001306 options.useFinal = false;
1307 }
1308
Adam Lesinski6a008172016-02-02 17:02:58 -08001309 const StringPiece16 actualPackage = mContext->getCompilationPackage();
1310 StringPiece16 outputPackage = mContext->getCompilationPackage();
Adam Lesinski52364f72016-01-11 13:10:24 -08001311 if (mOptions.customJavaPackage) {
1312 // Override the output java package to the custom one.
1313 outputPackage = mOptions.customJavaPackage.value();
1314 }
Adam Lesinski83f22552015-11-07 11:51:23 -08001315
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001316 if (mOptions.privateSymbols) {
1317 // If we defined a private symbols package, we only emit Public symbols
1318 // to the original package, and private and public symbols to the private package.
1319
1320 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
Adam Lesinski6a008172016-02-02 17:02:58 -08001321 if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(),
Adam Lesinski52364f72016-01-11 13:10:24 -08001322 outputPackage, options)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001323 return 1;
1324 }
1325
1326 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
Adam Lesinski83f22552015-11-07 11:51:23 -08001327 outputPackage = mOptions.privateSymbols.value();
1328 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001329
Adam Lesinskifb48d292015-11-07 15:52:13 -08001330 if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
Adam Lesinski83f22552015-11-07 11:51:23 -08001331 return 1;
1332 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001333
Adam Lesinski52364f72016-01-11 13:10:24 -08001334 for (const std::u16string& extraPackage : mOptions.extraJavaPackages) {
1335 if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001336 return 1;
1337 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001338 }
1339 }
1340
1341 if (mOptions.generateProguardRulesPath) {
1342 if (!writeProguardFile(proguardKeepSet)) {
1343 return 1;
1344 }
1345 }
1346
Adam Lesinski355f2852016-02-13 20:26:45 -08001347 if (mContext->verbose()) {
1348 DebugPrintTableOptions debugPrintTableOptions;
1349 debugPrintTableOptions.showSources = true;
1350 Debug::printTable(&mFinalTable, debugPrintTableOptions);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001351 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001352 return 0;
1353 }
Adam Lesinskifb48d292015-11-07 15:52:13 -08001354
1355private:
1356 LinkOptions mOptions;
Adam Lesinski6a008172016-02-02 17:02:58 -08001357 LinkContext* mContext;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001358 ResourceTable mFinalTable;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001359
Adam Lesinskifb48d292015-11-07 15:52:13 -08001360 std::unique_ptr<TableMerger> mTableMerger;
1361
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001362 // A pointer to the FileCollection representing the filesystem (not archives).
Adam Lesinski64587af2016-02-18 18:33:06 -08001363 std::unique_ptr<io::FileCollection> mFileCollection;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001364
1365 // A vector of IFileCollections. This is mainly here to keep ownership of the collections.
Adam Lesinskia40e9722015-11-24 19:11:46 -08001366 std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
Adam Lesinski64587af2016-02-18 18:33:06 -08001367
1368 // A vector of ResourceTables. This is here to retain ownership, so that the SymbolTable
1369 // can use these.
1370 std::vector<std::unique_ptr<ResourceTable>> mStaticTableIncludes;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001371};
1372
1373int link(const std::vector<StringPiece>& args) {
Adam Lesinski355f2852016-02-13 20:26:45 -08001374 LinkContext context;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001375 LinkOptions options;
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001376 Maybe<std::string> privateSymbolsPackage;
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001377 Maybe<std::string> minSdkVersion, targetSdkVersion;
Adam Lesinski52364f72016-01-11 13:10:24 -08001378 Maybe<std::string> renameManifestPackage, renameInstrumentationTargetPackage;
1379 Maybe<std::string> versionCode, versionName;
1380 Maybe<std::string> customJavaPackage;
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001381 std::vector<std::string> extraJavaPackages;
Adam Lesinski6a008172016-02-02 17:02:58 -08001382 Maybe<std::string> configs;
Adam Lesinski355f2852016-02-13 20:26:45 -08001383 Maybe<std::string> preferredDensity;
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001384 Maybe<std::string> productList;
Adam Lesinski8900aa82016-01-25 22:48:15 -08001385 bool legacyXFlag = false;
1386 bool requireLocalization = false;
Adam Lesinski64587af2016-02-18 18:33:06 -08001387 bool verbose = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001388 Flags flags = Flags()
1389 .requiredFlag("-o", "Output path", &options.outputPath)
1390 .requiredFlag("--manifest", "Path to the Android manifest to build",
1391 &options.manifestPath)
1392 .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
Adam Lesinski52364f72016-01-11 13:10:24 -08001393 .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n"
Adam Lesinskifb48d292015-11-07 15:52:13 -08001394 "The last conflicting resource given takes precedence.",
1395 &options.overlayFiles)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001396 .optionalFlag("--java", "Directory in which to generate R.java",
1397 &options.generateJavaClassPath)
1398 .optionalFlag("--proguard", "Output file for generated Proguard rules",
1399 &options.generateProguardRulesPath)
1400 .optionalSwitch("--no-auto-version",
1401 "Disables automatic style and layout SDK versioning",
1402 &options.noAutoVersion)
Adam Lesinski64587af2016-02-18 18:33:06 -08001403 .optionalSwitch("--no-version-vectors",
1404 "Disables automatic versioning of vector drawables. Use this only\n"
1405 "when building with vector drawable support library",
1406 &options.noVersionVectors)
Adam Lesinski8900aa82016-01-25 22:48:15 -08001407 .optionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01",
1408 &legacyXFlag)
1409 .optionalSwitch("-z", "Require localization of strings marked 'suggested'",
1410 &requireLocalization)
Adam Lesinski6a008172016-02-02 17:02:58 -08001411 .optionalFlag("-c", "Comma separated list of configurations to include. The default\n"
1412 "is all configurations", &configs)
Adam Lesinski355f2852016-02-13 20:26:45 -08001413 .optionalFlag("--preferred-density",
1414 "Selects the closest matching density and strips out all others.",
1415 &preferredDensity)
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001416 .optionalFlag("--product", "Comma separated list of product names to keep",
1417 &productList)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001418 .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
1419 "by -o",
1420 &options.outputToDirectory)
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001421 .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
1422 "AndroidManifest.xml", &minSdkVersion)
1423 .optionalFlag("--target-sdk-version", "Default target SDK version to use for "
1424 "AndroidManifest.xml", &targetSdkVersion)
Adam Lesinski52364f72016-01-11 13:10:24 -08001425 .optionalFlag("--version-code", "Version code (integer) to inject into the "
1426 "AndroidManifest.xml if none is present", &versionCode)
1427 .optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml "
1428 "if none is present", &versionName)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001429 .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
Adam Lesinski64587af2016-02-18 18:33:06 -08001430 .optionalSwitch("--no-static-lib-packages",
1431 "Merge all library resources under the app's package",
1432 &options.noStaticLibPackages)
Adam Lesinskief9c5012016-01-22 14:09:53 -08001433 .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n"
1434 "This is implied when --static-lib is specified.",
1435 &options.generateNonFinalIds)
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001436 .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001437 "private symbols.\n"
1438 "If not specified, public and private symbols will use the application's "
1439 "package name", &privateSymbolsPackage)
Adam Lesinski52364f72016-01-11 13:10:24 -08001440 .optionalFlag("--custom-package", "Custom Java package under which to generate R.java",
1441 &customJavaPackage)
Adam Lesinski83f22552015-11-07 11:51:23 -08001442 .optionalFlagList("--extra-packages", "Generate the same R.java but with different "
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001443 "package names", &extraJavaPackages)
Adam Lesinski3524a232016-04-01 19:19:24 -07001444 .optionalFlagList("--add-javadoc-annotation", "Adds a JavaDoc annotation to all "
1445 "generated Java classes", &options.javadocAnnotations)
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001446 .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in "
1447 "overlays without <add-resource> tags", &options.autoAddOverlay)
Adam Lesinski52364f72016-01-11 13:10:24 -08001448 .optionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml",
1449 &renameManifestPackage)
1450 .optionalFlag("--rename-instrumentation-target-package",
1451 "Changes the name of the target package for instrumentation. Most useful "
1452 "when used\nin conjunction with --rename-manifest-package",
1453 &renameInstrumentationTargetPackage)
1454 .optionalFlagList("-0", "File extensions not to compress",
1455 &options.extensionsToNotCompress)
Adam Lesinski64587af2016-02-18 18:33:06 -08001456 .optionalSwitch("-v", "Enables verbose logging", &verbose);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001457
1458 if (!flags.parse("aapt2 link", args, &std::cerr)) {
1459 return 1;
1460 }
1461
Adam Lesinskic51562c2016-04-28 11:12:38 -07001462 // Expand all argument-files passed into the command line. These start with '@'.
1463 std::vector<std::string> argList;
1464 for (const std::string& arg : flags.getArgs()) {
1465 if (util::stringStartsWith<char>(arg, "@")) {
1466 const std::string path = arg.substr(1, arg.size() - 1);
1467 std::string error;
1468 if (!file::appendArgsFromFile(path, &argList, &error)) {
1469 context.getDiagnostics()->error(DiagMessage(path) << error);
1470 return 1;
1471 }
1472 } else {
1473 argList.push_back(arg);
1474 }
1475 }
1476
Adam Lesinski64587af2016-02-18 18:33:06 -08001477 if (verbose) {
1478 context.setVerbose(verbose);
1479 }
1480
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001481 if (privateSymbolsPackage) {
1482 options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
1483 }
1484
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001485 if (minSdkVersion) {
Adam Lesinski52364f72016-01-11 13:10:24 -08001486 options.manifestFixerOptions.minSdkVersionDefault =
1487 util::utf8ToUtf16(minSdkVersion.value());
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001488 }
1489
1490 if (targetSdkVersion) {
Adam Lesinski52364f72016-01-11 13:10:24 -08001491 options.manifestFixerOptions.targetSdkVersionDefault =
1492 util::utf8ToUtf16(targetSdkVersion.value());
1493 }
1494
1495 if (renameManifestPackage) {
1496 options.manifestFixerOptions.renameManifestPackage =
1497 util::utf8ToUtf16(renameManifestPackage.value());
1498 }
1499
1500 if (renameInstrumentationTargetPackage) {
1501 options.manifestFixerOptions.renameInstrumentationTargetPackage =
1502 util::utf8ToUtf16(renameInstrumentationTargetPackage.value());
1503 }
1504
1505 if (versionCode) {
1506 options.manifestFixerOptions.versionCodeDefault = util::utf8ToUtf16(versionCode.value());
1507 }
1508
1509 if (versionName) {
1510 options.manifestFixerOptions.versionNameDefault = util::utf8ToUtf16(versionName.value());
1511 }
1512
1513 if (customJavaPackage) {
1514 options.customJavaPackage = util::utf8ToUtf16(customJavaPackage.value());
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001515 }
1516
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001517 // Populate the set of extra packages for which to generate R.java.
1518 for (std::string& extraPackage : extraJavaPackages) {
1519 // A given package can actually be a colon separated list of packages.
1520 for (StringPiece package : util::split(extraPackage, ':')) {
Adam Lesinski52364f72016-01-11 13:10:24 -08001521 options.extraJavaPackages.insert(util::utf8ToUtf16(package));
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001522 }
1523 }
1524
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001525 if (productList) {
1526 for (StringPiece product : util::tokenize<char>(productList.value(), ',')) {
1527 if (product != "" && product != "default") {
1528 options.products.insert(product.toString());
1529 }
1530 }
1531 }
1532
Adam Lesinski6a008172016-02-02 17:02:58 -08001533 AxisConfigFilter filter;
1534 if (configs) {
1535 for (const StringPiece& configStr : util::tokenize<char>(configs.value(), ',')) {
1536 ConfigDescription config;
1537 LocaleValue lv;
1538 if (lv.initFromFilterString(configStr)) {
1539 lv.writeTo(&config);
1540 } else if (!ConfigDescription::parse(configStr, &config)) {
1541 context.getDiagnostics()->error(
1542 DiagMessage() << "invalid config '" << configStr << "' for -c option");
1543 return 1;
1544 }
1545
1546 if (config.density != 0) {
1547 context.getDiagnostics()->warn(
1548 DiagMessage() << "ignoring density '" << config << "' for -c option");
1549 } else {
1550 filter.addConfig(config);
1551 }
1552 }
1553
Adam Lesinski355f2852016-02-13 20:26:45 -08001554 options.tableSplitterOptions.configFilter = &filter;
1555 }
1556
1557 if (preferredDensity) {
1558 ConfigDescription preferredDensityConfig;
1559 if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) {
1560 context.getDiagnostics()->error(DiagMessage() << "invalid density '"
1561 << preferredDensity.value()
1562 << "' for --preferred-density option");
1563 return 1;
1564 }
1565
1566 // Clear the version that can be automatically added.
1567 preferredDensityConfig.sdkVersion = 0;
1568
1569 if (preferredDensityConfig.diff(ConfigDescription::defaultConfig())
1570 != ConfigDescription::CONFIG_DENSITY) {
1571 context.getDiagnostics()->error(DiagMessage() << "invalid preferred density '"
1572 << preferredDensity.value() << "'. "
1573 << "Preferred density must only be a density value");
1574 return 1;
1575 }
1576 options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density;
Adam Lesinski6a008172016-02-02 17:02:58 -08001577 }
1578
Adam Lesinski626a69f2016-03-03 10:09:26 -08001579 // Turn off auto versioning for static-libs.
1580 if (options.staticLib) {
1581 options.noAutoVersion = true;
1582 options.noVersionVectors = true;
1583 }
1584
Adam Lesinski6a008172016-02-02 17:02:58 -08001585 LinkCommand cmd(&context, options);
Adam Lesinskic51562c2016-04-28 11:12:38 -07001586 return cmd.run(argList);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001587}
1588
1589} // namespace aapt