blob: ded661e6d049c9dfc0a29ba64f9e9dd230e8469c [file] [log] [blame]
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "AppInfo.h"
18#include "Debug.h"
19#include "Flags.h"
Adam Lesinski6a008172016-02-02 17:02:58 -080020#include "Locale.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070021#include "NameMangler.h"
Adam Lesinski59e04c62016-02-04 15:59:23 -080022#include "ResourceUtils.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070023#include "compile/IdAssigner.h"
Adam Lesinski6a008172016-02-02 17:02:58 -080024#include "filter/ConfigFilter.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070025#include "flatten/Archive.h"
26#include "flatten/TableFlattener.h"
27#include "flatten/XmlFlattener.h"
Adam Lesinskia40e9722015-11-24 19:11:46 -080028#include "io/FileSystem.h"
29#include "io/ZipArchive.h"
Adam Lesinskica5638f2015-10-21 14:42:43 -070030#include "java/JavaClassGenerator.h"
31#include "java/ManifestClassGenerator.h"
32#include "java/ProguardRules.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070033#include "link/Linkers.h"
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -080034#include "link/ProductFilter.h"
Adam Lesinski467f1712015-11-16 17:35:44 -080035#include "link/ReferenceLinker.h"
Adam Lesinski2ae4a872015-11-02 16:10:55 -080036#include "link/ManifestFixer.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070037#include "link/TableMerger.h"
38#include "process/IResourceTableConsumer.h"
39#include "process/SymbolTable.h"
Adam Lesinski59e04c62016-02-04 15:59:23 -080040#include "proto/ProtoSerialize.h"
Adam Lesinski355f2852016-02-13 20:26:45 -080041#include "split/TableSplitter.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070042#include "unflatten/BinaryResourceParser.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070043#include "util/Files.h"
44#include "util/StringPiece.h"
Adam Lesinski467f1712015-11-16 17:35:44 -080045#include "xml/XmlDom.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070046
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -070047#include <android-base/file.h>
Adam Lesinski59e04c62016-02-04 15:59:23 -080048#include <google/protobuf/io/coded_stream.h>
49
Adam Lesinski1ab598f2015-08-14 14:26:04 -070050#include <fstream>
51#include <sys/stat.h>
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -070052#include <unordered_map>
Adam Lesinski1ab598f2015-08-14 14:26:04 -070053#include <vector>
54
55namespace aapt {
56
57struct LinkOptions {
58 std::string outputPath;
59 std::string manifestPath;
60 std::vector<std::string> includePaths;
Adam Lesinskifb48d292015-11-07 15:52:13 -080061 std::vector<std::string> overlayFiles;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070062 Maybe<std::string> generateJavaClassPath;
Adam Lesinskid0f116b2016-07-08 15:00:32 -070063 Maybe<std::string> customJavaPackage;
64 std::set<std::string> extraJavaPackages;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070065 Maybe<std::string> generateProguardRulesPath;
Rohit Agrawale49bb302016-04-22 12:27:55 -070066 Maybe<std::string> generateMainDexProguardRulesPath;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070067 bool noAutoVersion = false;
Adam Lesinski64587af2016-02-18 18:33:06 -080068 bool noVersionVectors = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070069 bool staticLib = false;
Adam Lesinski64587af2016-02-18 18:33:06 -080070 bool noStaticLibPackages = false;
Adam Lesinskief9c5012016-01-22 14:09:53 -080071 bool generateNonFinalIds = false;
Adam Lesinski3524a232016-04-01 19:19:24 -070072 std::vector<std::string> javadocAnnotations;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070073 bool outputToDirectory = false;
Adam Lesinskia6fe3452015-12-09 15:20:52 -080074 bool autoAddOverlay = false;
Adam Lesinski52364f72016-01-11 13:10:24 -080075 bool doNotCompressAnything = false;
76 std::vector<std::string> extensionsToNotCompress;
Adam Lesinskid0f116b2016-07-08 15:00:32 -070077 Maybe<std::string> privateSymbols;
Adam Lesinski52364f72016-01-11 13:10:24 -080078 ManifestFixerOptions manifestFixerOptions;
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -080079 std::unordered_set<std::string> products;
Adam Lesinski355f2852016-02-13 20:26:45 -080080 TableSplitterOptions tableSplitterOptions;
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -070081 std::unordered_map<ResourceName, ResourceId> stableIdMap;
82 Maybe<std::string> resourceIdMapPath;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070083};
84
Adam Lesinski64587af2016-02-18 18:33:06 -080085class LinkContext : public IAaptContext {
86public:
87 LinkContext() : mNameMangler({}) {
88 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -070089
90 IDiagnostics* getDiagnostics() override {
91 return &mDiagnostics;
92 }
93
94 NameMangler* getNameMangler() override {
Adam Lesinski64587af2016-02-18 18:33:06 -080095 return &mNameMangler;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070096 }
97
Adam Lesinski64587af2016-02-18 18:33:06 -080098 void setNameManglerPolicy(const NameManglerPolicy& policy) {
99 mNameMangler = NameMangler(policy);
100 }
101
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700102 const std::string& getCompilationPackage() override {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700103 return mCompilationPackage;
104 }
105
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700106 void setCompilationPackage(const StringPiece& packageName) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800107 mCompilationPackage = packageName.toString();
108 }
109
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700110 uint8_t getPackageId() override {
111 return mPackageId;
112 }
113
Adam Lesinski64587af2016-02-18 18:33:06 -0800114 void setPackageId(uint8_t id) {
115 mPackageId = id;
116 }
117
118 SymbolTable* getExternalSymbols() override {
119 return &mSymbols;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700120 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800121
122 bool verbose() override {
123 return mVerbose;
124 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800125
126 void setVerbose(bool val) {
127 mVerbose = val;
128 }
129
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700130 int getMinSdkVersion() override {
131 return mMinSdkVersion;
132 }
133
134 void setMinSdkVersion(int minSdk) {
135 mMinSdkVersion = minSdk;
136 }
137
Adam Lesinski64587af2016-02-18 18:33:06 -0800138private:
139 StdErrDiagnostics mDiagnostics;
140 NameMangler mNameMangler;
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700141 std::string mCompilationPackage;
Adam Lesinski64587af2016-02-18 18:33:06 -0800142 uint8_t mPackageId = 0x0;
143 SymbolTable mSymbols;
144 bool mVerbose = false;
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700145 int mMinSdkVersion = 0;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700146};
147
Adam Lesinski355f2852016-02-13 20:26:45 -0800148static bool copyFileToArchive(io::IFile* file, const std::string& outPath,
149 uint32_t compressionFlags,
150 IArchiveWriter* writer, IAaptContext* context) {
151 std::unique_ptr<io::IData> data = file->openAsData();
152 if (!data) {
153 context->getDiagnostics()->error(DiagMessage(file->getSource())
154 << "failed to open file");
155 return false;
156 }
157
Adam Lesinski64587af2016-02-18 18:33:06 -0800158 const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data());
159 size_t bufferSize = data->size();
160
161 // If the file ends with .flat, we must strip off the CompiledFileHeader from it.
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700162 if (util::stringEndsWith(file->getSource().path, ".flat")) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800163 CompiledFileInputStream inputStream(data->data(), data->size());
164 if (!inputStream.CompiledFile()) {
165 context->getDiagnostics()->error(DiagMessage(file->getSource())
166 << "invalid compiled file header");
167 return false;
168 }
169 buffer = reinterpret_cast<const uint8_t*>(inputStream.data());
170 bufferSize = inputStream.size();
Adam Lesinski355f2852016-02-13 20:26:45 -0800171 }
172
173 if (context->verbose()) {
174 context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive");
175 }
176
177 if (writer->startEntry(outPath, compressionFlags)) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800178 if (writer->writeEntry(buffer, bufferSize)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800179 if (writer->finishEntry()) {
180 return true;
181 }
182 }
183 }
184
185 context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath);
186 return false;
187}
188
189static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
190 bool keepRawValues, IArchiveWriter* writer, IAaptContext* context) {
191 BigBuffer buffer(1024);
192 XmlFlattenerOptions options = {};
193 options.keepRawValues = keepRawValues;
194 options.maxSdkLevel = maxSdkLevel;
195 XmlFlattener flattener(&buffer, options);
196 if (!flattener.consume(context, xmlRes)) {
197 return false;
198 }
199
200 if (context->verbose()) {
201 DiagMessage msg;
202 msg << "writing " << path << " to archive";
203 if (maxSdkLevel) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800204 msg << " maxSdkLevel=" << maxSdkLevel.value() << " keepRawValues=" << keepRawValues;
Adam Lesinski355f2852016-02-13 20:26:45 -0800205 }
206 context->getDiagnostics()->note(msg);
207 }
208
209 if (writer->startEntry(path, ArchiveEntry::kCompress)) {
210 if (writer->writeEntry(buffer)) {
211 if (writer->finishEntry()) {
212 return true;
213 }
214 }
215 }
216 context->getDiagnostics()->error(DiagMessage() << "failed to write " << path << " to archive");
217 return false;
218}
219
Adam Lesinski355f2852016-02-13 20:26:45 -0800220static std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
221 const void* data, size_t len,
222 IDiagnostics* diag) {
223 pb::ResourceTable pbTable;
224 if (!pbTable.ParseFromArray(data, len)) {
225 diag->error(DiagMessage(source) << "invalid compiled table");
226 return {};
227 }
228
229 std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source, diag);
230 if (!table) {
231 return {};
232 }
233 return table;
234}
235
236/**
237 * Inflates an XML file from the source path.
238 */
239static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
240 std::ifstream fin(path, std::ifstream::binary);
241 if (!fin) {
242 diag->error(DiagMessage(path) << strerror(errno));
243 return {};
244 }
245 return xml::inflate(&fin, diag, Source(path));
246}
247
248static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const Source& source,
249 const void* data, size_t len,
250 IDiagnostics* diag) {
251 CompiledFileInputStream inputStream(data, len);
252 if (!inputStream.CompiledFile()) {
253 diag->error(DiagMessage(source) << "invalid compiled file header");
254 return {};
255 }
256
257 const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data());
258 const size_t xmlDataLen = inputStream.size();
259
260 std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source);
261 if (!xmlRes) {
262 return {};
263 }
264 return xmlRes;
265}
266
267static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
268 const void* data, size_t len,
269 IDiagnostics* diag) {
270 CompiledFileInputStream inputStream(data, len);
271 const pb::CompiledFile* pbFile = inputStream.CompiledFile();
272 if (!pbFile) {
273 diag->error(DiagMessage(source) << "invalid compiled file header");
274 return {};
275 }
276
277 std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source, diag);
278 if (!resFile) {
279 return {};
280 }
281 return resFile;
282}
283
284struct ResourceFileFlattenerOptions {
285 bool noAutoVersion = false;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800286 bool noVersionVectors = false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800287 bool keepRawValues = false;
288 bool doNotCompressAnything = false;
Rohit Agrawale49bb302016-04-22 12:27:55 -0700289 bool updateProguardSpec = false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800290 std::vector<std::string> extensionsToNotCompress;
291};
292
293class ResourceFileFlattener {
294public:
295 ResourceFileFlattener(const ResourceFileFlattenerOptions& options,
296 IAaptContext* context, proguard::KeepSet* keepSet) :
297 mOptions(options), mContext(context), mKeepSet(keepSet) {
298 }
299
300 bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter);
301
302private:
303 struct FileOperation {
304 io::IFile* fileToCopy;
305 std::unique_ptr<xml::XmlResource> xmlToFlatten;
306 std::string dstPath;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800307 bool skipVersion = false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800308 };
309
310 uint32_t getCompressionFlags(const StringPiece& str);
311
Adam Lesinski626a69f2016-03-03 10:09:26 -0800312 bool linkAndVersionXmlFile(const ResourceEntry* entry, const ResourceFile& fileDesc,
313 io::IFile* file, ResourceTable* table, FileOperation* outFileOp);
Adam Lesinski355f2852016-02-13 20:26:45 -0800314
315 ResourceFileFlattenerOptions mOptions;
316 IAaptContext* mContext;
317 proguard::KeepSet* mKeepSet;
318};
319
320uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) {
321 if (mOptions.doNotCompressAnything) {
322 return 0;
323 }
324
325 for (const std::string& extension : mOptions.extensionsToNotCompress) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700326 if (util::stringEndsWith(str, extension)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800327 return 0;
328 }
329 }
330 return ArchiveEntry::kCompress;
331}
332
Adam Lesinski626a69f2016-03-03 10:09:26 -0800333bool ResourceFileFlattener::linkAndVersionXmlFile(const ResourceEntry* entry,
334 const ResourceFile& fileDesc,
335 io::IFile* file,
336 ResourceTable* table,
337 FileOperation* outFileOp) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800338 const StringPiece srcPath = file->getSource().path;
339 if (mContext->verbose()) {
340 mContext->getDiagnostics()->note(DiagMessage() << "linking " << srcPath);
341 }
342
343 std::unique_ptr<io::IData> data = file->openAsData();
344 if (!data) {
345 mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file");
Adam Lesinski626a69f2016-03-03 10:09:26 -0800346 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800347 }
348
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700349 if (util::stringEndsWith(srcPath, ".flat")) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800350 outFileOp->xmlToFlatten = loadBinaryXmlSkipFileExport(file->getSource(),
351 data->data(), data->size(),
352 mContext->getDiagnostics());
Adam Lesinski355f2852016-02-13 20:26:45 -0800353 } else {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800354 outFileOp->xmlToFlatten = xml::inflate(data->data(), data->size(),
355 mContext->getDiagnostics(),
356 file->getSource());
Adam Lesinski355f2852016-02-13 20:26:45 -0800357 }
358
Adam Lesinski626a69f2016-03-03 10:09:26 -0800359 if (!outFileOp->xmlToFlatten) {
360 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800361 }
362
363 // Copy the the file description header.
Adam Lesinski626a69f2016-03-03 10:09:26 -0800364 outFileOp->xmlToFlatten->file = fileDesc;
Adam Lesinski355f2852016-02-13 20:26:45 -0800365
366 XmlReferenceLinker xmlLinker;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800367 if (!xmlLinker.consume(mContext, outFileOp->xmlToFlatten.get())) {
368 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800369 }
370
Rohit Agrawale49bb302016-04-22 12:27:55 -0700371 if (mOptions.updateProguardSpec && !proguard::collectProguardRules(
372 outFileOp->xmlToFlatten->file.source, outFileOp->xmlToFlatten.get(), mKeepSet)) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800373 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800374 }
375
376 if (!mOptions.noAutoVersion) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800377 if (mOptions.noVersionVectors) {
378 // Skip this if it is a vector or animated-vector.
379 xml::Element* el = xml::findRootElement(outFileOp->xmlToFlatten.get());
380 if (el && el->namespaceUri.empty()) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700381 if (el->name == "vector" || el->name == "animated-vector") {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800382 // We are NOT going to version this file.
383 outFileOp->skipVersion = true;
384 return true;
385 }
386 }
387 }
388
Adam Lesinski355f2852016-02-13 20:26:45 -0800389 // Find the first SDK level used that is higher than this defined config and
390 // not superseded by a lower or equal SDK level resource.
391 for (int sdkLevel : xmlLinker.getSdkLevels()) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800392 if (sdkLevel > outFileOp->xmlToFlatten->file.config.sdkVersion) {
393 if (!shouldGenerateVersionedResource(entry, outFileOp->xmlToFlatten->file.config,
394 sdkLevel)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800395 // If we shouldn't generate a versioned resource, stop checking.
396 break;
397 }
398
Adam Lesinski626a69f2016-03-03 10:09:26 -0800399 ResourceFile versionedFileDesc = outFileOp->xmlToFlatten->file;
Adam Lesinski64587af2016-02-18 18:33:06 -0800400 versionedFileDesc.config.sdkVersion = (uint16_t) sdkLevel;
Adam Lesinski355f2852016-02-13 20:26:45 -0800401
402 if (mContext->verbose()) {
403 mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source)
404 << "auto-versioning resource from config '"
Adam Lesinski626a69f2016-03-03 10:09:26 -0800405 << outFileOp->xmlToFlatten->file.config
406 << "' -> '"
Adam Lesinski355f2852016-02-13 20:26:45 -0800407 << versionedFileDesc.config << "'");
408 }
409
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700410 std::string genPath = ResourceUtils::buildResourceFileName(
411 versionedFileDesc, mContext->getNameMangler());
Adam Lesinski355f2852016-02-13 20:26:45 -0800412
413 bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name,
414 versionedFileDesc.config,
415 versionedFileDesc.source,
416 genPath,
417 file,
418 mContext->getDiagnostics());
419 if (!added) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800420 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800421 }
422 break;
423 }
424 }
425 }
Adam Lesinski626a69f2016-03-03 10:09:26 -0800426 return true;
Adam Lesinski355f2852016-02-13 20:26:45 -0800427}
428
429/**
430 * Do not insert or remove any resources while executing in this function. It will
431 * corrupt the iteration order.
432 */
433bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiveWriter) {
434 bool error = false;
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700435 std::map<std::pair<ConfigDescription, StringPiece>, FileOperation> configSortedFiles;
Adam Lesinski355f2852016-02-13 20:26:45 -0800436
437 for (auto& pkg : table->packages) {
438 for (auto& type : pkg->types) {
439 // Sort by config and name, so that we get better locality in the zip file.
440 configSortedFiles.clear();
441 for (auto& entry : type->entries) {
442 // Iterate via indices because auto generated values can be inserted ahead of
443 // the value being processed.
444 for (size_t i = 0; i < entry->values.size(); i++) {
445 ResourceConfigValue* configValue = entry->values[i].get();
446
447 FileReference* fileRef = valueCast<FileReference>(configValue->value.get());
448 if (!fileRef) {
449 continue;
450 }
451
452 io::IFile* file = fileRef->file;
453 if (!file) {
454 mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource())
455 << "file not found");
456 return false;
457 }
458
459 FileOperation fileOp;
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700460 fileOp.dstPath = *fileRef->path;
Adam Lesinski355f2852016-02-13 20:26:45 -0800461
462 const StringPiece srcPath = file->getSource().path;
463 if (type->type != ResourceType::kRaw &&
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700464 (util::stringEndsWith(srcPath, ".xml.flat") ||
465 util::stringEndsWith(srcPath, ".xml"))) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800466 ResourceFile fileDesc;
467 fileDesc.config = configValue->config;
468 fileDesc.name = ResourceName(pkg->name, type->type, entry->name);
469 fileDesc.source = fileRef->getSource();
Adam Lesinski626a69f2016-03-03 10:09:26 -0800470 if (!linkAndVersionXmlFile(entry.get(), fileDesc, file, table, &fileOp)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800471 error = true;
472 continue;
473 }
474
475 } else {
476 fileOp.fileToCopy = file;
477 }
478
479 // NOTE(adamlesinski): Explicitly construct a StringPiece16 here, or else
480 // we end up copying the string in the std::make_pair() method, then creating
481 // a StringPiece16 from the copy, which would cause us to end up referencing
482 // garbage in the map.
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700483 const StringPiece entryName(entry->name);
Adam Lesinski355f2852016-02-13 20:26:45 -0800484 configSortedFiles[std::make_pair(configValue->config, entryName)] =
485 std::move(fileOp);
486 }
487 }
488
489 if (error) {
490 return false;
491 }
492
493 // Now flatten the sorted values.
494 for (auto& mapEntry : configSortedFiles) {
495 const ConfigDescription& config = mapEntry.first.first;
496 const FileOperation& fileOp = mapEntry.second;
497
498 if (fileOp.xmlToFlatten) {
499 Maybe<size_t> maxSdkLevel;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800500 if (!mOptions.noAutoVersion && !fileOp.skipVersion) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800501 maxSdkLevel = std::max<size_t>(config.sdkVersion, 1u);
502 }
503
504 bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel,
505 mOptions.keepRawValues,
506 archiveWriter, mContext);
507 if (!result) {
508 error = true;
509 }
510 } else {
511 bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath,
512 getCompressionFlags(fileOp.dstPath),
513 archiveWriter, mContext);
514 if (!result) {
515 error = true;
516 }
517 }
518 }
519 }
520 }
521 return !error;
522}
523
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700524static bool writeStableIdMapToPath(IDiagnostics* diag,
525 const std::unordered_map<ResourceName, ResourceId>& idMap,
526 const std::string idMapPath) {
527 std::ofstream fout(idMapPath, std::ofstream::binary);
528 if (!fout) {
529 diag->error(DiagMessage(idMapPath) << strerror(errno));
530 return false;
531 }
532
533 for (const auto& entry : idMap) {
534 const ResourceName& name = entry.first;
535 const ResourceId& id = entry.second;
536 fout << name << " = " << id << "\n";
537 }
538
539 if (!fout) {
540 diag->error(DiagMessage(idMapPath) << "failed writing to file: " << strerror(errno));
541 return false;
542 }
543
544 return true;
545}
546
547static bool loadStableIdMap(IDiagnostics* diag, const std::string& path,
548 std::unordered_map<ResourceName, ResourceId>* outIdMap) {
549 std::string content;
550 if (!android::base::ReadFileToString(path, &content)) {
551 diag->error(DiagMessage(path) << "failed reading stable ID file");
552 return false;
553 }
554
555 outIdMap->clear();
556 size_t lineNo = 0;
557 for (StringPiece line : util::tokenize(content, '\n')) {
558 lineNo++;
559 line = util::trimWhitespace(line);
560 if (line.empty()) {
561 continue;
562 }
563
564 auto iter = std::find(line.begin(), line.end(), '=');
565 if (iter == line.end()) {
566 diag->error(DiagMessage(Source(path, lineNo)) << "missing '='");
567 return false;
568 }
569
570 ResourceNameRef name;
571 StringPiece resNameStr = util::trimWhitespace(
572 line.substr(0, std::distance(line.begin(), iter)));
573 if (!ResourceUtils::parseResourceName(resNameStr, &name)) {
574 diag->error(DiagMessage(Source(path, lineNo))
575 << "invalid resource name '" << resNameStr << "'");
576 return false;
577 }
578
579 const size_t resIdStartIdx = std::distance(line.begin(), iter) + 1;
580 const size_t resIdStrLen = line.size() - resIdStartIdx;
581 StringPiece resIdStr = util::trimWhitespace(line.substr(resIdStartIdx, resIdStrLen));
582
583 Maybe<ResourceId> maybeId = ResourceUtils::tryParseResourceId(resIdStr);
584 if (!maybeId) {
585 diag->error(DiagMessage(Source(path, lineNo)) << "invalid resource ID '"
586 << resIdStr << "'");
587 return false;
588 }
589
590 (*outIdMap)[name.toResourceName()] = maybeId.value();
591 }
592 return true;
593}
594
Adam Lesinskifb48d292015-11-07 15:52:13 -0800595class LinkCommand {
596public:
Adam Lesinski6a008172016-02-02 17:02:58 -0800597 LinkCommand(LinkContext* context, const LinkOptions& options) :
Adam Lesinski64587af2016-02-18 18:33:06 -0800598 mOptions(options), mContext(context), mFinalTable(),
599 mFileCollection(util::make_unique<io::FileCollection>()) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800600 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700601
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700602 /**
603 * Creates a SymbolTable that loads symbols from the various APKs and caches the
604 * results for faster lookup.
605 */
Adam Lesinski64587af2016-02-18 18:33:06 -0800606 bool loadSymbolsFromIncludePaths() {
607 std::unique_ptr<AssetManagerSymbolSource> assetSource =
608 util::make_unique<AssetManagerSymbolSource>();
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700609 for (const std::string& path : mOptions.includePaths) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800610 if (mContext->verbose()) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800611 mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700612 }
613
Adam Lesinski64587af2016-02-18 18:33:06 -0800614 // First try to load the file as a static lib.
615 std::string errorStr;
616 std::unique_ptr<ResourceTable> staticInclude = loadStaticLibrary(path, &errorStr);
617 if (staticInclude) {
618 if (!mOptions.staticLib) {
619 // Can't include static libraries when not building a static library.
620 mContext->getDiagnostics()->error(
621 DiagMessage(path) << "can't include static library when building app");
622 return false;
623 }
624
625 // If we are using --no-static-lib-packages, we need to rename the package of this
626 // table to our compilation package.
627 if (mOptions.noStaticLibPackages) {
628 if (ResourceTablePackage* pkg = staticInclude->findPackageById(0x7f)) {
629 pkg->name = mContext->getCompilationPackage();
630 }
631 }
632
633 mContext->getExternalSymbols()->appendSource(
634 util::make_unique<ResourceTableSymbolSource>(staticInclude.get()));
635
636 mStaticTableIncludes.push_back(std::move(staticInclude));
637
638 } else if (!errorStr.empty()) {
639 // We had an error with reading, so fail.
640 mContext->getDiagnostics()->error(DiagMessage(path) << errorStr);
641 return false;
642 }
643
644 if (!assetSource->addAssetPath(path)) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800645 mContext->getDiagnostics()->error(
Adam Lesinskifb48d292015-11-07 15:52:13 -0800646 DiagMessage(path) << "failed to load include path");
Adam Lesinski64587af2016-02-18 18:33:06 -0800647 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700648 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700649 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800650
651 mContext->getExternalSymbols()->appendSource(std::move(assetSource));
652 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700653 }
654
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700655 Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes, IDiagnostics* diag) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700656 // Make sure the first element is <manifest> with package attribute.
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800657 if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700658 AppInfo appInfo;
659
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700660 if (!manifestEl->namespaceUri.empty() || manifestEl->name != "manifest") {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700661 diag->error(DiagMessage(xmlRes->file.source) << "root tag must be <manifest>");
662 return {};
663 }
664
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700665 xml::Attribute* packageAttr = manifestEl->findAttribute({}, "package");
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700666 if (!packageAttr) {
667 diag->error(DiagMessage(xmlRes->file.source)
668 << "<manifest> must have a 'package' attribute");
669 return {};
670 }
671
672 appInfo.package = packageAttr->value;
673
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700674 if (xml::Element* usesSdkEl = manifestEl->findChild({}, "uses-sdk")) {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700675 if (xml::Attribute* minSdk =
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700676 usesSdkEl->findAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700677 appInfo.minSdkVersion = minSdk->value;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700678 }
679 }
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700680
681 return appInfo;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700682 }
683 return {};
684 }
685
Adam Lesinski979ccb22016-01-11 10:42:19 -0800686 /**
687 * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked.
688 * Postcondition: ResourceTable has only one package left. All others are stripped, or there
689 * is an error and false is returned.
690 */
Adam Lesinskifb48d292015-11-07 15:52:13 -0800691 bool verifyNoExternalPackages() {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800692 auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool {
Adam Lesinski6a008172016-02-02 17:02:58 -0800693 return mContext->getCompilationPackage() != pkg->name ||
Adam Lesinski979ccb22016-01-11 10:42:19 -0800694 !pkg->id ||
Adam Lesinski6a008172016-02-02 17:02:58 -0800695 pkg->id.value() != mContext->getPackageId();
Adam Lesinski979ccb22016-01-11 10:42:19 -0800696 };
697
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700698 bool error = false;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800699 for (const auto& package : mFinalTable.packages) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800700 if (isExtPackageFunc(package)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700701 // We have a package that is not related to the one we're building!
702 for (const auto& type : package->types) {
703 for (const auto& entry : type->entries) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800704 ResourceNameRef resName(package->name, type->type, entry->name);
705
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700706 for (const auto& configValue : entry->values) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800707 // Special case the occurrence of an ID that is being generated for the
708 // 'android' package. This is due to legacy reasons.
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800709 if (valueCast<Id>(configValue->value.get()) &&
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700710 package->name == "android") {
Adam Lesinski6a008172016-02-02 17:02:58 -0800711 mContext->getDiagnostics()->warn(
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800712 DiagMessage(configValue->value->getSource())
Adam Lesinski979ccb22016-01-11 10:42:19 -0800713 << "generated id '" << resName
714 << "' for external package '" << package->name
715 << "'");
716 } else {
Adam Lesinski6a008172016-02-02 17:02:58 -0800717 mContext->getDiagnostics()->error(
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800718 DiagMessage(configValue->value->getSource())
Adam Lesinski979ccb22016-01-11 10:42:19 -0800719 << "defined resource '" << resName
720 << "' for external package '" << package->name
721 << "'");
722 error = true;
723 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700724 }
725 }
726 }
727 }
728 }
Adam Lesinski979ccb22016-01-11 10:42:19 -0800729
730 auto newEndIter = std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(),
731 isExtPackageFunc);
732 mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700733 return !error;
734 }
735
Adam Lesinski64587af2016-02-18 18:33:06 -0800736 /**
737 * Returns true if no IDs have been set, false otherwise.
738 */
739 bool verifyNoIdsSet() {
740 for (const auto& package : mFinalTable.packages) {
741 for (const auto& type : package->types) {
742 if (type->id) {
743 mContext->getDiagnostics()->error(DiagMessage() << "type " << type->type
744 << " has ID " << std::hex
745 << (int) type->id.value()
746 << std::dec << " assigned");
747 return false;
748 }
749
750 for (const auto& entry : type->entries) {
751 if (entry->id) {
752 ResourceNameRef resName(package->name, type->type, entry->name);
753 mContext->getDiagnostics()->error(DiagMessage() << "entry " << resName
754 << " has ID " << std::hex
755 << (int) entry->id.value()
756 << std::dec << " assigned");
757 return false;
758 }
759 }
760 }
761 }
762 return true;
763 }
764
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700765 std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
766 if (mOptions.outputToDirectory) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800767 return createDirectoryArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700768 } else {
Adam Lesinski6a008172016-02-02 17:02:58 -0800769 return createZipFileArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700770 }
771 }
772
773 bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
774 BigBuffer buffer(1024);
Adam Lesinski59e04c62016-02-04 15:59:23 -0800775 TableFlattener flattener(&buffer);
Adam Lesinski6a008172016-02-02 17:02:58 -0800776 if (!flattener.consume(mContext, table)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700777 return false;
778 }
779
Adam Lesinskia40e9722015-11-24 19:11:46 -0800780 if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
781 if (writer->writeEntry(buffer)) {
782 if (writer->finishEntry()) {
783 return true;
784 }
785 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700786 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800787
Adam Lesinski6a008172016-02-02 17:02:58 -0800788 mContext->getDiagnostics()->error(
Adam Lesinskia40e9722015-11-24 19:11:46 -0800789 DiagMessage() << "failed to write resources.arsc to archive");
790 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700791 }
792
Adam Lesinski64587af2016-02-18 18:33:06 -0800793 bool flattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
794 // Create the file/zip entry.
795 if (!writer->startEntry("resources.arsc.flat", 0)) {
796 mContext->getDiagnostics()->error(DiagMessage() << "failed to open");
797 return false;
798 }
799
800 std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table);
801
802 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
803 // interface.
804 {
805 google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
806
807 if (!pbTable->SerializeToZeroCopyStream(&adaptor)) {
808 mContext->getDiagnostics()->error(DiagMessage() << "failed to write");
809 return false;
810 }
811 }
812
813 if (!writer->finishEntry()) {
814 mContext->getDiagnostics()->error(DiagMessage() << "failed to finish entry");
815 return false;
816 }
817 return true;
818 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700819
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700820 bool writeJavaFile(ResourceTable* table, const StringPiece& packageNameToGenerate,
821 const StringPiece& outPackage, JavaClassGeneratorOptions javaOptions) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700822 if (!mOptions.generateJavaClassPath) {
823 return true;
824 }
825
826 std::string outPath = mOptions.generateJavaClassPath.value();
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700827 file::appendPath(&outPath, file::packageToPath(outPackage));
Adam Lesinski96917c22016-03-09 13:11:25 -0800828 if (!file::mkdirs(outPath)) {
829 mContext->getDiagnostics()->error(
830 DiagMessage() << "failed to create directory '" << outPath << "'");
831 return false;
832 }
833
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700834 file::appendPath(&outPath, "R.java");
835
836 std::ofstream fout(outPath, std::ofstream::binary);
837 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800838 mContext->getDiagnostics()->error(
839 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700840 return false;
841 }
842
Adam Lesinski76565542016-03-10 21:55:04 -0800843 JavaClassGenerator generator(mContext, table, javaOptions);
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700844 if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800845 mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700846 return false;
847 }
Adam Lesinski96917c22016-03-09 13:11:25 -0800848
849 if (!fout) {
850 mContext->getDiagnostics()->error(
851 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
852 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700853 return true;
854 }
855
Adam Lesinski467f1712015-11-16 17:35:44 -0800856 bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
Adam Lesinskica5638f2015-10-21 14:42:43 -0700857 if (!mOptions.generateJavaClassPath) {
858 return true;
859 }
860
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700861 std::unique_ptr<ClassDefinition> manifestClass = generateManifestClass(
862 mContext->getDiagnostics(), manifestXml);
863
864 if (!manifestClass) {
865 // Something bad happened, but we already logged it, so exit.
866 return false;
867 }
868
869 if (manifestClass->empty()) {
870 // Empty Manifest class, no need to generate it.
871 return true;
872 }
873
Adam Lesinski3524a232016-04-01 19:19:24 -0700874 // Add any JavaDoc annotations to the generated class.
875 for (const std::string& annotation : mOptions.javadocAnnotations) {
876 std::string properAnnotation = "@";
877 properAnnotation += annotation;
878 manifestClass->getCommentBuilder()->appendComment(properAnnotation);
879 }
880
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700881 const std::string& packageUtf8 = mContext->getCompilationPackage();
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700882
Adam Lesinskica5638f2015-10-21 14:42:43 -0700883 std::string outPath = mOptions.generateJavaClassPath.value();
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700884 file::appendPath(&outPath, file::packageToPath(packageUtf8));
885
Adam Lesinski96917c22016-03-09 13:11:25 -0800886 if (!file::mkdirs(outPath)) {
887 mContext->getDiagnostics()->error(
888 DiagMessage() << "failed to create directory '" << outPath << "'");
889 return false;
890 }
891
Adam Lesinskica5638f2015-10-21 14:42:43 -0700892 file::appendPath(&outPath, "Manifest.java");
893
894 std::ofstream fout(outPath, std::ofstream::binary);
895 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800896 mContext->getDiagnostics()->error(
897 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinskica5638f2015-10-21 14:42:43 -0700898 return false;
899 }
900
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700901 if (!ClassDefinition::writeJavaFile(manifestClass.get(), packageUtf8, true, &fout)) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800902 mContext->getDiagnostics()->error(
903 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinskica5638f2015-10-21 14:42:43 -0700904 return false;
905 }
906 return true;
907 }
908
Rohit Agrawale49bb302016-04-22 12:27:55 -0700909 bool writeProguardFile(const Maybe<std::string>& out, const proguard::KeepSet& keepSet) {
910 if (!out) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700911 return true;
912 }
913
Rohit Agrawale49bb302016-04-22 12:27:55 -0700914 const std::string& outPath = out.value();
Adam Lesinski96917c22016-03-09 13:11:25 -0800915 std::ofstream fout(outPath, std::ofstream::binary);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700916 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800917 mContext->getDiagnostics()->error(
918 DiagMessage() << "failed to open '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700919 return false;
920 }
921
922 proguard::writeKeepSet(&fout, keepSet);
923 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800924 mContext->getDiagnostics()->error(
925 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700926 return false;
927 }
928 return true;
929 }
930
Adam Lesinski64587af2016-02-18 18:33:06 -0800931 std::unique_ptr<ResourceTable> loadStaticLibrary(const std::string& input,
932 std::string* outError) {
933 std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
934 input, outError);
935 if (!collection) {
936 return {};
937 }
938 return loadTablePbFromCollection(collection.get());
939 }
940
941 std::unique_ptr<ResourceTable> loadTablePbFromCollection(io::IFileCollection* collection) {
942 io::IFile* file = collection->findFile("resources.arsc.flat");
943 if (!file) {
944 return {};
945 }
946
947 std::unique_ptr<io::IData> data = file->openAsData();
948 return loadTableFromPb(file->getSource(), data->data(), data->size(),
949 mContext->getDiagnostics());
950 }
951
952 bool mergeStaticLibrary(const std::string& input, bool override) {
953 if (mContext->verbose()) {
954 mContext->getDiagnostics()->note(DiagMessage() << "merging static library " << input);
955 }
956
957 std::string errorStr;
958 std::unique_ptr<io::ZipFileCollection> collection =
959 io::ZipFileCollection::create(input, &errorStr);
960 if (!collection) {
961 mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
962 return false;
963 }
964
965 std::unique_ptr<ResourceTable> table = loadTablePbFromCollection(collection.get());
966 if (!table) {
967 mContext->getDiagnostics()->error(DiagMessage(input) << "invalid static library");
968 return false;
969 }
970
971 ResourceTablePackage* pkg = table->findPackageById(0x7f);
972 if (!pkg) {
973 mContext->getDiagnostics()->error(DiagMessage(input)
974 << "static library has no package");
975 return false;
976 }
977
978 bool result;
979 if (mOptions.noStaticLibPackages) {
980 // Merge all resources as if they were in the compilation package. This is the old
981 // behaviour of aapt.
982
983 // Add the package to the set of --extra-packages so we emit an R.java for each
984 // library package.
985 if (!pkg->name.empty()) {
986 mOptions.extraJavaPackages.insert(pkg->name);
987 }
988
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700989 pkg->name = "";
Adam Lesinski64587af2016-02-18 18:33:06 -0800990 if (override) {
991 result = mTableMerger->mergeOverlay(Source(input), table.get(), collection.get());
992 } else {
993 result = mTableMerger->merge(Source(input), table.get(), collection.get());
994 }
995
996 } else {
997 // This is the proper way to merge libraries, where the package name is preserved
998 // and resource names are mangled.
999 result = mTableMerger->mergeAndMangle(Source(input), pkg->name, table.get(),
1000 collection.get());
1001 }
1002
1003 if (!result) {
1004 return false;
1005 }
1006
1007 // Make sure to move the collection into the set of IFileCollections.
1008 mCollections.push_back(std::move(collection));
Adam Lesinskifb48d292015-11-07 15:52:13 -08001009 return true;
1010 }
1011
Adam Lesinskia40e9722015-11-24 19:11:46 -08001012 bool mergeResourceTable(io::IFile* file, bool override) {
Adam Lesinski355f2852016-02-13 20:26:45 -08001013 if (mContext->verbose()) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001014 mContext->getDiagnostics()->note(DiagMessage() << "merging resource table "
1015 << file->getSource());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001016 }
1017
Adam Lesinskia40e9722015-11-24 19:11:46 -08001018 std::unique_ptr<io::IData> data = file->openAsData();
1019 if (!data) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001020 mContext->getDiagnostics()->error(DiagMessage(file->getSource())
Adam Lesinskia40e9722015-11-24 19:11:46 -08001021 << "failed to open file");
1022 return false;
1023 }
1024
Adam Lesinski355f2852016-02-13 20:26:45 -08001025 std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(),
1026 data->data(), data->size(),
1027 mContext->getDiagnostics());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001028 if (!table) {
1029 return false;
1030 }
1031
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001032 bool result = false;
1033 if (override) {
1034 result = mTableMerger->mergeOverlay(file->getSource(), table.get());
1035 } else {
1036 result = mTableMerger->merge(file->getSource(), table.get());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001037 }
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001038 return result;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001039 }
1040
Adam Lesinski64587af2016-02-18 18:33:06 -08001041 bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc, bool override) {
Adam Lesinski355f2852016-02-13 20:26:45 -08001042 if (mContext->verbose()) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001043 mContext->getDiagnostics()->note(DiagMessage() << "merging compiled file "
1044 << file->getSource());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001045 }
1046
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001047 bool result = false;
Adam Lesinski64587af2016-02-18 18:33:06 -08001048 if (override) {
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001049 result = mTableMerger->mergeFileOverlay(*fileDesc, file);
Adam Lesinskifb48d292015-11-07 15:52:13 -08001050 } else {
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001051 result = mTableMerger->mergeFile(*fileDesc, file);
Adam Lesinskifb48d292015-11-07 15:52:13 -08001052 }
1053
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001054 if (!result) {
Adam Lesinskifb48d292015-11-07 15:52:13 -08001055 return false;
1056 }
1057
1058 // Add the exports of this file to the table.
Adam Lesinskia40e9722015-11-24 19:11:46 -08001059 for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
Adam Lesinskifb48d292015-11-07 15:52:13 -08001060 if (exportedSymbol.name.package.empty()) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001061 exportedSymbol.name.package = mContext->getCompilationPackage();
Adam Lesinskifb48d292015-11-07 15:52:13 -08001062 }
1063
1064 ResourceNameRef resName = exportedSymbol.name;
1065
Adam Lesinski6a008172016-02-02 17:02:58 -08001066 Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
Adam Lesinskifb48d292015-11-07 15:52:13 -08001067 exportedSymbol.name);
1068 if (mangledName) {
1069 resName = mangledName.value();
1070 }
1071
1072 std::unique_ptr<Id> id = util::make_unique<Id>();
Adam Lesinskia40e9722015-11-24 19:11:46 -08001073 id->setSource(fileDesc->source.withLine(exportedSymbol.line));
Adam Lesinski64587af2016-02-18 18:33:06 -08001074 bool result = mFinalTable.addResourceAllowMangled(
1075 resName, ConfigDescription::defaultConfig(), std::string(), std::move(id),
1076 mContext->getDiagnostics());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001077 if (!result) {
1078 return false;
1079 }
1080 }
Adam Lesinskifb48d292015-11-07 15:52:13 -08001081 return true;
1082 }
1083
Adam Lesinskia40e9722015-11-24 19:11:46 -08001084 /**
Adam Lesinski64587af2016-02-18 18:33:06 -08001085 * Takes a path to load as a ZIP file and merges the files within into the master ResourceTable.
1086 * If override is true, conflicting resources are allowed to override each other, in order of
1087 * last seen.
1088 *
1089 * An io::IFileCollection is created from the ZIP file and added to the set of
1090 * io::IFileCollections that are open.
Adam Lesinskia40e9722015-11-24 19:11:46 -08001091 */
1092 bool mergeArchive(const std::string& input, bool override) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001093 if (mContext->verbose()) {
1094 mContext->getDiagnostics()->note(DiagMessage() << "merging archive " << input);
1095 }
1096
Adam Lesinskia40e9722015-11-24 19:11:46 -08001097 std::string errorStr;
Adam Lesinski64587af2016-02-18 18:33:06 -08001098 std::unique_ptr<io::ZipFileCollection> collection =
1099 io::ZipFileCollection::create(input, &errorStr);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001100 if (!collection) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001101 mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001102 return false;
1103 }
1104
1105 bool error = false;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001106 for (auto iter = collection->iterator(); iter->hasNext(); ) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001107 if (!mergeFile(iter->next(), override)) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001108 error = true;
1109 }
1110 }
1111
1112 // Make sure to move the collection into the set of IFileCollections.
1113 mCollections.push_back(std::move(collection));
1114 return !error;
1115 }
1116
Adam Lesinski64587af2016-02-18 18:33:06 -08001117 /**
1118 * Takes a path to load and merge into the master ResourceTable. If override is true,
1119 * conflicting resources are allowed to override each other, in order of last seen.
1120 *
1121 * If the file path ends with .flata, .jar, .jack, or .zip the file is treated as ZIP archive
1122 * and the files within are merged individually.
1123 *
1124 * Otherwise the files is processed on its own.
1125 */
1126 bool mergePath(const std::string& path, bool override) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001127 if (util::stringEndsWith(path, ".flata") ||
1128 util::stringEndsWith(path, ".jar") ||
1129 util::stringEndsWith(path, ".jack") ||
1130 util::stringEndsWith(path, ".zip")) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001131 return mergeArchive(path, override);
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001132 } else if (util::stringEndsWith(path, ".apk")) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001133 return mergeStaticLibrary(path, override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001134 }
1135
1136 io::IFile* file = mFileCollection->insertFile(path);
Adam Lesinski64587af2016-02-18 18:33:06 -08001137 return mergeFile(file, override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001138 }
1139
Adam Lesinski64587af2016-02-18 18:33:06 -08001140 /**
1141 * Takes a file to load and merge into the master ResourceTable. If override is true,
1142 * conflicting resources are allowed to override each other, in order of last seen.
1143 *
1144 * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and merged into the
1145 * master ResourceTable. If the file ends with .flat, then it is treated like a compiled file
1146 * and the header data is read and merged into the final ResourceTable.
1147 *
1148 * All other file types are ignored. This is because these files could be coming from a zip,
1149 * where we could have other files like classes.dex.
1150 */
1151 bool mergeFile(io::IFile* file, bool override) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001152 const Source& src = file->getSource();
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001153 if (util::stringEndsWith(src.path, ".arsc.flat")) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001154 return mergeResourceTable(file, override);
Adam Lesinski64587af2016-02-18 18:33:06 -08001155
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001156 } else if (util::stringEndsWith(src.path, ".flat")){
Adam Lesinskia40e9722015-11-24 19:11:46 -08001157 // Try opening the file and looking for an Export header.
1158 std::unique_ptr<io::IData> data = file->openAsData();
1159 if (!data) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001160 mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open");
Adam Lesinskia40e9722015-11-24 19:11:46 -08001161 return false;
1162 }
1163
1164 std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader(
Adam Lesinski6a008172016-02-02 17:02:58 -08001165 src, data->data(), data->size(), mContext->getDiagnostics());
Adam Lesinskia40e9722015-11-24 19:11:46 -08001166 if (resourceFile) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001167 return mergeCompiledFile(file, resourceFile.get(), override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001168 }
Adam Lesinskic446a732016-01-21 11:04:46 -08001169 return false;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001170 }
Adam Lesinski52364f72016-01-11 13:10:24 -08001171
Adam Lesinskic446a732016-01-21 11:04:46 -08001172 // Ignore non .flat files. This could be classes.dex or something else that happens
1173 // to be in an archive.
1174 return true;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001175 }
1176
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001177 int run(const std::vector<std::string>& inputFiles) {
1178 // Load the AndroidManifest.xml
Adam Lesinskia40e9722015-11-24 19:11:46 -08001179 std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
Adam Lesinski6a008172016-02-02 17:02:58 -08001180 mContext->getDiagnostics());
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001181 if (!manifestXml) {
1182 return 1;
1183 }
1184
Adam Lesinskifb6312f2016-06-28 14:40:32 -07001185 if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(),
1186 mContext->getDiagnostics())) {
1187 AppInfo& appInfo = maybeAppInfo.value();
1188 mContext->setCompilationPackage(appInfo.package);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001189 } else {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001190 return 1;
1191 }
1192
Adam Lesinski64587af2016-02-18 18:33:06 -08001193 if (!util::isJavaPackageName(mContext->getCompilationPackage())) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001194 mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001195 << "invalid package name '"
Adam Lesinski64587af2016-02-18 18:33:06 -08001196 << mContext->getCompilationPackage()
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001197 << "'");
1198 return 1;
1199 }
1200
Adam Lesinski64587af2016-02-18 18:33:06 -08001201 mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() });
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001202
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001203 if (mContext->getCompilationPackage() == "android") {
Adam Lesinski64587af2016-02-18 18:33:06 -08001204 mContext->setPackageId(0x01);
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001205 } else {
Adam Lesinski64587af2016-02-18 18:33:06 -08001206 mContext->setPackageId(0x7f);
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001207 }
1208
Adam Lesinski64587af2016-02-18 18:33:06 -08001209 if (!loadSymbolsFromIncludePaths()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001210 return 1;
1211 }
1212
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001213 TableMergerOptions tableMergerOptions;
1214 tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
Adam Lesinski6a008172016-02-02 17:02:58 -08001215 mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions);
Adam Lesinskifb48d292015-11-07 15:52:13 -08001216
Adam Lesinski355f2852016-02-13 20:26:45 -08001217 if (mContext->verbose()) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001218 mContext->getDiagnostics()->note(
Adam Lesinski64587af2016-02-18 18:33:06 -08001219 DiagMessage() << "linking package '" << mContext->getCompilationPackage()
1220 << "' with package ID " << std::hex
1221 << (int) mContext->getPackageId());
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001222 }
1223
Adam Lesinskifb48d292015-11-07 15:52:13 -08001224
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001225 for (const std::string& input : inputFiles) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001226 if (!mergePath(input, false)) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001227 mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input");
Adam Lesinski467f1712015-11-16 17:35:44 -08001228 return 1;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001229 }
1230 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001231
Adam Lesinskifb48d292015-11-07 15:52:13 -08001232 for (const std::string& input : mOptions.overlayFiles) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001233 if (!mergePath(input, true)) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001234 mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
Adam Lesinski467f1712015-11-16 17:35:44 -08001235 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001236 }
1237 }
1238
Adam Lesinskifb48d292015-11-07 15:52:13 -08001239 if (!verifyNoExternalPackages()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001240 return 1;
1241 }
1242
1243 if (!mOptions.staticLib) {
1244 PrivateAttributeMover mover;
Adam Lesinski6a008172016-02-02 17:02:58 -08001245 if (!mover.consume(mContext, &mFinalTable)) {
1246 mContext->getDiagnostics()->error(
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001247 DiagMessage() << "failed moving private attributes");
1248 return 1;
1249 }
1250 }
1251
Adam Lesinski64587af2016-02-18 18:33:06 -08001252 if (!mOptions.staticLib) {
1253 // Assign IDs if we are building a regular app.
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001254 IdAssigner idAssigner(&mOptions.stableIdMap);
Adam Lesinski6a008172016-02-02 17:02:58 -08001255 if (!idAssigner.consume(mContext, &mFinalTable)) {
1256 mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001257 return 1;
1258 }
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001259
1260 // Now grab each ID and emit it as a file.
1261 if (mOptions.resourceIdMapPath) {
1262 for (auto& package : mFinalTable.packages) {
1263 for (auto& type : package->types) {
1264 for (auto& entry : type->entries) {
1265 ResourceName name(package->name, type->type, entry->name);
1266 // The IDs are guaranteed to exist.
1267 mOptions.stableIdMap[std::move(name)] = ResourceId(package->id.value(),
1268 type->id.value(),
1269 entry->id.value());
1270 }
1271 }
1272 }
1273
1274 if (!writeStableIdMapToPath(mContext->getDiagnostics(),
1275 mOptions.stableIdMap,
1276 mOptions.resourceIdMapPath.value())) {
1277 return 1;
1278 }
1279 }
Adam Lesinski64587af2016-02-18 18:33:06 -08001280 } else {
1281 // Static libs are merged with other apps, and ID collisions are bad, so verify that
1282 // no IDs have been set.
1283 if (!verifyNoIdsSet()) {
1284 return 1;
1285 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001286 }
1287
Adam Lesinski64587af2016-02-18 18:33:06 -08001288 // Add the names to mangle based on our source merge earlier.
1289 mContext->setNameManglerPolicy(NameManglerPolicy{
1290 mContext->getCompilationPackage(), mTableMerger->getMergedPackages() });
1291
1292 // Add our table to the symbol table.
1293 mContext->getExternalSymbols()->prependSource(
1294 util::make_unique<ResourceTableSymbolSource>(&mFinalTable));
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001295
1296 {
1297 ReferenceLinker linker;
Adam Lesinski6a008172016-02-02 17:02:58 -08001298 if (!linker.consume(mContext, &mFinalTable)) {
1299 mContext->getDiagnostics()->error(DiagMessage() << "failed linking references");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001300 return 1;
1301 }
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001302
Adam Lesinski64587af2016-02-18 18:33:06 -08001303 if (mOptions.staticLib) {
1304 if (!mOptions.products.empty()) {
1305 mContext->getDiagnostics()->warn(
1306 DiagMessage() << "can't select products when building static library");
1307 }
Adam Lesinski355f2852016-02-13 20:26:45 -08001308
Adam Lesinski64587af2016-02-18 18:33:06 -08001309 if (mOptions.tableSplitterOptions.configFilter != nullptr ||
1310 mOptions.tableSplitterOptions.preferredDensity) {
1311 mContext->getDiagnostics()->warn(
1312 DiagMessage() << "can't strip resources when building static library");
1313 }
1314 } else {
1315 ProductFilter productFilter(mOptions.products);
1316 if (!productFilter.consume(mContext, &mFinalTable)) {
1317 mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products");
1318 return 1;
1319 }
Adam Lesinski355f2852016-02-13 20:26:45 -08001320
Adam Lesinski64587af2016-02-18 18:33:06 -08001321 // TODO(adamlesinski): Actually pass in split constraints and handle splits at the file
1322 // level.
1323 TableSplitter tableSplitter({}, mOptions.tableSplitterOptions);
1324 if (!tableSplitter.verifySplitConstraints(mContext)) {
1325 return 1;
1326 }
1327 tableSplitter.splitTable(&mFinalTable);
1328 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001329 }
1330
1331 proguard::KeepSet proguardKeepSet;
Rohit Agrawale49bb302016-04-22 12:27:55 -07001332 proguard::KeepSet proguardMainDexKeepSet;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001333
1334 std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter();
1335 if (!archiveWriter) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001336 mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001337 return 1;
1338 }
1339
Adam Lesinski467f1712015-11-16 17:35:44 -08001340 bool error = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001341 {
Adam Lesinski52364f72016-01-11 13:10:24 -08001342 ManifestFixer manifestFixer(mOptions.manifestFixerOptions);
Adam Lesinski6a008172016-02-02 17:02:58 -08001343 if (!manifestFixer.consume(mContext, manifestXml.get())) {
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001344 error = true;
1345 }
1346
Adam Lesinski467f1712015-11-16 17:35:44 -08001347 // AndroidManifest.xml has no resource name, but the CallSite is built from the name
1348 // (aka, which package the AndroidManifest.xml is coming from).
1349 // So we give it a package name so it can see local resources.
Adam Lesinski64587af2016-02-18 18:33:06 -08001350 manifestXml->file.name.package = mContext->getCompilationPackage();
Adam Lesinski467f1712015-11-16 17:35:44 -08001351
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001352 XmlReferenceLinker manifestLinker;
Adam Lesinski6a008172016-02-02 17:02:58 -08001353 if (manifestLinker.consume(mContext, manifestXml.get())) {
Rohit Agrawale49bb302016-04-22 12:27:55 -07001354 if (mOptions.generateProguardRulesPath &&
1355 !proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
1356 manifestXml.get(),
1357 &proguardKeepSet)) {
1358 error = true;
1359 }
1360
1361 if (mOptions.generateMainDexProguardRulesPath &&
1362 !proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
1363 manifestXml.get(),
1364 &proguardMainDexKeepSet,
1365 true)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001366 error = true;
1367 }
1368
Adam Lesinskica5638f2015-10-21 14:42:43 -07001369 if (mOptions.generateJavaClassPath) {
1370 if (!writeManifestJavaFile(manifestXml.get())) {
1371 error = true;
1372 }
1373 }
1374
Adam Lesinski355f2852016-02-13 20:26:45 -08001375 const bool keepRawValues = mOptions.staticLib;
1376 bool result = flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
1377 keepRawValues, archiveWriter.get(), mContext);
1378 if (!result) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001379 error = true;
1380 }
1381 } else {
1382 error = true;
1383 }
1384 }
1385
Adam Lesinski467f1712015-11-16 17:35:44 -08001386 if (error) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001387 mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest");
Adam Lesinski467f1712015-11-16 17:35:44 -08001388 return 1;
1389 }
1390
Adam Lesinski355f2852016-02-13 20:26:45 -08001391 ResourceFileFlattenerOptions fileFlattenerOptions;
1392 fileFlattenerOptions.keepRawValues = mOptions.staticLib;
1393 fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything;
1394 fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress;
1395 fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion;
Adam Lesinski626a69f2016-03-03 10:09:26 -08001396 fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors;
Rohit Agrawale49bb302016-04-22 12:27:55 -07001397 fileFlattenerOptions.updateProguardSpec =
1398 static_cast<bool>(mOptions.generateProguardRulesPath);
Adam Lesinski355f2852016-02-13 20:26:45 -08001399 ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, &proguardKeepSet);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001400
Adam Lesinski355f2852016-02-13 20:26:45 -08001401 if (!fileFlattener.flatten(&mFinalTable, archiveWriter.get())) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001402 mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001403 return 1;
1404 }
1405
Adam Lesinski626a69f2016-03-03 10:09:26 -08001406 if (!mOptions.noAutoVersion) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001407 AutoVersioner versioner;
Adam Lesinski6a008172016-02-02 17:02:58 -08001408 if (!versioner.consume(mContext, &mFinalTable)) {
1409 mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001410 return 1;
1411 }
1412 }
1413
Adam Lesinskifb6312f2016-06-28 14:40:32 -07001414 Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(),
1415 mContext->getDiagnostics());
1416 if (maybeAppInfo && maybeAppInfo.value().minSdkVersion) {
1417 if (Maybe<int> maybeMinSdkVersion =
1418 ResourceUtils::tryParseSdkVersion(maybeAppInfo.value().minSdkVersion.value())) {
1419 mContext->setMinSdkVersion(maybeMinSdkVersion.value());
1420 }
1421 }
1422
1423 if (!mOptions.staticLib && mContext->getMinSdkVersion() > 0) {
1424 if (mContext->verbose()) {
1425 mContext->getDiagnostics()->note(
1426 DiagMessage() << "collapsing resource versions for minimum SDK "
1427 << mContext->getMinSdkVersion());
1428 }
1429
1430 VersionCollapser collapser;
1431 if (!collapser.consume(mContext, &mFinalTable)) {
1432 return 1;
1433 }
1434 }
1435
Adam Lesinski64587af2016-02-18 18:33:06 -08001436 if (mOptions.staticLib) {
1437 if (!flattenTableToPb(&mFinalTable, archiveWriter.get())) {
1438 mContext->getDiagnostics()->error(DiagMessage()
1439 << "failed to write resources.arsc.flat");
1440 return 1;
1441 }
1442 } else {
1443 if (!flattenTable(&mFinalTable, archiveWriter.get())) {
1444 mContext->getDiagnostics()->error(DiagMessage()
1445 << "failed to write resources.arsc");
1446 return 1;
1447 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001448 }
1449
1450 if (mOptions.generateJavaClassPath) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001451 JavaClassGeneratorOptions options;
Adam Lesinski52364f72016-01-11 13:10:24 -08001452 options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
Adam Lesinski3524a232016-04-01 19:19:24 -07001453 options.javadocAnnotations = mOptions.javadocAnnotations;
Adam Lesinski52364f72016-01-11 13:10:24 -08001454
Adam Lesinskief9c5012016-01-22 14:09:53 -08001455 if (mOptions.staticLib || mOptions.generateNonFinalIds) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001456 options.useFinal = false;
1457 }
1458
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001459 const StringPiece actualPackage = mContext->getCompilationPackage();
1460 StringPiece outputPackage = mContext->getCompilationPackage();
Adam Lesinski52364f72016-01-11 13:10:24 -08001461 if (mOptions.customJavaPackage) {
1462 // Override the output java package to the custom one.
1463 outputPackage = mOptions.customJavaPackage.value();
1464 }
Adam Lesinski83f22552015-11-07 11:51:23 -08001465
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001466 if (mOptions.privateSymbols) {
1467 // If we defined a private symbols package, we only emit Public symbols
1468 // to the original package, and private and public symbols to the private package.
1469
1470 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
Adam Lesinski6a008172016-02-02 17:02:58 -08001471 if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(),
Adam Lesinski52364f72016-01-11 13:10:24 -08001472 outputPackage, options)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001473 return 1;
1474 }
1475
1476 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
Adam Lesinski83f22552015-11-07 11:51:23 -08001477 outputPackage = mOptions.privateSymbols.value();
1478 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001479
Adam Lesinskifb48d292015-11-07 15:52:13 -08001480 if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
Adam Lesinski83f22552015-11-07 11:51:23 -08001481 return 1;
1482 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001483
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001484 for (const std::string& extraPackage : mOptions.extraJavaPackages) {
Adam Lesinski52364f72016-01-11 13:10:24 -08001485 if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001486 return 1;
1487 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001488 }
1489 }
1490
Rohit Agrawale49bb302016-04-22 12:27:55 -07001491 if (!writeProguardFile(mOptions.generateProguardRulesPath, proguardKeepSet)) {
1492 return 1;
1493 }
1494
1495 if (!writeProguardFile(mOptions.generateMainDexProguardRulesPath, proguardMainDexKeepSet)) {
1496 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001497 }
1498
Adam Lesinski355f2852016-02-13 20:26:45 -08001499 if (mContext->verbose()) {
1500 DebugPrintTableOptions debugPrintTableOptions;
1501 debugPrintTableOptions.showSources = true;
1502 Debug::printTable(&mFinalTable, debugPrintTableOptions);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001503 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001504 return 0;
1505 }
Adam Lesinskifb48d292015-11-07 15:52:13 -08001506
1507private:
1508 LinkOptions mOptions;
Adam Lesinski6a008172016-02-02 17:02:58 -08001509 LinkContext* mContext;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001510 ResourceTable mFinalTable;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001511
Adam Lesinskifb48d292015-11-07 15:52:13 -08001512 std::unique_ptr<TableMerger> mTableMerger;
1513
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001514 // A pointer to the FileCollection representing the filesystem (not archives).
Adam Lesinski64587af2016-02-18 18:33:06 -08001515 std::unique_ptr<io::FileCollection> mFileCollection;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001516
1517 // A vector of IFileCollections. This is mainly here to keep ownership of the collections.
Adam Lesinskia40e9722015-11-24 19:11:46 -08001518 std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
Adam Lesinski64587af2016-02-18 18:33:06 -08001519
1520 // A vector of ResourceTables. This is here to retain ownership, so that the SymbolTable
1521 // can use these.
1522 std::vector<std::unique_ptr<ResourceTable>> mStaticTableIncludes;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001523};
1524
1525int link(const std::vector<StringPiece>& args) {
Adam Lesinski355f2852016-02-13 20:26:45 -08001526 LinkContext context;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001527 LinkOptions options;
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001528 std::vector<std::string> overlayArgList;
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001529 std::vector<std::string> extraJavaPackages;
Adam Lesinski6a008172016-02-02 17:02:58 -08001530 Maybe<std::string> configs;
Adam Lesinski355f2852016-02-13 20:26:45 -08001531 Maybe<std::string> preferredDensity;
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001532 Maybe<std::string> productList;
Adam Lesinski8900aa82016-01-25 22:48:15 -08001533 bool legacyXFlag = false;
1534 bool requireLocalization = false;
Adam Lesinski64587af2016-02-18 18:33:06 -08001535 bool verbose = false;
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001536 Maybe<std::string> stableIdFilePath;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001537 Flags flags = Flags()
1538 .requiredFlag("-o", "Output path", &options.outputPath)
1539 .requiredFlag("--manifest", "Path to the Android manifest to build",
1540 &options.manifestPath)
1541 .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
Adam Lesinski52364f72016-01-11 13:10:24 -08001542 .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n"
Adam Lesinskifb48d292015-11-07 15:52:13 -08001543 "The last conflicting resource given takes precedence.",
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001544 &overlayArgList)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001545 .optionalFlag("--java", "Directory in which to generate R.java",
1546 &options.generateJavaClassPath)
1547 .optionalFlag("--proguard", "Output file for generated Proguard rules",
1548 &options.generateProguardRulesPath)
Rohit Agrawale49bb302016-04-22 12:27:55 -07001549 .optionalFlag("--proguard-main-dex",
1550 "Output file for generated Proguard rules for the main dex",
1551 &options.generateMainDexProguardRulesPath)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001552 .optionalSwitch("--no-auto-version",
1553 "Disables automatic style and layout SDK versioning",
1554 &options.noAutoVersion)
Adam Lesinski64587af2016-02-18 18:33:06 -08001555 .optionalSwitch("--no-version-vectors",
1556 "Disables automatic versioning of vector drawables. Use this only\n"
1557 "when building with vector drawable support library",
1558 &options.noVersionVectors)
Adam Lesinski8900aa82016-01-25 22:48:15 -08001559 .optionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01",
1560 &legacyXFlag)
1561 .optionalSwitch("-z", "Require localization of strings marked 'suggested'",
1562 &requireLocalization)
Adam Lesinski6a008172016-02-02 17:02:58 -08001563 .optionalFlag("-c", "Comma separated list of configurations to include. The default\n"
1564 "is all configurations", &configs)
Adam Lesinski355f2852016-02-13 20:26:45 -08001565 .optionalFlag("--preferred-density",
1566 "Selects the closest matching density and strips out all others.",
1567 &preferredDensity)
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001568 .optionalFlag("--product", "Comma separated list of product names to keep",
1569 &productList)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001570 .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
1571 "by -o",
1572 &options.outputToDirectory)
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001573 .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001574 "AndroidManifest.xml",
1575 &options.manifestFixerOptions.minSdkVersionDefault)
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001576 .optionalFlag("--target-sdk-version", "Default target SDK version to use for "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001577 "AndroidManifest.xml",
1578 &options.manifestFixerOptions.targetSdkVersionDefault)
Adam Lesinski52364f72016-01-11 13:10:24 -08001579 .optionalFlag("--version-code", "Version code (integer) to inject into the "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001580 "AndroidManifest.xml if none is present",
1581 &options.manifestFixerOptions.versionCodeDefault)
Adam Lesinski52364f72016-01-11 13:10:24 -08001582 .optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001583 "if none is present",
1584 &options.manifestFixerOptions.versionNameDefault)
1585 .optionalSwitch("--static-lib", "Generate a static Android library",
1586 &options.staticLib)
Adam Lesinski64587af2016-02-18 18:33:06 -08001587 .optionalSwitch("--no-static-lib-packages",
1588 "Merge all library resources under the app's package",
1589 &options.noStaticLibPackages)
Adam Lesinskief9c5012016-01-22 14:09:53 -08001590 .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n"
1591 "This is implied when --static-lib is specified.",
1592 &options.generateNonFinalIds)
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001593 .optionalFlag("--stable-ids", "File containing a list of name to ID mapping.",
1594 &stableIdFilePath)
1595 .optionalFlag("--emit-ids", "Emit a file at the given path with a list of name to ID\n"
1596 "mappings, suitable for use with --stable-ids.",
1597 &options.resourceIdMapPath)
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001598 .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001599 "private symbols.\n"
1600 "If not specified, public and private symbols will use the application's "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001601 "package name",
1602 &options.privateSymbols)
Adam Lesinski52364f72016-01-11 13:10:24 -08001603 .optionalFlag("--custom-package", "Custom Java package under which to generate R.java",
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001604 &options.customJavaPackage)
Adam Lesinski83f22552015-11-07 11:51:23 -08001605 .optionalFlagList("--extra-packages", "Generate the same R.java but with different "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001606 "package names",
1607 &extraJavaPackages)
Adam Lesinski3524a232016-04-01 19:19:24 -07001608 .optionalFlagList("--add-javadoc-annotation", "Adds a JavaDoc annotation to all "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001609 "generated Java classes",
1610 &options.javadocAnnotations)
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001611 .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001612 "overlays without <add-resource> tags",
1613 &options.autoAddOverlay)
Adam Lesinski52364f72016-01-11 13:10:24 -08001614 .optionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml",
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001615 &options.manifestFixerOptions.renameManifestPackage)
Adam Lesinski52364f72016-01-11 13:10:24 -08001616 .optionalFlag("--rename-instrumentation-target-package",
1617 "Changes the name of the target package for instrumentation. Most useful "
1618 "when used\nin conjunction with --rename-manifest-package",
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001619 &options.manifestFixerOptions.renameInstrumentationTargetPackage)
Adam Lesinski52364f72016-01-11 13:10:24 -08001620 .optionalFlagList("-0", "File extensions not to compress",
1621 &options.extensionsToNotCompress)
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001622 .optionalSwitch("-v", "Enables verbose logging",
1623 &verbose);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001624
1625 if (!flags.parse("aapt2 link", args, &std::cerr)) {
1626 return 1;
1627 }
1628
Adam Lesinskic51562c2016-04-28 11:12:38 -07001629 // Expand all argument-files passed into the command line. These start with '@'.
1630 std::vector<std::string> argList;
1631 for (const std::string& arg : flags.getArgs()) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001632 if (util::stringStartsWith(arg, "@")) {
Adam Lesinskic51562c2016-04-28 11:12:38 -07001633 const std::string path = arg.substr(1, arg.size() - 1);
1634 std::string error;
1635 if (!file::appendArgsFromFile(path, &argList, &error)) {
1636 context.getDiagnostics()->error(DiagMessage(path) << error);
1637 return 1;
1638 }
1639 } else {
1640 argList.push_back(arg);
1641 }
1642 }
1643
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001644 // Expand all argument-files passed to -R.
1645 for (const std::string& arg : overlayArgList) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001646 if (util::stringStartsWith(arg, "@")) {
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001647 const std::string path = arg.substr(1, arg.size() - 1);
1648 std::string error;
1649 if (!file::appendArgsFromFile(path, &options.overlayFiles, &error)) {
1650 context.getDiagnostics()->error(DiagMessage(path) << error);
1651 return 1;
1652 }
1653 } else {
1654 options.overlayFiles.push_back(arg);
1655 }
1656 }
1657
Adam Lesinski64587af2016-02-18 18:33:06 -08001658 if (verbose) {
1659 context.setVerbose(verbose);
1660 }
1661
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001662 // Populate the set of extra packages for which to generate R.java.
1663 for (std::string& extraPackage : extraJavaPackages) {
1664 // A given package can actually be a colon separated list of packages.
1665 for (StringPiece package : util::split(extraPackage, ':')) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001666 options.extraJavaPackages.insert(package.toString());
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001667 }
1668 }
1669
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001670 if (productList) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001671 for (StringPiece product : util::tokenize(productList.value(), ',')) {
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001672 if (product != "" && product != "default") {
1673 options.products.insert(product.toString());
1674 }
1675 }
1676 }
1677
Adam Lesinski6a008172016-02-02 17:02:58 -08001678 AxisConfigFilter filter;
1679 if (configs) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001680 for (const StringPiece& configStr : util::tokenize(configs.value(), ',')) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001681 ConfigDescription config;
1682 LocaleValue lv;
1683 if (lv.initFromFilterString(configStr)) {
1684 lv.writeTo(&config);
1685 } else if (!ConfigDescription::parse(configStr, &config)) {
1686 context.getDiagnostics()->error(
1687 DiagMessage() << "invalid config '" << configStr << "' for -c option");
1688 return 1;
1689 }
1690
1691 if (config.density != 0) {
1692 context.getDiagnostics()->warn(
1693 DiagMessage() << "ignoring density '" << config << "' for -c option");
1694 } else {
1695 filter.addConfig(config);
1696 }
1697 }
1698
Adam Lesinski355f2852016-02-13 20:26:45 -08001699 options.tableSplitterOptions.configFilter = &filter;
1700 }
1701
1702 if (preferredDensity) {
1703 ConfigDescription preferredDensityConfig;
1704 if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) {
1705 context.getDiagnostics()->error(DiagMessage() << "invalid density '"
1706 << preferredDensity.value()
1707 << "' for --preferred-density option");
1708 return 1;
1709 }
1710
1711 // Clear the version that can be automatically added.
1712 preferredDensityConfig.sdkVersion = 0;
1713
1714 if (preferredDensityConfig.diff(ConfigDescription::defaultConfig())
1715 != ConfigDescription::CONFIG_DENSITY) {
1716 context.getDiagnostics()->error(DiagMessage() << "invalid preferred density '"
1717 << preferredDensity.value() << "'. "
1718 << "Preferred density must only be a density value");
1719 return 1;
1720 }
1721 options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density;
Adam Lesinski6a008172016-02-02 17:02:58 -08001722 }
1723
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001724 if (!options.staticLib && stableIdFilePath) {
1725 if (!loadStableIdMap(context.getDiagnostics(), stableIdFilePath.value(),
1726 &options.stableIdMap)) {
1727 return 1;
1728 }
1729 }
1730
Adam Lesinski626a69f2016-03-03 10:09:26 -08001731 // Turn off auto versioning for static-libs.
1732 if (options.staticLib) {
1733 options.noAutoVersion = true;
1734 options.noVersionVectors = true;
1735 }
1736
Adam Lesinski6a008172016-02-02 17:02:58 -08001737 LinkCommand cmd(&context, options);
Adam Lesinskic51562c2016-04-28 11:12:38 -07001738 return cmd.run(argList);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001739}
1740
1741} // namespace aapt