blob: c1c5ba22026b4bb5f08cee075bcf52505fd716e9 [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;
Adam Lesinskia6fe3452015-12-09 15:20:52 -080077 bool autoAddOverlay = false;
Adam Lesinski52364f72016-01-11 13:10:24 -080078 bool doNotCompressAnything = false;
Adam Lesinski9756dec2016-08-08 12:35:04 -070079 std::unordered_set<std::string> extensionsToNotCompress;
Adam Lesinskid0f116b2016-07-08 15:00:32 -070080 Maybe<std::string> privateSymbols;
Adam Lesinski52364f72016-01-11 13:10:24 -080081 ManifestFixerOptions manifestFixerOptions;
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -080082 std::unordered_set<std::string> products;
Adam Lesinski36c73a52016-08-11 13:39:24 -070083
84 // Split APK options.
Adam Lesinski355f2852016-02-13 20:26:45 -080085 TableSplitterOptions tableSplitterOptions;
Adam Lesinski36c73a52016-08-11 13:39:24 -070086 std::vector<SplitConstraints> splitConstraints;
87 std::vector<std::string> splitPaths;
88
89 // Stable ID options.
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -070090 std::unordered_map<ResourceName, ResourceId> stableIdMap;
91 Maybe<std::string> resourceIdMapPath;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070092};
93
Adam Lesinski64587af2016-02-18 18:33:06 -080094class LinkContext : public IAaptContext {
95public:
96 LinkContext() : mNameMangler({}) {
97 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -070098
99 IDiagnostics* getDiagnostics() override {
100 return &mDiagnostics;
101 }
102
103 NameMangler* getNameMangler() override {
Adam Lesinski64587af2016-02-18 18:33:06 -0800104 return &mNameMangler;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700105 }
106
Adam Lesinski64587af2016-02-18 18:33:06 -0800107 void setNameManglerPolicy(const NameManglerPolicy& policy) {
108 mNameMangler = NameMangler(policy);
109 }
110
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700111 const std::string& getCompilationPackage() override {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700112 return mCompilationPackage;
113 }
114
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700115 void setCompilationPackage(const StringPiece& packageName) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800116 mCompilationPackage = packageName.toString();
117 }
118
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700119 uint8_t getPackageId() override {
120 return mPackageId;
121 }
122
Adam Lesinski64587af2016-02-18 18:33:06 -0800123 void setPackageId(uint8_t id) {
124 mPackageId = id;
125 }
126
127 SymbolTable* getExternalSymbols() override {
128 return &mSymbols;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700129 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800130
131 bool verbose() override {
132 return mVerbose;
133 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800134
135 void setVerbose(bool val) {
136 mVerbose = val;
137 }
138
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700139 int getMinSdkVersion() override {
140 return mMinSdkVersion;
141 }
142
143 void setMinSdkVersion(int minSdk) {
144 mMinSdkVersion = minSdk;
145 }
146
Adam Lesinski64587af2016-02-18 18:33:06 -0800147private:
148 StdErrDiagnostics mDiagnostics;
149 NameMangler mNameMangler;
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700150 std::string mCompilationPackage;
Adam Lesinski64587af2016-02-18 18:33:06 -0800151 uint8_t mPackageId = 0x0;
152 SymbolTable mSymbols;
153 bool mVerbose = false;
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700154 int mMinSdkVersion = 0;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700155};
156
Adam Lesinski355f2852016-02-13 20:26:45 -0800157static bool copyFileToArchive(io::IFile* file, const std::string& outPath,
158 uint32_t compressionFlags,
159 IArchiveWriter* writer, IAaptContext* context) {
160 std::unique_ptr<io::IData> data = file->openAsData();
161 if (!data) {
162 context->getDiagnostics()->error(DiagMessage(file->getSource())
163 << "failed to open file");
164 return false;
165 }
166
Adam Lesinski64587af2016-02-18 18:33:06 -0800167 const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data());
168 size_t bufferSize = data->size();
169
170 // If the file ends with .flat, we must strip off the CompiledFileHeader from it.
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700171 if (util::stringEndsWith(file->getSource().path, ".flat")) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800172 CompiledFileInputStream inputStream(data->data(), data->size());
173 if (!inputStream.CompiledFile()) {
174 context->getDiagnostics()->error(DiagMessage(file->getSource())
175 << "invalid compiled file header");
176 return false;
177 }
178 buffer = reinterpret_cast<const uint8_t*>(inputStream.data());
179 bufferSize = inputStream.size();
Adam Lesinski355f2852016-02-13 20:26:45 -0800180 }
181
182 if (context->verbose()) {
183 context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive");
184 }
185
186 if (writer->startEntry(outPath, compressionFlags)) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800187 if (writer->writeEntry(buffer, bufferSize)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800188 if (writer->finishEntry()) {
189 return true;
190 }
191 }
192 }
193
194 context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath);
195 return false;
196}
197
198static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
199 bool keepRawValues, IArchiveWriter* writer, IAaptContext* context) {
200 BigBuffer buffer(1024);
201 XmlFlattenerOptions options = {};
202 options.keepRawValues = keepRawValues;
203 options.maxSdkLevel = maxSdkLevel;
204 XmlFlattener flattener(&buffer, options);
205 if (!flattener.consume(context, xmlRes)) {
206 return false;
207 }
208
209 if (context->verbose()) {
210 DiagMessage msg;
211 msg << "writing " << path << " to archive";
212 if (maxSdkLevel) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800213 msg << " maxSdkLevel=" << maxSdkLevel.value() << " keepRawValues=" << keepRawValues;
Adam Lesinski355f2852016-02-13 20:26:45 -0800214 }
215 context->getDiagnostics()->note(msg);
216 }
217
218 if (writer->startEntry(path, ArchiveEntry::kCompress)) {
219 if (writer->writeEntry(buffer)) {
220 if (writer->finishEntry()) {
221 return true;
222 }
223 }
224 }
225 context->getDiagnostics()->error(DiagMessage() << "failed to write " << path << " to archive");
226 return false;
227}
228
Adam Lesinski355f2852016-02-13 20:26:45 -0800229static std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
230 const void* data, size_t len,
231 IDiagnostics* diag) {
232 pb::ResourceTable pbTable;
233 if (!pbTable.ParseFromArray(data, len)) {
234 diag->error(DiagMessage(source) << "invalid compiled table");
235 return {};
236 }
237
238 std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source, diag);
239 if (!table) {
240 return {};
241 }
242 return table;
243}
244
245/**
246 * Inflates an XML file from the source path.
247 */
248static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
249 std::ifstream fin(path, std::ifstream::binary);
250 if (!fin) {
251 diag->error(DiagMessage(path) << strerror(errno));
252 return {};
253 }
254 return xml::inflate(&fin, diag, Source(path));
255}
256
257static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const Source& source,
258 const void* data, size_t len,
259 IDiagnostics* diag) {
260 CompiledFileInputStream inputStream(data, len);
261 if (!inputStream.CompiledFile()) {
262 diag->error(DiagMessage(source) << "invalid compiled file header");
263 return {};
264 }
265
266 const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data());
267 const size_t xmlDataLen = inputStream.size();
268
269 std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source);
270 if (!xmlRes) {
271 return {};
272 }
273 return xmlRes;
274}
275
276static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
277 const void* data, size_t len,
278 IDiagnostics* diag) {
279 CompiledFileInputStream inputStream(data, len);
280 const pb::CompiledFile* pbFile = inputStream.CompiledFile();
281 if (!pbFile) {
282 diag->error(DiagMessage(source) << "invalid compiled file header");
283 return {};
284 }
285
286 std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source, diag);
287 if (!resFile) {
288 return {};
289 }
290 return resFile;
291}
292
293struct ResourceFileFlattenerOptions {
294 bool noAutoVersion = false;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800295 bool noVersionVectors = false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800296 bool keepRawValues = false;
297 bool doNotCompressAnything = false;
Rohit Agrawale49bb302016-04-22 12:27:55 -0700298 bool updateProguardSpec = false;
Adam Lesinski9756dec2016-08-08 12:35:04 -0700299 std::unordered_set<std::string> extensionsToNotCompress;
Adam Lesinski355f2852016-02-13 20:26:45 -0800300};
301
302class ResourceFileFlattener {
303public:
304 ResourceFileFlattener(const ResourceFileFlattenerOptions& options,
305 IAaptContext* context, proguard::KeepSet* keepSet) :
306 mOptions(options), mContext(context), mKeepSet(keepSet) {
307 }
308
309 bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter);
310
311private:
312 struct FileOperation {
313 io::IFile* fileToCopy;
314 std::unique_ptr<xml::XmlResource> xmlToFlatten;
315 std::string dstPath;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800316 bool skipVersion = false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800317 };
318
319 uint32_t getCompressionFlags(const StringPiece& str);
320
Adam Lesinski626a69f2016-03-03 10:09:26 -0800321 bool linkAndVersionXmlFile(const ResourceEntry* entry, const ResourceFile& fileDesc,
322 io::IFile* file, ResourceTable* table, FileOperation* outFileOp);
Adam Lesinski355f2852016-02-13 20:26:45 -0800323
324 ResourceFileFlattenerOptions mOptions;
325 IAaptContext* mContext;
326 proguard::KeepSet* mKeepSet;
327};
328
329uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) {
330 if (mOptions.doNotCompressAnything) {
331 return 0;
332 }
333
334 for (const std::string& extension : mOptions.extensionsToNotCompress) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700335 if (util::stringEndsWith(str, extension)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800336 return 0;
337 }
338 }
339 return ArchiveEntry::kCompress;
340}
341
Adam Lesinski626a69f2016-03-03 10:09:26 -0800342bool ResourceFileFlattener::linkAndVersionXmlFile(const ResourceEntry* entry,
343 const ResourceFile& fileDesc,
344 io::IFile* file,
345 ResourceTable* table,
346 FileOperation* outFileOp) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800347 const StringPiece srcPath = file->getSource().path;
348 if (mContext->verbose()) {
349 mContext->getDiagnostics()->note(DiagMessage() << "linking " << srcPath);
350 }
351
352 std::unique_ptr<io::IData> data = file->openAsData();
353 if (!data) {
354 mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file");
Adam Lesinski626a69f2016-03-03 10:09:26 -0800355 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800356 }
357
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700358 if (util::stringEndsWith(srcPath, ".flat")) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800359 outFileOp->xmlToFlatten = loadBinaryXmlSkipFileExport(file->getSource(),
360 data->data(), data->size(),
361 mContext->getDiagnostics());
Adam Lesinski355f2852016-02-13 20:26:45 -0800362 } else {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800363 outFileOp->xmlToFlatten = xml::inflate(data->data(), data->size(),
364 mContext->getDiagnostics(),
365 file->getSource());
Adam Lesinski355f2852016-02-13 20:26:45 -0800366 }
367
Adam Lesinski626a69f2016-03-03 10:09:26 -0800368 if (!outFileOp->xmlToFlatten) {
369 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800370 }
371
372 // Copy the the file description header.
Adam Lesinski626a69f2016-03-03 10:09:26 -0800373 outFileOp->xmlToFlatten->file = fileDesc;
Adam Lesinski355f2852016-02-13 20:26:45 -0800374
375 XmlReferenceLinker xmlLinker;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800376 if (!xmlLinker.consume(mContext, outFileOp->xmlToFlatten.get())) {
377 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800378 }
379
Rohit Agrawale49bb302016-04-22 12:27:55 -0700380 if (mOptions.updateProguardSpec && !proguard::collectProguardRules(
381 outFileOp->xmlToFlatten->file.source, outFileOp->xmlToFlatten.get(), mKeepSet)) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800382 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800383 }
384
385 if (!mOptions.noAutoVersion) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800386 if (mOptions.noVersionVectors) {
387 // Skip this if it is a vector or animated-vector.
388 xml::Element* el = xml::findRootElement(outFileOp->xmlToFlatten.get());
389 if (el && el->namespaceUri.empty()) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700390 if (el->name == "vector" || el->name == "animated-vector") {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800391 // We are NOT going to version this file.
392 outFileOp->skipVersion = true;
393 return true;
394 }
395 }
396 }
397
Adam Lesinski355f2852016-02-13 20:26:45 -0800398 // Find the first SDK level used that is higher than this defined config and
399 // not superseded by a lower or equal SDK level resource.
Alexandria Cornwallf6762fc2016-08-09 12:36:46 -0700400 const int minSdkVersion = mContext->getMinSdkVersion();
Adam Lesinski355f2852016-02-13 20:26:45 -0800401 for (int sdkLevel : xmlLinker.getSdkLevels()) {
Alexandria Cornwallf6762fc2016-08-09 12:36:46 -0700402 if (sdkLevel > minSdkVersion
403 && sdkLevel > outFileOp->xmlToFlatten->file.config.sdkVersion) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800404 if (!shouldGenerateVersionedResource(entry, outFileOp->xmlToFlatten->file.config,
405 sdkLevel)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800406 // If we shouldn't generate a versioned resource, stop checking.
407 break;
408 }
409
Adam Lesinski626a69f2016-03-03 10:09:26 -0800410 ResourceFile versionedFileDesc = outFileOp->xmlToFlatten->file;
Adam Lesinski64587af2016-02-18 18:33:06 -0800411 versionedFileDesc.config.sdkVersion = (uint16_t) sdkLevel;
Adam Lesinski355f2852016-02-13 20:26:45 -0800412
413 if (mContext->verbose()) {
414 mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source)
415 << "auto-versioning resource from config '"
Adam Lesinski626a69f2016-03-03 10:09:26 -0800416 << outFileOp->xmlToFlatten->file.config
417 << "' -> '"
Adam Lesinski355f2852016-02-13 20:26:45 -0800418 << versionedFileDesc.config << "'");
419 }
420
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700421 std::string genPath = ResourceUtils::buildResourceFileName(
422 versionedFileDesc, mContext->getNameMangler());
Adam Lesinski355f2852016-02-13 20:26:45 -0800423
424 bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name,
425 versionedFileDesc.config,
426 versionedFileDesc.source,
427 genPath,
428 file,
429 mContext->getDiagnostics());
430 if (!added) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800431 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800432 }
433 break;
434 }
435 }
436 }
Adam Lesinski626a69f2016-03-03 10:09:26 -0800437 return true;
Adam Lesinski355f2852016-02-13 20:26:45 -0800438}
439
440/**
441 * Do not insert or remove any resources while executing in this function. It will
442 * corrupt the iteration order.
443 */
444bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiveWriter) {
445 bool error = false;
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700446 std::map<std::pair<ConfigDescription, StringPiece>, FileOperation> configSortedFiles;
Adam Lesinski355f2852016-02-13 20:26:45 -0800447
448 for (auto& pkg : table->packages) {
449 for (auto& type : pkg->types) {
450 // Sort by config and name, so that we get better locality in the zip file.
451 configSortedFiles.clear();
452 for (auto& entry : type->entries) {
453 // Iterate via indices because auto generated values can be inserted ahead of
454 // the value being processed.
455 for (size_t i = 0; i < entry->values.size(); i++) {
456 ResourceConfigValue* configValue = entry->values[i].get();
457
458 FileReference* fileRef = valueCast<FileReference>(configValue->value.get());
459 if (!fileRef) {
460 continue;
461 }
462
463 io::IFile* file = fileRef->file;
464 if (!file) {
465 mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource())
466 << "file not found");
467 return false;
468 }
469
470 FileOperation fileOp;
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700471 fileOp.dstPath = *fileRef->path;
Adam Lesinski355f2852016-02-13 20:26:45 -0800472
473 const StringPiece srcPath = file->getSource().path;
474 if (type->type != ResourceType::kRaw &&
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700475 (util::stringEndsWith(srcPath, ".xml.flat") ||
476 util::stringEndsWith(srcPath, ".xml"))) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800477 ResourceFile fileDesc;
478 fileDesc.config = configValue->config;
479 fileDesc.name = ResourceName(pkg->name, type->type, entry->name);
480 fileDesc.source = fileRef->getSource();
Adam Lesinski626a69f2016-03-03 10:09:26 -0800481 if (!linkAndVersionXmlFile(entry.get(), fileDesc, file, table, &fileOp)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800482 error = true;
483 continue;
484 }
485
486 } else {
487 fileOp.fileToCopy = file;
488 }
489
490 // NOTE(adamlesinski): Explicitly construct a StringPiece16 here, or else
491 // we end up copying the string in the std::make_pair() method, then creating
492 // a StringPiece16 from the copy, which would cause us to end up referencing
493 // garbage in the map.
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700494 const StringPiece entryName(entry->name);
Adam Lesinski355f2852016-02-13 20:26:45 -0800495 configSortedFiles[std::make_pair(configValue->config, entryName)] =
496 std::move(fileOp);
497 }
498 }
499
500 if (error) {
501 return false;
502 }
503
504 // Now flatten the sorted values.
505 for (auto& mapEntry : configSortedFiles) {
506 const ConfigDescription& config = mapEntry.first.first;
507 const FileOperation& fileOp = mapEntry.second;
508
509 if (fileOp.xmlToFlatten) {
510 Maybe<size_t> maxSdkLevel;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800511 if (!mOptions.noAutoVersion && !fileOp.skipVersion) {
Alexandria Cornwallf6762fc2016-08-09 12:36:46 -0700512 maxSdkLevel =
513 std::max<size_t>(
514 std::max<size_t>(config.sdkVersion, 1u),
515 mContext->getMinSdkVersion());
Adam Lesinski355f2852016-02-13 20:26:45 -0800516 }
517
518 bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel,
519 mOptions.keepRawValues,
520 archiveWriter, mContext);
521 if (!result) {
522 error = true;
523 }
524 } else {
525 bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath,
526 getCompressionFlags(fileOp.dstPath),
527 archiveWriter, mContext);
528 if (!result) {
529 error = true;
530 }
531 }
532 }
533 }
534 }
535 return !error;
536}
537
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700538static bool writeStableIdMapToPath(IDiagnostics* diag,
539 const std::unordered_map<ResourceName, ResourceId>& idMap,
540 const std::string idMapPath) {
541 std::ofstream fout(idMapPath, std::ofstream::binary);
542 if (!fout) {
543 diag->error(DiagMessage(idMapPath) << strerror(errno));
544 return false;
545 }
546
547 for (const auto& entry : idMap) {
548 const ResourceName& name = entry.first;
549 const ResourceId& id = entry.second;
550 fout << name << " = " << id << "\n";
551 }
552
553 if (!fout) {
554 diag->error(DiagMessage(idMapPath) << "failed writing to file: " << strerror(errno));
555 return false;
556 }
557
558 return true;
559}
560
561static bool loadStableIdMap(IDiagnostics* diag, const std::string& path,
562 std::unordered_map<ResourceName, ResourceId>* outIdMap) {
563 std::string content;
564 if (!android::base::ReadFileToString(path, &content)) {
565 diag->error(DiagMessage(path) << "failed reading stable ID file");
566 return false;
567 }
568
569 outIdMap->clear();
570 size_t lineNo = 0;
571 for (StringPiece line : util::tokenize(content, '\n')) {
572 lineNo++;
573 line = util::trimWhitespace(line);
574 if (line.empty()) {
575 continue;
576 }
577
578 auto iter = std::find(line.begin(), line.end(), '=');
579 if (iter == line.end()) {
580 diag->error(DiagMessage(Source(path, lineNo)) << "missing '='");
581 return false;
582 }
583
584 ResourceNameRef name;
585 StringPiece resNameStr = util::trimWhitespace(
586 line.substr(0, std::distance(line.begin(), iter)));
587 if (!ResourceUtils::parseResourceName(resNameStr, &name)) {
588 diag->error(DiagMessage(Source(path, lineNo))
589 << "invalid resource name '" << resNameStr << "'");
590 return false;
591 }
592
593 const size_t resIdStartIdx = std::distance(line.begin(), iter) + 1;
594 const size_t resIdStrLen = line.size() - resIdStartIdx;
595 StringPiece resIdStr = util::trimWhitespace(line.substr(resIdStartIdx, resIdStrLen));
596
Adam Lesinski36c73a52016-08-11 13:39:24 -0700597 Maybe<ResourceId> maybeId = ResourceUtils::parseResourceId(resIdStr);
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700598 if (!maybeId) {
599 diag->error(DiagMessage(Source(path, lineNo)) << "invalid resource ID '"
600 << resIdStr << "'");
601 return false;
602 }
603
604 (*outIdMap)[name.toResourceName()] = maybeId.value();
605 }
606 return true;
607}
608
Adam Lesinski36c73a52016-08-11 13:39:24 -0700609static bool parseSplitParameter(const StringPiece& arg, IDiagnostics* diag,
610 std::string* outPath, SplitConstraints* outSplit) {
611 std::vector<std::string> parts = util::split(arg, ':');
612 if (parts.size() != 2) {
613 diag->error(DiagMessage() << "invalid split parameter '" << arg << "'");
614 diag->note(DiagMessage() << "should be --split path/to/output.apk:<config>[,<config>...]");
615 return false;
616 }
617 *outPath = parts[0];
618 std::vector<ConfigDescription> configs;
619 for (const StringPiece& configStr : util::tokenize(parts[1], ',')) {
620 configs.push_back({});
621 if (!ConfigDescription::parse(configStr, &configs.back())) {
622 diag->error(DiagMessage() << "invalid config '" << configStr
623 << "' in split parameter '" << arg << "'");
624 return false;
625 }
626 }
627 outSplit->configs.insert(configs.begin(), configs.end());
628 return true;
629}
630
Adam Lesinskifb48d292015-11-07 15:52:13 -0800631class LinkCommand {
632public:
Adam Lesinski6a008172016-02-02 17:02:58 -0800633 LinkCommand(LinkContext* context, const LinkOptions& options) :
Adam Lesinski64587af2016-02-18 18:33:06 -0800634 mOptions(options), mContext(context), mFinalTable(),
635 mFileCollection(util::make_unique<io::FileCollection>()) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800636 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700637
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700638 /**
639 * Creates a SymbolTable that loads symbols from the various APKs and caches the
640 * results for faster lookup.
641 */
Adam Lesinski64587af2016-02-18 18:33:06 -0800642 bool loadSymbolsFromIncludePaths() {
643 std::unique_ptr<AssetManagerSymbolSource> assetSource =
644 util::make_unique<AssetManagerSymbolSource>();
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700645 for (const std::string& path : mOptions.includePaths) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800646 if (mContext->verbose()) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800647 mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700648 }
649
Adam Lesinski64587af2016-02-18 18:33:06 -0800650 // First try to load the file as a static lib.
651 std::string errorStr;
652 std::unique_ptr<ResourceTable> staticInclude = loadStaticLibrary(path, &errorStr);
653 if (staticInclude) {
654 if (!mOptions.staticLib) {
655 // Can't include static libraries when not building a static library.
656 mContext->getDiagnostics()->error(
657 DiagMessage(path) << "can't include static library when building app");
658 return false;
659 }
660
661 // If we are using --no-static-lib-packages, we need to rename the package of this
662 // table to our compilation package.
663 if (mOptions.noStaticLibPackages) {
664 if (ResourceTablePackage* pkg = staticInclude->findPackageById(0x7f)) {
665 pkg->name = mContext->getCompilationPackage();
666 }
667 }
668
669 mContext->getExternalSymbols()->appendSource(
670 util::make_unique<ResourceTableSymbolSource>(staticInclude.get()));
671
672 mStaticTableIncludes.push_back(std::move(staticInclude));
673
674 } else if (!errorStr.empty()) {
675 // We had an error with reading, so fail.
676 mContext->getDiagnostics()->error(DiagMessage(path) << errorStr);
677 return false;
678 }
679
680 if (!assetSource->addAssetPath(path)) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800681 mContext->getDiagnostics()->error(
Adam Lesinskifb48d292015-11-07 15:52:13 -0800682 DiagMessage(path) << "failed to load include path");
Adam Lesinski64587af2016-02-18 18:33:06 -0800683 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700684 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700685 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800686
687 mContext->getExternalSymbols()->appendSource(std::move(assetSource));
688 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700689 }
690
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700691 Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes, IDiagnostics* diag) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700692 // Make sure the first element is <manifest> with package attribute.
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800693 if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700694 AppInfo appInfo;
695
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700696 if (!manifestEl->namespaceUri.empty() || manifestEl->name != "manifest") {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700697 diag->error(DiagMessage(xmlRes->file.source) << "root tag must be <manifest>");
698 return {};
699 }
700
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700701 xml::Attribute* packageAttr = manifestEl->findAttribute({}, "package");
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700702 if (!packageAttr) {
703 diag->error(DiagMessage(xmlRes->file.source)
704 << "<manifest> must have a 'package' attribute");
705 return {};
706 }
707
708 appInfo.package = packageAttr->value;
709
Adam Lesinski36c73a52016-08-11 13:39:24 -0700710 if (xml::Attribute* versionCodeAttr =
711 manifestEl->findAttribute(xml::kSchemaAndroid, "versionCode")) {
712 Maybe<uint32_t> maybeCode = ResourceUtils::parseInt(versionCodeAttr->value);
713 if (!maybeCode) {
714 diag->error(DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber))
715 << "invalid android:versionCode '"
716 << versionCodeAttr->value << "'");
717 return {};
718 }
719 appInfo.versionCode = maybeCode.value();
720 }
721
722 if (xml::Attribute* revisionCodeAttr =
723 manifestEl->findAttribute(xml::kSchemaAndroid, "revisionCode")) {
724 Maybe<uint32_t> maybeCode = ResourceUtils::parseInt(revisionCodeAttr->value);
725 if (!maybeCode) {
726 diag->error(DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber))
727 << "invalid android:revisionCode '"
728 << revisionCodeAttr->value << "'");
729 return {};
730 }
731 appInfo.revisionCode = maybeCode.value();
732 }
733
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700734 if (xml::Element* usesSdkEl = manifestEl->findChild({}, "uses-sdk")) {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700735 if (xml::Attribute* minSdk =
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700736 usesSdkEl->findAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700737 appInfo.minSdkVersion = minSdk->value;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700738 }
739 }
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700740
741 return appInfo;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700742 }
743 return {};
744 }
745
Adam Lesinski979ccb22016-01-11 10:42:19 -0800746 /**
747 * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked.
748 * Postcondition: ResourceTable has only one package left. All others are stripped, or there
749 * is an error and false is returned.
750 */
Adam Lesinskifb48d292015-11-07 15:52:13 -0800751 bool verifyNoExternalPackages() {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800752 auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool {
Adam Lesinski6a008172016-02-02 17:02:58 -0800753 return mContext->getCompilationPackage() != pkg->name ||
Adam Lesinski979ccb22016-01-11 10:42:19 -0800754 !pkg->id ||
Adam Lesinski6a008172016-02-02 17:02:58 -0800755 pkg->id.value() != mContext->getPackageId();
Adam Lesinski979ccb22016-01-11 10:42:19 -0800756 };
757
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700758 bool error = false;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800759 for (const auto& package : mFinalTable.packages) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800760 if (isExtPackageFunc(package)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700761 // We have a package that is not related to the one we're building!
762 for (const auto& type : package->types) {
763 for (const auto& entry : type->entries) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800764 ResourceNameRef resName(package->name, type->type, entry->name);
765
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700766 for (const auto& configValue : entry->values) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800767 // Special case the occurrence of an ID that is being generated for the
768 // 'android' package. This is due to legacy reasons.
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800769 if (valueCast<Id>(configValue->value.get()) &&
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700770 package->name == "android") {
Adam Lesinski6a008172016-02-02 17:02:58 -0800771 mContext->getDiagnostics()->warn(
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800772 DiagMessage(configValue->value->getSource())
Adam Lesinski979ccb22016-01-11 10:42:19 -0800773 << "generated id '" << resName
774 << "' for external package '" << package->name
775 << "'");
776 } else {
Adam Lesinski6a008172016-02-02 17:02:58 -0800777 mContext->getDiagnostics()->error(
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800778 DiagMessage(configValue->value->getSource())
Adam Lesinski979ccb22016-01-11 10:42:19 -0800779 << "defined resource '" << resName
780 << "' for external package '" << package->name
781 << "'");
782 error = true;
783 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700784 }
785 }
786 }
787 }
788 }
Adam Lesinski979ccb22016-01-11 10:42:19 -0800789
790 auto newEndIter = std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(),
791 isExtPackageFunc);
792 mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700793 return !error;
794 }
795
Adam Lesinski64587af2016-02-18 18:33:06 -0800796 /**
797 * Returns true if no IDs have been set, false otherwise.
798 */
799 bool verifyNoIdsSet() {
800 for (const auto& package : mFinalTable.packages) {
801 for (const auto& type : package->types) {
802 if (type->id) {
803 mContext->getDiagnostics()->error(DiagMessage() << "type " << type->type
804 << " has ID " << std::hex
805 << (int) type->id.value()
806 << std::dec << " assigned");
807 return false;
808 }
809
810 for (const auto& entry : type->entries) {
811 if (entry->id) {
812 ResourceNameRef resName(package->name, type->type, entry->name);
813 mContext->getDiagnostics()->error(DiagMessage() << "entry " << resName
814 << " has ID " << std::hex
815 << (int) entry->id.value()
816 << std::dec << " assigned");
817 return false;
818 }
819 }
820 }
821 }
822 return true;
823 }
824
Adam Lesinski36c73a52016-08-11 13:39:24 -0700825 std::unique_ptr<IArchiveWriter> makeArchiveWriter(const StringPiece& out) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700826 if (mOptions.outputToDirectory) {
Adam Lesinski36c73a52016-08-11 13:39:24 -0700827 return createDirectoryArchiveWriter(mContext->getDiagnostics(), out);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700828 } else {
Adam Lesinski36c73a52016-08-11 13:39:24 -0700829 return createZipFileArchiveWriter(mContext->getDiagnostics(), out);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700830 }
831 }
832
833 bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
834 BigBuffer buffer(1024);
Adam Lesinski59e04c62016-02-04 15:59:23 -0800835 TableFlattener flattener(&buffer);
Adam Lesinski6a008172016-02-02 17:02:58 -0800836 if (!flattener.consume(mContext, table)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700837 return false;
838 }
839
Adam Lesinskia40e9722015-11-24 19:11:46 -0800840 if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
841 if (writer->writeEntry(buffer)) {
842 if (writer->finishEntry()) {
843 return true;
844 }
845 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700846 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800847
Adam Lesinski6a008172016-02-02 17:02:58 -0800848 mContext->getDiagnostics()->error(
Adam Lesinskia40e9722015-11-24 19:11:46 -0800849 DiagMessage() << "failed to write resources.arsc to archive");
850 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700851 }
852
Adam Lesinski64587af2016-02-18 18:33:06 -0800853 bool flattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
854 // Create the file/zip entry.
855 if (!writer->startEntry("resources.arsc.flat", 0)) {
856 mContext->getDiagnostics()->error(DiagMessage() << "failed to open");
857 return false;
858 }
859
860 std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table);
861
862 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
863 // interface.
864 {
865 google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
866
867 if (!pbTable->SerializeToZeroCopyStream(&adaptor)) {
868 mContext->getDiagnostics()->error(DiagMessage() << "failed to write");
869 return false;
870 }
871 }
872
873 if (!writer->finishEntry()) {
874 mContext->getDiagnostics()->error(DiagMessage() << "failed to finish entry");
875 return false;
876 }
877 return true;
878 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700879
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700880 bool writeJavaFile(ResourceTable* table, const StringPiece& packageNameToGenerate,
881 const StringPiece& outPackage, JavaClassGeneratorOptions javaOptions) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700882 if (!mOptions.generateJavaClassPath) {
883 return true;
884 }
885
886 std::string outPath = mOptions.generateJavaClassPath.value();
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700887 file::appendPath(&outPath, file::packageToPath(outPackage));
Adam Lesinski96917c22016-03-09 13:11:25 -0800888 if (!file::mkdirs(outPath)) {
889 mContext->getDiagnostics()->error(
890 DiagMessage() << "failed to create directory '" << outPath << "'");
891 return false;
892 }
893
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700894 file::appendPath(&outPath, "R.java");
895
896 std::ofstream fout(outPath, std::ofstream::binary);
897 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800898 mContext->getDiagnostics()->error(
899 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700900 return false;
901 }
902
Adam Lesinski76565542016-03-10 21:55:04 -0800903 JavaClassGenerator generator(mContext, table, javaOptions);
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700904 if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800905 mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700906 return false;
907 }
Adam Lesinski96917c22016-03-09 13:11:25 -0800908
909 if (!fout) {
910 mContext->getDiagnostics()->error(
911 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
912 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700913 return true;
914 }
915
Adam Lesinski467f1712015-11-16 17:35:44 -0800916 bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
Adam Lesinskica5638f2015-10-21 14:42:43 -0700917 if (!mOptions.generateJavaClassPath) {
918 return true;
919 }
920
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700921 std::unique_ptr<ClassDefinition> manifestClass = generateManifestClass(
922 mContext->getDiagnostics(), manifestXml);
923
924 if (!manifestClass) {
925 // Something bad happened, but we already logged it, so exit.
926 return false;
927 }
928
929 if (manifestClass->empty()) {
930 // Empty Manifest class, no need to generate it.
931 return true;
932 }
933
Adam Lesinski3524a232016-04-01 19:19:24 -0700934 // Add any JavaDoc annotations to the generated class.
935 for (const std::string& annotation : mOptions.javadocAnnotations) {
936 std::string properAnnotation = "@";
937 properAnnotation += annotation;
938 manifestClass->getCommentBuilder()->appendComment(properAnnotation);
939 }
940
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700941 const std::string& packageUtf8 = mContext->getCompilationPackage();
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700942
Adam Lesinskica5638f2015-10-21 14:42:43 -0700943 std::string outPath = mOptions.generateJavaClassPath.value();
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700944 file::appendPath(&outPath, file::packageToPath(packageUtf8));
945
Adam Lesinski96917c22016-03-09 13:11:25 -0800946 if (!file::mkdirs(outPath)) {
947 mContext->getDiagnostics()->error(
948 DiagMessage() << "failed to create directory '" << outPath << "'");
949 return false;
950 }
951
Adam Lesinskica5638f2015-10-21 14:42:43 -0700952 file::appendPath(&outPath, "Manifest.java");
953
954 std::ofstream fout(outPath, std::ofstream::binary);
955 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800956 mContext->getDiagnostics()->error(
957 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinskica5638f2015-10-21 14:42:43 -0700958 return false;
959 }
960
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700961 if (!ClassDefinition::writeJavaFile(manifestClass.get(), packageUtf8, true, &fout)) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800962 mContext->getDiagnostics()->error(
963 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinskica5638f2015-10-21 14:42:43 -0700964 return false;
965 }
966 return true;
967 }
968
Rohit Agrawale49bb302016-04-22 12:27:55 -0700969 bool writeProguardFile(const Maybe<std::string>& out, const proguard::KeepSet& keepSet) {
970 if (!out) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700971 return true;
972 }
973
Rohit Agrawale49bb302016-04-22 12:27:55 -0700974 const std::string& outPath = out.value();
Adam Lesinski96917c22016-03-09 13:11:25 -0800975 std::ofstream fout(outPath, std::ofstream::binary);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700976 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800977 mContext->getDiagnostics()->error(
978 DiagMessage() << "failed to open '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700979 return false;
980 }
981
982 proguard::writeKeepSet(&fout, keepSet);
983 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800984 mContext->getDiagnostics()->error(
985 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700986 return false;
987 }
988 return true;
989 }
990
Adam Lesinski64587af2016-02-18 18:33:06 -0800991 std::unique_ptr<ResourceTable> loadStaticLibrary(const std::string& input,
992 std::string* outError) {
993 std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
994 input, outError);
995 if (!collection) {
996 return {};
997 }
998 return loadTablePbFromCollection(collection.get());
999 }
1000
1001 std::unique_ptr<ResourceTable> loadTablePbFromCollection(io::IFileCollection* collection) {
1002 io::IFile* file = collection->findFile("resources.arsc.flat");
1003 if (!file) {
1004 return {};
1005 }
1006
1007 std::unique_ptr<io::IData> data = file->openAsData();
1008 return loadTableFromPb(file->getSource(), data->data(), data->size(),
1009 mContext->getDiagnostics());
1010 }
1011
1012 bool mergeStaticLibrary(const std::string& input, bool override) {
1013 if (mContext->verbose()) {
1014 mContext->getDiagnostics()->note(DiagMessage() << "merging static library " << input);
1015 }
1016
1017 std::string errorStr;
1018 std::unique_ptr<io::ZipFileCollection> collection =
1019 io::ZipFileCollection::create(input, &errorStr);
1020 if (!collection) {
1021 mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
1022 return false;
1023 }
1024
1025 std::unique_ptr<ResourceTable> table = loadTablePbFromCollection(collection.get());
1026 if (!table) {
1027 mContext->getDiagnostics()->error(DiagMessage(input) << "invalid static library");
1028 return false;
1029 }
1030
1031 ResourceTablePackage* pkg = table->findPackageById(0x7f);
1032 if (!pkg) {
1033 mContext->getDiagnostics()->error(DiagMessage(input)
1034 << "static library has no package");
1035 return false;
1036 }
1037
1038 bool result;
1039 if (mOptions.noStaticLibPackages) {
1040 // Merge all resources as if they were in the compilation package. This is the old
1041 // behaviour of aapt.
1042
1043 // Add the package to the set of --extra-packages so we emit an R.java for each
1044 // library package.
1045 if (!pkg->name.empty()) {
1046 mOptions.extraJavaPackages.insert(pkg->name);
1047 }
1048
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001049 pkg->name = "";
Adam Lesinski64587af2016-02-18 18:33:06 -08001050 if (override) {
1051 result = mTableMerger->mergeOverlay(Source(input), table.get(), collection.get());
1052 } else {
1053 result = mTableMerger->merge(Source(input), table.get(), collection.get());
1054 }
1055
1056 } else {
1057 // This is the proper way to merge libraries, where the package name is preserved
1058 // and resource names are mangled.
1059 result = mTableMerger->mergeAndMangle(Source(input), pkg->name, table.get(),
1060 collection.get());
1061 }
1062
1063 if (!result) {
1064 return false;
1065 }
1066
1067 // Make sure to move the collection into the set of IFileCollections.
1068 mCollections.push_back(std::move(collection));
Adam Lesinskifb48d292015-11-07 15:52:13 -08001069 return true;
1070 }
1071
Adam Lesinskia40e9722015-11-24 19:11:46 -08001072 bool mergeResourceTable(io::IFile* file, bool override) {
Adam Lesinski355f2852016-02-13 20:26:45 -08001073 if (mContext->verbose()) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001074 mContext->getDiagnostics()->note(DiagMessage() << "merging resource table "
1075 << file->getSource());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001076 }
1077
Adam Lesinskia40e9722015-11-24 19:11:46 -08001078 std::unique_ptr<io::IData> data = file->openAsData();
1079 if (!data) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001080 mContext->getDiagnostics()->error(DiagMessage(file->getSource())
Adam Lesinskia40e9722015-11-24 19:11:46 -08001081 << "failed to open file");
1082 return false;
1083 }
1084
Adam Lesinski355f2852016-02-13 20:26:45 -08001085 std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(),
1086 data->data(), data->size(),
1087 mContext->getDiagnostics());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001088 if (!table) {
1089 return false;
1090 }
1091
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001092 bool result = false;
1093 if (override) {
1094 result = mTableMerger->mergeOverlay(file->getSource(), table.get());
1095 } else {
1096 result = mTableMerger->merge(file->getSource(), table.get());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001097 }
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001098 return result;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001099 }
1100
Adam Lesinski64587af2016-02-18 18:33:06 -08001101 bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc, bool override) {
Adam Lesinski355f2852016-02-13 20:26:45 -08001102 if (mContext->verbose()) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001103 mContext->getDiagnostics()->note(DiagMessage() << "merging compiled file "
1104 << file->getSource());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001105 }
1106
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001107 bool result = false;
Adam Lesinski64587af2016-02-18 18:33:06 -08001108 if (override) {
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001109 result = mTableMerger->mergeFileOverlay(*fileDesc, file);
Adam Lesinskifb48d292015-11-07 15:52:13 -08001110 } else {
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001111 result = mTableMerger->mergeFile(*fileDesc, file);
Adam Lesinskifb48d292015-11-07 15:52:13 -08001112 }
1113
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001114 if (!result) {
Adam Lesinskifb48d292015-11-07 15:52:13 -08001115 return false;
1116 }
1117
1118 // Add the exports of this file to the table.
Adam Lesinskia40e9722015-11-24 19:11:46 -08001119 for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
Adam Lesinskifb48d292015-11-07 15:52:13 -08001120 if (exportedSymbol.name.package.empty()) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001121 exportedSymbol.name.package = mContext->getCompilationPackage();
Adam Lesinskifb48d292015-11-07 15:52:13 -08001122 }
1123
1124 ResourceNameRef resName = exportedSymbol.name;
1125
Adam Lesinski6a008172016-02-02 17:02:58 -08001126 Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
Adam Lesinskifb48d292015-11-07 15:52:13 -08001127 exportedSymbol.name);
1128 if (mangledName) {
1129 resName = mangledName.value();
1130 }
1131
1132 std::unique_ptr<Id> id = util::make_unique<Id>();
Adam Lesinskia40e9722015-11-24 19:11:46 -08001133 id->setSource(fileDesc->source.withLine(exportedSymbol.line));
Adam Lesinski64587af2016-02-18 18:33:06 -08001134 bool result = mFinalTable.addResourceAllowMangled(
1135 resName, ConfigDescription::defaultConfig(), std::string(), std::move(id),
1136 mContext->getDiagnostics());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001137 if (!result) {
1138 return false;
1139 }
1140 }
Adam Lesinskifb48d292015-11-07 15:52:13 -08001141 return true;
1142 }
1143
Adam Lesinskia40e9722015-11-24 19:11:46 -08001144 /**
Adam Lesinski64587af2016-02-18 18:33:06 -08001145 * Takes a path to load as a ZIP file and merges the files within into the master ResourceTable.
1146 * If override is true, conflicting resources are allowed to override each other, in order of
1147 * last seen.
1148 *
1149 * An io::IFileCollection is created from the ZIP file and added to the set of
1150 * io::IFileCollections that are open.
Adam Lesinskia40e9722015-11-24 19:11:46 -08001151 */
1152 bool mergeArchive(const std::string& input, bool override) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001153 if (mContext->verbose()) {
1154 mContext->getDiagnostics()->note(DiagMessage() << "merging archive " << input);
1155 }
1156
Adam Lesinskia40e9722015-11-24 19:11:46 -08001157 std::string errorStr;
Adam Lesinski64587af2016-02-18 18:33:06 -08001158 std::unique_ptr<io::ZipFileCollection> collection =
1159 io::ZipFileCollection::create(input, &errorStr);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001160 if (!collection) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001161 mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001162 return false;
1163 }
1164
1165 bool error = false;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001166 for (auto iter = collection->iterator(); iter->hasNext(); ) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001167 if (!mergeFile(iter->next(), override)) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001168 error = true;
1169 }
1170 }
1171
1172 // Make sure to move the collection into the set of IFileCollections.
1173 mCollections.push_back(std::move(collection));
1174 return !error;
1175 }
1176
Adam Lesinski64587af2016-02-18 18:33:06 -08001177 /**
1178 * Takes a path to load and merge into the master ResourceTable. If override is true,
1179 * conflicting resources are allowed to override each other, in order of last seen.
1180 *
1181 * If the file path ends with .flata, .jar, .jack, or .zip the file is treated as ZIP archive
1182 * and the files within are merged individually.
1183 *
1184 * Otherwise the files is processed on its own.
1185 */
1186 bool mergePath(const std::string& path, bool override) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001187 if (util::stringEndsWith(path, ".flata") ||
1188 util::stringEndsWith(path, ".jar") ||
1189 util::stringEndsWith(path, ".jack") ||
1190 util::stringEndsWith(path, ".zip")) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001191 return mergeArchive(path, override);
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001192 } else if (util::stringEndsWith(path, ".apk")) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001193 return mergeStaticLibrary(path, override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001194 }
1195
1196 io::IFile* file = mFileCollection->insertFile(path);
Adam Lesinski64587af2016-02-18 18:33:06 -08001197 return mergeFile(file, override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001198 }
1199
Adam Lesinski64587af2016-02-18 18:33:06 -08001200 /**
1201 * Takes a file to load and merge into the master ResourceTable. If override is true,
1202 * conflicting resources are allowed to override each other, in order of last seen.
1203 *
1204 * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and merged into the
1205 * master ResourceTable. If the file ends with .flat, then it is treated like a compiled file
1206 * and the header data is read and merged into the final ResourceTable.
1207 *
1208 * All other file types are ignored. This is because these files could be coming from a zip,
1209 * where we could have other files like classes.dex.
1210 */
1211 bool mergeFile(io::IFile* file, bool override) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001212 const Source& src = file->getSource();
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001213 if (util::stringEndsWith(src.path, ".arsc.flat")) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001214 return mergeResourceTable(file, override);
Adam Lesinski64587af2016-02-18 18:33:06 -08001215
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001216 } else if (util::stringEndsWith(src.path, ".flat")){
Adam Lesinskia40e9722015-11-24 19:11:46 -08001217 // Try opening the file and looking for an Export header.
1218 std::unique_ptr<io::IData> data = file->openAsData();
1219 if (!data) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001220 mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open");
Adam Lesinskia40e9722015-11-24 19:11:46 -08001221 return false;
1222 }
1223
1224 std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader(
Adam Lesinski6a008172016-02-02 17:02:58 -08001225 src, data->data(), data->size(), mContext->getDiagnostics());
Adam Lesinskia40e9722015-11-24 19:11:46 -08001226 if (resourceFile) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001227 return mergeCompiledFile(file, resourceFile.get(), override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001228 }
Adam Lesinskic446a732016-01-21 11:04:46 -08001229 return false;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001230 }
Adam Lesinski52364f72016-01-11 13:10:24 -08001231
Adam Lesinskic446a732016-01-21 11:04:46 -08001232 // Ignore non .flat files. This could be classes.dex or something else that happens
1233 // to be in an archive.
1234 return true;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001235 }
1236
Adam Lesinski36c73a52016-08-11 13:39:24 -07001237 std::unique_ptr<xml::XmlResource> generateSplitManifest(const AppInfo& appInfo,
1238 const SplitConstraints& constraints) {
1239 std::unique_ptr<xml::XmlResource> doc = util::make_unique<xml::XmlResource>();
1240
1241 std::unique_ptr<xml::Namespace> namespaceAndroid = util::make_unique<xml::Namespace>();
1242 namespaceAndroid->namespaceUri = xml::kSchemaAndroid;
1243 namespaceAndroid->namespacePrefix = "android";
1244
1245 std::unique_ptr<xml::Element> manifestEl = util::make_unique<xml::Element>();
1246 manifestEl->name = "manifest";
1247 manifestEl->attributes.push_back(
1248 xml::Attribute{ "", "package", appInfo.package });
1249
1250 if (appInfo.versionCode) {
1251 manifestEl->attributes.push_back(xml::Attribute{
1252 xml::kSchemaAndroid,
1253 "versionCode",
1254 std::to_string(appInfo.versionCode.value()) });
1255 }
1256
1257 if (appInfo.revisionCode) {
1258 manifestEl->attributes.push_back(xml::Attribute{
1259 xml::kSchemaAndroid,
1260 "revisionCode", std::to_string(appInfo.revisionCode.value()) });
1261 }
1262
1263 std::stringstream splitName;
1264 splitName << "config." << util::joiner(constraints.configs, "_");
1265
1266 manifestEl->attributes.push_back(
1267 xml::Attribute{ "", "split", splitName.str() });
1268
1269 std::unique_ptr<xml::Element> applicationEl = util::make_unique<xml::Element>();
1270 applicationEl->name = "application";
1271 applicationEl->attributes.push_back(
1272 xml::Attribute{ xml::kSchemaAndroid, "hasCode", "false" });
1273
1274 manifestEl->addChild(std::move(applicationEl));
1275 namespaceAndroid->addChild(std::move(manifestEl));
1276 doc->root = std::move(namespaceAndroid);
1277 return doc;
1278 }
1279
1280 /**
1281 * Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable
1282 * to the IArchiveWriter.
1283 */
1284 bool writeApk(IArchiveWriter* writer, proguard::KeepSet* keepSet, xml::XmlResource* manifest,
1285 ResourceTable* table) {
1286 const bool keepRawValues = mOptions.staticLib;
1287 bool result = flattenXml(manifest, "AndroidManifest.xml", {}, keepRawValues, writer,
1288 mContext);
1289 if (!result) {
1290 return false;
1291 }
1292
1293 ResourceFileFlattenerOptions fileFlattenerOptions;
1294 fileFlattenerOptions.keepRawValues = keepRawValues;
1295 fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything;
1296 fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress;
1297 fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion;
1298 fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors;
1299 fileFlattenerOptions.updateProguardSpec =
1300 static_cast<bool>(mOptions.generateProguardRulesPath);
1301
1302 ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, keepSet);
1303
1304 if (!fileFlattener.flatten(table, writer)) {
1305 mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources");
1306 return false;
1307 }
1308
1309 if (mOptions.staticLib) {
1310 if (!flattenTableToPb(table, writer)) {
1311 mContext->getDiagnostics()->error(DiagMessage()
1312 << "failed to write resources.arsc.flat");
1313 return false;
1314 }
1315 } else {
1316 if (!flattenTable(table, writer)) {
1317 mContext->getDiagnostics()->error(DiagMessage()
1318 << "failed to write resources.arsc");
1319 return false;
1320 }
1321 }
1322 return true;
1323 }
1324
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001325 int run(const std::vector<std::string>& inputFiles) {
1326 // Load the AndroidManifest.xml
Adam Lesinskia40e9722015-11-24 19:11:46 -08001327 std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
Adam Lesinski6a008172016-02-02 17:02:58 -08001328 mContext->getDiagnostics());
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001329 if (!manifestXml) {
1330 return 1;
1331 }
1332
Adam Lesinski36c73a52016-08-11 13:39:24 -07001333 // First extract the Package name without modifying it (via --rename-manifest-package).
Adam Lesinskifb6312f2016-06-28 14:40:32 -07001334 if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(),
1335 mContext->getDiagnostics())) {
Adam Lesinski36c73a52016-08-11 13:39:24 -07001336 const AppInfo& appInfo = maybeAppInfo.value();
Adam Lesinskifb6312f2016-06-28 14:40:32 -07001337 mContext->setCompilationPackage(appInfo.package);
Adam Lesinski36c73a52016-08-11 13:39:24 -07001338 }
1339
1340 ManifestFixer manifestFixer(mOptions.manifestFixerOptions);
1341 if (!manifestFixer.consume(mContext, manifestXml.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001342 return 1;
1343 }
1344
Adam Lesinski36c73a52016-08-11 13:39:24 -07001345 Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(),
1346 mContext->getDiagnostics());
1347 if (!maybeAppInfo) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001348 return 1;
1349 }
1350
Adam Lesinski36c73a52016-08-11 13:39:24 -07001351 const AppInfo& appInfo = maybeAppInfo.value();
1352 if (appInfo.minSdkVersion) {
1353 if (Maybe<int> maybeMinSdkVersion =
1354 ResourceUtils::parseSdkVersion(appInfo.minSdkVersion.value())) {
1355 mContext->setMinSdkVersion(maybeMinSdkVersion.value());
1356 }
1357 }
1358
Adam Lesinski64587af2016-02-18 18:33:06 -08001359 mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() });
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001360 if (mContext->getCompilationPackage() == "android") {
Adam Lesinski64587af2016-02-18 18:33:06 -08001361 mContext->setPackageId(0x01);
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001362 } else {
Adam Lesinski64587af2016-02-18 18:33:06 -08001363 mContext->setPackageId(0x7f);
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001364 }
1365
Adam Lesinski64587af2016-02-18 18:33:06 -08001366 if (!loadSymbolsFromIncludePaths()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001367 return 1;
1368 }
1369
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001370 TableMergerOptions tableMergerOptions;
1371 tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
Adam Lesinski6a008172016-02-02 17:02:58 -08001372 mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions);
Adam Lesinskifb48d292015-11-07 15:52:13 -08001373
Adam Lesinski355f2852016-02-13 20:26:45 -08001374 if (mContext->verbose()) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001375 mContext->getDiagnostics()->note(
Adam Lesinski64587af2016-02-18 18:33:06 -08001376 DiagMessage() << "linking package '" << mContext->getCompilationPackage()
1377 << "' with package ID " << std::hex
1378 << (int) mContext->getPackageId());
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001379 }
1380
Adam Lesinskifb48d292015-11-07 15:52:13 -08001381
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001382 for (const std::string& input : inputFiles) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001383 if (!mergePath(input, false)) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001384 mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input");
Adam Lesinski467f1712015-11-16 17:35:44 -08001385 return 1;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001386 }
1387 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001388
Adam Lesinskifb48d292015-11-07 15:52:13 -08001389 for (const std::string& input : mOptions.overlayFiles) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001390 if (!mergePath(input, true)) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001391 mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
Adam Lesinski467f1712015-11-16 17:35:44 -08001392 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001393 }
1394 }
1395
Adam Lesinskifb48d292015-11-07 15:52:13 -08001396 if (!verifyNoExternalPackages()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001397 return 1;
1398 }
1399
1400 if (!mOptions.staticLib) {
1401 PrivateAttributeMover mover;
Adam Lesinski6a008172016-02-02 17:02:58 -08001402 if (!mover.consume(mContext, &mFinalTable)) {
1403 mContext->getDiagnostics()->error(
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001404 DiagMessage() << "failed moving private attributes");
1405 return 1;
1406 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001407
Adam Lesinski64587af2016-02-18 18:33:06 -08001408 // Assign IDs if we are building a regular app.
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001409 IdAssigner idAssigner(&mOptions.stableIdMap);
Adam Lesinski6a008172016-02-02 17:02:58 -08001410 if (!idAssigner.consume(mContext, &mFinalTable)) {
1411 mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001412 return 1;
1413 }
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001414
1415 // Now grab each ID and emit it as a file.
1416 if (mOptions.resourceIdMapPath) {
1417 for (auto& package : mFinalTable.packages) {
1418 for (auto& type : package->types) {
1419 for (auto& entry : type->entries) {
1420 ResourceName name(package->name, type->type, entry->name);
1421 // The IDs are guaranteed to exist.
1422 mOptions.stableIdMap[std::move(name)] = ResourceId(package->id.value(),
1423 type->id.value(),
1424 entry->id.value());
1425 }
1426 }
1427 }
1428
1429 if (!writeStableIdMapToPath(mContext->getDiagnostics(),
1430 mOptions.stableIdMap,
1431 mOptions.resourceIdMapPath.value())) {
1432 return 1;
1433 }
1434 }
Adam Lesinski64587af2016-02-18 18:33:06 -08001435 } else {
1436 // Static libs are merged with other apps, and ID collisions are bad, so verify that
1437 // no IDs have been set.
1438 if (!verifyNoIdsSet()) {
1439 return 1;
1440 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001441 }
1442
Adam Lesinski64587af2016-02-18 18:33:06 -08001443 // Add the names to mangle based on our source merge earlier.
1444 mContext->setNameManglerPolicy(NameManglerPolicy{
1445 mContext->getCompilationPackage(), mTableMerger->getMergedPackages() });
1446
1447 // Add our table to the symbol table.
1448 mContext->getExternalSymbols()->prependSource(
1449 util::make_unique<ResourceTableSymbolSource>(&mFinalTable));
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001450
Adam Lesinski36c73a52016-08-11 13:39:24 -07001451 ReferenceLinker linker;
1452 if (!linker.consume(mContext, &mFinalTable)) {
1453 mContext->getDiagnostics()->error(DiagMessage() << "failed linking references");
1454 return 1;
1455 }
1456
1457 if (mOptions.staticLib) {
1458 if (!mOptions.products.empty()) {
1459 mContext->getDiagnostics()->warn(
1460 DiagMessage() << "can't select products when building static library");
1461 }
1462 } else {
1463 ProductFilter productFilter(mOptions.products);
1464 if (!productFilter.consume(mContext, &mFinalTable)) {
1465 mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001466 return 1;
1467 }
Adam Lesinski36c73a52016-08-11 13:39:24 -07001468 }
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001469
Adam Lesinski36c73a52016-08-11 13:39:24 -07001470 if (!mOptions.noAutoVersion) {
1471 AutoVersioner versioner;
1472 if (!versioner.consume(mContext, &mFinalTable)) {
1473 mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles");
1474 return 1;
1475 }
1476 }
Adam Lesinski355f2852016-02-13 20:26:45 -08001477
Adam Lesinski36c73a52016-08-11 13:39:24 -07001478 if (!mOptions.staticLib && mContext->getMinSdkVersion() > 0) {
1479 if (mContext->verbose()) {
1480 mContext->getDiagnostics()->note(
1481 DiagMessage() << "collapsing resource versions for minimum SDK "
1482 << mContext->getMinSdkVersion());
1483 }
Adam Lesinski355f2852016-02-13 20:26:45 -08001484
Adam Lesinski36c73a52016-08-11 13:39:24 -07001485 VersionCollapser collapser;
1486 if (!collapser.consume(mContext, &mFinalTable)) {
1487 return 1;
Adam Lesinski64587af2016-02-18 18:33:06 -08001488 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001489 }
1490
1491 proguard::KeepSet proguardKeepSet;
Rohit Agrawale49bb302016-04-22 12:27:55 -07001492 proguard::KeepSet proguardMainDexKeepSet;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001493
Adam Lesinski36c73a52016-08-11 13:39:24 -07001494 if (mOptions.staticLib) {
1495 if (mOptions.tableSplitterOptions.configFilter != nullptr ||
1496 mOptions.tableSplitterOptions.preferredDensity) {
1497 mContext->getDiagnostics()->warn(
1498 DiagMessage() << "can't strip resources when building static library");
1499 }
1500 } else {
1501 // Adjust the SplitConstraints so that their SDK version is stripped if it is less
1502 // than or equal to the minSdk. Otherwise the resources that have had their SDK version
1503 // stripped due to minSdk won't ever match.
1504 std::vector<SplitConstraints> adjustedConstraintsList;
1505 adjustedConstraintsList.reserve(mOptions.splitConstraints.size());
1506 for (const SplitConstraints& constraints : mOptions.splitConstraints) {
1507 SplitConstraints adjustedConstraints;
1508 for (const ConfigDescription& config : constraints.configs) {
1509 if (config.sdkVersion <= mContext->getMinSdkVersion()) {
1510 adjustedConstraints.configs.insert(config.copyWithoutSdkVersion());
1511 } else {
1512 adjustedConstraints.configs.insert(config);
1513 }
1514 }
1515 adjustedConstraintsList.push_back(std::move(adjustedConstraints));
1516 }
1517
1518 TableSplitter tableSplitter(adjustedConstraintsList, mOptions.tableSplitterOptions);
1519 if (!tableSplitter.verifySplitConstraints(mContext)) {
1520 return 1;
1521 }
1522 tableSplitter.splitTable(&mFinalTable);
1523
1524 // Now we need to write out the Split APKs.
1525 auto pathIter = mOptions.splitPaths.begin();
1526 auto splitConstraintsIter = adjustedConstraintsList.begin();
1527 for (std::unique_ptr<ResourceTable>& splitTable : tableSplitter.getSplits()) {
1528 if (mContext->verbose()) {
1529 mContext->getDiagnostics()->note(
1530 DiagMessage(*pathIter) << "generating split with configurations '"
1531 << util::joiner(splitConstraintsIter->configs, ", ") << "'");
1532 }
1533
1534 std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(*pathIter);
1535 if (!archiveWriter) {
1536 mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive");
1537 return 1;
1538 }
1539
1540 // Generate an AndroidManifest.xml for each split.
1541 std::unique_ptr<xml::XmlResource> splitManifest =
1542 generateSplitManifest(appInfo, *splitConstraintsIter);
1543
1544 XmlReferenceLinker linker;
1545 if (!linker.consume(mContext, splitManifest.get())) {
1546 mContext->getDiagnostics()->error(
1547 DiagMessage() << "failed to create Split AndroidManifest.xml");
1548 return 1;
1549 }
1550
1551 if (!writeApk(archiveWriter.get(), &proguardKeepSet, splitManifest.get(),
1552 splitTable.get())) {
1553 return 1;
1554 }
1555
1556 ++pathIter;
1557 ++splitConstraintsIter;
1558 }
1559 }
1560
1561 // Start writing the base APK.
1562 std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(mOptions.outputPath);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001563 if (!archiveWriter) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001564 mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001565 return 1;
1566 }
1567
Adam Lesinski467f1712015-11-16 17:35:44 -08001568 bool error = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001569 {
Adam Lesinski467f1712015-11-16 17:35:44 -08001570 // AndroidManifest.xml has no resource name, but the CallSite is built from the name
1571 // (aka, which package the AndroidManifest.xml is coming from).
1572 // So we give it a package name so it can see local resources.
Adam Lesinski64587af2016-02-18 18:33:06 -08001573 manifestXml->file.name.package = mContext->getCompilationPackage();
Adam Lesinski467f1712015-11-16 17:35:44 -08001574
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001575 XmlReferenceLinker manifestLinker;
Adam Lesinski6a008172016-02-02 17:02:58 -08001576 if (manifestLinker.consume(mContext, manifestXml.get())) {
Rohit Agrawale49bb302016-04-22 12:27:55 -07001577 if (mOptions.generateProguardRulesPath &&
1578 !proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
1579 manifestXml.get(),
1580 &proguardKeepSet)) {
1581 error = true;
1582 }
1583
1584 if (mOptions.generateMainDexProguardRulesPath &&
1585 !proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
1586 manifestXml.get(),
1587 &proguardMainDexKeepSet,
1588 true)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001589 error = true;
1590 }
1591
Adam Lesinskica5638f2015-10-21 14:42:43 -07001592 if (mOptions.generateJavaClassPath) {
1593 if (!writeManifestJavaFile(manifestXml.get())) {
1594 error = true;
1595 }
1596 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001597 } else {
1598 error = true;
1599 }
1600 }
1601
Adam Lesinski467f1712015-11-16 17:35:44 -08001602 if (error) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001603 mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest");
Adam Lesinski467f1712015-11-16 17:35:44 -08001604 return 1;
1605 }
1606
Adam Lesinski36c73a52016-08-11 13:39:24 -07001607 if (!writeApk(archiveWriter.get(), &proguardKeepSet, manifestXml.get(), &mFinalTable)) {
Alexandria Cornwall637b4822016-08-11 09:53:16 -07001608 return 1;
1609 }
1610
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001611 if (mOptions.generateJavaClassPath) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001612 JavaClassGeneratorOptions options;
Adam Lesinski52364f72016-01-11 13:10:24 -08001613 options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
Adam Lesinski3524a232016-04-01 19:19:24 -07001614 options.javadocAnnotations = mOptions.javadocAnnotations;
Adam Lesinski52364f72016-01-11 13:10:24 -08001615
Adam Lesinskief9c5012016-01-22 14:09:53 -08001616 if (mOptions.staticLib || mOptions.generateNonFinalIds) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001617 options.useFinal = false;
1618 }
1619
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001620 const StringPiece actualPackage = mContext->getCompilationPackage();
1621 StringPiece outputPackage = mContext->getCompilationPackage();
Adam Lesinski52364f72016-01-11 13:10:24 -08001622 if (mOptions.customJavaPackage) {
1623 // Override the output java package to the custom one.
1624 outputPackage = mOptions.customJavaPackage.value();
1625 }
Adam Lesinski83f22552015-11-07 11:51:23 -08001626
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001627 if (mOptions.privateSymbols) {
1628 // If we defined a private symbols package, we only emit Public symbols
1629 // to the original package, and private and public symbols to the private package.
1630
1631 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
Adam Lesinski6a008172016-02-02 17:02:58 -08001632 if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(),
Adam Lesinski52364f72016-01-11 13:10:24 -08001633 outputPackage, options)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001634 return 1;
1635 }
1636
1637 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
Adam Lesinski83f22552015-11-07 11:51:23 -08001638 outputPackage = mOptions.privateSymbols.value();
1639 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001640
Adam Lesinskifb48d292015-11-07 15:52:13 -08001641 if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
Adam Lesinski83f22552015-11-07 11:51:23 -08001642 return 1;
1643 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001644
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001645 for (const std::string& extraPackage : mOptions.extraJavaPackages) {
Adam Lesinski52364f72016-01-11 13:10:24 -08001646 if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001647 return 1;
1648 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001649 }
1650 }
1651
Rohit Agrawale49bb302016-04-22 12:27:55 -07001652 if (!writeProguardFile(mOptions.generateProguardRulesPath, proguardKeepSet)) {
1653 return 1;
1654 }
1655
1656 if (!writeProguardFile(mOptions.generateMainDexProguardRulesPath, proguardMainDexKeepSet)) {
1657 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001658 }
1659
Adam Lesinski355f2852016-02-13 20:26:45 -08001660 if (mContext->verbose()) {
1661 DebugPrintTableOptions debugPrintTableOptions;
1662 debugPrintTableOptions.showSources = true;
1663 Debug::printTable(&mFinalTable, debugPrintTableOptions);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001664 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001665 return 0;
1666 }
Adam Lesinskifb48d292015-11-07 15:52:13 -08001667
1668private:
1669 LinkOptions mOptions;
Adam Lesinski6a008172016-02-02 17:02:58 -08001670 LinkContext* mContext;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001671 ResourceTable mFinalTable;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001672
Adam Lesinskifb48d292015-11-07 15:52:13 -08001673 std::unique_ptr<TableMerger> mTableMerger;
1674
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001675 // A pointer to the FileCollection representing the filesystem (not archives).
Adam Lesinski64587af2016-02-18 18:33:06 -08001676 std::unique_ptr<io::FileCollection> mFileCollection;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001677
1678 // A vector of IFileCollections. This is mainly here to keep ownership of the collections.
Adam Lesinskia40e9722015-11-24 19:11:46 -08001679 std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
Adam Lesinski64587af2016-02-18 18:33:06 -08001680
1681 // A vector of ResourceTables. This is here to retain ownership, so that the SymbolTable
1682 // can use these.
1683 std::vector<std::unique_ptr<ResourceTable>> mStaticTableIncludes;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001684};
1685
1686int link(const std::vector<StringPiece>& args) {
Adam Lesinski355f2852016-02-13 20:26:45 -08001687 LinkContext context;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001688 LinkOptions options;
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001689 std::vector<std::string> overlayArgList;
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001690 std::vector<std::string> extraJavaPackages;
Adam Lesinski6a008172016-02-02 17:02:58 -08001691 Maybe<std::string> configs;
Adam Lesinski355f2852016-02-13 20:26:45 -08001692 Maybe<std::string> preferredDensity;
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001693 Maybe<std::string> productList;
Adam Lesinski8900aa82016-01-25 22:48:15 -08001694 bool legacyXFlag = false;
1695 bool requireLocalization = false;
Adam Lesinski64587af2016-02-18 18:33:06 -08001696 bool verbose = false;
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001697 Maybe<std::string> stableIdFilePath;
Adam Lesinski36c73a52016-08-11 13:39:24 -07001698 std::vector<std::string> splitArgs;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001699 Flags flags = Flags()
1700 .requiredFlag("-o", "Output path", &options.outputPath)
1701 .requiredFlag("--manifest", "Path to the Android manifest to build",
1702 &options.manifestPath)
1703 .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
Adam Lesinski52364f72016-01-11 13:10:24 -08001704 .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n"
Adam Lesinskifb48d292015-11-07 15:52:13 -08001705 "The last conflicting resource given takes precedence.",
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001706 &overlayArgList)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001707 .optionalFlag("--java", "Directory in which to generate R.java",
1708 &options.generateJavaClassPath)
1709 .optionalFlag("--proguard", "Output file for generated Proguard rules",
1710 &options.generateProguardRulesPath)
Rohit Agrawale49bb302016-04-22 12:27:55 -07001711 .optionalFlag("--proguard-main-dex",
1712 "Output file for generated Proguard rules for the main dex",
1713 &options.generateMainDexProguardRulesPath)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001714 .optionalSwitch("--no-auto-version",
1715 "Disables automatic style and layout SDK versioning",
1716 &options.noAutoVersion)
Adam Lesinski64587af2016-02-18 18:33:06 -08001717 .optionalSwitch("--no-version-vectors",
1718 "Disables automatic versioning of vector drawables. Use this only\n"
1719 "when building with vector drawable support library",
1720 &options.noVersionVectors)
Adam Lesinski8900aa82016-01-25 22:48:15 -08001721 .optionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01",
1722 &legacyXFlag)
1723 .optionalSwitch("-z", "Require localization of strings marked 'suggested'",
1724 &requireLocalization)
Adam Lesinski6a008172016-02-02 17:02:58 -08001725 .optionalFlag("-c", "Comma separated list of configurations to include. The default\n"
1726 "is all configurations", &configs)
Adam Lesinski355f2852016-02-13 20:26:45 -08001727 .optionalFlag("--preferred-density",
1728 "Selects the closest matching density and strips out all others.",
1729 &preferredDensity)
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001730 .optionalFlag("--product", "Comma separated list of product names to keep",
1731 &productList)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001732 .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
1733 "by -o",
1734 &options.outputToDirectory)
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001735 .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001736 "AndroidManifest.xml",
1737 &options.manifestFixerOptions.minSdkVersionDefault)
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001738 .optionalFlag("--target-sdk-version", "Default target SDK version to use for "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001739 "AndroidManifest.xml",
1740 &options.manifestFixerOptions.targetSdkVersionDefault)
Adam Lesinski52364f72016-01-11 13:10:24 -08001741 .optionalFlag("--version-code", "Version code (integer) to inject into the "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001742 "AndroidManifest.xml if none is present",
1743 &options.manifestFixerOptions.versionCodeDefault)
Adam Lesinski52364f72016-01-11 13:10:24 -08001744 .optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001745 "if none is present",
1746 &options.manifestFixerOptions.versionNameDefault)
1747 .optionalSwitch("--static-lib", "Generate a static Android library",
1748 &options.staticLib)
Adam Lesinski64587af2016-02-18 18:33:06 -08001749 .optionalSwitch("--no-static-lib-packages",
1750 "Merge all library resources under the app's package",
1751 &options.noStaticLibPackages)
Adam Lesinskief9c5012016-01-22 14:09:53 -08001752 .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n"
1753 "This is implied when --static-lib is specified.",
1754 &options.generateNonFinalIds)
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001755 .optionalFlag("--stable-ids", "File containing a list of name to ID mapping.",
1756 &stableIdFilePath)
1757 .optionalFlag("--emit-ids", "Emit a file at the given path with a list of name to ID\n"
1758 "mappings, suitable for use with --stable-ids.",
1759 &options.resourceIdMapPath)
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001760 .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001761 "private symbols.\n"
1762 "If not specified, public and private symbols will use the application's "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001763 "package name",
1764 &options.privateSymbols)
Adam Lesinski52364f72016-01-11 13:10:24 -08001765 .optionalFlag("--custom-package", "Custom Java package under which to generate R.java",
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001766 &options.customJavaPackage)
Adam Lesinski83f22552015-11-07 11:51:23 -08001767 .optionalFlagList("--extra-packages", "Generate the same R.java but with different "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001768 "package names",
1769 &extraJavaPackages)
Adam Lesinski3524a232016-04-01 19:19:24 -07001770 .optionalFlagList("--add-javadoc-annotation", "Adds a JavaDoc annotation to all "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001771 "generated Java classes",
1772 &options.javadocAnnotations)
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001773 .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001774 "overlays without <add-resource> tags",
1775 &options.autoAddOverlay)
Adam Lesinski52364f72016-01-11 13:10:24 -08001776 .optionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml",
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001777 &options.manifestFixerOptions.renameManifestPackage)
Adam Lesinski52364f72016-01-11 13:10:24 -08001778 .optionalFlag("--rename-instrumentation-target-package",
1779 "Changes the name of the target package for instrumentation. Most useful "
1780 "when used\nin conjunction with --rename-manifest-package",
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001781 &options.manifestFixerOptions.renameInstrumentationTargetPackage)
Adam Lesinski52364f72016-01-11 13:10:24 -08001782 .optionalFlagList("-0", "File extensions not to compress",
1783 &options.extensionsToNotCompress)
Adam Lesinski36c73a52016-08-11 13:39:24 -07001784 .optionalFlagList("--split", "Split resources matching a set of configs out to a "
1785 "Split APK.\nSyntax: path/to/output.apk:<config>[,<config>[...]]",
1786 &splitArgs)
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001787 .optionalSwitch("-v", "Enables verbose logging",
1788 &verbose);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001789
1790 if (!flags.parse("aapt2 link", args, &std::cerr)) {
1791 return 1;
1792 }
1793
Adam Lesinskic51562c2016-04-28 11:12:38 -07001794 // Expand all argument-files passed into the command line. These start with '@'.
1795 std::vector<std::string> argList;
1796 for (const std::string& arg : flags.getArgs()) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001797 if (util::stringStartsWith(arg, "@")) {
Adam Lesinskic51562c2016-04-28 11:12:38 -07001798 const std::string path = arg.substr(1, arg.size() - 1);
1799 std::string error;
1800 if (!file::appendArgsFromFile(path, &argList, &error)) {
1801 context.getDiagnostics()->error(DiagMessage(path) << error);
1802 return 1;
1803 }
1804 } else {
1805 argList.push_back(arg);
1806 }
1807 }
1808
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001809 // Expand all argument-files passed to -R.
1810 for (const std::string& arg : overlayArgList) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001811 if (util::stringStartsWith(arg, "@")) {
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001812 const std::string path = arg.substr(1, arg.size() - 1);
1813 std::string error;
1814 if (!file::appendArgsFromFile(path, &options.overlayFiles, &error)) {
1815 context.getDiagnostics()->error(DiagMessage(path) << error);
1816 return 1;
1817 }
1818 } else {
1819 options.overlayFiles.push_back(arg);
1820 }
1821 }
1822
Adam Lesinski64587af2016-02-18 18:33:06 -08001823 if (verbose) {
1824 context.setVerbose(verbose);
1825 }
1826
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001827 // Populate the set of extra packages for which to generate R.java.
1828 for (std::string& extraPackage : extraJavaPackages) {
1829 // A given package can actually be a colon separated list of packages.
1830 for (StringPiece package : util::split(extraPackage, ':')) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001831 options.extraJavaPackages.insert(package.toString());
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001832 }
1833 }
1834
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001835 if (productList) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001836 for (StringPiece product : util::tokenize(productList.value(), ',')) {
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001837 if (product != "" && product != "default") {
1838 options.products.insert(product.toString());
1839 }
1840 }
1841 }
1842
Adam Lesinski6a008172016-02-02 17:02:58 -08001843 AxisConfigFilter filter;
1844 if (configs) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001845 for (const StringPiece& configStr : util::tokenize(configs.value(), ',')) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001846 ConfigDescription config;
1847 LocaleValue lv;
1848 if (lv.initFromFilterString(configStr)) {
1849 lv.writeTo(&config);
1850 } else if (!ConfigDescription::parse(configStr, &config)) {
1851 context.getDiagnostics()->error(
1852 DiagMessage() << "invalid config '" << configStr << "' for -c option");
1853 return 1;
1854 }
1855
1856 if (config.density != 0) {
1857 context.getDiagnostics()->warn(
1858 DiagMessage() << "ignoring density '" << config << "' for -c option");
1859 } else {
1860 filter.addConfig(config);
1861 }
1862 }
1863
Adam Lesinski355f2852016-02-13 20:26:45 -08001864 options.tableSplitterOptions.configFilter = &filter;
1865 }
1866
1867 if (preferredDensity) {
1868 ConfigDescription preferredDensityConfig;
1869 if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) {
1870 context.getDiagnostics()->error(DiagMessage() << "invalid density '"
1871 << preferredDensity.value()
1872 << "' for --preferred-density option");
1873 return 1;
1874 }
1875
1876 // Clear the version that can be automatically added.
1877 preferredDensityConfig.sdkVersion = 0;
1878
1879 if (preferredDensityConfig.diff(ConfigDescription::defaultConfig())
1880 != ConfigDescription::CONFIG_DENSITY) {
1881 context.getDiagnostics()->error(DiagMessage() << "invalid preferred density '"
1882 << preferredDensity.value() << "'. "
1883 << "Preferred density must only be a density value");
1884 return 1;
1885 }
1886 options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density;
Adam Lesinski6a008172016-02-02 17:02:58 -08001887 }
1888
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001889 if (!options.staticLib && stableIdFilePath) {
1890 if (!loadStableIdMap(context.getDiagnostics(), stableIdFilePath.value(),
1891 &options.stableIdMap)) {
1892 return 1;
1893 }
1894 }
1895
Adam Lesinski9756dec2016-08-08 12:35:04 -07001896 // Populate some default no-compress extensions that are already compressed.
1897 options.extensionsToNotCompress.insert({
1898 ".jpg", ".jpeg", ".png", ".gif",
1899 ".wav", ".mp2", ".mp3", ".ogg", ".aac",
1900 ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
1901 ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
1902 ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
1903 ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"});
1904
Adam Lesinski36c73a52016-08-11 13:39:24 -07001905 // Parse the split parameters.
1906 for (const std::string& splitArg : splitArgs) {
1907 options.splitPaths.push_back({});
1908 options.splitConstraints.push_back({});
1909 if (!parseSplitParameter(splitArg, context.getDiagnostics(), &options.splitPaths.back(),
1910 &options.splitConstraints.back())) {
1911 return 1;
1912 }
1913 }
1914
Adam Lesinski626a69f2016-03-03 10:09:26 -08001915 // Turn off auto versioning for static-libs.
1916 if (options.staticLib) {
1917 options.noAutoVersion = true;
1918 options.noVersionVectors = true;
1919 }
1920
Adam Lesinski6a008172016-02-02 17:02:58 -08001921 LinkCommand cmd(&context, options);
Adam Lesinskic51562c2016-04-28 11:12:38 -07001922 return cmd.run(argList);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001923}
1924
1925} // namespace aapt