blob: ea95dd1e6c2f3ed88962e0f84df8f709ff22b53c [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 Lesinski36c73a52016-08-11 13:39:24 -070062
63 // Java/Proguard options.
Adam Lesinski1ab598f2015-08-14 14:26:04 -070064 Maybe<std::string> generateJavaClassPath;
Adam Lesinskid0f116b2016-07-08 15:00:32 -070065 Maybe<std::string> customJavaPackage;
66 std::set<std::string> extraJavaPackages;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070067 Maybe<std::string> generateProguardRulesPath;
Rohit Agrawale49bb302016-04-22 12:27:55 -070068 Maybe<std::string> generateMainDexProguardRulesPath;
Adam Lesinski36c73a52016-08-11 13:39:24 -070069
Adam Lesinski1ab598f2015-08-14 14:26:04 -070070 bool noAutoVersion = false;
Adam Lesinski64587af2016-02-18 18:33:06 -080071 bool noVersionVectors = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070072 bool staticLib = false;
Adam Lesinski64587af2016-02-18 18:33:06 -080073 bool noStaticLibPackages = false;
Adam Lesinskief9c5012016-01-22 14:09:53 -080074 bool generateNonFinalIds = false;
Adam Lesinski3524a232016-04-01 19:19:24 -070075 std::vector<std::string> javadocAnnotations;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070076 bool outputToDirectory = false;
Alexandria Cornwalla7cc3f12016-08-16 13:33:32 -070077 bool noXmlNamespaces = false;
Adam Lesinskia6fe3452015-12-09 15:20:52 -080078 bool autoAddOverlay = false;
Adam Lesinski52364f72016-01-11 13:10:24 -080079 bool doNotCompressAnything = false;
Adam Lesinski9756dec2016-08-08 12:35:04 -070080 std::unordered_set<std::string> extensionsToNotCompress;
Adam Lesinskid0f116b2016-07-08 15:00:32 -070081 Maybe<std::string> privateSymbols;
Adam Lesinski52364f72016-01-11 13:10:24 -080082 ManifestFixerOptions manifestFixerOptions;
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -080083 std::unordered_set<std::string> products;
Adam Lesinski36c73a52016-08-11 13:39:24 -070084
85 // Split APK options.
Adam Lesinski355f2852016-02-13 20:26:45 -080086 TableSplitterOptions tableSplitterOptions;
Adam Lesinski36c73a52016-08-11 13:39:24 -070087 std::vector<SplitConstraints> splitConstraints;
88 std::vector<std::string> splitPaths;
89
90 // Stable ID options.
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -070091 std::unordered_map<ResourceName, ResourceId> stableIdMap;
92 Maybe<std::string> resourceIdMapPath;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070093};
94
Adam Lesinski64587af2016-02-18 18:33:06 -080095class LinkContext : public IAaptContext {
96public:
97 LinkContext() : mNameMangler({}) {
98 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -070099
100 IDiagnostics* getDiagnostics() override {
101 return &mDiagnostics;
102 }
103
104 NameMangler* getNameMangler() override {
Adam Lesinski64587af2016-02-18 18:33:06 -0800105 return &mNameMangler;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700106 }
107
Adam Lesinski64587af2016-02-18 18:33:06 -0800108 void setNameManglerPolicy(const NameManglerPolicy& policy) {
109 mNameMangler = NameMangler(policy);
110 }
111
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700112 const std::string& getCompilationPackage() override {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700113 return mCompilationPackage;
114 }
115
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700116 void setCompilationPackage(const StringPiece& packageName) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800117 mCompilationPackage = packageName.toString();
118 }
119
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700120 uint8_t getPackageId() override {
121 return mPackageId;
122 }
123
Adam Lesinski64587af2016-02-18 18:33:06 -0800124 void setPackageId(uint8_t id) {
125 mPackageId = id;
126 }
127
128 SymbolTable* getExternalSymbols() override {
129 return &mSymbols;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700130 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800131
132 bool verbose() override {
133 return mVerbose;
134 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800135
136 void setVerbose(bool val) {
137 mVerbose = val;
138 }
139
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700140 int getMinSdkVersion() override {
141 return mMinSdkVersion;
142 }
143
144 void setMinSdkVersion(int minSdk) {
145 mMinSdkVersion = minSdk;
146 }
147
Adam Lesinski64587af2016-02-18 18:33:06 -0800148private:
149 StdErrDiagnostics mDiagnostics;
150 NameMangler mNameMangler;
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700151 std::string mCompilationPackage;
Adam Lesinski64587af2016-02-18 18:33:06 -0800152 uint8_t mPackageId = 0x0;
153 SymbolTable mSymbols;
154 bool mVerbose = false;
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700155 int mMinSdkVersion = 0;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700156};
157
Adam Lesinski355f2852016-02-13 20:26:45 -0800158static bool copyFileToArchive(io::IFile* file, const std::string& outPath,
159 uint32_t compressionFlags,
160 IArchiveWriter* writer, IAaptContext* context) {
161 std::unique_ptr<io::IData> data = file->openAsData();
162 if (!data) {
163 context->getDiagnostics()->error(DiagMessage(file->getSource())
164 << "failed to open file");
165 return false;
166 }
167
Adam Lesinski64587af2016-02-18 18:33:06 -0800168 const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data());
169 size_t bufferSize = data->size();
170
171 // If the file ends with .flat, we must strip off the CompiledFileHeader from it.
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700172 if (util::stringEndsWith(file->getSource().path, ".flat")) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800173 CompiledFileInputStream inputStream(data->data(), data->size());
174 if (!inputStream.CompiledFile()) {
175 context->getDiagnostics()->error(DiagMessage(file->getSource())
176 << "invalid compiled file header");
177 return false;
178 }
179 buffer = reinterpret_cast<const uint8_t*>(inputStream.data());
180 bufferSize = inputStream.size();
Adam Lesinski355f2852016-02-13 20:26:45 -0800181 }
182
183 if (context->verbose()) {
184 context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive");
185 }
186
187 if (writer->startEntry(outPath, compressionFlags)) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800188 if (writer->writeEntry(buffer, bufferSize)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800189 if (writer->finishEntry()) {
190 return true;
191 }
192 }
193 }
194
195 context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath);
196 return false;
197}
198
199static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
200 bool keepRawValues, IArchiveWriter* writer, IAaptContext* context) {
201 BigBuffer buffer(1024);
202 XmlFlattenerOptions options = {};
203 options.keepRawValues = keepRawValues;
204 options.maxSdkLevel = maxSdkLevel;
205 XmlFlattener flattener(&buffer, options);
206 if (!flattener.consume(context, xmlRes)) {
207 return false;
208 }
209
210 if (context->verbose()) {
211 DiagMessage msg;
212 msg << "writing " << path << " to archive";
213 if (maxSdkLevel) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800214 msg << " maxSdkLevel=" << maxSdkLevel.value() << " keepRawValues=" << keepRawValues;
Adam Lesinski355f2852016-02-13 20:26:45 -0800215 }
216 context->getDiagnostics()->note(msg);
217 }
218
219 if (writer->startEntry(path, ArchiveEntry::kCompress)) {
220 if (writer->writeEntry(buffer)) {
221 if (writer->finishEntry()) {
222 return true;
223 }
224 }
225 }
226 context->getDiagnostics()->error(DiagMessage() << "failed to write " << path << " to archive");
227 return false;
228}
229
Adam Lesinski355f2852016-02-13 20:26:45 -0800230static std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
231 const void* data, size_t len,
232 IDiagnostics* diag) {
233 pb::ResourceTable pbTable;
234 if (!pbTable.ParseFromArray(data, len)) {
235 diag->error(DiagMessage(source) << "invalid compiled table");
236 return {};
237 }
238
239 std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source, diag);
240 if (!table) {
241 return {};
242 }
243 return table;
244}
245
246/**
247 * Inflates an XML file from the source path.
248 */
249static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
250 std::ifstream fin(path, std::ifstream::binary);
251 if (!fin) {
252 diag->error(DiagMessage(path) << strerror(errno));
253 return {};
254 }
255 return xml::inflate(&fin, diag, Source(path));
256}
257
258static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const Source& source,
259 const void* data, size_t len,
260 IDiagnostics* diag) {
261 CompiledFileInputStream inputStream(data, len);
262 if (!inputStream.CompiledFile()) {
263 diag->error(DiagMessage(source) << "invalid compiled file header");
264 return {};
265 }
266
267 const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data());
268 const size_t xmlDataLen = inputStream.size();
269
270 std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source);
271 if (!xmlRes) {
272 return {};
273 }
274 return xmlRes;
275}
276
277static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
278 const void* data, size_t len,
279 IDiagnostics* diag) {
280 CompiledFileInputStream inputStream(data, len);
281 const pb::CompiledFile* pbFile = inputStream.CompiledFile();
282 if (!pbFile) {
283 diag->error(DiagMessage(source) << "invalid compiled file header");
284 return {};
285 }
286
287 std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source, diag);
288 if (!resFile) {
289 return {};
290 }
291 return resFile;
292}
293
294struct ResourceFileFlattenerOptions {
295 bool noAutoVersion = false;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800296 bool noVersionVectors = false;
Alexandria Cornwalla7cc3f12016-08-16 13:33:32 -0700297 bool noXmlNamespaces = false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800298 bool keepRawValues = false;
299 bool doNotCompressAnything = false;
Rohit Agrawale49bb302016-04-22 12:27:55 -0700300 bool updateProguardSpec = false;
Adam Lesinski9756dec2016-08-08 12:35:04 -0700301 std::unordered_set<std::string> extensionsToNotCompress;
Adam Lesinski355f2852016-02-13 20:26:45 -0800302};
303
304class ResourceFileFlattener {
305public:
306 ResourceFileFlattener(const ResourceFileFlattenerOptions& options,
307 IAaptContext* context, proguard::KeepSet* keepSet) :
308 mOptions(options), mContext(context), mKeepSet(keepSet) {
309 }
310
311 bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter);
312
313private:
314 struct FileOperation {
315 io::IFile* fileToCopy;
316 std::unique_ptr<xml::XmlResource> xmlToFlatten;
317 std::string dstPath;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800318 bool skipVersion = false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800319 };
320
321 uint32_t getCompressionFlags(const StringPiece& str);
322
Adam Lesinski626a69f2016-03-03 10:09:26 -0800323 bool linkAndVersionXmlFile(const ResourceEntry* entry, const ResourceFile& fileDesc,
324 io::IFile* file, ResourceTable* table, FileOperation* outFileOp);
Adam Lesinski355f2852016-02-13 20:26:45 -0800325
326 ResourceFileFlattenerOptions mOptions;
327 IAaptContext* mContext;
328 proguard::KeepSet* mKeepSet;
329};
330
331uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) {
332 if (mOptions.doNotCompressAnything) {
333 return 0;
334 }
335
336 for (const std::string& extension : mOptions.extensionsToNotCompress) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700337 if (util::stringEndsWith(str, extension)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800338 return 0;
339 }
340 }
341 return ArchiveEntry::kCompress;
342}
343
Adam Lesinski626a69f2016-03-03 10:09:26 -0800344bool ResourceFileFlattener::linkAndVersionXmlFile(const ResourceEntry* entry,
345 const ResourceFile& fileDesc,
346 io::IFile* file,
347 ResourceTable* table,
348 FileOperation* outFileOp) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800349 const StringPiece srcPath = file->getSource().path;
350 if (mContext->verbose()) {
351 mContext->getDiagnostics()->note(DiagMessage() << "linking " << srcPath);
352 }
353
354 std::unique_ptr<io::IData> data = file->openAsData();
355 if (!data) {
356 mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file");
Adam Lesinski626a69f2016-03-03 10:09:26 -0800357 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800358 }
359
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700360 if (util::stringEndsWith(srcPath, ".flat")) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800361 outFileOp->xmlToFlatten = loadBinaryXmlSkipFileExport(file->getSource(),
362 data->data(), data->size(),
363 mContext->getDiagnostics());
Adam Lesinski355f2852016-02-13 20:26:45 -0800364 } else {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800365 outFileOp->xmlToFlatten = xml::inflate(data->data(), data->size(),
366 mContext->getDiagnostics(),
367 file->getSource());
Adam Lesinski355f2852016-02-13 20:26:45 -0800368 }
369
Adam Lesinski626a69f2016-03-03 10:09:26 -0800370 if (!outFileOp->xmlToFlatten) {
371 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800372 }
373
374 // Copy the the file description header.
Adam Lesinski626a69f2016-03-03 10:09:26 -0800375 outFileOp->xmlToFlatten->file = fileDesc;
Adam Lesinski355f2852016-02-13 20:26:45 -0800376
377 XmlReferenceLinker xmlLinker;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800378 if (!xmlLinker.consume(mContext, outFileOp->xmlToFlatten.get())) {
379 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800380 }
381
Rohit Agrawale49bb302016-04-22 12:27:55 -0700382 if (mOptions.updateProguardSpec && !proguard::collectProguardRules(
383 outFileOp->xmlToFlatten->file.source, outFileOp->xmlToFlatten.get(), mKeepSet)) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800384 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800385 }
386
Alexandria Cornwalla7cc3f12016-08-16 13:33:32 -0700387 if (mOptions.noXmlNamespaces) {
388 XmlNamespaceRemover namespaceRemover;
389 if (!namespaceRemover.consume(mContext, outFileOp->xmlToFlatten.get())) {
390 return false;
391 }
392 }
393
Adam Lesinski355f2852016-02-13 20:26:45 -0800394 if (!mOptions.noAutoVersion) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800395 if (mOptions.noVersionVectors) {
396 // Skip this if it is a vector or animated-vector.
397 xml::Element* el = xml::findRootElement(outFileOp->xmlToFlatten.get());
398 if (el && el->namespaceUri.empty()) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700399 if (el->name == "vector" || el->name == "animated-vector") {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800400 // We are NOT going to version this file.
401 outFileOp->skipVersion = true;
402 return true;
403 }
404 }
405 }
406
Adam Lesinski355f2852016-02-13 20:26:45 -0800407 // Find the first SDK level used that is higher than this defined config and
408 // not superseded by a lower or equal SDK level resource.
Alexandria Cornwallf6762fc2016-08-09 12:36:46 -0700409 const int minSdkVersion = mContext->getMinSdkVersion();
Adam Lesinski355f2852016-02-13 20:26:45 -0800410 for (int sdkLevel : xmlLinker.getSdkLevels()) {
Alexandria Cornwallf6762fc2016-08-09 12:36:46 -0700411 if (sdkLevel > minSdkVersion
412 && sdkLevel > outFileOp->xmlToFlatten->file.config.sdkVersion) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800413 if (!shouldGenerateVersionedResource(entry, outFileOp->xmlToFlatten->file.config,
414 sdkLevel)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800415 // If we shouldn't generate a versioned resource, stop checking.
416 break;
417 }
418
Adam Lesinski626a69f2016-03-03 10:09:26 -0800419 ResourceFile versionedFileDesc = outFileOp->xmlToFlatten->file;
Adam Lesinski64587af2016-02-18 18:33:06 -0800420 versionedFileDesc.config.sdkVersion = (uint16_t) sdkLevel;
Adam Lesinski355f2852016-02-13 20:26:45 -0800421
422 if (mContext->verbose()) {
423 mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source)
424 << "auto-versioning resource from config '"
Adam Lesinski626a69f2016-03-03 10:09:26 -0800425 << outFileOp->xmlToFlatten->file.config
426 << "' -> '"
Adam Lesinski355f2852016-02-13 20:26:45 -0800427 << versionedFileDesc.config << "'");
428 }
429
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700430 std::string genPath = ResourceUtils::buildResourceFileName(
431 versionedFileDesc, mContext->getNameMangler());
Adam Lesinski355f2852016-02-13 20:26:45 -0800432
433 bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name,
434 versionedFileDesc.config,
435 versionedFileDesc.source,
436 genPath,
437 file,
438 mContext->getDiagnostics());
439 if (!added) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800440 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800441 }
442 break;
443 }
444 }
445 }
Adam Lesinski626a69f2016-03-03 10:09:26 -0800446 return true;
Adam Lesinski355f2852016-02-13 20:26:45 -0800447}
448
449/**
450 * Do not insert or remove any resources while executing in this function. It will
451 * corrupt the iteration order.
452 */
453bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiveWriter) {
454 bool error = false;
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700455 std::map<std::pair<ConfigDescription, StringPiece>, FileOperation> configSortedFiles;
Adam Lesinski355f2852016-02-13 20:26:45 -0800456
457 for (auto& pkg : table->packages) {
458 for (auto& type : pkg->types) {
459 // Sort by config and name, so that we get better locality in the zip file.
460 configSortedFiles.clear();
461 for (auto& entry : type->entries) {
462 // Iterate via indices because auto generated values can be inserted ahead of
463 // the value being processed.
464 for (size_t i = 0; i < entry->values.size(); i++) {
465 ResourceConfigValue* configValue = entry->values[i].get();
466
467 FileReference* fileRef = valueCast<FileReference>(configValue->value.get());
468 if (!fileRef) {
469 continue;
470 }
471
472 io::IFile* file = fileRef->file;
473 if (!file) {
474 mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource())
475 << "file not found");
476 return false;
477 }
478
479 FileOperation fileOp;
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700480 fileOp.dstPath = *fileRef->path;
Adam Lesinski355f2852016-02-13 20:26:45 -0800481
482 const StringPiece srcPath = file->getSource().path;
483 if (type->type != ResourceType::kRaw &&
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700484 (util::stringEndsWith(srcPath, ".xml.flat") ||
485 util::stringEndsWith(srcPath, ".xml"))) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800486 ResourceFile fileDesc;
487 fileDesc.config = configValue->config;
488 fileDesc.name = ResourceName(pkg->name, type->type, entry->name);
489 fileDesc.source = fileRef->getSource();
Adam Lesinski626a69f2016-03-03 10:09:26 -0800490 if (!linkAndVersionXmlFile(entry.get(), fileDesc, file, table, &fileOp)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800491 error = true;
492 continue;
493 }
494
495 } else {
496 fileOp.fileToCopy = file;
497 }
498
499 // NOTE(adamlesinski): Explicitly construct a StringPiece16 here, or else
500 // we end up copying the string in the std::make_pair() method, then creating
501 // a StringPiece16 from the copy, which would cause us to end up referencing
502 // garbage in the map.
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700503 const StringPiece entryName(entry->name);
Adam Lesinski355f2852016-02-13 20:26:45 -0800504 configSortedFiles[std::make_pair(configValue->config, entryName)] =
505 std::move(fileOp);
506 }
507 }
508
509 if (error) {
510 return false;
511 }
512
513 // Now flatten the sorted values.
514 for (auto& mapEntry : configSortedFiles) {
515 const ConfigDescription& config = mapEntry.first.first;
516 const FileOperation& fileOp = mapEntry.second;
517
518 if (fileOp.xmlToFlatten) {
519 Maybe<size_t> maxSdkLevel;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800520 if (!mOptions.noAutoVersion && !fileOp.skipVersion) {
Alexandria Cornwallf6762fc2016-08-09 12:36:46 -0700521 maxSdkLevel =
522 std::max<size_t>(
523 std::max<size_t>(config.sdkVersion, 1u),
524 mContext->getMinSdkVersion());
Adam Lesinski355f2852016-02-13 20:26:45 -0800525 }
526
527 bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel,
528 mOptions.keepRawValues,
529 archiveWriter, mContext);
530 if (!result) {
531 error = true;
532 }
533 } else {
534 bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath,
535 getCompressionFlags(fileOp.dstPath),
536 archiveWriter, mContext);
537 if (!result) {
538 error = true;
539 }
540 }
541 }
542 }
543 }
544 return !error;
545}
546
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700547static bool writeStableIdMapToPath(IDiagnostics* diag,
548 const std::unordered_map<ResourceName, ResourceId>& idMap,
Chih-Hung Hsieh9b8528f2016-08-10 14:15:30 -0700549 const std::string& idMapPath) {
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700550 std::ofstream fout(idMapPath, std::ofstream::binary);
551 if (!fout) {
552 diag->error(DiagMessage(idMapPath) << strerror(errno));
553 return false;
554 }
555
556 for (const auto& entry : idMap) {
557 const ResourceName& name = entry.first;
558 const ResourceId& id = entry.second;
559 fout << name << " = " << id << "\n";
560 }
561
562 if (!fout) {
563 diag->error(DiagMessage(idMapPath) << "failed writing to file: " << strerror(errno));
564 return false;
565 }
566
567 return true;
568}
569
570static bool loadStableIdMap(IDiagnostics* diag, const std::string& path,
571 std::unordered_map<ResourceName, ResourceId>* outIdMap) {
572 std::string content;
573 if (!android::base::ReadFileToString(path, &content)) {
574 diag->error(DiagMessage(path) << "failed reading stable ID file");
575 return false;
576 }
577
578 outIdMap->clear();
579 size_t lineNo = 0;
580 for (StringPiece line : util::tokenize(content, '\n')) {
581 lineNo++;
582 line = util::trimWhitespace(line);
583 if (line.empty()) {
584 continue;
585 }
586
587 auto iter = std::find(line.begin(), line.end(), '=');
588 if (iter == line.end()) {
589 diag->error(DiagMessage(Source(path, lineNo)) << "missing '='");
590 return false;
591 }
592
593 ResourceNameRef name;
594 StringPiece resNameStr = util::trimWhitespace(
595 line.substr(0, std::distance(line.begin(), iter)));
596 if (!ResourceUtils::parseResourceName(resNameStr, &name)) {
597 diag->error(DiagMessage(Source(path, lineNo))
598 << "invalid resource name '" << resNameStr << "'");
599 return false;
600 }
601
602 const size_t resIdStartIdx = std::distance(line.begin(), iter) + 1;
603 const size_t resIdStrLen = line.size() - resIdStartIdx;
604 StringPiece resIdStr = util::trimWhitespace(line.substr(resIdStartIdx, resIdStrLen));
605
Adam Lesinski36c73a52016-08-11 13:39:24 -0700606 Maybe<ResourceId> maybeId = ResourceUtils::parseResourceId(resIdStr);
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700607 if (!maybeId) {
608 diag->error(DiagMessage(Source(path, lineNo)) << "invalid resource ID '"
609 << resIdStr << "'");
610 return false;
611 }
612
613 (*outIdMap)[name.toResourceName()] = maybeId.value();
614 }
615 return true;
616}
617
Adam Lesinski36c73a52016-08-11 13:39:24 -0700618static bool parseSplitParameter(const StringPiece& arg, IDiagnostics* diag,
619 std::string* outPath, SplitConstraints* outSplit) {
620 std::vector<std::string> parts = util::split(arg, ':');
621 if (parts.size() != 2) {
622 diag->error(DiagMessage() << "invalid split parameter '" << arg << "'");
623 diag->note(DiagMessage() << "should be --split path/to/output.apk:<config>[,<config>...]");
624 return false;
625 }
626 *outPath = parts[0];
627 std::vector<ConfigDescription> configs;
628 for (const StringPiece& configStr : util::tokenize(parts[1], ',')) {
629 configs.push_back({});
630 if (!ConfigDescription::parse(configStr, &configs.back())) {
631 diag->error(DiagMessage() << "invalid config '" << configStr
632 << "' in split parameter '" << arg << "'");
633 return false;
634 }
635 }
636 outSplit->configs.insert(configs.begin(), configs.end());
637 return true;
638}
639
Adam Lesinskifb48d292015-11-07 15:52:13 -0800640class LinkCommand {
641public:
Adam Lesinski6a008172016-02-02 17:02:58 -0800642 LinkCommand(LinkContext* context, const LinkOptions& options) :
Adam Lesinski64587af2016-02-18 18:33:06 -0800643 mOptions(options), mContext(context), mFinalTable(),
644 mFileCollection(util::make_unique<io::FileCollection>()) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800645 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700646
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700647 /**
648 * Creates a SymbolTable that loads symbols from the various APKs and caches the
649 * results for faster lookup.
650 */
Adam Lesinski64587af2016-02-18 18:33:06 -0800651 bool loadSymbolsFromIncludePaths() {
652 std::unique_ptr<AssetManagerSymbolSource> assetSource =
653 util::make_unique<AssetManagerSymbolSource>();
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700654 for (const std::string& path : mOptions.includePaths) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800655 if (mContext->verbose()) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800656 mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700657 }
658
Adam Lesinski64587af2016-02-18 18:33:06 -0800659 // First try to load the file as a static lib.
660 std::string errorStr;
661 std::unique_ptr<ResourceTable> staticInclude = loadStaticLibrary(path, &errorStr);
662 if (staticInclude) {
663 if (!mOptions.staticLib) {
664 // Can't include static libraries when not building a static library.
665 mContext->getDiagnostics()->error(
666 DiagMessage(path) << "can't include static library when building app");
667 return false;
668 }
669
670 // If we are using --no-static-lib-packages, we need to rename the package of this
671 // table to our compilation package.
672 if (mOptions.noStaticLibPackages) {
673 if (ResourceTablePackage* pkg = staticInclude->findPackageById(0x7f)) {
674 pkg->name = mContext->getCompilationPackage();
675 }
676 }
677
678 mContext->getExternalSymbols()->appendSource(
679 util::make_unique<ResourceTableSymbolSource>(staticInclude.get()));
680
681 mStaticTableIncludes.push_back(std::move(staticInclude));
682
683 } else if (!errorStr.empty()) {
684 // We had an error with reading, so fail.
685 mContext->getDiagnostics()->error(DiagMessage(path) << errorStr);
686 return false;
687 }
688
689 if (!assetSource->addAssetPath(path)) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800690 mContext->getDiagnostics()->error(
Adam Lesinskifb48d292015-11-07 15:52:13 -0800691 DiagMessage(path) << "failed to load include path");
Adam Lesinski64587af2016-02-18 18:33:06 -0800692 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700693 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700694 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800695
696 mContext->getExternalSymbols()->appendSource(std::move(assetSource));
697 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700698 }
699
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700700 Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes, IDiagnostics* diag) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700701 // Make sure the first element is <manifest> with package attribute.
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800702 if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700703 AppInfo appInfo;
704
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700705 if (!manifestEl->namespaceUri.empty() || manifestEl->name != "manifest") {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700706 diag->error(DiagMessage(xmlRes->file.source) << "root tag must be <manifest>");
707 return {};
708 }
709
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700710 xml::Attribute* packageAttr = manifestEl->findAttribute({}, "package");
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700711 if (!packageAttr) {
712 diag->error(DiagMessage(xmlRes->file.source)
713 << "<manifest> must have a 'package' attribute");
714 return {};
715 }
716
717 appInfo.package = packageAttr->value;
718
Adam Lesinski36c73a52016-08-11 13:39:24 -0700719 if (xml::Attribute* versionCodeAttr =
720 manifestEl->findAttribute(xml::kSchemaAndroid, "versionCode")) {
721 Maybe<uint32_t> maybeCode = ResourceUtils::parseInt(versionCodeAttr->value);
722 if (!maybeCode) {
723 diag->error(DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber))
724 << "invalid android:versionCode '"
725 << versionCodeAttr->value << "'");
726 return {};
727 }
728 appInfo.versionCode = maybeCode.value();
729 }
730
731 if (xml::Attribute* revisionCodeAttr =
732 manifestEl->findAttribute(xml::kSchemaAndroid, "revisionCode")) {
733 Maybe<uint32_t> maybeCode = ResourceUtils::parseInt(revisionCodeAttr->value);
734 if (!maybeCode) {
735 diag->error(DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber))
736 << "invalid android:revisionCode '"
737 << revisionCodeAttr->value << "'");
738 return {};
739 }
740 appInfo.revisionCode = maybeCode.value();
741 }
742
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700743 if (xml::Element* usesSdkEl = manifestEl->findChild({}, "uses-sdk")) {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700744 if (xml::Attribute* minSdk =
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700745 usesSdkEl->findAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700746 appInfo.minSdkVersion = minSdk->value;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700747 }
748 }
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700749
750 return appInfo;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700751 }
752 return {};
753 }
754
Adam Lesinski979ccb22016-01-11 10:42:19 -0800755 /**
756 * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked.
757 * Postcondition: ResourceTable has only one package left. All others are stripped, or there
758 * is an error and false is returned.
759 */
Adam Lesinskifb48d292015-11-07 15:52:13 -0800760 bool verifyNoExternalPackages() {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800761 auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool {
Adam Lesinski6a008172016-02-02 17:02:58 -0800762 return mContext->getCompilationPackage() != pkg->name ||
Adam Lesinski979ccb22016-01-11 10:42:19 -0800763 !pkg->id ||
Adam Lesinski6a008172016-02-02 17:02:58 -0800764 pkg->id.value() != mContext->getPackageId();
Adam Lesinski979ccb22016-01-11 10:42:19 -0800765 };
766
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700767 bool error = false;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800768 for (const auto& package : mFinalTable.packages) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800769 if (isExtPackageFunc(package)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700770 // We have a package that is not related to the one we're building!
771 for (const auto& type : package->types) {
772 for (const auto& entry : type->entries) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800773 ResourceNameRef resName(package->name, type->type, entry->name);
774
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700775 for (const auto& configValue : entry->values) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800776 // Special case the occurrence of an ID that is being generated for the
777 // 'android' package. This is due to legacy reasons.
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800778 if (valueCast<Id>(configValue->value.get()) &&
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700779 package->name == "android") {
Adam Lesinski6a008172016-02-02 17:02:58 -0800780 mContext->getDiagnostics()->warn(
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800781 DiagMessage(configValue->value->getSource())
Adam Lesinski979ccb22016-01-11 10:42:19 -0800782 << "generated id '" << resName
783 << "' for external package '" << package->name
784 << "'");
785 } else {
Adam Lesinski6a008172016-02-02 17:02:58 -0800786 mContext->getDiagnostics()->error(
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800787 DiagMessage(configValue->value->getSource())
Adam Lesinski979ccb22016-01-11 10:42:19 -0800788 << "defined resource '" << resName
789 << "' for external package '" << package->name
790 << "'");
791 error = true;
792 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700793 }
794 }
795 }
796 }
797 }
Adam Lesinski979ccb22016-01-11 10:42:19 -0800798
799 auto newEndIter = std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(),
800 isExtPackageFunc);
801 mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700802 return !error;
803 }
804
Adam Lesinski64587af2016-02-18 18:33:06 -0800805 /**
806 * Returns true if no IDs have been set, false otherwise.
807 */
808 bool verifyNoIdsSet() {
809 for (const auto& package : mFinalTable.packages) {
810 for (const auto& type : package->types) {
811 if (type->id) {
812 mContext->getDiagnostics()->error(DiagMessage() << "type " << type->type
813 << " has ID " << std::hex
814 << (int) type->id.value()
815 << std::dec << " assigned");
816 return false;
817 }
818
819 for (const auto& entry : type->entries) {
820 if (entry->id) {
821 ResourceNameRef resName(package->name, type->type, entry->name);
822 mContext->getDiagnostics()->error(DiagMessage() << "entry " << resName
823 << " has ID " << std::hex
824 << (int) entry->id.value()
825 << std::dec << " assigned");
826 return false;
827 }
828 }
829 }
830 }
831 return true;
832 }
833
Adam Lesinski36c73a52016-08-11 13:39:24 -0700834 std::unique_ptr<IArchiveWriter> makeArchiveWriter(const StringPiece& out) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700835 if (mOptions.outputToDirectory) {
Adam Lesinski36c73a52016-08-11 13:39:24 -0700836 return createDirectoryArchiveWriter(mContext->getDiagnostics(), out);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700837 } else {
Adam Lesinski36c73a52016-08-11 13:39:24 -0700838 return createZipFileArchiveWriter(mContext->getDiagnostics(), out);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700839 }
840 }
841
842 bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
843 BigBuffer buffer(1024);
Adam Lesinski59e04c62016-02-04 15:59:23 -0800844 TableFlattener flattener(&buffer);
Adam Lesinski6a008172016-02-02 17:02:58 -0800845 if (!flattener.consume(mContext, table)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700846 return false;
847 }
848
Adam Lesinskia40e9722015-11-24 19:11:46 -0800849 if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
850 if (writer->writeEntry(buffer)) {
851 if (writer->finishEntry()) {
852 return true;
853 }
854 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700855 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800856
Adam Lesinski6a008172016-02-02 17:02:58 -0800857 mContext->getDiagnostics()->error(
Adam Lesinskia40e9722015-11-24 19:11:46 -0800858 DiagMessage() << "failed to write resources.arsc to archive");
859 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700860 }
861
Adam Lesinski64587af2016-02-18 18:33:06 -0800862 bool flattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
863 // Create the file/zip entry.
864 if (!writer->startEntry("resources.arsc.flat", 0)) {
865 mContext->getDiagnostics()->error(DiagMessage() << "failed to open");
866 return false;
867 }
868
869 std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table);
870
871 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
872 // interface.
873 {
874 google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
875
876 if (!pbTable->SerializeToZeroCopyStream(&adaptor)) {
877 mContext->getDiagnostics()->error(DiagMessage() << "failed to write");
878 return false;
879 }
880 }
881
882 if (!writer->finishEntry()) {
883 mContext->getDiagnostics()->error(DiagMessage() << "failed to finish entry");
884 return false;
885 }
886 return true;
887 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700888
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700889 bool writeJavaFile(ResourceTable* table, const StringPiece& packageNameToGenerate,
Chih-Hung Hsieh9b8528f2016-08-10 14:15:30 -0700890 const StringPiece& outPackage, const JavaClassGeneratorOptions& javaOptions) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700891 if (!mOptions.generateJavaClassPath) {
892 return true;
893 }
894
895 std::string outPath = mOptions.generateJavaClassPath.value();
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700896 file::appendPath(&outPath, file::packageToPath(outPackage));
Adam Lesinski96917c22016-03-09 13:11:25 -0800897 if (!file::mkdirs(outPath)) {
898 mContext->getDiagnostics()->error(
899 DiagMessage() << "failed to create directory '" << outPath << "'");
900 return false;
901 }
902
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700903 file::appendPath(&outPath, "R.java");
904
905 std::ofstream fout(outPath, std::ofstream::binary);
906 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800907 mContext->getDiagnostics()->error(
908 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700909 return false;
910 }
911
Adam Lesinski76565542016-03-10 21:55:04 -0800912 JavaClassGenerator generator(mContext, table, javaOptions);
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700913 if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800914 mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700915 return false;
916 }
Adam Lesinski96917c22016-03-09 13:11:25 -0800917
918 if (!fout) {
919 mContext->getDiagnostics()->error(
920 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
921 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700922 return true;
923 }
924
Adam Lesinski467f1712015-11-16 17:35:44 -0800925 bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
Adam Lesinskica5638f2015-10-21 14:42:43 -0700926 if (!mOptions.generateJavaClassPath) {
927 return true;
928 }
929
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700930 std::unique_ptr<ClassDefinition> manifestClass = generateManifestClass(
931 mContext->getDiagnostics(), manifestXml);
932
933 if (!manifestClass) {
934 // Something bad happened, but we already logged it, so exit.
935 return false;
936 }
937
938 if (manifestClass->empty()) {
939 // Empty Manifest class, no need to generate it.
940 return true;
941 }
942
Adam Lesinski3524a232016-04-01 19:19:24 -0700943 // Add any JavaDoc annotations to the generated class.
944 for (const std::string& annotation : mOptions.javadocAnnotations) {
945 std::string properAnnotation = "@";
946 properAnnotation += annotation;
947 manifestClass->getCommentBuilder()->appendComment(properAnnotation);
948 }
949
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700950 const std::string& packageUtf8 = mContext->getCompilationPackage();
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700951
Adam Lesinskica5638f2015-10-21 14:42:43 -0700952 std::string outPath = mOptions.generateJavaClassPath.value();
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700953 file::appendPath(&outPath, file::packageToPath(packageUtf8));
954
Adam Lesinski96917c22016-03-09 13:11:25 -0800955 if (!file::mkdirs(outPath)) {
956 mContext->getDiagnostics()->error(
957 DiagMessage() << "failed to create directory '" << outPath << "'");
958 return false;
959 }
960
Adam Lesinskica5638f2015-10-21 14:42:43 -0700961 file::appendPath(&outPath, "Manifest.java");
962
963 std::ofstream fout(outPath, std::ofstream::binary);
964 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800965 mContext->getDiagnostics()->error(
966 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinskica5638f2015-10-21 14:42:43 -0700967 return false;
968 }
969
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700970 if (!ClassDefinition::writeJavaFile(manifestClass.get(), packageUtf8, true, &fout)) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800971 mContext->getDiagnostics()->error(
972 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinskica5638f2015-10-21 14:42:43 -0700973 return false;
974 }
975 return true;
976 }
977
Rohit Agrawale49bb302016-04-22 12:27:55 -0700978 bool writeProguardFile(const Maybe<std::string>& out, const proguard::KeepSet& keepSet) {
979 if (!out) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700980 return true;
981 }
982
Rohit Agrawale49bb302016-04-22 12:27:55 -0700983 const std::string& outPath = out.value();
Adam Lesinski96917c22016-03-09 13:11:25 -0800984 std::ofstream fout(outPath, std::ofstream::binary);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700985 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800986 mContext->getDiagnostics()->error(
987 DiagMessage() << "failed to open '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700988 return false;
989 }
990
991 proguard::writeKeepSet(&fout, keepSet);
992 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800993 mContext->getDiagnostics()->error(
994 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700995 return false;
996 }
997 return true;
998 }
999
Adam Lesinski64587af2016-02-18 18:33:06 -08001000 std::unique_ptr<ResourceTable> loadStaticLibrary(const std::string& input,
1001 std::string* outError) {
1002 std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
1003 input, outError);
1004 if (!collection) {
1005 return {};
1006 }
1007 return loadTablePbFromCollection(collection.get());
1008 }
1009
1010 std::unique_ptr<ResourceTable> loadTablePbFromCollection(io::IFileCollection* collection) {
1011 io::IFile* file = collection->findFile("resources.arsc.flat");
1012 if (!file) {
1013 return {};
1014 }
1015
1016 std::unique_ptr<io::IData> data = file->openAsData();
1017 return loadTableFromPb(file->getSource(), data->data(), data->size(),
1018 mContext->getDiagnostics());
1019 }
1020
1021 bool mergeStaticLibrary(const std::string& input, bool override) {
1022 if (mContext->verbose()) {
1023 mContext->getDiagnostics()->note(DiagMessage() << "merging static library " << input);
1024 }
1025
1026 std::string errorStr;
1027 std::unique_ptr<io::ZipFileCollection> collection =
1028 io::ZipFileCollection::create(input, &errorStr);
1029 if (!collection) {
1030 mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
1031 return false;
1032 }
1033
1034 std::unique_ptr<ResourceTable> table = loadTablePbFromCollection(collection.get());
1035 if (!table) {
1036 mContext->getDiagnostics()->error(DiagMessage(input) << "invalid static library");
1037 return false;
1038 }
1039
1040 ResourceTablePackage* pkg = table->findPackageById(0x7f);
1041 if (!pkg) {
1042 mContext->getDiagnostics()->error(DiagMessage(input)
1043 << "static library has no package");
1044 return false;
1045 }
1046
1047 bool result;
1048 if (mOptions.noStaticLibPackages) {
1049 // Merge all resources as if they were in the compilation package. This is the old
1050 // behaviour of aapt.
1051
1052 // Add the package to the set of --extra-packages so we emit an R.java for each
1053 // library package.
1054 if (!pkg->name.empty()) {
1055 mOptions.extraJavaPackages.insert(pkg->name);
1056 }
1057
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001058 pkg->name = "";
Adam Lesinski64587af2016-02-18 18:33:06 -08001059 if (override) {
1060 result = mTableMerger->mergeOverlay(Source(input), table.get(), collection.get());
1061 } else {
1062 result = mTableMerger->merge(Source(input), table.get(), collection.get());
1063 }
1064
1065 } else {
1066 // This is the proper way to merge libraries, where the package name is preserved
1067 // and resource names are mangled.
1068 result = mTableMerger->mergeAndMangle(Source(input), pkg->name, table.get(),
1069 collection.get());
1070 }
1071
1072 if (!result) {
1073 return false;
1074 }
1075
1076 // Make sure to move the collection into the set of IFileCollections.
1077 mCollections.push_back(std::move(collection));
Adam Lesinskifb48d292015-11-07 15:52:13 -08001078 return true;
1079 }
1080
Adam Lesinskia40e9722015-11-24 19:11:46 -08001081 bool mergeResourceTable(io::IFile* file, bool override) {
Adam Lesinski355f2852016-02-13 20:26:45 -08001082 if (mContext->verbose()) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001083 mContext->getDiagnostics()->note(DiagMessage() << "merging resource table "
1084 << file->getSource());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001085 }
1086
Adam Lesinskia40e9722015-11-24 19:11:46 -08001087 std::unique_ptr<io::IData> data = file->openAsData();
1088 if (!data) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001089 mContext->getDiagnostics()->error(DiagMessage(file->getSource())
Adam Lesinskia40e9722015-11-24 19:11:46 -08001090 << "failed to open file");
1091 return false;
1092 }
1093
Adam Lesinski355f2852016-02-13 20:26:45 -08001094 std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(),
1095 data->data(), data->size(),
1096 mContext->getDiagnostics());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001097 if (!table) {
1098 return false;
1099 }
1100
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001101 bool result = false;
1102 if (override) {
1103 result = mTableMerger->mergeOverlay(file->getSource(), table.get());
1104 } else {
1105 result = mTableMerger->merge(file->getSource(), table.get());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001106 }
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001107 return result;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001108 }
1109
Adam Lesinski64587af2016-02-18 18:33:06 -08001110 bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc, bool override) {
Adam Lesinski355f2852016-02-13 20:26:45 -08001111 if (mContext->verbose()) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001112 mContext->getDiagnostics()->note(DiagMessage() << "merging compiled file "
1113 << file->getSource());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001114 }
1115
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001116 bool result = false;
Adam Lesinski64587af2016-02-18 18:33:06 -08001117 if (override) {
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001118 result = mTableMerger->mergeFileOverlay(*fileDesc, file);
Adam Lesinskifb48d292015-11-07 15:52:13 -08001119 } else {
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001120 result = mTableMerger->mergeFile(*fileDesc, file);
Adam Lesinskifb48d292015-11-07 15:52:13 -08001121 }
1122
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001123 if (!result) {
Adam Lesinskifb48d292015-11-07 15:52:13 -08001124 return false;
1125 }
1126
1127 // Add the exports of this file to the table.
Adam Lesinskia40e9722015-11-24 19:11:46 -08001128 for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
Adam Lesinskifb48d292015-11-07 15:52:13 -08001129 if (exportedSymbol.name.package.empty()) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001130 exportedSymbol.name.package = mContext->getCompilationPackage();
Adam Lesinskifb48d292015-11-07 15:52:13 -08001131 }
1132
1133 ResourceNameRef resName = exportedSymbol.name;
1134
Adam Lesinski6a008172016-02-02 17:02:58 -08001135 Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
Adam Lesinskifb48d292015-11-07 15:52:13 -08001136 exportedSymbol.name);
1137 if (mangledName) {
1138 resName = mangledName.value();
1139 }
1140
1141 std::unique_ptr<Id> id = util::make_unique<Id>();
Adam Lesinskia40e9722015-11-24 19:11:46 -08001142 id->setSource(fileDesc->source.withLine(exportedSymbol.line));
Adam Lesinski64587af2016-02-18 18:33:06 -08001143 bool result = mFinalTable.addResourceAllowMangled(
1144 resName, ConfigDescription::defaultConfig(), std::string(), std::move(id),
1145 mContext->getDiagnostics());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001146 if (!result) {
1147 return false;
1148 }
1149 }
Adam Lesinskifb48d292015-11-07 15:52:13 -08001150 return true;
1151 }
1152
Adam Lesinskia40e9722015-11-24 19:11:46 -08001153 /**
Adam Lesinski64587af2016-02-18 18:33:06 -08001154 * Takes a path to load as a ZIP file and merges the files within into the master ResourceTable.
1155 * If override is true, conflicting resources are allowed to override each other, in order of
1156 * last seen.
1157 *
1158 * An io::IFileCollection is created from the ZIP file and added to the set of
1159 * io::IFileCollections that are open.
Adam Lesinskia40e9722015-11-24 19:11:46 -08001160 */
1161 bool mergeArchive(const std::string& input, bool override) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001162 if (mContext->verbose()) {
1163 mContext->getDiagnostics()->note(DiagMessage() << "merging archive " << input);
1164 }
1165
Adam Lesinskia40e9722015-11-24 19:11:46 -08001166 std::string errorStr;
Adam Lesinski64587af2016-02-18 18:33:06 -08001167 std::unique_ptr<io::ZipFileCollection> collection =
1168 io::ZipFileCollection::create(input, &errorStr);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001169 if (!collection) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001170 mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001171 return false;
1172 }
1173
1174 bool error = false;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001175 for (auto iter = collection->iterator(); iter->hasNext(); ) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001176 if (!mergeFile(iter->next(), override)) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001177 error = true;
1178 }
1179 }
1180
1181 // Make sure to move the collection into the set of IFileCollections.
1182 mCollections.push_back(std::move(collection));
1183 return !error;
1184 }
1185
Adam Lesinski64587af2016-02-18 18:33:06 -08001186 /**
1187 * Takes a path to load and merge into the master ResourceTable. If override is true,
1188 * conflicting resources are allowed to override each other, in order of last seen.
1189 *
1190 * If the file path ends with .flata, .jar, .jack, or .zip the file is treated as ZIP archive
1191 * and the files within are merged individually.
1192 *
1193 * Otherwise the files is processed on its own.
1194 */
1195 bool mergePath(const std::string& path, bool override) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001196 if (util::stringEndsWith(path, ".flata") ||
1197 util::stringEndsWith(path, ".jar") ||
1198 util::stringEndsWith(path, ".jack") ||
1199 util::stringEndsWith(path, ".zip")) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001200 return mergeArchive(path, override);
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001201 } else if (util::stringEndsWith(path, ".apk")) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001202 return mergeStaticLibrary(path, override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001203 }
1204
1205 io::IFile* file = mFileCollection->insertFile(path);
Adam Lesinski64587af2016-02-18 18:33:06 -08001206 return mergeFile(file, override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001207 }
1208
Adam Lesinski64587af2016-02-18 18:33:06 -08001209 /**
1210 * Takes a file to load and merge into the master ResourceTable. If override is true,
1211 * conflicting resources are allowed to override each other, in order of last seen.
1212 *
1213 * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and merged into the
1214 * master ResourceTable. If the file ends with .flat, then it is treated like a compiled file
1215 * and the header data is read and merged into the final ResourceTable.
1216 *
1217 * All other file types are ignored. This is because these files could be coming from a zip,
1218 * where we could have other files like classes.dex.
1219 */
1220 bool mergeFile(io::IFile* file, bool override) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001221 const Source& src = file->getSource();
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001222 if (util::stringEndsWith(src.path, ".arsc.flat")) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001223 return mergeResourceTable(file, override);
Adam Lesinski64587af2016-02-18 18:33:06 -08001224
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001225 } else if (util::stringEndsWith(src.path, ".flat")){
Adam Lesinskia40e9722015-11-24 19:11:46 -08001226 // Try opening the file and looking for an Export header.
1227 std::unique_ptr<io::IData> data = file->openAsData();
1228 if (!data) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001229 mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open");
Adam Lesinskia40e9722015-11-24 19:11:46 -08001230 return false;
1231 }
1232
1233 std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader(
Adam Lesinski6a008172016-02-02 17:02:58 -08001234 src, data->data(), data->size(), mContext->getDiagnostics());
Adam Lesinskia40e9722015-11-24 19:11:46 -08001235 if (resourceFile) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001236 return mergeCompiledFile(file, resourceFile.get(), override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001237 }
Adam Lesinskic446a732016-01-21 11:04:46 -08001238 return false;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001239 }
Adam Lesinski52364f72016-01-11 13:10:24 -08001240
Adam Lesinskic446a732016-01-21 11:04:46 -08001241 // Ignore non .flat files. This could be classes.dex or something else that happens
1242 // to be in an archive.
1243 return true;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001244 }
1245
Adam Lesinski36c73a52016-08-11 13:39:24 -07001246 std::unique_ptr<xml::XmlResource> generateSplitManifest(const AppInfo& appInfo,
1247 const SplitConstraints& constraints) {
1248 std::unique_ptr<xml::XmlResource> doc = util::make_unique<xml::XmlResource>();
1249
1250 std::unique_ptr<xml::Namespace> namespaceAndroid = util::make_unique<xml::Namespace>();
1251 namespaceAndroid->namespaceUri = xml::kSchemaAndroid;
1252 namespaceAndroid->namespacePrefix = "android";
1253
1254 std::unique_ptr<xml::Element> manifestEl = util::make_unique<xml::Element>();
1255 manifestEl->name = "manifest";
1256 manifestEl->attributes.push_back(
1257 xml::Attribute{ "", "package", appInfo.package });
1258
1259 if (appInfo.versionCode) {
1260 manifestEl->attributes.push_back(xml::Attribute{
1261 xml::kSchemaAndroid,
1262 "versionCode",
1263 std::to_string(appInfo.versionCode.value()) });
1264 }
1265
1266 if (appInfo.revisionCode) {
1267 manifestEl->attributes.push_back(xml::Attribute{
1268 xml::kSchemaAndroid,
1269 "revisionCode", std::to_string(appInfo.revisionCode.value()) });
1270 }
1271
1272 std::stringstream splitName;
1273 splitName << "config." << util::joiner(constraints.configs, "_");
1274
1275 manifestEl->attributes.push_back(
1276 xml::Attribute{ "", "split", splitName.str() });
1277
1278 std::unique_ptr<xml::Element> applicationEl = util::make_unique<xml::Element>();
1279 applicationEl->name = "application";
1280 applicationEl->attributes.push_back(
1281 xml::Attribute{ xml::kSchemaAndroid, "hasCode", "false" });
1282
1283 manifestEl->addChild(std::move(applicationEl));
1284 namespaceAndroid->addChild(std::move(manifestEl));
1285 doc->root = std::move(namespaceAndroid);
1286 return doc;
1287 }
1288
1289 /**
1290 * Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable
1291 * to the IArchiveWriter.
1292 */
1293 bool writeApk(IArchiveWriter* writer, proguard::KeepSet* keepSet, xml::XmlResource* manifest,
1294 ResourceTable* table) {
1295 const bool keepRawValues = mOptions.staticLib;
1296 bool result = flattenXml(manifest, "AndroidManifest.xml", {}, keepRawValues, writer,
1297 mContext);
1298 if (!result) {
1299 return false;
1300 }
1301
1302 ResourceFileFlattenerOptions fileFlattenerOptions;
1303 fileFlattenerOptions.keepRawValues = keepRawValues;
1304 fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything;
1305 fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress;
1306 fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion;
1307 fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors;
Alexandria Cornwalla7cc3f12016-08-16 13:33:32 -07001308 fileFlattenerOptions.noXmlNamespaces = mOptions.noXmlNamespaces;
Adam Lesinski36c73a52016-08-11 13:39:24 -07001309 fileFlattenerOptions.updateProguardSpec =
1310 static_cast<bool>(mOptions.generateProguardRulesPath);
1311
1312 ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, keepSet);
1313
1314 if (!fileFlattener.flatten(table, writer)) {
1315 mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources");
1316 return false;
1317 }
1318
1319 if (mOptions.staticLib) {
1320 if (!flattenTableToPb(table, writer)) {
1321 mContext->getDiagnostics()->error(DiagMessage()
1322 << "failed to write resources.arsc.flat");
1323 return false;
1324 }
1325 } else {
1326 if (!flattenTable(table, writer)) {
1327 mContext->getDiagnostics()->error(DiagMessage()
1328 << "failed to write resources.arsc");
1329 return false;
1330 }
1331 }
1332 return true;
1333 }
1334
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001335 int run(const std::vector<std::string>& inputFiles) {
1336 // Load the AndroidManifest.xml
Adam Lesinskia40e9722015-11-24 19:11:46 -08001337 std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
Adam Lesinski6a008172016-02-02 17:02:58 -08001338 mContext->getDiagnostics());
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001339 if (!manifestXml) {
1340 return 1;
1341 }
1342
Adam Lesinski36c73a52016-08-11 13:39:24 -07001343 // First extract the Package name without modifying it (via --rename-manifest-package).
Adam Lesinskifb6312f2016-06-28 14:40:32 -07001344 if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(),
1345 mContext->getDiagnostics())) {
Adam Lesinski36c73a52016-08-11 13:39:24 -07001346 const AppInfo& appInfo = maybeAppInfo.value();
Adam Lesinskifb6312f2016-06-28 14:40:32 -07001347 mContext->setCompilationPackage(appInfo.package);
Adam Lesinski36c73a52016-08-11 13:39:24 -07001348 }
1349
1350 ManifestFixer manifestFixer(mOptions.manifestFixerOptions);
1351 if (!manifestFixer.consume(mContext, manifestXml.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001352 return 1;
1353 }
1354
Adam Lesinski36c73a52016-08-11 13:39:24 -07001355 Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(),
1356 mContext->getDiagnostics());
1357 if (!maybeAppInfo) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001358 return 1;
1359 }
1360
Adam Lesinski36c73a52016-08-11 13:39:24 -07001361 const AppInfo& appInfo = maybeAppInfo.value();
1362 if (appInfo.minSdkVersion) {
1363 if (Maybe<int> maybeMinSdkVersion =
1364 ResourceUtils::parseSdkVersion(appInfo.minSdkVersion.value())) {
1365 mContext->setMinSdkVersion(maybeMinSdkVersion.value());
1366 }
1367 }
1368
Adam Lesinski64587af2016-02-18 18:33:06 -08001369 mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() });
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001370 if (mContext->getCompilationPackage() == "android") {
Adam Lesinski64587af2016-02-18 18:33:06 -08001371 mContext->setPackageId(0x01);
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001372 } else {
Adam Lesinski64587af2016-02-18 18:33:06 -08001373 mContext->setPackageId(0x7f);
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001374 }
1375
Adam Lesinski64587af2016-02-18 18:33:06 -08001376 if (!loadSymbolsFromIncludePaths()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001377 return 1;
1378 }
1379
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001380 TableMergerOptions tableMergerOptions;
1381 tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
Adam Lesinski6a008172016-02-02 17:02:58 -08001382 mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions);
Adam Lesinskifb48d292015-11-07 15:52:13 -08001383
Adam Lesinski355f2852016-02-13 20:26:45 -08001384 if (mContext->verbose()) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001385 mContext->getDiagnostics()->note(
Adam Lesinski64587af2016-02-18 18:33:06 -08001386 DiagMessage() << "linking package '" << mContext->getCompilationPackage()
1387 << "' with package ID " << std::hex
1388 << (int) mContext->getPackageId());
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001389 }
1390
Adam Lesinskifb48d292015-11-07 15:52:13 -08001391
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001392 for (const std::string& input : inputFiles) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001393 if (!mergePath(input, false)) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001394 mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input");
Adam Lesinski467f1712015-11-16 17:35:44 -08001395 return 1;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001396 }
1397 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001398
Adam Lesinskifb48d292015-11-07 15:52:13 -08001399 for (const std::string& input : mOptions.overlayFiles) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001400 if (!mergePath(input, true)) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001401 mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
Adam Lesinski467f1712015-11-16 17:35:44 -08001402 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001403 }
1404 }
1405
Adam Lesinskifb48d292015-11-07 15:52:13 -08001406 if (!verifyNoExternalPackages()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001407 return 1;
1408 }
1409
1410 if (!mOptions.staticLib) {
1411 PrivateAttributeMover mover;
Adam Lesinski6a008172016-02-02 17:02:58 -08001412 if (!mover.consume(mContext, &mFinalTable)) {
1413 mContext->getDiagnostics()->error(
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001414 DiagMessage() << "failed moving private attributes");
1415 return 1;
1416 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001417
Adam Lesinski64587af2016-02-18 18:33:06 -08001418 // Assign IDs if we are building a regular app.
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001419 IdAssigner idAssigner(&mOptions.stableIdMap);
Adam Lesinski6a008172016-02-02 17:02:58 -08001420 if (!idAssigner.consume(mContext, &mFinalTable)) {
1421 mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001422 return 1;
1423 }
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001424
1425 // Now grab each ID and emit it as a file.
1426 if (mOptions.resourceIdMapPath) {
1427 for (auto& package : mFinalTable.packages) {
1428 for (auto& type : package->types) {
1429 for (auto& entry : type->entries) {
1430 ResourceName name(package->name, type->type, entry->name);
1431 // The IDs are guaranteed to exist.
1432 mOptions.stableIdMap[std::move(name)] = ResourceId(package->id.value(),
1433 type->id.value(),
1434 entry->id.value());
1435 }
1436 }
1437 }
1438
1439 if (!writeStableIdMapToPath(mContext->getDiagnostics(),
1440 mOptions.stableIdMap,
1441 mOptions.resourceIdMapPath.value())) {
1442 return 1;
1443 }
1444 }
Adam Lesinski64587af2016-02-18 18:33:06 -08001445 } else {
1446 // Static libs are merged with other apps, and ID collisions are bad, so verify that
1447 // no IDs have been set.
1448 if (!verifyNoIdsSet()) {
1449 return 1;
1450 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001451 }
1452
Adam Lesinski64587af2016-02-18 18:33:06 -08001453 // Add the names to mangle based on our source merge earlier.
1454 mContext->setNameManglerPolicy(NameManglerPolicy{
1455 mContext->getCompilationPackage(), mTableMerger->getMergedPackages() });
1456
1457 // Add our table to the symbol table.
1458 mContext->getExternalSymbols()->prependSource(
1459 util::make_unique<ResourceTableSymbolSource>(&mFinalTable));
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001460
Adam Lesinski36c73a52016-08-11 13:39:24 -07001461 ReferenceLinker linker;
1462 if (!linker.consume(mContext, &mFinalTable)) {
1463 mContext->getDiagnostics()->error(DiagMessage() << "failed linking references");
1464 return 1;
1465 }
1466
1467 if (mOptions.staticLib) {
1468 if (!mOptions.products.empty()) {
1469 mContext->getDiagnostics()->warn(
1470 DiagMessage() << "can't select products when building static library");
1471 }
1472 } else {
1473 ProductFilter productFilter(mOptions.products);
1474 if (!productFilter.consume(mContext, &mFinalTable)) {
1475 mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001476 return 1;
1477 }
Adam Lesinski36c73a52016-08-11 13:39:24 -07001478 }
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001479
Adam Lesinski36c73a52016-08-11 13:39:24 -07001480 if (!mOptions.noAutoVersion) {
1481 AutoVersioner versioner;
1482 if (!versioner.consume(mContext, &mFinalTable)) {
1483 mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles");
1484 return 1;
1485 }
1486 }
Adam Lesinski355f2852016-02-13 20:26:45 -08001487
Adam Lesinski36c73a52016-08-11 13:39:24 -07001488 if (!mOptions.staticLib && mContext->getMinSdkVersion() > 0) {
1489 if (mContext->verbose()) {
1490 mContext->getDiagnostics()->note(
1491 DiagMessage() << "collapsing resource versions for minimum SDK "
1492 << mContext->getMinSdkVersion());
1493 }
Adam Lesinski355f2852016-02-13 20:26:45 -08001494
Adam Lesinski36c73a52016-08-11 13:39:24 -07001495 VersionCollapser collapser;
1496 if (!collapser.consume(mContext, &mFinalTable)) {
1497 return 1;
Adam Lesinski64587af2016-02-18 18:33:06 -08001498 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001499 }
1500
1501 proguard::KeepSet proguardKeepSet;
Rohit Agrawale49bb302016-04-22 12:27:55 -07001502 proguard::KeepSet proguardMainDexKeepSet;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001503
Adam Lesinski36c73a52016-08-11 13:39:24 -07001504 if (mOptions.staticLib) {
1505 if (mOptions.tableSplitterOptions.configFilter != nullptr ||
1506 mOptions.tableSplitterOptions.preferredDensity) {
1507 mContext->getDiagnostics()->warn(
1508 DiagMessage() << "can't strip resources when building static library");
1509 }
1510 } else {
1511 // Adjust the SplitConstraints so that their SDK version is stripped if it is less
1512 // than or equal to the minSdk. Otherwise the resources that have had their SDK version
1513 // stripped due to minSdk won't ever match.
1514 std::vector<SplitConstraints> adjustedConstraintsList;
1515 adjustedConstraintsList.reserve(mOptions.splitConstraints.size());
1516 for (const SplitConstraints& constraints : mOptions.splitConstraints) {
1517 SplitConstraints adjustedConstraints;
1518 for (const ConfigDescription& config : constraints.configs) {
1519 if (config.sdkVersion <= mContext->getMinSdkVersion()) {
1520 adjustedConstraints.configs.insert(config.copyWithoutSdkVersion());
1521 } else {
1522 adjustedConstraints.configs.insert(config);
1523 }
1524 }
1525 adjustedConstraintsList.push_back(std::move(adjustedConstraints));
1526 }
1527
1528 TableSplitter tableSplitter(adjustedConstraintsList, mOptions.tableSplitterOptions);
1529 if (!tableSplitter.verifySplitConstraints(mContext)) {
1530 return 1;
1531 }
1532 tableSplitter.splitTable(&mFinalTable);
1533
1534 // Now we need to write out the Split APKs.
1535 auto pathIter = mOptions.splitPaths.begin();
1536 auto splitConstraintsIter = adjustedConstraintsList.begin();
1537 for (std::unique_ptr<ResourceTable>& splitTable : tableSplitter.getSplits()) {
1538 if (mContext->verbose()) {
1539 mContext->getDiagnostics()->note(
1540 DiagMessage(*pathIter) << "generating split with configurations '"
1541 << util::joiner(splitConstraintsIter->configs, ", ") << "'");
1542 }
1543
1544 std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(*pathIter);
1545 if (!archiveWriter) {
1546 mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive");
1547 return 1;
1548 }
1549
1550 // Generate an AndroidManifest.xml for each split.
1551 std::unique_ptr<xml::XmlResource> splitManifest =
1552 generateSplitManifest(appInfo, *splitConstraintsIter);
1553
1554 XmlReferenceLinker linker;
1555 if (!linker.consume(mContext, splitManifest.get())) {
1556 mContext->getDiagnostics()->error(
1557 DiagMessage() << "failed to create Split AndroidManifest.xml");
1558 return 1;
1559 }
1560
1561 if (!writeApk(archiveWriter.get(), &proguardKeepSet, splitManifest.get(),
1562 splitTable.get())) {
1563 return 1;
1564 }
1565
1566 ++pathIter;
1567 ++splitConstraintsIter;
1568 }
1569 }
1570
1571 // Start writing the base APK.
1572 std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(mOptions.outputPath);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001573 if (!archiveWriter) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001574 mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001575 return 1;
1576 }
1577
Adam Lesinski467f1712015-11-16 17:35:44 -08001578 bool error = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001579 {
Adam Lesinski467f1712015-11-16 17:35:44 -08001580 // AndroidManifest.xml has no resource name, but the CallSite is built from the name
1581 // (aka, which package the AndroidManifest.xml is coming from).
1582 // So we give it a package name so it can see local resources.
Adam Lesinski64587af2016-02-18 18:33:06 -08001583 manifestXml->file.name.package = mContext->getCompilationPackage();
Adam Lesinski467f1712015-11-16 17:35:44 -08001584
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001585 XmlReferenceLinker manifestLinker;
Adam Lesinski6a008172016-02-02 17:02:58 -08001586 if (manifestLinker.consume(mContext, manifestXml.get())) {
Rohit Agrawale49bb302016-04-22 12:27:55 -07001587 if (mOptions.generateProguardRulesPath &&
1588 !proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
1589 manifestXml.get(),
1590 &proguardKeepSet)) {
1591 error = true;
1592 }
1593
1594 if (mOptions.generateMainDexProguardRulesPath &&
1595 !proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
1596 manifestXml.get(),
1597 &proguardMainDexKeepSet,
1598 true)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001599 error = true;
1600 }
1601
Adam Lesinskica5638f2015-10-21 14:42:43 -07001602 if (mOptions.generateJavaClassPath) {
1603 if (!writeManifestJavaFile(manifestXml.get())) {
1604 error = true;
1605 }
1606 }
Alexandria Cornwalla7cc3f12016-08-16 13:33:32 -07001607
1608 if (mOptions.noXmlNamespaces) {
1609 // PackageParser will fail if URIs are removed from AndroidManifest.xml.
1610 XmlNamespaceRemover namespaceRemover(true /* keepUris */);
1611 if (!namespaceRemover.consume(mContext, manifestXml.get())) {
1612 error = true;
1613 }
1614 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001615 } else {
1616 error = true;
1617 }
1618 }
1619
Adam Lesinski467f1712015-11-16 17:35:44 -08001620 if (error) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001621 mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest");
Adam Lesinski467f1712015-11-16 17:35:44 -08001622 return 1;
1623 }
1624
Adam Lesinski36c73a52016-08-11 13:39:24 -07001625 if (!writeApk(archiveWriter.get(), &proguardKeepSet, manifestXml.get(), &mFinalTable)) {
Alexandria Cornwall637b4822016-08-11 09:53:16 -07001626 return 1;
1627 }
1628
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001629 if (mOptions.generateJavaClassPath) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001630 JavaClassGeneratorOptions options;
Adam Lesinski52364f72016-01-11 13:10:24 -08001631 options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
Adam Lesinski3524a232016-04-01 19:19:24 -07001632 options.javadocAnnotations = mOptions.javadocAnnotations;
Adam Lesinski52364f72016-01-11 13:10:24 -08001633
Adam Lesinskief9c5012016-01-22 14:09:53 -08001634 if (mOptions.staticLib || mOptions.generateNonFinalIds) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001635 options.useFinal = false;
1636 }
1637
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001638 const StringPiece actualPackage = mContext->getCompilationPackage();
1639 StringPiece outputPackage = mContext->getCompilationPackage();
Adam Lesinski52364f72016-01-11 13:10:24 -08001640 if (mOptions.customJavaPackage) {
1641 // Override the output java package to the custom one.
1642 outputPackage = mOptions.customJavaPackage.value();
1643 }
Adam Lesinski83f22552015-11-07 11:51:23 -08001644
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001645 if (mOptions.privateSymbols) {
1646 // If we defined a private symbols package, we only emit Public symbols
1647 // to the original package, and private and public symbols to the private package.
1648
1649 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
Adam Lesinski6a008172016-02-02 17:02:58 -08001650 if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(),
Adam Lesinski52364f72016-01-11 13:10:24 -08001651 outputPackage, options)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001652 return 1;
1653 }
1654
1655 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
Adam Lesinski83f22552015-11-07 11:51:23 -08001656 outputPackage = mOptions.privateSymbols.value();
1657 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001658
Adam Lesinskifb48d292015-11-07 15:52:13 -08001659 if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
Adam Lesinski83f22552015-11-07 11:51:23 -08001660 return 1;
1661 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001662
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001663 for (const std::string& extraPackage : mOptions.extraJavaPackages) {
Adam Lesinski52364f72016-01-11 13:10:24 -08001664 if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001665 return 1;
1666 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001667 }
1668 }
1669
Rohit Agrawale49bb302016-04-22 12:27:55 -07001670 if (!writeProguardFile(mOptions.generateProguardRulesPath, proguardKeepSet)) {
1671 return 1;
1672 }
1673
1674 if (!writeProguardFile(mOptions.generateMainDexProguardRulesPath, proguardMainDexKeepSet)) {
1675 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001676 }
1677
Adam Lesinski355f2852016-02-13 20:26:45 -08001678 if (mContext->verbose()) {
1679 DebugPrintTableOptions debugPrintTableOptions;
1680 debugPrintTableOptions.showSources = true;
1681 Debug::printTable(&mFinalTable, debugPrintTableOptions);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001682 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001683 return 0;
1684 }
Adam Lesinskifb48d292015-11-07 15:52:13 -08001685
1686private:
1687 LinkOptions mOptions;
Adam Lesinski6a008172016-02-02 17:02:58 -08001688 LinkContext* mContext;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001689 ResourceTable mFinalTable;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001690
Adam Lesinskifb48d292015-11-07 15:52:13 -08001691 std::unique_ptr<TableMerger> mTableMerger;
1692
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001693 // A pointer to the FileCollection representing the filesystem (not archives).
Adam Lesinski64587af2016-02-18 18:33:06 -08001694 std::unique_ptr<io::FileCollection> mFileCollection;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001695
1696 // A vector of IFileCollections. This is mainly here to keep ownership of the collections.
Adam Lesinskia40e9722015-11-24 19:11:46 -08001697 std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
Adam Lesinski64587af2016-02-18 18:33:06 -08001698
1699 // A vector of ResourceTables. This is here to retain ownership, so that the SymbolTable
1700 // can use these.
1701 std::vector<std::unique_ptr<ResourceTable>> mStaticTableIncludes;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001702};
1703
1704int link(const std::vector<StringPiece>& args) {
Adam Lesinski355f2852016-02-13 20:26:45 -08001705 LinkContext context;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001706 LinkOptions options;
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001707 std::vector<std::string> overlayArgList;
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001708 std::vector<std::string> extraJavaPackages;
Adam Lesinski6a008172016-02-02 17:02:58 -08001709 Maybe<std::string> configs;
Adam Lesinski355f2852016-02-13 20:26:45 -08001710 Maybe<std::string> preferredDensity;
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001711 Maybe<std::string> productList;
Adam Lesinski8900aa82016-01-25 22:48:15 -08001712 bool legacyXFlag = false;
1713 bool requireLocalization = false;
Adam Lesinski64587af2016-02-18 18:33:06 -08001714 bool verbose = false;
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001715 Maybe<std::string> stableIdFilePath;
Adam Lesinski36c73a52016-08-11 13:39:24 -07001716 std::vector<std::string> splitArgs;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001717 Flags flags = Flags()
1718 .requiredFlag("-o", "Output path", &options.outputPath)
1719 .requiredFlag("--manifest", "Path to the Android manifest to build",
1720 &options.manifestPath)
1721 .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
Adam Lesinski52364f72016-01-11 13:10:24 -08001722 .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n"
Adam Lesinskifb48d292015-11-07 15:52:13 -08001723 "The last conflicting resource given takes precedence.",
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001724 &overlayArgList)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001725 .optionalFlag("--java", "Directory in which to generate R.java",
1726 &options.generateJavaClassPath)
1727 .optionalFlag("--proguard", "Output file for generated Proguard rules",
1728 &options.generateProguardRulesPath)
Rohit Agrawale49bb302016-04-22 12:27:55 -07001729 .optionalFlag("--proguard-main-dex",
1730 "Output file for generated Proguard rules for the main dex",
1731 &options.generateMainDexProguardRulesPath)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001732 .optionalSwitch("--no-auto-version",
1733 "Disables automatic style and layout SDK versioning",
1734 &options.noAutoVersion)
Adam Lesinski64587af2016-02-18 18:33:06 -08001735 .optionalSwitch("--no-version-vectors",
1736 "Disables automatic versioning of vector drawables. Use this only\n"
1737 "when building with vector drawable support library",
1738 &options.noVersionVectors)
Adam Lesinski8900aa82016-01-25 22:48:15 -08001739 .optionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01",
1740 &legacyXFlag)
1741 .optionalSwitch("-z", "Require localization of strings marked 'suggested'",
1742 &requireLocalization)
Adam Lesinski6a008172016-02-02 17:02:58 -08001743 .optionalFlag("-c", "Comma separated list of configurations to include. The default\n"
1744 "is all configurations", &configs)
Adam Lesinski355f2852016-02-13 20:26:45 -08001745 .optionalFlag("--preferred-density",
1746 "Selects the closest matching density and strips out all others.",
1747 &preferredDensity)
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001748 .optionalFlag("--product", "Comma separated list of product names to keep",
1749 &productList)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001750 .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
1751 "by -o",
1752 &options.outputToDirectory)
Alexandria Cornwalla7cc3f12016-08-16 13:33:32 -07001753 .optionalSwitch("--no-xml-namespaces", "Removes XML namespace prefix and URI "
1754 "information from AndroidManifest.xml\nand XML binaries in res/*.",
1755 &options.noXmlNamespaces)
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001756 .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001757 "AndroidManifest.xml",
1758 &options.manifestFixerOptions.minSdkVersionDefault)
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001759 .optionalFlag("--target-sdk-version", "Default target SDK version to use for "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001760 "AndroidManifest.xml",
1761 &options.manifestFixerOptions.targetSdkVersionDefault)
Adam Lesinski52364f72016-01-11 13:10:24 -08001762 .optionalFlag("--version-code", "Version code (integer) to inject into the "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001763 "AndroidManifest.xml if none is present",
1764 &options.manifestFixerOptions.versionCodeDefault)
Adam Lesinski52364f72016-01-11 13:10:24 -08001765 .optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001766 "if none is present",
1767 &options.manifestFixerOptions.versionNameDefault)
1768 .optionalSwitch("--static-lib", "Generate a static Android library",
1769 &options.staticLib)
Adam Lesinski64587af2016-02-18 18:33:06 -08001770 .optionalSwitch("--no-static-lib-packages",
1771 "Merge all library resources under the app's package",
1772 &options.noStaticLibPackages)
Adam Lesinskief9c5012016-01-22 14:09:53 -08001773 .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n"
1774 "This is implied when --static-lib is specified.",
1775 &options.generateNonFinalIds)
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001776 .optionalFlag("--stable-ids", "File containing a list of name to ID mapping.",
1777 &stableIdFilePath)
1778 .optionalFlag("--emit-ids", "Emit a file at the given path with a list of name to ID\n"
1779 "mappings, suitable for use with --stable-ids.",
1780 &options.resourceIdMapPath)
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001781 .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001782 "private symbols.\n"
1783 "If not specified, public and private symbols will use the application's "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001784 "package name",
1785 &options.privateSymbols)
Adam Lesinski52364f72016-01-11 13:10:24 -08001786 .optionalFlag("--custom-package", "Custom Java package under which to generate R.java",
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001787 &options.customJavaPackage)
Adam Lesinski83f22552015-11-07 11:51:23 -08001788 .optionalFlagList("--extra-packages", "Generate the same R.java but with different "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001789 "package names",
1790 &extraJavaPackages)
Adam Lesinski3524a232016-04-01 19:19:24 -07001791 .optionalFlagList("--add-javadoc-annotation", "Adds a JavaDoc annotation to all "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001792 "generated Java classes",
1793 &options.javadocAnnotations)
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001794 .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001795 "overlays without <add-resource> tags",
1796 &options.autoAddOverlay)
Adam Lesinski52364f72016-01-11 13:10:24 -08001797 .optionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml",
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001798 &options.manifestFixerOptions.renameManifestPackage)
Adam Lesinski52364f72016-01-11 13:10:24 -08001799 .optionalFlag("--rename-instrumentation-target-package",
1800 "Changes the name of the target package for instrumentation. Most useful "
1801 "when used\nin conjunction with --rename-manifest-package",
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001802 &options.manifestFixerOptions.renameInstrumentationTargetPackage)
Adam Lesinski52364f72016-01-11 13:10:24 -08001803 .optionalFlagList("-0", "File extensions not to compress",
1804 &options.extensionsToNotCompress)
Adam Lesinski36c73a52016-08-11 13:39:24 -07001805 .optionalFlagList("--split", "Split resources matching a set of configs out to a "
1806 "Split APK.\nSyntax: path/to/output.apk:<config>[,<config>[...]]",
1807 &splitArgs)
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001808 .optionalSwitch("-v", "Enables verbose logging",
1809 &verbose);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001810
1811 if (!flags.parse("aapt2 link", args, &std::cerr)) {
1812 return 1;
1813 }
1814
Adam Lesinskic51562c2016-04-28 11:12:38 -07001815 // Expand all argument-files passed into the command line. These start with '@'.
1816 std::vector<std::string> argList;
1817 for (const std::string& arg : flags.getArgs()) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001818 if (util::stringStartsWith(arg, "@")) {
Adam Lesinskic51562c2016-04-28 11:12:38 -07001819 const std::string path = arg.substr(1, arg.size() - 1);
1820 std::string error;
1821 if (!file::appendArgsFromFile(path, &argList, &error)) {
1822 context.getDiagnostics()->error(DiagMessage(path) << error);
1823 return 1;
1824 }
1825 } else {
1826 argList.push_back(arg);
1827 }
1828 }
1829
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001830 // Expand all argument-files passed to -R.
1831 for (const std::string& arg : overlayArgList) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001832 if (util::stringStartsWith(arg, "@")) {
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001833 const std::string path = arg.substr(1, arg.size() - 1);
1834 std::string error;
1835 if (!file::appendArgsFromFile(path, &options.overlayFiles, &error)) {
1836 context.getDiagnostics()->error(DiagMessage(path) << error);
1837 return 1;
1838 }
1839 } else {
1840 options.overlayFiles.push_back(arg);
1841 }
1842 }
1843
Adam Lesinski64587af2016-02-18 18:33:06 -08001844 if (verbose) {
1845 context.setVerbose(verbose);
1846 }
1847
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001848 // Populate the set of extra packages for which to generate R.java.
1849 for (std::string& extraPackage : extraJavaPackages) {
1850 // A given package can actually be a colon separated list of packages.
1851 for (StringPiece package : util::split(extraPackage, ':')) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001852 options.extraJavaPackages.insert(package.toString());
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001853 }
1854 }
1855
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001856 if (productList) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001857 for (StringPiece product : util::tokenize(productList.value(), ',')) {
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001858 if (product != "" && product != "default") {
1859 options.products.insert(product.toString());
1860 }
1861 }
1862 }
1863
Adam Lesinski6a008172016-02-02 17:02:58 -08001864 AxisConfigFilter filter;
1865 if (configs) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001866 for (const StringPiece& configStr : util::tokenize(configs.value(), ',')) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001867 ConfigDescription config;
1868 LocaleValue lv;
1869 if (lv.initFromFilterString(configStr)) {
1870 lv.writeTo(&config);
1871 } else if (!ConfigDescription::parse(configStr, &config)) {
1872 context.getDiagnostics()->error(
1873 DiagMessage() << "invalid config '" << configStr << "' for -c option");
1874 return 1;
1875 }
1876
1877 if (config.density != 0) {
1878 context.getDiagnostics()->warn(
1879 DiagMessage() << "ignoring density '" << config << "' for -c option");
1880 } else {
1881 filter.addConfig(config);
1882 }
1883 }
1884
Adam Lesinski355f2852016-02-13 20:26:45 -08001885 options.tableSplitterOptions.configFilter = &filter;
1886 }
1887
1888 if (preferredDensity) {
1889 ConfigDescription preferredDensityConfig;
1890 if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) {
1891 context.getDiagnostics()->error(DiagMessage() << "invalid density '"
1892 << preferredDensity.value()
1893 << "' for --preferred-density option");
1894 return 1;
1895 }
1896
1897 // Clear the version that can be automatically added.
1898 preferredDensityConfig.sdkVersion = 0;
1899
1900 if (preferredDensityConfig.diff(ConfigDescription::defaultConfig())
1901 != ConfigDescription::CONFIG_DENSITY) {
1902 context.getDiagnostics()->error(DiagMessage() << "invalid preferred density '"
1903 << preferredDensity.value() << "'. "
1904 << "Preferred density must only be a density value");
1905 return 1;
1906 }
1907 options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density;
Adam Lesinski6a008172016-02-02 17:02:58 -08001908 }
1909
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001910 if (!options.staticLib && stableIdFilePath) {
1911 if (!loadStableIdMap(context.getDiagnostics(), stableIdFilePath.value(),
1912 &options.stableIdMap)) {
1913 return 1;
1914 }
1915 }
1916
Adam Lesinski9756dec2016-08-08 12:35:04 -07001917 // Populate some default no-compress extensions that are already compressed.
1918 options.extensionsToNotCompress.insert({
1919 ".jpg", ".jpeg", ".png", ".gif",
1920 ".wav", ".mp2", ".mp3", ".ogg", ".aac",
1921 ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
1922 ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
1923 ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
1924 ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"});
1925
Adam Lesinski36c73a52016-08-11 13:39:24 -07001926 // Parse the split parameters.
1927 for (const std::string& splitArg : splitArgs) {
1928 options.splitPaths.push_back({});
1929 options.splitConstraints.push_back({});
1930 if (!parseSplitParameter(splitArg, context.getDiagnostics(), &options.splitPaths.back(),
1931 &options.splitConstraints.back())) {
1932 return 1;
1933 }
1934 }
1935
Adam Lesinski626a69f2016-03-03 10:09:26 -08001936 // Turn off auto versioning for static-libs.
1937 if (options.staticLib) {
1938 options.noAutoVersion = true;
1939 options.noVersionVectors = true;
1940 }
1941
Adam Lesinski6a008172016-02-02 17:02:58 -08001942 LinkCommand cmd(&context, options);
Adam Lesinskic51562c2016-04-28 11:12:38 -07001943 return cmd.run(argList);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001944}
1945
1946} // namespace aapt