blob: 9411053170db260ca68cce1456d86ef853680403 [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;
Rohit Agrawale49bb302016-04-22 12:27:55 -070064 Maybe<std::string> generateMainDexProguardRulesPath;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070065 bool noAutoVersion = false;
Adam Lesinski64587af2016-02-18 18:33:06 -080066 bool noVersionVectors = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070067 bool staticLib = false;
Adam Lesinski64587af2016-02-18 18:33:06 -080068 bool noStaticLibPackages = false;
Adam Lesinskief9c5012016-01-22 14:09:53 -080069 bool generateNonFinalIds = false;
Adam Lesinski3524a232016-04-01 19:19:24 -070070 std::vector<std::string> javadocAnnotations;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070071 bool outputToDirectory = false;
Adam Lesinskia6fe3452015-12-09 15:20:52 -080072 bool autoAddOverlay = false;
Adam Lesinski52364f72016-01-11 13:10:24 -080073 bool doNotCompressAnything = false;
74 std::vector<std::string> extensionsToNotCompress;
Adam Lesinski9e10ac72015-10-16 14:37:48 -070075 Maybe<std::u16string> privateSymbols;
Adam Lesinski52364f72016-01-11 13:10:24 -080076 ManifestFixerOptions manifestFixerOptions;
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -080077 std::unordered_set<std::string> products;
Adam Lesinski355f2852016-02-13 20:26:45 -080078 TableSplitterOptions tableSplitterOptions;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070079};
80
Adam Lesinski64587af2016-02-18 18:33:06 -080081class LinkContext : public IAaptContext {
82public:
83 LinkContext() : mNameMangler({}) {
84 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -070085
86 IDiagnostics* getDiagnostics() override {
87 return &mDiagnostics;
88 }
89
90 NameMangler* getNameMangler() override {
Adam Lesinski64587af2016-02-18 18:33:06 -080091 return &mNameMangler;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070092 }
93
Adam Lesinski64587af2016-02-18 18:33:06 -080094 void setNameManglerPolicy(const NameManglerPolicy& policy) {
95 mNameMangler = NameMangler(policy);
96 }
97
98 const std::u16string& getCompilationPackage() override {
Adam Lesinski1ab598f2015-08-14 14:26:04 -070099 return mCompilationPackage;
100 }
101
Adam Lesinski64587af2016-02-18 18:33:06 -0800102 void setCompilationPackage(const StringPiece16& packageName) {
103 mCompilationPackage = packageName.toString();
104 }
105
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700106 uint8_t getPackageId() override {
107 return mPackageId;
108 }
109
Adam Lesinski64587af2016-02-18 18:33:06 -0800110 void setPackageId(uint8_t id) {
111 mPackageId = id;
112 }
113
114 SymbolTable* getExternalSymbols() override {
115 return &mSymbols;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700116 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800117
118 bool verbose() override {
119 return mVerbose;
120 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800121
122 void setVerbose(bool val) {
123 mVerbose = val;
124 }
125
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700126 int getMinSdkVersion() override {
127 return mMinSdkVersion;
128 }
129
130 void setMinSdkVersion(int minSdk) {
131 mMinSdkVersion = minSdk;
132 }
133
Adam Lesinski64587af2016-02-18 18:33:06 -0800134private:
135 StdErrDiagnostics mDiagnostics;
136 NameMangler mNameMangler;
137 std::u16string mCompilationPackage;
138 uint8_t mPackageId = 0x0;
139 SymbolTable mSymbols;
140 bool mVerbose = false;
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700141 int mMinSdkVersion = 0;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700142};
143
Adam Lesinski355f2852016-02-13 20:26:45 -0800144static bool copyFileToArchive(io::IFile* file, const std::string& outPath,
145 uint32_t compressionFlags,
146 IArchiveWriter* writer, IAaptContext* context) {
147 std::unique_ptr<io::IData> data = file->openAsData();
148 if (!data) {
149 context->getDiagnostics()->error(DiagMessage(file->getSource())
150 << "failed to open file");
151 return false;
152 }
153
Adam Lesinski64587af2016-02-18 18:33:06 -0800154 const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data());
155 size_t bufferSize = data->size();
156
157 // If the file ends with .flat, we must strip off the CompiledFileHeader from it.
158 if (util::stringEndsWith<char>(file->getSource().path, ".flat")) {
159 CompiledFileInputStream inputStream(data->data(), data->size());
160 if (!inputStream.CompiledFile()) {
161 context->getDiagnostics()->error(DiagMessage(file->getSource())
162 << "invalid compiled file header");
163 return false;
164 }
165 buffer = reinterpret_cast<const uint8_t*>(inputStream.data());
166 bufferSize = inputStream.size();
Adam Lesinski355f2852016-02-13 20:26:45 -0800167 }
168
169 if (context->verbose()) {
170 context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive");
171 }
172
173 if (writer->startEntry(outPath, compressionFlags)) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800174 if (writer->writeEntry(buffer, bufferSize)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800175 if (writer->finishEntry()) {
176 return true;
177 }
178 }
179 }
180
181 context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath);
182 return false;
183}
184
185static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
186 bool keepRawValues, IArchiveWriter* writer, IAaptContext* context) {
187 BigBuffer buffer(1024);
188 XmlFlattenerOptions options = {};
189 options.keepRawValues = keepRawValues;
190 options.maxSdkLevel = maxSdkLevel;
191 XmlFlattener flattener(&buffer, options);
192 if (!flattener.consume(context, xmlRes)) {
193 return false;
194 }
195
196 if (context->verbose()) {
197 DiagMessage msg;
198 msg << "writing " << path << " to archive";
199 if (maxSdkLevel) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800200 msg << " maxSdkLevel=" << maxSdkLevel.value() << " keepRawValues=" << keepRawValues;
Adam Lesinski355f2852016-02-13 20:26:45 -0800201 }
202 context->getDiagnostics()->note(msg);
203 }
204
205 if (writer->startEntry(path, ArchiveEntry::kCompress)) {
206 if (writer->writeEntry(buffer)) {
207 if (writer->finishEntry()) {
208 return true;
209 }
210 }
211 }
212 context->getDiagnostics()->error(DiagMessage() << "failed to write " << path << " to archive");
213 return false;
214}
215
216/*static std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len,
217 IDiagnostics* diag) {
218 std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
219 BinaryResourceParser parser(diag, table.get(), source, data, len);
220 if (!parser.parse()) {
221 return {};
222 }
223 return table;
224}*/
225
226static std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
227 const void* data, size_t len,
228 IDiagnostics* diag) {
229 pb::ResourceTable pbTable;
230 if (!pbTable.ParseFromArray(data, len)) {
231 diag->error(DiagMessage(source) << "invalid compiled table");
232 return {};
233 }
234
235 std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source, diag);
236 if (!table) {
237 return {};
238 }
239 return table;
240}
241
242/**
243 * Inflates an XML file from the source path.
244 */
245static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
246 std::ifstream fin(path, std::ifstream::binary);
247 if (!fin) {
248 diag->error(DiagMessage(path) << strerror(errno));
249 return {};
250 }
251 return xml::inflate(&fin, diag, Source(path));
252}
253
254static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const Source& source,
255 const void* data, size_t len,
256 IDiagnostics* diag) {
257 CompiledFileInputStream inputStream(data, len);
258 if (!inputStream.CompiledFile()) {
259 diag->error(DiagMessage(source) << "invalid compiled file header");
260 return {};
261 }
262
263 const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data());
264 const size_t xmlDataLen = inputStream.size();
265
266 std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source);
267 if (!xmlRes) {
268 return {};
269 }
270 return xmlRes;
271}
272
273static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
274 const void* data, size_t len,
275 IDiagnostics* diag) {
276 CompiledFileInputStream inputStream(data, len);
277 const pb::CompiledFile* pbFile = inputStream.CompiledFile();
278 if (!pbFile) {
279 diag->error(DiagMessage(source) << "invalid compiled file header");
280 return {};
281 }
282
283 std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source, diag);
284 if (!resFile) {
285 return {};
286 }
287 return resFile;
288}
289
290struct ResourceFileFlattenerOptions {
291 bool noAutoVersion = false;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800292 bool noVersionVectors = false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800293 bool keepRawValues = false;
294 bool doNotCompressAnything = false;
Rohit Agrawale49bb302016-04-22 12:27:55 -0700295 bool updateProguardSpec = false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800296 std::vector<std::string> extensionsToNotCompress;
297};
298
299class ResourceFileFlattener {
300public:
301 ResourceFileFlattener(const ResourceFileFlattenerOptions& options,
302 IAaptContext* context, proguard::KeepSet* keepSet) :
303 mOptions(options), mContext(context), mKeepSet(keepSet) {
304 }
305
306 bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter);
307
308private:
309 struct FileOperation {
310 io::IFile* fileToCopy;
311 std::unique_ptr<xml::XmlResource> xmlToFlatten;
312 std::string dstPath;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800313 bool skipVersion = false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800314 };
315
316 uint32_t getCompressionFlags(const StringPiece& str);
317
Adam Lesinski626a69f2016-03-03 10:09:26 -0800318 bool linkAndVersionXmlFile(const ResourceEntry* entry, const ResourceFile& fileDesc,
319 io::IFile* file, ResourceTable* table, FileOperation* outFileOp);
Adam Lesinski355f2852016-02-13 20:26:45 -0800320
321 ResourceFileFlattenerOptions mOptions;
322 IAaptContext* mContext;
323 proguard::KeepSet* mKeepSet;
324};
325
326uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) {
327 if (mOptions.doNotCompressAnything) {
328 return 0;
329 }
330
331 for (const std::string& extension : mOptions.extensionsToNotCompress) {
332 if (util::stringEndsWith<char>(str, extension)) {
333 return 0;
334 }
335 }
336 return ArchiveEntry::kCompress;
337}
338
Adam Lesinski626a69f2016-03-03 10:09:26 -0800339bool ResourceFileFlattener::linkAndVersionXmlFile(const ResourceEntry* entry,
340 const ResourceFile& fileDesc,
341 io::IFile* file,
342 ResourceTable* table,
343 FileOperation* outFileOp) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800344 const StringPiece srcPath = file->getSource().path;
345 if (mContext->verbose()) {
346 mContext->getDiagnostics()->note(DiagMessage() << "linking " << srcPath);
347 }
348
349 std::unique_ptr<io::IData> data = file->openAsData();
350 if (!data) {
351 mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file");
Adam Lesinski626a69f2016-03-03 10:09:26 -0800352 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800353 }
354
Adam Lesinski355f2852016-02-13 20:26:45 -0800355 if (util::stringEndsWith<char>(srcPath, ".flat")) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800356 outFileOp->xmlToFlatten = loadBinaryXmlSkipFileExport(file->getSource(),
357 data->data(), data->size(),
358 mContext->getDiagnostics());
Adam Lesinski355f2852016-02-13 20:26:45 -0800359 } else {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800360 outFileOp->xmlToFlatten = xml::inflate(data->data(), data->size(),
361 mContext->getDiagnostics(),
362 file->getSource());
Adam Lesinski355f2852016-02-13 20:26:45 -0800363 }
364
Adam Lesinski626a69f2016-03-03 10:09:26 -0800365 if (!outFileOp->xmlToFlatten) {
366 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800367 }
368
369 // Copy the the file description header.
Adam Lesinski626a69f2016-03-03 10:09:26 -0800370 outFileOp->xmlToFlatten->file = fileDesc;
Adam Lesinski355f2852016-02-13 20:26:45 -0800371
372 XmlReferenceLinker xmlLinker;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800373 if (!xmlLinker.consume(mContext, outFileOp->xmlToFlatten.get())) {
374 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800375 }
376
Rohit Agrawale49bb302016-04-22 12:27:55 -0700377 if (mOptions.updateProguardSpec && !proguard::collectProguardRules(
378 outFileOp->xmlToFlatten->file.source, outFileOp->xmlToFlatten.get(), mKeepSet)) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800379 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800380 }
381
382 if (!mOptions.noAutoVersion) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800383 if (mOptions.noVersionVectors) {
384 // Skip this if it is a vector or animated-vector.
385 xml::Element* el = xml::findRootElement(outFileOp->xmlToFlatten.get());
386 if (el && el->namespaceUri.empty()) {
387 if (el->name == u"vector" || el->name == u"animated-vector") {
388 // We are NOT going to version this file.
389 outFileOp->skipVersion = true;
390 return true;
391 }
392 }
393 }
394
Adam Lesinski355f2852016-02-13 20:26:45 -0800395 // Find the first SDK level used that is higher than this defined config and
396 // not superseded by a lower or equal SDK level resource.
397 for (int sdkLevel : xmlLinker.getSdkLevels()) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800398 if (sdkLevel > outFileOp->xmlToFlatten->file.config.sdkVersion) {
399 if (!shouldGenerateVersionedResource(entry, outFileOp->xmlToFlatten->file.config,
400 sdkLevel)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800401 // If we shouldn't generate a versioned resource, stop checking.
402 break;
403 }
404
Adam Lesinski626a69f2016-03-03 10:09:26 -0800405 ResourceFile versionedFileDesc = outFileOp->xmlToFlatten->file;
Adam Lesinski64587af2016-02-18 18:33:06 -0800406 versionedFileDesc.config.sdkVersion = (uint16_t) sdkLevel;
Adam Lesinski355f2852016-02-13 20:26:45 -0800407
408 if (mContext->verbose()) {
409 mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source)
410 << "auto-versioning resource from config '"
Adam Lesinski626a69f2016-03-03 10:09:26 -0800411 << outFileOp->xmlToFlatten->file.config
412 << "' -> '"
Adam Lesinski355f2852016-02-13 20:26:45 -0800413 << versionedFileDesc.config << "'");
414 }
415
416 std::u16string genPath = util::utf8ToUtf16(ResourceUtils::buildResourceFileName(
417 versionedFileDesc, mContext->getNameMangler()));
418
419 bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name,
420 versionedFileDesc.config,
421 versionedFileDesc.source,
422 genPath,
423 file,
424 mContext->getDiagnostics());
425 if (!added) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800426 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800427 }
428 break;
429 }
430 }
431 }
Adam Lesinski626a69f2016-03-03 10:09:26 -0800432 return true;
Adam Lesinski355f2852016-02-13 20:26:45 -0800433}
434
435/**
436 * Do not insert or remove any resources while executing in this function. It will
437 * corrupt the iteration order.
438 */
439bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiveWriter) {
440 bool error = false;
441 std::map<std::pair<ConfigDescription, StringPiece16>, FileOperation> configSortedFiles;
442
443 for (auto& pkg : table->packages) {
444 for (auto& type : pkg->types) {
445 // Sort by config and name, so that we get better locality in the zip file.
446 configSortedFiles.clear();
447 for (auto& entry : type->entries) {
448 // Iterate via indices because auto generated values can be inserted ahead of
449 // the value being processed.
450 for (size_t i = 0; i < entry->values.size(); i++) {
451 ResourceConfigValue* configValue = entry->values[i].get();
452
453 FileReference* fileRef = valueCast<FileReference>(configValue->value.get());
454 if (!fileRef) {
455 continue;
456 }
457
458 io::IFile* file = fileRef->file;
459 if (!file) {
460 mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource())
461 << "file not found");
462 return false;
463 }
464
465 FileOperation fileOp;
466 fileOp.dstPath = util::utf16ToUtf8(*fileRef->path);
467
468 const StringPiece srcPath = file->getSource().path;
469 if (type->type != ResourceType::kRaw &&
470 (util::stringEndsWith<char>(srcPath, ".xml.flat") ||
471 util::stringEndsWith<char>(srcPath, ".xml"))) {
472 ResourceFile fileDesc;
473 fileDesc.config = configValue->config;
474 fileDesc.name = ResourceName(pkg->name, type->type, entry->name);
475 fileDesc.source = fileRef->getSource();
Adam Lesinski626a69f2016-03-03 10:09:26 -0800476 if (!linkAndVersionXmlFile(entry.get(), fileDesc, file, table, &fileOp)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800477 error = true;
478 continue;
479 }
480
481 } else {
482 fileOp.fileToCopy = file;
483 }
484
485 // NOTE(adamlesinski): Explicitly construct a StringPiece16 here, or else
486 // we end up copying the string in the std::make_pair() method, then creating
487 // a StringPiece16 from the copy, which would cause us to end up referencing
488 // garbage in the map.
489 const StringPiece16 entryName(entry->name);
490 configSortedFiles[std::make_pair(configValue->config, entryName)] =
491 std::move(fileOp);
492 }
493 }
494
495 if (error) {
496 return false;
497 }
498
499 // Now flatten the sorted values.
500 for (auto& mapEntry : configSortedFiles) {
501 const ConfigDescription& config = mapEntry.first.first;
502 const FileOperation& fileOp = mapEntry.second;
503
504 if (fileOp.xmlToFlatten) {
505 Maybe<size_t> maxSdkLevel;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800506 if (!mOptions.noAutoVersion && !fileOp.skipVersion) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800507 maxSdkLevel = std::max<size_t>(config.sdkVersion, 1u);
508 }
509
510 bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel,
511 mOptions.keepRawValues,
512 archiveWriter, mContext);
513 if (!result) {
514 error = true;
515 }
516 } else {
517 bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath,
518 getCompressionFlags(fileOp.dstPath),
519 archiveWriter, mContext);
520 if (!result) {
521 error = true;
522 }
523 }
524 }
525 }
526 }
527 return !error;
528}
529
Adam Lesinskifb48d292015-11-07 15:52:13 -0800530class LinkCommand {
531public:
Adam Lesinski6a008172016-02-02 17:02:58 -0800532 LinkCommand(LinkContext* context, const LinkOptions& options) :
Adam Lesinski64587af2016-02-18 18:33:06 -0800533 mOptions(options), mContext(context), mFinalTable(),
534 mFileCollection(util::make_unique<io::FileCollection>()) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800535 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700536
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700537 /**
538 * Creates a SymbolTable that loads symbols from the various APKs and caches the
539 * results for faster lookup.
540 */
Adam Lesinski64587af2016-02-18 18:33:06 -0800541 bool loadSymbolsFromIncludePaths() {
542 std::unique_ptr<AssetManagerSymbolSource> assetSource =
543 util::make_unique<AssetManagerSymbolSource>();
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700544 for (const std::string& path : mOptions.includePaths) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800545 if (mContext->verbose()) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800546 mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700547 }
548
Adam Lesinski64587af2016-02-18 18:33:06 -0800549 // First try to load the file as a static lib.
550 std::string errorStr;
551 std::unique_ptr<ResourceTable> staticInclude = loadStaticLibrary(path, &errorStr);
552 if (staticInclude) {
553 if (!mOptions.staticLib) {
554 // Can't include static libraries when not building a static library.
555 mContext->getDiagnostics()->error(
556 DiagMessage(path) << "can't include static library when building app");
557 return false;
558 }
559
560 // If we are using --no-static-lib-packages, we need to rename the package of this
561 // table to our compilation package.
562 if (mOptions.noStaticLibPackages) {
563 if (ResourceTablePackage* pkg = staticInclude->findPackageById(0x7f)) {
564 pkg->name = mContext->getCompilationPackage();
565 }
566 }
567
568 mContext->getExternalSymbols()->appendSource(
569 util::make_unique<ResourceTableSymbolSource>(staticInclude.get()));
570
571 mStaticTableIncludes.push_back(std::move(staticInclude));
572
573 } else if (!errorStr.empty()) {
574 // We had an error with reading, so fail.
575 mContext->getDiagnostics()->error(DiagMessage(path) << errorStr);
576 return false;
577 }
578
579 if (!assetSource->addAssetPath(path)) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800580 mContext->getDiagnostics()->error(
Adam Lesinskifb48d292015-11-07 15:52:13 -0800581 DiagMessage(path) << "failed to load include path");
Adam Lesinski64587af2016-02-18 18:33:06 -0800582 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700583 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700584 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800585
586 mContext->getExternalSymbols()->appendSource(std::move(assetSource));
587 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700588 }
589
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700590 Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes, IDiagnostics* diag) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700591 // Make sure the first element is <manifest> with package attribute.
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800592 if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700593 AppInfo appInfo;
594
595 if (!manifestEl->namespaceUri.empty() || manifestEl->name != u"manifest") {
596 diag->error(DiagMessage(xmlRes->file.source) << "root tag must be <manifest>");
597 return {};
598 }
599
600 xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package");
601 if (!packageAttr) {
602 diag->error(DiagMessage(xmlRes->file.source)
603 << "<manifest> must have a 'package' attribute");
604 return {};
605 }
606
607 appInfo.package = packageAttr->value;
608
609 if (xml::Element* usesSdkEl = manifestEl->findChild({}, u"uses-sdk")) {
610 if (xml::Attribute* minSdk =
611 usesSdkEl->findAttribute(xml::kSchemaAndroid, u"minSdkVersion")) {
612 appInfo.minSdkVersion = minSdk->value;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700613 }
614 }
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700615
616 return appInfo;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700617 }
618 return {};
619 }
620
Adam Lesinski979ccb22016-01-11 10:42:19 -0800621 /**
622 * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked.
623 * Postcondition: ResourceTable has only one package left. All others are stripped, or there
624 * is an error and false is returned.
625 */
Adam Lesinskifb48d292015-11-07 15:52:13 -0800626 bool verifyNoExternalPackages() {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800627 auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool {
Adam Lesinski6a008172016-02-02 17:02:58 -0800628 return mContext->getCompilationPackage() != pkg->name ||
Adam Lesinski979ccb22016-01-11 10:42:19 -0800629 !pkg->id ||
Adam Lesinski6a008172016-02-02 17:02:58 -0800630 pkg->id.value() != mContext->getPackageId();
Adam Lesinski979ccb22016-01-11 10:42:19 -0800631 };
632
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700633 bool error = false;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800634 for (const auto& package : mFinalTable.packages) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800635 if (isExtPackageFunc(package)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700636 // We have a package that is not related to the one we're building!
637 for (const auto& type : package->types) {
638 for (const auto& entry : type->entries) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800639 ResourceNameRef resName(package->name, type->type, entry->name);
640
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700641 for (const auto& configValue : entry->values) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800642 // Special case the occurrence of an ID that is being generated for the
643 // 'android' package. This is due to legacy reasons.
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800644 if (valueCast<Id>(configValue->value.get()) &&
Adam Lesinski979ccb22016-01-11 10:42:19 -0800645 package->name == u"android") {
Adam Lesinski6a008172016-02-02 17:02:58 -0800646 mContext->getDiagnostics()->warn(
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800647 DiagMessage(configValue->value->getSource())
Adam Lesinski979ccb22016-01-11 10:42:19 -0800648 << "generated id '" << resName
649 << "' for external package '" << package->name
650 << "'");
651 } else {
Adam Lesinski6a008172016-02-02 17:02:58 -0800652 mContext->getDiagnostics()->error(
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800653 DiagMessage(configValue->value->getSource())
Adam Lesinski979ccb22016-01-11 10:42:19 -0800654 << "defined resource '" << resName
655 << "' for external package '" << package->name
656 << "'");
657 error = true;
658 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700659 }
660 }
661 }
662 }
663 }
Adam Lesinski979ccb22016-01-11 10:42:19 -0800664
665 auto newEndIter = std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(),
666 isExtPackageFunc);
667 mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700668 return !error;
669 }
670
Adam Lesinski64587af2016-02-18 18:33:06 -0800671 /**
672 * Returns true if no IDs have been set, false otherwise.
673 */
674 bool verifyNoIdsSet() {
675 for (const auto& package : mFinalTable.packages) {
676 for (const auto& type : package->types) {
677 if (type->id) {
678 mContext->getDiagnostics()->error(DiagMessage() << "type " << type->type
679 << " has ID " << std::hex
680 << (int) type->id.value()
681 << std::dec << " assigned");
682 return false;
683 }
684
685 for (const auto& entry : type->entries) {
686 if (entry->id) {
687 ResourceNameRef resName(package->name, type->type, entry->name);
688 mContext->getDiagnostics()->error(DiagMessage() << "entry " << resName
689 << " has ID " << std::hex
690 << (int) entry->id.value()
691 << std::dec << " assigned");
692 return false;
693 }
694 }
695 }
696 }
697 return true;
698 }
699
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700700 std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
701 if (mOptions.outputToDirectory) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800702 return createDirectoryArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700703 } else {
Adam Lesinski6a008172016-02-02 17:02:58 -0800704 return createZipFileArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700705 }
706 }
707
708 bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
709 BigBuffer buffer(1024);
Adam Lesinski59e04c62016-02-04 15:59:23 -0800710 TableFlattener flattener(&buffer);
Adam Lesinski6a008172016-02-02 17:02:58 -0800711 if (!flattener.consume(mContext, table)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700712 return false;
713 }
714
Adam Lesinskia40e9722015-11-24 19:11:46 -0800715 if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
716 if (writer->writeEntry(buffer)) {
717 if (writer->finishEntry()) {
718 return true;
719 }
720 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700721 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800722
Adam Lesinski6a008172016-02-02 17:02:58 -0800723 mContext->getDiagnostics()->error(
Adam Lesinskia40e9722015-11-24 19:11:46 -0800724 DiagMessage() << "failed to write resources.arsc to archive");
725 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700726 }
727
Adam Lesinski64587af2016-02-18 18:33:06 -0800728 bool flattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
729 // Create the file/zip entry.
730 if (!writer->startEntry("resources.arsc.flat", 0)) {
731 mContext->getDiagnostics()->error(DiagMessage() << "failed to open");
732 return false;
733 }
734
735 std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table);
736
737 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
738 // interface.
739 {
740 google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
741
742 if (!pbTable->SerializeToZeroCopyStream(&adaptor)) {
743 mContext->getDiagnostics()->error(DiagMessage() << "failed to write");
744 return false;
745 }
746 }
747
748 if (!writer->finishEntry()) {
749 mContext->getDiagnostics()->error(DiagMessage() << "failed to finish entry");
750 return false;
751 }
752 return true;
753 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700754
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700755 bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
756 const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700757 if (!mOptions.generateJavaClassPath) {
758 return true;
759 }
760
761 std::string outPath = mOptions.generateJavaClassPath.value();
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700762 file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(outPackage)));
Adam Lesinski96917c22016-03-09 13:11:25 -0800763 if (!file::mkdirs(outPath)) {
764 mContext->getDiagnostics()->error(
765 DiagMessage() << "failed to create directory '" << outPath << "'");
766 return false;
767 }
768
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700769 file::appendPath(&outPath, "R.java");
770
771 std::ofstream fout(outPath, std::ofstream::binary);
772 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800773 mContext->getDiagnostics()->error(
774 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700775 return false;
776 }
777
Adam Lesinski76565542016-03-10 21:55:04 -0800778 JavaClassGenerator generator(mContext, table, javaOptions);
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700779 if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800780 mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700781 return false;
782 }
Adam Lesinski96917c22016-03-09 13:11:25 -0800783
784 if (!fout) {
785 mContext->getDiagnostics()->error(
786 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
787 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700788 return true;
789 }
790
Adam Lesinski467f1712015-11-16 17:35:44 -0800791 bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
Adam Lesinskica5638f2015-10-21 14:42:43 -0700792 if (!mOptions.generateJavaClassPath) {
793 return true;
794 }
795
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700796 std::unique_ptr<ClassDefinition> manifestClass = generateManifestClass(
797 mContext->getDiagnostics(), manifestXml);
798
799 if (!manifestClass) {
800 // Something bad happened, but we already logged it, so exit.
801 return false;
802 }
803
804 if (manifestClass->empty()) {
805 // Empty Manifest class, no need to generate it.
806 return true;
807 }
808
Adam Lesinski3524a232016-04-01 19:19:24 -0700809 // Add any JavaDoc annotations to the generated class.
810 for (const std::string& annotation : mOptions.javadocAnnotations) {
811 std::string properAnnotation = "@";
812 properAnnotation += annotation;
813 manifestClass->getCommentBuilder()->appendComment(properAnnotation);
814 }
815
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700816 const std::string packageUtf8 = util::utf16ToUtf8(mContext->getCompilationPackage());
817
Adam Lesinskica5638f2015-10-21 14:42:43 -0700818 std::string outPath = mOptions.generateJavaClassPath.value();
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700819 file::appendPath(&outPath, file::packageToPath(packageUtf8));
820
Adam Lesinski96917c22016-03-09 13:11:25 -0800821 if (!file::mkdirs(outPath)) {
822 mContext->getDiagnostics()->error(
823 DiagMessage() << "failed to create directory '" << outPath << "'");
824 return false;
825 }
826
Adam Lesinskica5638f2015-10-21 14:42:43 -0700827 file::appendPath(&outPath, "Manifest.java");
828
829 std::ofstream fout(outPath, std::ofstream::binary);
830 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800831 mContext->getDiagnostics()->error(
832 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinskica5638f2015-10-21 14:42:43 -0700833 return false;
834 }
835
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700836 if (!ClassDefinition::writeJavaFile(manifestClass.get(), packageUtf8, true, &fout)) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800837 mContext->getDiagnostics()->error(
838 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinskica5638f2015-10-21 14:42:43 -0700839 return false;
840 }
841 return true;
842 }
843
Rohit Agrawale49bb302016-04-22 12:27:55 -0700844 bool writeProguardFile(const Maybe<std::string>& out, const proguard::KeepSet& keepSet) {
845 if (!out) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700846 return true;
847 }
848
Rohit Agrawale49bb302016-04-22 12:27:55 -0700849 const std::string& outPath = out.value();
Adam Lesinski96917c22016-03-09 13:11:25 -0800850 std::ofstream fout(outPath, std::ofstream::binary);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700851 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800852 mContext->getDiagnostics()->error(
853 DiagMessage() << "failed to open '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700854 return false;
855 }
856
857 proguard::writeKeepSet(&fout, keepSet);
858 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800859 mContext->getDiagnostics()->error(
860 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700861 return false;
862 }
863 return true;
864 }
865
Adam Lesinski64587af2016-02-18 18:33:06 -0800866 std::unique_ptr<ResourceTable> loadStaticLibrary(const std::string& input,
867 std::string* outError) {
868 std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
869 input, outError);
870 if (!collection) {
871 return {};
872 }
873 return loadTablePbFromCollection(collection.get());
874 }
875
876 std::unique_ptr<ResourceTable> loadTablePbFromCollection(io::IFileCollection* collection) {
877 io::IFile* file = collection->findFile("resources.arsc.flat");
878 if (!file) {
879 return {};
880 }
881
882 std::unique_ptr<io::IData> data = file->openAsData();
883 return loadTableFromPb(file->getSource(), data->data(), data->size(),
884 mContext->getDiagnostics());
885 }
886
887 bool mergeStaticLibrary(const std::string& input, bool override) {
888 if (mContext->verbose()) {
889 mContext->getDiagnostics()->note(DiagMessage() << "merging static library " << input);
890 }
891
892 std::string errorStr;
893 std::unique_ptr<io::ZipFileCollection> collection =
894 io::ZipFileCollection::create(input, &errorStr);
895 if (!collection) {
896 mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
897 return false;
898 }
899
900 std::unique_ptr<ResourceTable> table = loadTablePbFromCollection(collection.get());
901 if (!table) {
902 mContext->getDiagnostics()->error(DiagMessage(input) << "invalid static library");
903 return false;
904 }
905
906 ResourceTablePackage* pkg = table->findPackageById(0x7f);
907 if (!pkg) {
908 mContext->getDiagnostics()->error(DiagMessage(input)
909 << "static library has no package");
910 return false;
911 }
912
913 bool result;
914 if (mOptions.noStaticLibPackages) {
915 // Merge all resources as if they were in the compilation package. This is the old
916 // behaviour of aapt.
917
918 // Add the package to the set of --extra-packages so we emit an R.java for each
919 // library package.
920 if (!pkg->name.empty()) {
921 mOptions.extraJavaPackages.insert(pkg->name);
922 }
923
924 pkg->name = u"";
925 if (override) {
926 result = mTableMerger->mergeOverlay(Source(input), table.get(), collection.get());
927 } else {
928 result = mTableMerger->merge(Source(input), table.get(), collection.get());
929 }
930
931 } else {
932 // This is the proper way to merge libraries, where the package name is preserved
933 // and resource names are mangled.
934 result = mTableMerger->mergeAndMangle(Source(input), pkg->name, table.get(),
935 collection.get());
936 }
937
938 if (!result) {
939 return false;
940 }
941
942 // Make sure to move the collection into the set of IFileCollections.
943 mCollections.push_back(std::move(collection));
Adam Lesinskifb48d292015-11-07 15:52:13 -0800944 return true;
945 }
946
Adam Lesinskia40e9722015-11-24 19:11:46 -0800947 bool mergeResourceTable(io::IFile* file, bool override) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800948 if (mContext->verbose()) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800949 mContext->getDiagnostics()->note(DiagMessage() << "merging resource table "
950 << file->getSource());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800951 }
952
Adam Lesinskia40e9722015-11-24 19:11:46 -0800953 std::unique_ptr<io::IData> data = file->openAsData();
954 if (!data) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800955 mContext->getDiagnostics()->error(DiagMessage(file->getSource())
Adam Lesinskia40e9722015-11-24 19:11:46 -0800956 << "failed to open file");
957 return false;
958 }
959
Adam Lesinski355f2852016-02-13 20:26:45 -0800960 std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(),
961 data->data(), data->size(),
962 mContext->getDiagnostics());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800963 if (!table) {
964 return false;
965 }
966
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800967 bool result = false;
968 if (override) {
969 result = mTableMerger->mergeOverlay(file->getSource(), table.get());
970 } else {
971 result = mTableMerger->merge(file->getSource(), table.get());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800972 }
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800973 return result;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800974 }
975
Adam Lesinski64587af2016-02-18 18:33:06 -0800976 bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc, bool override) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800977 if (mContext->verbose()) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800978 mContext->getDiagnostics()->note(DiagMessage() << "merging compiled file "
979 << file->getSource());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800980 }
981
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800982 bool result = false;
Adam Lesinski64587af2016-02-18 18:33:06 -0800983 if (override) {
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800984 result = mTableMerger->mergeFileOverlay(*fileDesc, file);
Adam Lesinskifb48d292015-11-07 15:52:13 -0800985 } else {
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800986 result = mTableMerger->mergeFile(*fileDesc, file);
Adam Lesinskifb48d292015-11-07 15:52:13 -0800987 }
988
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800989 if (!result) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800990 return false;
991 }
992
993 // Add the exports of this file to the table.
Adam Lesinskia40e9722015-11-24 19:11:46 -0800994 for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800995 if (exportedSymbol.name.package.empty()) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800996 exportedSymbol.name.package = mContext->getCompilationPackage();
Adam Lesinskifb48d292015-11-07 15:52:13 -0800997 }
998
999 ResourceNameRef resName = exportedSymbol.name;
1000
Adam Lesinski6a008172016-02-02 17:02:58 -08001001 Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
Adam Lesinskifb48d292015-11-07 15:52:13 -08001002 exportedSymbol.name);
1003 if (mangledName) {
1004 resName = mangledName.value();
1005 }
1006
1007 std::unique_ptr<Id> id = util::make_unique<Id>();
Adam Lesinskia40e9722015-11-24 19:11:46 -08001008 id->setSource(fileDesc->source.withLine(exportedSymbol.line));
Adam Lesinski64587af2016-02-18 18:33:06 -08001009 bool result = mFinalTable.addResourceAllowMangled(
1010 resName, ConfigDescription::defaultConfig(), std::string(), std::move(id),
1011 mContext->getDiagnostics());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001012 if (!result) {
1013 return false;
1014 }
1015 }
Adam Lesinskifb48d292015-11-07 15:52:13 -08001016 return true;
1017 }
1018
Adam Lesinskia40e9722015-11-24 19:11:46 -08001019 /**
Adam Lesinski64587af2016-02-18 18:33:06 -08001020 * Takes a path to load as a ZIP file and merges the files within into the master ResourceTable.
1021 * If override is true, conflicting resources are allowed to override each other, in order of
1022 * last seen.
1023 *
1024 * An io::IFileCollection is created from the ZIP file and added to the set of
1025 * io::IFileCollections that are open.
Adam Lesinskia40e9722015-11-24 19:11:46 -08001026 */
1027 bool mergeArchive(const std::string& input, bool override) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001028 if (mContext->verbose()) {
1029 mContext->getDiagnostics()->note(DiagMessage() << "merging archive " << input);
1030 }
1031
Adam Lesinskia40e9722015-11-24 19:11:46 -08001032 std::string errorStr;
Adam Lesinski64587af2016-02-18 18:33:06 -08001033 std::unique_ptr<io::ZipFileCollection> collection =
1034 io::ZipFileCollection::create(input, &errorStr);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001035 if (!collection) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001036 mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001037 return false;
1038 }
1039
1040 bool error = false;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001041 for (auto iter = collection->iterator(); iter->hasNext(); ) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001042 if (!mergeFile(iter->next(), override)) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001043 error = true;
1044 }
1045 }
1046
1047 // Make sure to move the collection into the set of IFileCollections.
1048 mCollections.push_back(std::move(collection));
1049 return !error;
1050 }
1051
Adam Lesinski64587af2016-02-18 18:33:06 -08001052 /**
1053 * Takes a path to load and merge into the master ResourceTable. If override is true,
1054 * conflicting resources are allowed to override each other, in order of last seen.
1055 *
1056 * If the file path ends with .flata, .jar, .jack, or .zip the file is treated as ZIP archive
1057 * and the files within are merged individually.
1058 *
1059 * Otherwise the files is processed on its own.
1060 */
1061 bool mergePath(const std::string& path, bool override) {
Adam Lesinski656a5772016-01-14 15:17:41 -08001062 if (util::stringEndsWith<char>(path, ".flata") ||
1063 util::stringEndsWith<char>(path, ".jar") ||
1064 util::stringEndsWith<char>(path, ".jack") ||
1065 util::stringEndsWith<char>(path, ".zip")) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001066 return mergeArchive(path, override);
Adam Lesinski64587af2016-02-18 18:33:06 -08001067 } else if (util::stringEndsWith<char>(path, ".apk")) {
1068 return mergeStaticLibrary(path, override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001069 }
1070
1071 io::IFile* file = mFileCollection->insertFile(path);
Adam Lesinski64587af2016-02-18 18:33:06 -08001072 return mergeFile(file, override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001073 }
1074
Adam Lesinski64587af2016-02-18 18:33:06 -08001075 /**
1076 * Takes a file to load and merge into the master ResourceTable. If override is true,
1077 * conflicting resources are allowed to override each other, in order of last seen.
1078 *
1079 * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and merged into the
1080 * master ResourceTable. If the file ends with .flat, then it is treated like a compiled file
1081 * and the header data is read and merged into the final ResourceTable.
1082 *
1083 * All other file types are ignored. This is because these files could be coming from a zip,
1084 * where we could have other files like classes.dex.
1085 */
1086 bool mergeFile(io::IFile* file, bool override) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001087 const Source& src = file->getSource();
1088 if (util::stringEndsWith<char>(src.path, ".arsc.flat")) {
1089 return mergeResourceTable(file, override);
Adam Lesinski64587af2016-02-18 18:33:06 -08001090
Adam Lesinski52364f72016-01-11 13:10:24 -08001091 } else if (util::stringEndsWith<char>(src.path, ".flat")){
Adam Lesinskia40e9722015-11-24 19:11:46 -08001092 // Try opening the file and looking for an Export header.
1093 std::unique_ptr<io::IData> data = file->openAsData();
1094 if (!data) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001095 mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open");
Adam Lesinskia40e9722015-11-24 19:11:46 -08001096 return false;
1097 }
1098
1099 std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader(
Adam Lesinski6a008172016-02-02 17:02:58 -08001100 src, data->data(), data->size(), mContext->getDiagnostics());
Adam Lesinskia40e9722015-11-24 19:11:46 -08001101 if (resourceFile) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001102 return mergeCompiledFile(file, resourceFile.get(), override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001103 }
Adam Lesinskic446a732016-01-21 11:04:46 -08001104 return false;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001105 }
Adam Lesinski52364f72016-01-11 13:10:24 -08001106
Adam Lesinskic446a732016-01-21 11:04:46 -08001107 // Ignore non .flat files. This could be classes.dex or something else that happens
1108 // to be in an archive.
1109 return true;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001110 }
1111
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001112 int run(const std::vector<std::string>& inputFiles) {
1113 // Load the AndroidManifest.xml
Adam Lesinskia40e9722015-11-24 19:11:46 -08001114 std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
Adam Lesinski6a008172016-02-02 17:02:58 -08001115 mContext->getDiagnostics());
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001116 if (!manifestXml) {
1117 return 1;
1118 }
1119
Adam Lesinskifb6312f2016-06-28 14:40:32 -07001120 if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(),
1121 mContext->getDiagnostics())) {
1122 AppInfo& appInfo = maybeAppInfo.value();
1123 mContext->setCompilationPackage(appInfo.package);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001124 } else {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001125 return 1;
1126 }
1127
Adam Lesinski64587af2016-02-18 18:33:06 -08001128 if (!util::isJavaPackageName(mContext->getCompilationPackage())) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001129 mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001130 << "invalid package name '"
Adam Lesinski64587af2016-02-18 18:33:06 -08001131 << mContext->getCompilationPackage()
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001132 << "'");
1133 return 1;
1134 }
1135
Adam Lesinski64587af2016-02-18 18:33:06 -08001136 mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() });
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001137
Adam Lesinski64587af2016-02-18 18:33:06 -08001138 if (mContext->getCompilationPackage() == u"android") {
1139 mContext->setPackageId(0x01);
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001140 } else {
Adam Lesinski64587af2016-02-18 18:33:06 -08001141 mContext->setPackageId(0x7f);
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001142 }
1143
Adam Lesinski64587af2016-02-18 18:33:06 -08001144 if (!loadSymbolsFromIncludePaths()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001145 return 1;
1146 }
1147
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001148 TableMergerOptions tableMergerOptions;
1149 tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
Adam Lesinski6a008172016-02-02 17:02:58 -08001150 mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions);
Adam Lesinskifb48d292015-11-07 15:52:13 -08001151
Adam Lesinski355f2852016-02-13 20:26:45 -08001152 if (mContext->verbose()) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001153 mContext->getDiagnostics()->note(
Adam Lesinski64587af2016-02-18 18:33:06 -08001154 DiagMessage() << "linking package '" << mContext->getCompilationPackage()
1155 << "' with package ID " << std::hex
1156 << (int) mContext->getPackageId());
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001157 }
1158
Adam Lesinskifb48d292015-11-07 15:52:13 -08001159
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001160 for (const std::string& input : inputFiles) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001161 if (!mergePath(input, false)) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001162 mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input");
Adam Lesinski467f1712015-11-16 17:35:44 -08001163 return 1;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001164 }
1165 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001166
Adam Lesinskifb48d292015-11-07 15:52:13 -08001167 for (const std::string& input : mOptions.overlayFiles) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001168 if (!mergePath(input, true)) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001169 mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
Adam Lesinski467f1712015-11-16 17:35:44 -08001170 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001171 }
1172 }
1173
Adam Lesinskifb48d292015-11-07 15:52:13 -08001174 if (!verifyNoExternalPackages()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001175 return 1;
1176 }
1177
1178 if (!mOptions.staticLib) {
1179 PrivateAttributeMover mover;
Adam Lesinski6a008172016-02-02 17:02:58 -08001180 if (!mover.consume(mContext, &mFinalTable)) {
1181 mContext->getDiagnostics()->error(
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001182 DiagMessage() << "failed moving private attributes");
1183 return 1;
1184 }
1185 }
1186
Adam Lesinski64587af2016-02-18 18:33:06 -08001187 if (!mOptions.staticLib) {
1188 // Assign IDs if we are building a regular app.
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001189 IdAssigner idAssigner;
Adam Lesinski6a008172016-02-02 17:02:58 -08001190 if (!idAssigner.consume(mContext, &mFinalTable)) {
1191 mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001192 return 1;
1193 }
Adam Lesinski64587af2016-02-18 18:33:06 -08001194 } else {
1195 // Static libs are merged with other apps, and ID collisions are bad, so verify that
1196 // no IDs have been set.
1197 if (!verifyNoIdsSet()) {
1198 return 1;
1199 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001200 }
1201
Adam Lesinski64587af2016-02-18 18:33:06 -08001202 // Add the names to mangle based on our source merge earlier.
1203 mContext->setNameManglerPolicy(NameManglerPolicy{
1204 mContext->getCompilationPackage(), mTableMerger->getMergedPackages() });
1205
1206 // Add our table to the symbol table.
1207 mContext->getExternalSymbols()->prependSource(
1208 util::make_unique<ResourceTableSymbolSource>(&mFinalTable));
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001209
1210 {
1211 ReferenceLinker linker;
Adam Lesinski6a008172016-02-02 17:02:58 -08001212 if (!linker.consume(mContext, &mFinalTable)) {
1213 mContext->getDiagnostics()->error(DiagMessage() << "failed linking references");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001214 return 1;
1215 }
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001216
Adam Lesinski64587af2016-02-18 18:33:06 -08001217 if (mOptions.staticLib) {
1218 if (!mOptions.products.empty()) {
1219 mContext->getDiagnostics()->warn(
1220 DiagMessage() << "can't select products when building static library");
1221 }
Adam Lesinski355f2852016-02-13 20:26:45 -08001222
Adam Lesinski64587af2016-02-18 18:33:06 -08001223 if (mOptions.tableSplitterOptions.configFilter != nullptr ||
1224 mOptions.tableSplitterOptions.preferredDensity) {
1225 mContext->getDiagnostics()->warn(
1226 DiagMessage() << "can't strip resources when building static library");
1227 }
1228 } else {
1229 ProductFilter productFilter(mOptions.products);
1230 if (!productFilter.consume(mContext, &mFinalTable)) {
1231 mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products");
1232 return 1;
1233 }
Adam Lesinski355f2852016-02-13 20:26:45 -08001234
Adam Lesinski64587af2016-02-18 18:33:06 -08001235 // TODO(adamlesinski): Actually pass in split constraints and handle splits at the file
1236 // level.
1237 TableSplitter tableSplitter({}, mOptions.tableSplitterOptions);
1238 if (!tableSplitter.verifySplitConstraints(mContext)) {
1239 return 1;
1240 }
1241 tableSplitter.splitTable(&mFinalTable);
1242 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001243 }
1244
1245 proguard::KeepSet proguardKeepSet;
Rohit Agrawale49bb302016-04-22 12:27:55 -07001246 proguard::KeepSet proguardMainDexKeepSet;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001247
1248 std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter();
1249 if (!archiveWriter) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001250 mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001251 return 1;
1252 }
1253
Adam Lesinski467f1712015-11-16 17:35:44 -08001254 bool error = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001255 {
Adam Lesinski52364f72016-01-11 13:10:24 -08001256 ManifestFixer manifestFixer(mOptions.manifestFixerOptions);
Adam Lesinski6a008172016-02-02 17:02:58 -08001257 if (!manifestFixer.consume(mContext, manifestXml.get())) {
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001258 error = true;
1259 }
1260
Adam Lesinski467f1712015-11-16 17:35:44 -08001261 // AndroidManifest.xml has no resource name, but the CallSite is built from the name
1262 // (aka, which package the AndroidManifest.xml is coming from).
1263 // So we give it a package name so it can see local resources.
Adam Lesinski64587af2016-02-18 18:33:06 -08001264 manifestXml->file.name.package = mContext->getCompilationPackage();
Adam Lesinski467f1712015-11-16 17:35:44 -08001265
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001266 XmlReferenceLinker manifestLinker;
Adam Lesinski6a008172016-02-02 17:02:58 -08001267 if (manifestLinker.consume(mContext, manifestXml.get())) {
Rohit Agrawale49bb302016-04-22 12:27:55 -07001268 if (mOptions.generateProguardRulesPath &&
1269 !proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
1270 manifestXml.get(),
1271 &proguardKeepSet)) {
1272 error = true;
1273 }
1274
1275 if (mOptions.generateMainDexProguardRulesPath &&
1276 !proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
1277 manifestXml.get(),
1278 &proguardMainDexKeepSet,
1279 true)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001280 error = true;
1281 }
1282
Adam Lesinskica5638f2015-10-21 14:42:43 -07001283 if (mOptions.generateJavaClassPath) {
1284 if (!writeManifestJavaFile(manifestXml.get())) {
1285 error = true;
1286 }
1287 }
1288
Adam Lesinski355f2852016-02-13 20:26:45 -08001289 const bool keepRawValues = mOptions.staticLib;
1290 bool result = flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
1291 keepRawValues, archiveWriter.get(), mContext);
1292 if (!result) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001293 error = true;
1294 }
1295 } else {
1296 error = true;
1297 }
1298 }
1299
Adam Lesinski467f1712015-11-16 17:35:44 -08001300 if (error) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001301 mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest");
Adam Lesinski467f1712015-11-16 17:35:44 -08001302 return 1;
1303 }
1304
Adam Lesinski355f2852016-02-13 20:26:45 -08001305 ResourceFileFlattenerOptions fileFlattenerOptions;
1306 fileFlattenerOptions.keepRawValues = mOptions.staticLib;
1307 fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything;
1308 fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress;
1309 fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion;
Adam Lesinski626a69f2016-03-03 10:09:26 -08001310 fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors;
Rohit Agrawale49bb302016-04-22 12:27:55 -07001311 fileFlattenerOptions.updateProguardSpec =
1312 static_cast<bool>(mOptions.generateProguardRulesPath);
Adam Lesinski355f2852016-02-13 20:26:45 -08001313 ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, &proguardKeepSet);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001314
Adam Lesinski355f2852016-02-13 20:26:45 -08001315 if (!fileFlattener.flatten(&mFinalTable, archiveWriter.get())) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001316 mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001317 return 1;
1318 }
1319
Adam Lesinski626a69f2016-03-03 10:09:26 -08001320 if (!mOptions.noAutoVersion) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001321 AutoVersioner versioner;
Adam Lesinski6a008172016-02-02 17:02:58 -08001322 if (!versioner.consume(mContext, &mFinalTable)) {
1323 mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001324 return 1;
1325 }
1326 }
1327
Adam Lesinskifb6312f2016-06-28 14:40:32 -07001328 Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(),
1329 mContext->getDiagnostics());
1330 if (maybeAppInfo && maybeAppInfo.value().minSdkVersion) {
1331 if (Maybe<int> maybeMinSdkVersion =
1332 ResourceUtils::tryParseSdkVersion(maybeAppInfo.value().minSdkVersion.value())) {
1333 mContext->setMinSdkVersion(maybeMinSdkVersion.value());
1334 }
1335 }
1336
1337 if (!mOptions.staticLib && mContext->getMinSdkVersion() > 0) {
1338 if (mContext->verbose()) {
1339 mContext->getDiagnostics()->note(
1340 DiagMessage() << "collapsing resource versions for minimum SDK "
1341 << mContext->getMinSdkVersion());
1342 }
1343
1344 VersionCollapser collapser;
1345 if (!collapser.consume(mContext, &mFinalTable)) {
1346 return 1;
1347 }
1348 }
1349
Adam Lesinski64587af2016-02-18 18:33:06 -08001350 if (mOptions.staticLib) {
1351 if (!flattenTableToPb(&mFinalTable, archiveWriter.get())) {
1352 mContext->getDiagnostics()->error(DiagMessage()
1353 << "failed to write resources.arsc.flat");
1354 return 1;
1355 }
1356 } else {
1357 if (!flattenTable(&mFinalTable, archiveWriter.get())) {
1358 mContext->getDiagnostics()->error(DiagMessage()
1359 << "failed to write resources.arsc");
1360 return 1;
1361 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001362 }
1363
1364 if (mOptions.generateJavaClassPath) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001365 JavaClassGeneratorOptions options;
Adam Lesinski52364f72016-01-11 13:10:24 -08001366 options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
Adam Lesinski3524a232016-04-01 19:19:24 -07001367 options.javadocAnnotations = mOptions.javadocAnnotations;
Adam Lesinski52364f72016-01-11 13:10:24 -08001368
Adam Lesinskief9c5012016-01-22 14:09:53 -08001369 if (mOptions.staticLib || mOptions.generateNonFinalIds) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001370 options.useFinal = false;
1371 }
1372
Adam Lesinski6a008172016-02-02 17:02:58 -08001373 const StringPiece16 actualPackage = mContext->getCompilationPackage();
1374 StringPiece16 outputPackage = mContext->getCompilationPackage();
Adam Lesinski52364f72016-01-11 13:10:24 -08001375 if (mOptions.customJavaPackage) {
1376 // Override the output java package to the custom one.
1377 outputPackage = mOptions.customJavaPackage.value();
1378 }
Adam Lesinski83f22552015-11-07 11:51:23 -08001379
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001380 if (mOptions.privateSymbols) {
1381 // If we defined a private symbols package, we only emit Public symbols
1382 // to the original package, and private and public symbols to the private package.
1383
1384 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
Adam Lesinski6a008172016-02-02 17:02:58 -08001385 if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(),
Adam Lesinski52364f72016-01-11 13:10:24 -08001386 outputPackage, options)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001387 return 1;
1388 }
1389
1390 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
Adam Lesinski83f22552015-11-07 11:51:23 -08001391 outputPackage = mOptions.privateSymbols.value();
1392 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001393
Adam Lesinskifb48d292015-11-07 15:52:13 -08001394 if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
Adam Lesinski83f22552015-11-07 11:51:23 -08001395 return 1;
1396 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001397
Adam Lesinski52364f72016-01-11 13:10:24 -08001398 for (const std::u16string& extraPackage : mOptions.extraJavaPackages) {
1399 if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001400 return 1;
1401 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001402 }
1403 }
1404
Rohit Agrawale49bb302016-04-22 12:27:55 -07001405 if (!writeProguardFile(mOptions.generateProguardRulesPath, proguardKeepSet)) {
1406 return 1;
1407 }
1408
1409 if (!writeProguardFile(mOptions.generateMainDexProguardRulesPath, proguardMainDexKeepSet)) {
1410 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001411 }
1412
Adam Lesinski355f2852016-02-13 20:26:45 -08001413 if (mContext->verbose()) {
1414 DebugPrintTableOptions debugPrintTableOptions;
1415 debugPrintTableOptions.showSources = true;
1416 Debug::printTable(&mFinalTable, debugPrintTableOptions);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001417 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001418 return 0;
1419 }
Adam Lesinskifb48d292015-11-07 15:52:13 -08001420
1421private:
1422 LinkOptions mOptions;
Adam Lesinski6a008172016-02-02 17:02:58 -08001423 LinkContext* mContext;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001424 ResourceTable mFinalTable;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001425
Adam Lesinskifb48d292015-11-07 15:52:13 -08001426 std::unique_ptr<TableMerger> mTableMerger;
1427
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001428 // A pointer to the FileCollection representing the filesystem (not archives).
Adam Lesinski64587af2016-02-18 18:33:06 -08001429 std::unique_ptr<io::FileCollection> mFileCollection;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001430
1431 // A vector of IFileCollections. This is mainly here to keep ownership of the collections.
Adam Lesinskia40e9722015-11-24 19:11:46 -08001432 std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
Adam Lesinski64587af2016-02-18 18:33:06 -08001433
1434 // A vector of ResourceTables. This is here to retain ownership, so that the SymbolTable
1435 // can use these.
1436 std::vector<std::unique_ptr<ResourceTable>> mStaticTableIncludes;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001437};
1438
1439int link(const std::vector<StringPiece>& args) {
Adam Lesinski355f2852016-02-13 20:26:45 -08001440 LinkContext context;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001441 LinkOptions options;
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001442 std::vector<std::string> overlayArgList;
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001443 Maybe<std::string> privateSymbolsPackage;
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001444 Maybe<std::string> minSdkVersion, targetSdkVersion;
Adam Lesinski52364f72016-01-11 13:10:24 -08001445 Maybe<std::string> renameManifestPackage, renameInstrumentationTargetPackage;
1446 Maybe<std::string> versionCode, versionName;
1447 Maybe<std::string> customJavaPackage;
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001448 std::vector<std::string> extraJavaPackages;
Adam Lesinski6a008172016-02-02 17:02:58 -08001449 Maybe<std::string> configs;
Adam Lesinski355f2852016-02-13 20:26:45 -08001450 Maybe<std::string> preferredDensity;
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001451 Maybe<std::string> productList;
Adam Lesinski8900aa82016-01-25 22:48:15 -08001452 bool legacyXFlag = false;
1453 bool requireLocalization = false;
Adam Lesinski64587af2016-02-18 18:33:06 -08001454 bool verbose = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001455 Flags flags = Flags()
1456 .requiredFlag("-o", "Output path", &options.outputPath)
1457 .requiredFlag("--manifest", "Path to the Android manifest to build",
1458 &options.manifestPath)
1459 .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
Adam Lesinski52364f72016-01-11 13:10:24 -08001460 .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n"
Adam Lesinskifb48d292015-11-07 15:52:13 -08001461 "The last conflicting resource given takes precedence.",
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001462 &overlayArgList)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001463 .optionalFlag("--java", "Directory in which to generate R.java",
1464 &options.generateJavaClassPath)
1465 .optionalFlag("--proguard", "Output file for generated Proguard rules",
1466 &options.generateProguardRulesPath)
Rohit Agrawale49bb302016-04-22 12:27:55 -07001467 .optionalFlag("--proguard-main-dex",
1468 "Output file for generated Proguard rules for the main dex",
1469 &options.generateMainDexProguardRulesPath)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001470 .optionalSwitch("--no-auto-version",
1471 "Disables automatic style and layout SDK versioning",
1472 &options.noAutoVersion)
Adam Lesinski64587af2016-02-18 18:33:06 -08001473 .optionalSwitch("--no-version-vectors",
1474 "Disables automatic versioning of vector drawables. Use this only\n"
1475 "when building with vector drawable support library",
1476 &options.noVersionVectors)
Adam Lesinski8900aa82016-01-25 22:48:15 -08001477 .optionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01",
1478 &legacyXFlag)
1479 .optionalSwitch("-z", "Require localization of strings marked 'suggested'",
1480 &requireLocalization)
Adam Lesinski6a008172016-02-02 17:02:58 -08001481 .optionalFlag("-c", "Comma separated list of configurations to include. The default\n"
1482 "is all configurations", &configs)
Adam Lesinski355f2852016-02-13 20:26:45 -08001483 .optionalFlag("--preferred-density",
1484 "Selects the closest matching density and strips out all others.",
1485 &preferredDensity)
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001486 .optionalFlag("--product", "Comma separated list of product names to keep",
1487 &productList)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001488 .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
1489 "by -o",
1490 &options.outputToDirectory)
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001491 .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
1492 "AndroidManifest.xml", &minSdkVersion)
1493 .optionalFlag("--target-sdk-version", "Default target SDK version to use for "
1494 "AndroidManifest.xml", &targetSdkVersion)
Adam Lesinski52364f72016-01-11 13:10:24 -08001495 .optionalFlag("--version-code", "Version code (integer) to inject into the "
1496 "AndroidManifest.xml if none is present", &versionCode)
1497 .optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml "
1498 "if none is present", &versionName)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001499 .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
Adam Lesinski64587af2016-02-18 18:33:06 -08001500 .optionalSwitch("--no-static-lib-packages",
1501 "Merge all library resources under the app's package",
1502 &options.noStaticLibPackages)
Adam Lesinskief9c5012016-01-22 14:09:53 -08001503 .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n"
1504 "This is implied when --static-lib is specified.",
1505 &options.generateNonFinalIds)
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001506 .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001507 "private symbols.\n"
1508 "If not specified, public and private symbols will use the application's "
1509 "package name", &privateSymbolsPackage)
Adam Lesinski52364f72016-01-11 13:10:24 -08001510 .optionalFlag("--custom-package", "Custom Java package under which to generate R.java",
1511 &customJavaPackage)
Adam Lesinski83f22552015-11-07 11:51:23 -08001512 .optionalFlagList("--extra-packages", "Generate the same R.java but with different "
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001513 "package names", &extraJavaPackages)
Adam Lesinski3524a232016-04-01 19:19:24 -07001514 .optionalFlagList("--add-javadoc-annotation", "Adds a JavaDoc annotation to all "
1515 "generated Java classes", &options.javadocAnnotations)
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001516 .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in "
1517 "overlays without <add-resource> tags", &options.autoAddOverlay)
Adam Lesinski52364f72016-01-11 13:10:24 -08001518 .optionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml",
1519 &renameManifestPackage)
1520 .optionalFlag("--rename-instrumentation-target-package",
1521 "Changes the name of the target package for instrumentation. Most useful "
1522 "when used\nin conjunction with --rename-manifest-package",
1523 &renameInstrumentationTargetPackage)
1524 .optionalFlagList("-0", "File extensions not to compress",
1525 &options.extensionsToNotCompress)
Adam Lesinski64587af2016-02-18 18:33:06 -08001526 .optionalSwitch("-v", "Enables verbose logging", &verbose);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001527
1528 if (!flags.parse("aapt2 link", args, &std::cerr)) {
1529 return 1;
1530 }
1531
Adam Lesinskic51562c2016-04-28 11:12:38 -07001532 // Expand all argument-files passed into the command line. These start with '@'.
1533 std::vector<std::string> argList;
1534 for (const std::string& arg : flags.getArgs()) {
1535 if (util::stringStartsWith<char>(arg, "@")) {
1536 const std::string path = arg.substr(1, arg.size() - 1);
1537 std::string error;
1538 if (!file::appendArgsFromFile(path, &argList, &error)) {
1539 context.getDiagnostics()->error(DiagMessage(path) << error);
1540 return 1;
1541 }
1542 } else {
1543 argList.push_back(arg);
1544 }
1545 }
1546
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001547 // Expand all argument-files passed to -R.
1548 for (const std::string& arg : overlayArgList) {
1549 if (util::stringStartsWith<char>(arg, "@")) {
1550 const std::string path = arg.substr(1, arg.size() - 1);
1551 std::string error;
1552 if (!file::appendArgsFromFile(path, &options.overlayFiles, &error)) {
1553 context.getDiagnostics()->error(DiagMessage(path) << error);
1554 return 1;
1555 }
1556 } else {
1557 options.overlayFiles.push_back(arg);
1558 }
1559 }
1560
Adam Lesinski64587af2016-02-18 18:33:06 -08001561 if (verbose) {
1562 context.setVerbose(verbose);
1563 }
1564
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001565 if (privateSymbolsPackage) {
1566 options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
1567 }
1568
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001569 if (minSdkVersion) {
Adam Lesinski52364f72016-01-11 13:10:24 -08001570 options.manifestFixerOptions.minSdkVersionDefault =
1571 util::utf8ToUtf16(minSdkVersion.value());
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001572 }
1573
1574 if (targetSdkVersion) {
Adam Lesinski52364f72016-01-11 13:10:24 -08001575 options.manifestFixerOptions.targetSdkVersionDefault =
1576 util::utf8ToUtf16(targetSdkVersion.value());
1577 }
1578
1579 if (renameManifestPackage) {
1580 options.manifestFixerOptions.renameManifestPackage =
1581 util::utf8ToUtf16(renameManifestPackage.value());
1582 }
1583
1584 if (renameInstrumentationTargetPackage) {
1585 options.manifestFixerOptions.renameInstrumentationTargetPackage =
1586 util::utf8ToUtf16(renameInstrumentationTargetPackage.value());
1587 }
1588
1589 if (versionCode) {
1590 options.manifestFixerOptions.versionCodeDefault = util::utf8ToUtf16(versionCode.value());
1591 }
1592
1593 if (versionName) {
1594 options.manifestFixerOptions.versionNameDefault = util::utf8ToUtf16(versionName.value());
1595 }
1596
1597 if (customJavaPackage) {
1598 options.customJavaPackage = util::utf8ToUtf16(customJavaPackage.value());
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001599 }
1600
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001601 // Populate the set of extra packages for which to generate R.java.
1602 for (std::string& extraPackage : extraJavaPackages) {
1603 // A given package can actually be a colon separated list of packages.
1604 for (StringPiece package : util::split(extraPackage, ':')) {
Adam Lesinski52364f72016-01-11 13:10:24 -08001605 options.extraJavaPackages.insert(util::utf8ToUtf16(package));
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001606 }
1607 }
1608
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001609 if (productList) {
1610 for (StringPiece product : util::tokenize<char>(productList.value(), ',')) {
1611 if (product != "" && product != "default") {
1612 options.products.insert(product.toString());
1613 }
1614 }
1615 }
1616
Adam Lesinski6a008172016-02-02 17:02:58 -08001617 AxisConfigFilter filter;
1618 if (configs) {
1619 for (const StringPiece& configStr : util::tokenize<char>(configs.value(), ',')) {
1620 ConfigDescription config;
1621 LocaleValue lv;
1622 if (lv.initFromFilterString(configStr)) {
1623 lv.writeTo(&config);
1624 } else if (!ConfigDescription::parse(configStr, &config)) {
1625 context.getDiagnostics()->error(
1626 DiagMessage() << "invalid config '" << configStr << "' for -c option");
1627 return 1;
1628 }
1629
1630 if (config.density != 0) {
1631 context.getDiagnostics()->warn(
1632 DiagMessage() << "ignoring density '" << config << "' for -c option");
1633 } else {
1634 filter.addConfig(config);
1635 }
1636 }
1637
Adam Lesinski355f2852016-02-13 20:26:45 -08001638 options.tableSplitterOptions.configFilter = &filter;
1639 }
1640
1641 if (preferredDensity) {
1642 ConfigDescription preferredDensityConfig;
1643 if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) {
1644 context.getDiagnostics()->error(DiagMessage() << "invalid density '"
1645 << preferredDensity.value()
1646 << "' for --preferred-density option");
1647 return 1;
1648 }
1649
1650 // Clear the version that can be automatically added.
1651 preferredDensityConfig.sdkVersion = 0;
1652
1653 if (preferredDensityConfig.diff(ConfigDescription::defaultConfig())
1654 != ConfigDescription::CONFIG_DENSITY) {
1655 context.getDiagnostics()->error(DiagMessage() << "invalid preferred density '"
1656 << preferredDensity.value() << "'. "
1657 << "Preferred density must only be a density value");
1658 return 1;
1659 }
1660 options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density;
Adam Lesinski6a008172016-02-02 17:02:58 -08001661 }
1662
Adam Lesinski626a69f2016-03-03 10:09:26 -08001663 // Turn off auto versioning for static-libs.
1664 if (options.staticLib) {
1665 options.noAutoVersion = true;
1666 options.noVersionVectors = true;
1667 }
1668
Adam Lesinski6a008172016-02-02 17:02:58 -08001669 LinkCommand cmd(&context, options);
Adam Lesinskic51562c2016-04-28 11:12:38 -07001670 return cmd.run(argList);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001671}
1672
1673} // namespace aapt