blob: b6b4b4732669d102e439de48a1da2fd5980e2571 [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>
Adam Lesinski5eeaadd2016-08-25 12:26:56 -070051#include <queue>
Adam Lesinski1ab598f2015-08-14 14:26:04 -070052#include <sys/stat.h>
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -070053#include <unordered_map>
Adam Lesinski1ab598f2015-08-14 14:26:04 -070054#include <vector>
55
Adam Lesinski5eeaadd2016-08-25 12:26:56 -070056using google::protobuf::io::CopyingOutputStreamAdaptor;
57
Adam Lesinski1ab598f2015-08-14 14:26:04 -070058namespace aapt {
59
60struct LinkOptions {
61 std::string outputPath;
62 std::string manifestPath;
63 std::vector<std::string> includePaths;
Adam Lesinskifb48d292015-11-07 15:52:13 -080064 std::vector<std::string> overlayFiles;
Adam Lesinski36c73a52016-08-11 13:39:24 -070065
66 // Java/Proguard options.
Adam Lesinski1ab598f2015-08-14 14:26:04 -070067 Maybe<std::string> generateJavaClassPath;
Adam Lesinskid0f116b2016-07-08 15:00:32 -070068 Maybe<std::string> customJavaPackage;
69 std::set<std::string> extraJavaPackages;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070070 Maybe<std::string> generateProguardRulesPath;
Rohit Agrawale49bb302016-04-22 12:27:55 -070071 Maybe<std::string> generateMainDexProguardRulesPath;
Adam Lesinski36c73a52016-08-11 13:39:24 -070072
Adam Lesinski1ab598f2015-08-14 14:26:04 -070073 bool noAutoVersion = false;
Adam Lesinski64587af2016-02-18 18:33:06 -080074 bool noVersionVectors = false;
Alexandria Cornwall77788eb2016-09-06 15:16:49 -070075 bool noResourceDeduping = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070076 bool staticLib = false;
Adam Lesinski64587af2016-02-18 18:33:06 -080077 bool noStaticLibPackages = false;
Adam Lesinskief9c5012016-01-22 14:09:53 -080078 bool generateNonFinalIds = false;
Adam Lesinski3524a232016-04-01 19:19:24 -070079 std::vector<std::string> javadocAnnotations;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070080 bool outputToDirectory = false;
Alexandria Cornwalla7cc3f12016-08-16 13:33:32 -070081 bool noXmlNamespaces = false;
Adam Lesinskia6fe3452015-12-09 15:20:52 -080082 bool autoAddOverlay = false;
Adam Lesinski52364f72016-01-11 13:10:24 -080083 bool doNotCompressAnything = false;
Adam Lesinski9756dec2016-08-08 12:35:04 -070084 std::unordered_set<std::string> extensionsToNotCompress;
Adam Lesinskid0f116b2016-07-08 15:00:32 -070085 Maybe<std::string> privateSymbols;
Adam Lesinski52364f72016-01-11 13:10:24 -080086 ManifestFixerOptions manifestFixerOptions;
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -080087 std::unordered_set<std::string> products;
Adam Lesinski36c73a52016-08-11 13:39:24 -070088
89 // Split APK options.
Adam Lesinski355f2852016-02-13 20:26:45 -080090 TableSplitterOptions tableSplitterOptions;
Adam Lesinski36c73a52016-08-11 13:39:24 -070091 std::vector<SplitConstraints> splitConstraints;
92 std::vector<std::string> splitPaths;
93
94 // Stable ID options.
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -070095 std::unordered_map<ResourceName, ResourceId> stableIdMap;
96 Maybe<std::string> resourceIdMapPath;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070097};
98
Adam Lesinski64587af2016-02-18 18:33:06 -080099class LinkContext : public IAaptContext {
100public:
101 LinkContext() : mNameMangler({}) {
102 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700103
104 IDiagnostics* getDiagnostics() override {
105 return &mDiagnostics;
106 }
107
108 NameMangler* getNameMangler() override {
Adam Lesinski64587af2016-02-18 18:33:06 -0800109 return &mNameMangler;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700110 }
111
Adam Lesinski64587af2016-02-18 18:33:06 -0800112 void setNameManglerPolicy(const NameManglerPolicy& policy) {
113 mNameMangler = NameMangler(policy);
114 }
115
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700116 const std::string& getCompilationPackage() override {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700117 return mCompilationPackage;
118 }
119
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700120 void setCompilationPackage(const StringPiece& packageName) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800121 mCompilationPackage = packageName.toString();
122 }
123
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700124 uint8_t getPackageId() override {
125 return mPackageId;
126 }
127
Adam Lesinski64587af2016-02-18 18:33:06 -0800128 void setPackageId(uint8_t id) {
129 mPackageId = id;
130 }
131
132 SymbolTable* getExternalSymbols() override {
133 return &mSymbols;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700134 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800135
136 bool verbose() override {
137 return mVerbose;
138 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800139
140 void setVerbose(bool val) {
141 mVerbose = val;
142 }
143
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700144 int getMinSdkVersion() override {
145 return mMinSdkVersion;
146 }
147
148 void setMinSdkVersion(int minSdk) {
149 mMinSdkVersion = minSdk;
150 }
151
Adam Lesinski64587af2016-02-18 18:33:06 -0800152private:
153 StdErrDiagnostics mDiagnostics;
154 NameMangler mNameMangler;
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700155 std::string mCompilationPackage;
Adam Lesinski64587af2016-02-18 18:33:06 -0800156 uint8_t mPackageId = 0x0;
157 SymbolTable mSymbols;
158 bool mVerbose = false;
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700159 int mMinSdkVersion = 0;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700160};
161
Adam Lesinski355f2852016-02-13 20:26:45 -0800162static bool copyFileToArchive(io::IFile* file, const std::string& outPath,
163 uint32_t compressionFlags,
164 IArchiveWriter* writer, IAaptContext* context) {
165 std::unique_ptr<io::IData> data = file->openAsData();
166 if (!data) {
167 context->getDiagnostics()->error(DiagMessage(file->getSource())
168 << "failed to open file");
169 return false;
170 }
171
Adam Lesinski64587af2016-02-18 18:33:06 -0800172 const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data());
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700173 const size_t bufferSize = data->size();
Adam Lesinski355f2852016-02-13 20:26:45 -0800174
175 if (context->verbose()) {
176 context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive");
177 }
178
179 if (writer->startEntry(outPath, compressionFlags)) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800180 if (writer->writeEntry(buffer, bufferSize)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800181 if (writer->finishEntry()) {
182 return true;
183 }
184 }
185 }
186
187 context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath);
188 return false;
189}
190
191static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
192 bool keepRawValues, IArchiveWriter* writer, IAaptContext* context) {
193 BigBuffer buffer(1024);
194 XmlFlattenerOptions options = {};
195 options.keepRawValues = keepRawValues;
196 options.maxSdkLevel = maxSdkLevel;
197 XmlFlattener flattener(&buffer, options);
198 if (!flattener.consume(context, xmlRes)) {
199 return false;
200 }
201
202 if (context->verbose()) {
203 DiagMessage msg;
204 msg << "writing " << path << " to archive";
205 if (maxSdkLevel) {
Adam Lesinski64587af2016-02-18 18:33:06 -0800206 msg << " maxSdkLevel=" << maxSdkLevel.value() << " keepRawValues=" << keepRawValues;
Adam Lesinski355f2852016-02-13 20:26:45 -0800207 }
208 context->getDiagnostics()->note(msg);
209 }
210
211 if (writer->startEntry(path, ArchiveEntry::kCompress)) {
212 if (writer->writeEntry(buffer)) {
213 if (writer->finishEntry()) {
214 return true;
215 }
216 }
217 }
218 context->getDiagnostics()->error(DiagMessage() << "failed to write " << path << " to archive");
219 return false;
220}
221
Adam Lesinski355f2852016-02-13 20:26:45 -0800222static std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
223 const void* data, size_t len,
224 IDiagnostics* diag) {
225 pb::ResourceTable pbTable;
226 if (!pbTable.ParseFromArray(data, len)) {
227 diag->error(DiagMessage(source) << "invalid compiled table");
228 return {};
229 }
230
231 std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source, diag);
232 if (!table) {
233 return {};
234 }
235 return table;
236}
237
238/**
239 * Inflates an XML file from the source path.
240 */
241static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
242 std::ifstream fin(path, std::ifstream::binary);
243 if (!fin) {
244 diag->error(DiagMessage(path) << strerror(errno));
245 return {};
246 }
247 return xml::inflate(&fin, diag, Source(path));
248}
249
Adam Lesinski355f2852016-02-13 20:26:45 -0800250struct ResourceFileFlattenerOptions {
251 bool noAutoVersion = false;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800252 bool noVersionVectors = false;
Alexandria Cornwalla7cc3f12016-08-16 13:33:32 -0700253 bool noXmlNamespaces = false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800254 bool keepRawValues = false;
255 bool doNotCompressAnything = false;
Rohit Agrawale49bb302016-04-22 12:27:55 -0700256 bool updateProguardSpec = false;
Adam Lesinski9756dec2016-08-08 12:35:04 -0700257 std::unordered_set<std::string> extensionsToNotCompress;
Adam Lesinski355f2852016-02-13 20:26:45 -0800258};
259
260class ResourceFileFlattener {
261public:
262 ResourceFileFlattener(const ResourceFileFlattenerOptions& options,
263 IAaptContext* context, proguard::KeepSet* keepSet) :
264 mOptions(options), mContext(context), mKeepSet(keepSet) {
265 }
266
267 bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter);
268
269private:
270 struct FileOperation {
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700271 ConfigDescription config;
272
273 // The entry this file came from.
274 const ResourceEntry* entry;
275
276 // The file to copy as-is.
Adam Lesinski355f2852016-02-13 20:26:45 -0800277 io::IFile* fileToCopy;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700278
279 // The XML to process and flatten.
Adam Lesinski355f2852016-02-13 20:26:45 -0800280 std::unique_ptr<xml::XmlResource> xmlToFlatten;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700281
282 // The destination to write this file to.
Adam Lesinski355f2852016-02-13 20:26:45 -0800283 std::string dstPath;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800284 bool skipVersion = false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800285 };
286
287 uint32_t getCompressionFlags(const StringPiece& str);
288
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700289 bool linkAndVersionXmlFile(ResourceTable* table, FileOperation* fileOp,
290 std::queue<FileOperation>* outFileOpQueue);
Adam Lesinski355f2852016-02-13 20:26:45 -0800291
292 ResourceFileFlattenerOptions mOptions;
293 IAaptContext* mContext;
294 proguard::KeepSet* mKeepSet;
295};
296
297uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) {
298 if (mOptions.doNotCompressAnything) {
299 return 0;
300 }
301
302 for (const std::string& extension : mOptions.extensionsToNotCompress) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700303 if (util::stringEndsWith(str, extension)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800304 return 0;
305 }
306 }
307 return ArchiveEntry::kCompress;
308}
309
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700310bool ResourceFileFlattener::linkAndVersionXmlFile(ResourceTable* table,
311 FileOperation* fileOp,
312 std::queue<FileOperation>* outFileOpQueue) {
313 xml::XmlResource* doc = fileOp->xmlToFlatten.get();
314 const Source& src = doc->file.source;
315
Adam Lesinski355f2852016-02-13 20:26:45 -0800316 if (mContext->verbose()) {
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700317 mContext->getDiagnostics()->note(DiagMessage() << "linking " << src.path);
Adam Lesinski355f2852016-02-13 20:26:45 -0800318 }
319
Adam Lesinski355f2852016-02-13 20:26:45 -0800320 XmlReferenceLinker xmlLinker;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700321 if (!xmlLinker.consume(mContext, doc)) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800322 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800323 }
324
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700325 if (mOptions.updateProguardSpec && !proguard::collectProguardRules(src, doc, mKeepSet)) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800326 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800327 }
328
Alexandria Cornwalla7cc3f12016-08-16 13:33:32 -0700329 if (mOptions.noXmlNamespaces) {
330 XmlNamespaceRemover namespaceRemover;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700331 if (!namespaceRemover.consume(mContext, doc)) {
Alexandria Cornwalla7cc3f12016-08-16 13:33:32 -0700332 return false;
333 }
334 }
335
Adam Lesinski355f2852016-02-13 20:26:45 -0800336 if (!mOptions.noAutoVersion) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800337 if (mOptions.noVersionVectors) {
338 // Skip this if it is a vector or animated-vector.
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700339 xml::Element* el = xml::findRootElement(doc);
Adam Lesinski626a69f2016-03-03 10:09:26 -0800340 if (el && el->namespaceUri.empty()) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700341 if (el->name == "vector" || el->name == "animated-vector") {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800342 // We are NOT going to version this file.
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700343 fileOp->skipVersion = true;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800344 return true;
345 }
346 }
347 }
348
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700349 const ConfigDescription& config = fileOp->config;
350
Adam Lesinski355f2852016-02-13 20:26:45 -0800351 // Find the first SDK level used that is higher than this defined config and
352 // not superseded by a lower or equal SDK level resource.
Alexandria Cornwallf6762fc2016-08-09 12:36:46 -0700353 const int minSdkVersion = mContext->getMinSdkVersion();
Adam Lesinski355f2852016-02-13 20:26:45 -0800354 for (int sdkLevel : xmlLinker.getSdkLevels()) {
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700355 if (sdkLevel > minSdkVersion && sdkLevel > config.sdkVersion) {
356 if (!shouldGenerateVersionedResource(fileOp->entry, config, sdkLevel)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800357 // If we shouldn't generate a versioned resource, stop checking.
358 break;
359 }
360
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700361 ResourceFile versionedFileDesc = doc->file;
Adam Lesinski64587af2016-02-18 18:33:06 -0800362 versionedFileDesc.config.sdkVersion = (uint16_t) sdkLevel;
Adam Lesinski355f2852016-02-13 20:26:45 -0800363
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700364 FileOperation newFileOp;
365 newFileOp.xmlToFlatten = util::make_unique<xml::XmlResource>(
366 versionedFileDesc, doc->root->clone());
367 newFileOp.config = versionedFileDesc.config;
368 newFileOp.entry = fileOp->entry;
369 newFileOp.dstPath = ResourceUtils::buildResourceFileName(
370 versionedFileDesc, mContext->getNameMangler());
371
Adam Lesinski355f2852016-02-13 20:26:45 -0800372 if (mContext->verbose()) {
373 mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source)
374 << "auto-versioning resource from config '"
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700375 << config
Adam Lesinski626a69f2016-03-03 10:09:26 -0800376 << "' -> '"
Adam Lesinski355f2852016-02-13 20:26:45 -0800377 << versionedFileDesc.config << "'");
378 }
379
Adam Lesinski355f2852016-02-13 20:26:45 -0800380 bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name,
381 versionedFileDesc.config,
382 versionedFileDesc.source,
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700383 newFileOp.dstPath,
384 nullptr,
Adam Lesinski355f2852016-02-13 20:26:45 -0800385 mContext->getDiagnostics());
386 if (!added) {
Adam Lesinski626a69f2016-03-03 10:09:26 -0800387 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800388 }
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700389
390 outFileOpQueue->push(std::move(newFileOp));
Adam Lesinski355f2852016-02-13 20:26:45 -0800391 break;
392 }
393 }
394 }
Adam Lesinski626a69f2016-03-03 10:09:26 -0800395 return true;
Adam Lesinski355f2852016-02-13 20:26:45 -0800396}
397
398/**
399 * Do not insert or remove any resources while executing in this function. It will
400 * corrupt the iteration order.
401 */
402bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiveWriter) {
403 bool error = false;
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700404 std::map<std::pair<ConfigDescription, StringPiece>, FileOperation> configSortedFiles;
Adam Lesinski355f2852016-02-13 20:26:45 -0800405
406 for (auto& pkg : table->packages) {
407 for (auto& type : pkg->types) {
408 // Sort by config and name, so that we get better locality in the zip file.
409 configSortedFiles.clear();
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700410 std::queue<FileOperation> fileOperations;
Adam Lesinski355f2852016-02-13 20:26:45 -0800411
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700412 // Populate the queue with all files in the ResourceTable.
413 for (auto& entry : type->entries) {
414 for (auto& configValue : entry->values) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800415 FileReference* fileRef = valueCast<FileReference>(configValue->value.get());
416 if (!fileRef) {
417 continue;
418 }
419
420 io::IFile* file = fileRef->file;
421 if (!file) {
422 mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource())
423 << "file not found");
424 return false;
425 }
426
427 FileOperation fileOp;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700428 fileOp.entry = entry.get();
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700429 fileOp.dstPath = *fileRef->path;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700430 fileOp.config = configValue->config;
Adam Lesinski355f2852016-02-13 20:26:45 -0800431
432 const StringPiece srcPath = file->getSource().path;
433 if (type->type != ResourceType::kRaw &&
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700434 (util::stringEndsWith(srcPath, ".xml.flat") ||
435 util::stringEndsWith(srcPath, ".xml"))) {
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700436 std::unique_ptr<io::IData> data = file->openAsData();
437 if (!data) {
438 mContext->getDiagnostics()->error(DiagMessage(file->getSource())
439 << "failed to open file");
440 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800441 }
442
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700443 fileOp.xmlToFlatten = xml::inflate(data->data(), data->size(),
444 mContext->getDiagnostics(),
445 file->getSource());
446
447 if (!fileOp.xmlToFlatten) {
448 return false;
449 }
450
451 fileOp.xmlToFlatten->file.config = configValue->config;
452 fileOp.xmlToFlatten->file.source = fileRef->getSource();
453 fileOp.xmlToFlatten->file.name =
454 ResourceName(pkg->name, type->type, entry->name);
455
456 // Enqueue the XML files to be processed.
457 fileOperations.push(std::move(fileOp));
Adam Lesinski355f2852016-02-13 20:26:45 -0800458 } else {
459 fileOp.fileToCopy = file;
Adam Lesinski355f2852016-02-13 20:26:45 -0800460
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700461 // NOTE(adamlesinski): Explicitly construct a StringPiece here, or else
462 // we end up copying the string in the std::make_pair() method, then
463 // creating a StringPiece from the copy, which would cause us to end up
464 // referencing garbage in the map.
465 const StringPiece entryName(entry->name);
466 configSortedFiles[std::make_pair(configValue->config, entryName)] =
467 std::move(fileOp);
468 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800469 }
470 }
471
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700472 // Now process the XML queue
473 for (; !fileOperations.empty(); fileOperations.pop()) {
474 FileOperation& fileOp = fileOperations.front();
475
476 if (!linkAndVersionXmlFile(table, &fileOp, &fileOperations)) {
477 error = true;
478 continue;
479 }
480
481 // NOTE(adamlesinski): Explicitly construct a StringPiece here, or else
482 // we end up copying the string in the std::make_pair() method, then creating
483 // a StringPiece from the copy, which would cause us to end up referencing
484 // garbage in the map.
485 const StringPiece entryName(fileOp.entry->name);
486 configSortedFiles[std::make_pair(fileOp.config, entryName)] = std::move(fileOp);
487 }
488
Adam Lesinski355f2852016-02-13 20:26:45 -0800489 if (error) {
490 return false;
491 }
492
493 // Now flatten the sorted values.
494 for (auto& mapEntry : configSortedFiles) {
495 const ConfigDescription& config = mapEntry.first.first;
496 const FileOperation& fileOp = mapEntry.second;
497
498 if (fileOp.xmlToFlatten) {
499 Maybe<size_t> maxSdkLevel;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800500 if (!mOptions.noAutoVersion && !fileOp.skipVersion) {
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700501 maxSdkLevel = std::max<size_t>(std::max<size_t>(config.sdkVersion, 1u),
502 mContext->getMinSdkVersion());
Adam Lesinski355f2852016-02-13 20:26:45 -0800503 }
504
505 bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel,
506 mOptions.keepRawValues,
507 archiveWriter, mContext);
508 if (!result) {
509 error = true;
510 }
511 } else {
512 bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath,
513 getCompressionFlags(fileOp.dstPath),
514 archiveWriter, mContext);
515 if (!result) {
516 error = true;
517 }
518 }
519 }
520 }
521 }
522 return !error;
523}
524
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700525static bool writeStableIdMapToPath(IDiagnostics* diag,
526 const std::unordered_map<ResourceName, ResourceId>& idMap,
Chih-Hung Hsieh9b8528f2016-08-10 14:15:30 -0700527 const std::string& idMapPath) {
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700528 std::ofstream fout(idMapPath, std::ofstream::binary);
529 if (!fout) {
530 diag->error(DiagMessage(idMapPath) << strerror(errno));
531 return false;
532 }
533
534 for (const auto& entry : idMap) {
535 const ResourceName& name = entry.first;
536 const ResourceId& id = entry.second;
537 fout << name << " = " << id << "\n";
538 }
539
540 if (!fout) {
541 diag->error(DiagMessage(idMapPath) << "failed writing to file: " << strerror(errno));
542 return false;
543 }
544
545 return true;
546}
547
548static bool loadStableIdMap(IDiagnostics* diag, const std::string& path,
549 std::unordered_map<ResourceName, ResourceId>* outIdMap) {
550 std::string content;
551 if (!android::base::ReadFileToString(path, &content)) {
552 diag->error(DiagMessage(path) << "failed reading stable ID file");
553 return false;
554 }
555
556 outIdMap->clear();
557 size_t lineNo = 0;
558 for (StringPiece line : util::tokenize(content, '\n')) {
559 lineNo++;
560 line = util::trimWhitespace(line);
561 if (line.empty()) {
562 continue;
563 }
564
565 auto iter = std::find(line.begin(), line.end(), '=');
566 if (iter == line.end()) {
567 diag->error(DiagMessage(Source(path, lineNo)) << "missing '='");
568 return false;
569 }
570
571 ResourceNameRef name;
572 StringPiece resNameStr = util::trimWhitespace(
573 line.substr(0, std::distance(line.begin(), iter)));
574 if (!ResourceUtils::parseResourceName(resNameStr, &name)) {
575 diag->error(DiagMessage(Source(path, lineNo))
576 << "invalid resource name '" << resNameStr << "'");
577 return false;
578 }
579
580 const size_t resIdStartIdx = std::distance(line.begin(), iter) + 1;
581 const size_t resIdStrLen = line.size() - resIdStartIdx;
582 StringPiece resIdStr = util::trimWhitespace(line.substr(resIdStartIdx, resIdStrLen));
583
Adam Lesinski36c73a52016-08-11 13:39:24 -0700584 Maybe<ResourceId> maybeId = ResourceUtils::parseResourceId(resIdStr);
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700585 if (!maybeId) {
586 diag->error(DiagMessage(Source(path, lineNo)) << "invalid resource ID '"
587 << resIdStr << "'");
588 return false;
589 }
590
591 (*outIdMap)[name.toResourceName()] = maybeId.value();
592 }
593 return true;
594}
595
Adam Lesinski36c73a52016-08-11 13:39:24 -0700596static bool parseSplitParameter(const StringPiece& arg, IDiagnostics* diag,
597 std::string* outPath, SplitConstraints* outSplit) {
598 std::vector<std::string> parts = util::split(arg, ':');
599 if (parts.size() != 2) {
600 diag->error(DiagMessage() << "invalid split parameter '" << arg << "'");
601 diag->note(DiagMessage() << "should be --split path/to/output.apk:<config>[,<config>...]");
602 return false;
603 }
604 *outPath = parts[0];
605 std::vector<ConfigDescription> configs;
606 for (const StringPiece& configStr : util::tokenize(parts[1], ',')) {
607 configs.push_back({});
608 if (!ConfigDescription::parse(configStr, &configs.back())) {
609 diag->error(DiagMessage() << "invalid config '" << configStr
610 << "' in split parameter '" << arg << "'");
611 return false;
612 }
613 }
614 outSplit->configs.insert(configs.begin(), configs.end());
615 return true;
616}
617
Adam Lesinskifb48d292015-11-07 15:52:13 -0800618class LinkCommand {
619public:
Adam Lesinski6a008172016-02-02 17:02:58 -0800620 LinkCommand(LinkContext* context, const LinkOptions& options) :
Adam Lesinski64587af2016-02-18 18:33:06 -0800621 mOptions(options), mContext(context), mFinalTable(),
622 mFileCollection(util::make_unique<io::FileCollection>()) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800623 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700624
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700625 /**
626 * Creates a SymbolTable that loads symbols from the various APKs and caches the
627 * results for faster lookup.
628 */
Adam Lesinski64587af2016-02-18 18:33:06 -0800629 bool loadSymbolsFromIncludePaths() {
630 std::unique_ptr<AssetManagerSymbolSource> assetSource =
631 util::make_unique<AssetManagerSymbolSource>();
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700632 for (const std::string& path : mOptions.includePaths) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800633 if (mContext->verbose()) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800634 mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700635 }
636
Adam Lesinski64587af2016-02-18 18:33:06 -0800637 // First try to load the file as a static lib.
638 std::string errorStr;
639 std::unique_ptr<ResourceTable> staticInclude = loadStaticLibrary(path, &errorStr);
640 if (staticInclude) {
641 if (!mOptions.staticLib) {
642 // Can't include static libraries when not building a static library.
643 mContext->getDiagnostics()->error(
644 DiagMessage(path) << "can't include static library when building app");
645 return false;
646 }
647
648 // If we are using --no-static-lib-packages, we need to rename the package of this
649 // table to our compilation package.
650 if (mOptions.noStaticLibPackages) {
651 if (ResourceTablePackage* pkg = staticInclude->findPackageById(0x7f)) {
652 pkg->name = mContext->getCompilationPackage();
653 }
654 }
655
656 mContext->getExternalSymbols()->appendSource(
657 util::make_unique<ResourceTableSymbolSource>(staticInclude.get()));
658
659 mStaticTableIncludes.push_back(std::move(staticInclude));
660
661 } else if (!errorStr.empty()) {
662 // We had an error with reading, so fail.
663 mContext->getDiagnostics()->error(DiagMessage(path) << errorStr);
664 return false;
665 }
666
667 if (!assetSource->addAssetPath(path)) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800668 mContext->getDiagnostics()->error(
Adam Lesinskifb48d292015-11-07 15:52:13 -0800669 DiagMessage(path) << "failed to load include path");
Adam Lesinski64587af2016-02-18 18:33:06 -0800670 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700671 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700672 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800673
674 mContext->getExternalSymbols()->appendSource(std::move(assetSource));
675 return true;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700676 }
677
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700678 Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes, IDiagnostics* diag) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700679 // Make sure the first element is <manifest> with package attribute.
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800680 if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700681 AppInfo appInfo;
682
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700683 if (!manifestEl->namespaceUri.empty() || manifestEl->name != "manifest") {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700684 diag->error(DiagMessage(xmlRes->file.source) << "root tag must be <manifest>");
685 return {};
686 }
687
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700688 xml::Attribute* packageAttr = manifestEl->findAttribute({}, "package");
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700689 if (!packageAttr) {
690 diag->error(DiagMessage(xmlRes->file.source)
691 << "<manifest> must have a 'package' attribute");
692 return {};
693 }
694
695 appInfo.package = packageAttr->value;
696
Adam Lesinski36c73a52016-08-11 13:39:24 -0700697 if (xml::Attribute* versionCodeAttr =
698 manifestEl->findAttribute(xml::kSchemaAndroid, "versionCode")) {
699 Maybe<uint32_t> maybeCode = ResourceUtils::parseInt(versionCodeAttr->value);
700 if (!maybeCode) {
701 diag->error(DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber))
702 << "invalid android:versionCode '"
703 << versionCodeAttr->value << "'");
704 return {};
705 }
706 appInfo.versionCode = maybeCode.value();
707 }
708
709 if (xml::Attribute* revisionCodeAttr =
710 manifestEl->findAttribute(xml::kSchemaAndroid, "revisionCode")) {
711 Maybe<uint32_t> maybeCode = ResourceUtils::parseInt(revisionCodeAttr->value);
712 if (!maybeCode) {
713 diag->error(DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber))
714 << "invalid android:revisionCode '"
715 << revisionCodeAttr->value << "'");
716 return {};
717 }
718 appInfo.revisionCode = maybeCode.value();
719 }
720
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700721 if (xml::Element* usesSdkEl = manifestEl->findChild({}, "uses-sdk")) {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700722 if (xml::Attribute* minSdk =
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700723 usesSdkEl->findAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700724 appInfo.minSdkVersion = minSdk->value;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700725 }
726 }
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700727
728 return appInfo;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700729 }
730 return {};
731 }
732
Adam Lesinski979ccb22016-01-11 10:42:19 -0800733 /**
734 * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked.
735 * Postcondition: ResourceTable has only one package left. All others are stripped, or there
736 * is an error and false is returned.
737 */
Adam Lesinskifb48d292015-11-07 15:52:13 -0800738 bool verifyNoExternalPackages() {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800739 auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool {
Adam Lesinski6a008172016-02-02 17:02:58 -0800740 return mContext->getCompilationPackage() != pkg->name ||
Adam Lesinski979ccb22016-01-11 10:42:19 -0800741 !pkg->id ||
Adam Lesinski6a008172016-02-02 17:02:58 -0800742 pkg->id.value() != mContext->getPackageId();
Adam Lesinski979ccb22016-01-11 10:42:19 -0800743 };
744
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700745 bool error = false;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800746 for (const auto& package : mFinalTable.packages) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800747 if (isExtPackageFunc(package)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700748 // We have a package that is not related to the one we're building!
749 for (const auto& type : package->types) {
750 for (const auto& entry : type->entries) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800751 ResourceNameRef resName(package->name, type->type, entry->name);
752
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700753 for (const auto& configValue : entry->values) {
Adam Lesinski979ccb22016-01-11 10:42:19 -0800754 // Special case the occurrence of an ID that is being generated for the
755 // 'android' package. This is due to legacy reasons.
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800756 if (valueCast<Id>(configValue->value.get()) &&
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700757 package->name == "android") {
Adam Lesinski6a008172016-02-02 17:02:58 -0800758 mContext->getDiagnostics()->warn(
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800759 DiagMessage(configValue->value->getSource())
Adam Lesinski979ccb22016-01-11 10:42:19 -0800760 << "generated id '" << resName
761 << "' for external package '" << package->name
762 << "'");
763 } else {
Adam Lesinski6a008172016-02-02 17:02:58 -0800764 mContext->getDiagnostics()->error(
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -0800765 DiagMessage(configValue->value->getSource())
Adam Lesinski979ccb22016-01-11 10:42:19 -0800766 << "defined resource '" << resName
767 << "' for external package '" << package->name
768 << "'");
769 error = true;
770 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700771 }
772 }
773 }
774 }
775 }
Adam Lesinski979ccb22016-01-11 10:42:19 -0800776
777 auto newEndIter = std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(),
778 isExtPackageFunc);
779 mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700780 return !error;
781 }
782
Adam Lesinski64587af2016-02-18 18:33:06 -0800783 /**
784 * Returns true if no IDs have been set, false otherwise.
785 */
786 bool verifyNoIdsSet() {
787 for (const auto& package : mFinalTable.packages) {
788 for (const auto& type : package->types) {
789 if (type->id) {
790 mContext->getDiagnostics()->error(DiagMessage() << "type " << type->type
791 << " has ID " << std::hex
792 << (int) type->id.value()
793 << std::dec << " assigned");
794 return false;
795 }
796
797 for (const auto& entry : type->entries) {
798 if (entry->id) {
799 ResourceNameRef resName(package->name, type->type, entry->name);
800 mContext->getDiagnostics()->error(DiagMessage() << "entry " << resName
801 << " has ID " << std::hex
802 << (int) entry->id.value()
803 << std::dec << " assigned");
804 return false;
805 }
806 }
807 }
808 }
809 return true;
810 }
811
Adam Lesinski36c73a52016-08-11 13:39:24 -0700812 std::unique_ptr<IArchiveWriter> makeArchiveWriter(const StringPiece& out) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700813 if (mOptions.outputToDirectory) {
Adam Lesinski36c73a52016-08-11 13:39:24 -0700814 return createDirectoryArchiveWriter(mContext->getDiagnostics(), out);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700815 } else {
Adam Lesinski36c73a52016-08-11 13:39:24 -0700816 return createZipFileArchiveWriter(mContext->getDiagnostics(), out);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700817 }
818 }
819
820 bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
821 BigBuffer buffer(1024);
Adam Lesinski59e04c62016-02-04 15:59:23 -0800822 TableFlattener flattener(&buffer);
Adam Lesinski6a008172016-02-02 17:02:58 -0800823 if (!flattener.consume(mContext, table)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700824 return false;
825 }
826
Adam Lesinskia40e9722015-11-24 19:11:46 -0800827 if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
828 if (writer->writeEntry(buffer)) {
829 if (writer->finishEntry()) {
830 return true;
831 }
832 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700833 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800834
Adam Lesinski6a008172016-02-02 17:02:58 -0800835 mContext->getDiagnostics()->error(
Adam Lesinskia40e9722015-11-24 19:11:46 -0800836 DiagMessage() << "failed to write resources.arsc to archive");
837 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700838 }
839
Adam Lesinski64587af2016-02-18 18:33:06 -0800840 bool flattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
841 // Create the file/zip entry.
842 if (!writer->startEntry("resources.arsc.flat", 0)) {
843 mContext->getDiagnostics()->error(DiagMessage() << "failed to open");
844 return false;
845 }
846
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700847 // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry().
Adam Lesinski64587af2016-02-18 18:33:06 -0800848 {
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700849 // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
850 // interface.
851 CopyingOutputStreamAdaptor adaptor(writer);
Adam Lesinski64587af2016-02-18 18:33:06 -0800852
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700853 std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table);
Adam Lesinski64587af2016-02-18 18:33:06 -0800854 if (!pbTable->SerializeToZeroCopyStream(&adaptor)) {
855 mContext->getDiagnostics()->error(DiagMessage() << "failed to write");
856 return false;
857 }
858 }
859
860 if (!writer->finishEntry()) {
861 mContext->getDiagnostics()->error(DiagMessage() << "failed to finish entry");
862 return false;
863 }
864 return true;
865 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700866
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700867 bool writeJavaFile(ResourceTable* table, const StringPiece& packageNameToGenerate,
Chih-Hung Hsieh9b8528f2016-08-10 14:15:30 -0700868 const StringPiece& outPackage, const JavaClassGeneratorOptions& javaOptions) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700869 if (!mOptions.generateJavaClassPath) {
870 return true;
871 }
872
873 std::string outPath = mOptions.generateJavaClassPath.value();
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700874 file::appendPath(&outPath, file::packageToPath(outPackage));
Adam Lesinski96917c22016-03-09 13:11:25 -0800875 if (!file::mkdirs(outPath)) {
876 mContext->getDiagnostics()->error(
877 DiagMessage() << "failed to create directory '" << outPath << "'");
878 return false;
879 }
880
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700881 file::appendPath(&outPath, "R.java");
882
883 std::ofstream fout(outPath, std::ofstream::binary);
884 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800885 mContext->getDiagnostics()->error(
886 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700887 return false;
888 }
889
Adam Lesinski7656554f2016-03-10 21:55:04 -0800890 JavaClassGenerator generator(mContext, table, javaOptions);
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700891 if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
Adam Lesinski6a008172016-02-02 17:02:58 -0800892 mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700893 return false;
894 }
Adam Lesinski96917c22016-03-09 13:11:25 -0800895
896 if (!fout) {
897 mContext->getDiagnostics()->error(
898 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
899 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700900 return true;
901 }
902
Adam Lesinski467f1712015-11-16 17:35:44 -0800903 bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
Adam Lesinskica5638f2015-10-21 14:42:43 -0700904 if (!mOptions.generateJavaClassPath) {
905 return true;
906 }
907
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700908 std::unique_ptr<ClassDefinition> manifestClass = generateManifestClass(
909 mContext->getDiagnostics(), manifestXml);
910
911 if (!manifestClass) {
912 // Something bad happened, but we already logged it, so exit.
913 return false;
914 }
915
916 if (manifestClass->empty()) {
917 // Empty Manifest class, no need to generate it.
918 return true;
919 }
920
Adam Lesinski3524a232016-04-01 19:19:24 -0700921 // Add any JavaDoc annotations to the generated class.
922 for (const std::string& annotation : mOptions.javadocAnnotations) {
923 std::string properAnnotation = "@";
924 properAnnotation += annotation;
925 manifestClass->getCommentBuilder()->appendComment(properAnnotation);
926 }
927
Adam Lesinskid0f116b2016-07-08 15:00:32 -0700928 const std::string& packageUtf8 = mContext->getCompilationPackage();
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700929
Adam Lesinskica5638f2015-10-21 14:42:43 -0700930 std::string outPath = mOptions.generateJavaClassPath.value();
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700931 file::appendPath(&outPath, file::packageToPath(packageUtf8));
932
Adam Lesinski96917c22016-03-09 13:11:25 -0800933 if (!file::mkdirs(outPath)) {
934 mContext->getDiagnostics()->error(
935 DiagMessage() << "failed to create directory '" << outPath << "'");
936 return false;
937 }
938
Adam Lesinskica5638f2015-10-21 14:42:43 -0700939 file::appendPath(&outPath, "Manifest.java");
940
941 std::ofstream fout(outPath, std::ofstream::binary);
942 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800943 mContext->getDiagnostics()->error(
944 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinskica5638f2015-10-21 14:42:43 -0700945 return false;
946 }
947
Adam Lesinski6cbfb1d2016-03-31 13:33:02 -0700948 if (!ClassDefinition::writeJavaFile(manifestClass.get(), packageUtf8, true, &fout)) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800949 mContext->getDiagnostics()->error(
950 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinskica5638f2015-10-21 14:42:43 -0700951 return false;
952 }
953 return true;
954 }
955
Rohit Agrawale49bb302016-04-22 12:27:55 -0700956 bool writeProguardFile(const Maybe<std::string>& out, const proguard::KeepSet& keepSet) {
957 if (!out) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700958 return true;
959 }
960
Rohit Agrawale49bb302016-04-22 12:27:55 -0700961 const std::string& outPath = out.value();
Adam Lesinski96917c22016-03-09 13:11:25 -0800962 std::ofstream fout(outPath, std::ofstream::binary);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700963 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800964 mContext->getDiagnostics()->error(
965 DiagMessage() << "failed to open '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700966 return false;
967 }
968
969 proguard::writeKeepSet(&fout, keepSet);
970 if (!fout) {
Adam Lesinski96917c22016-03-09 13:11:25 -0800971 mContext->getDiagnostics()->error(
972 DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700973 return false;
974 }
975 return true;
976 }
977
Adam Lesinski64587af2016-02-18 18:33:06 -0800978 std::unique_ptr<ResourceTable> loadStaticLibrary(const std::string& input,
979 std::string* outError) {
980 std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
981 input, outError);
982 if (!collection) {
983 return {};
984 }
985 return loadTablePbFromCollection(collection.get());
986 }
987
988 std::unique_ptr<ResourceTable> loadTablePbFromCollection(io::IFileCollection* collection) {
989 io::IFile* file = collection->findFile("resources.arsc.flat");
990 if (!file) {
991 return {};
992 }
993
994 std::unique_ptr<io::IData> data = file->openAsData();
995 return loadTableFromPb(file->getSource(), data->data(), data->size(),
996 mContext->getDiagnostics());
997 }
998
999 bool mergeStaticLibrary(const std::string& input, bool override) {
1000 if (mContext->verbose()) {
1001 mContext->getDiagnostics()->note(DiagMessage() << "merging static library " << input);
1002 }
1003
1004 std::string errorStr;
1005 std::unique_ptr<io::ZipFileCollection> collection =
1006 io::ZipFileCollection::create(input, &errorStr);
1007 if (!collection) {
1008 mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
1009 return false;
1010 }
1011
1012 std::unique_ptr<ResourceTable> table = loadTablePbFromCollection(collection.get());
1013 if (!table) {
1014 mContext->getDiagnostics()->error(DiagMessage(input) << "invalid static library");
1015 return false;
1016 }
1017
1018 ResourceTablePackage* pkg = table->findPackageById(0x7f);
1019 if (!pkg) {
1020 mContext->getDiagnostics()->error(DiagMessage(input)
1021 << "static library has no package");
1022 return false;
1023 }
1024
1025 bool result;
1026 if (mOptions.noStaticLibPackages) {
1027 // Merge all resources as if they were in the compilation package. This is the old
1028 // behaviour of aapt.
1029
1030 // Add the package to the set of --extra-packages so we emit an R.java for each
1031 // library package.
1032 if (!pkg->name.empty()) {
1033 mOptions.extraJavaPackages.insert(pkg->name);
1034 }
1035
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001036 pkg->name = "";
Adam Lesinski64587af2016-02-18 18:33:06 -08001037 if (override) {
1038 result = mTableMerger->mergeOverlay(Source(input), table.get(), collection.get());
1039 } else {
1040 result = mTableMerger->merge(Source(input), table.get(), collection.get());
1041 }
1042
1043 } else {
1044 // This is the proper way to merge libraries, where the package name is preserved
1045 // and resource names are mangled.
1046 result = mTableMerger->mergeAndMangle(Source(input), pkg->name, table.get(),
1047 collection.get());
1048 }
1049
1050 if (!result) {
1051 return false;
1052 }
1053
1054 // Make sure to move the collection into the set of IFileCollections.
1055 mCollections.push_back(std::move(collection));
Adam Lesinskifb48d292015-11-07 15:52:13 -08001056 return true;
1057 }
1058
Adam Lesinskia40e9722015-11-24 19:11:46 -08001059 bool mergeResourceTable(io::IFile* file, bool override) {
Adam Lesinski355f2852016-02-13 20:26:45 -08001060 if (mContext->verbose()) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001061 mContext->getDiagnostics()->note(DiagMessage() << "merging resource table "
1062 << file->getSource());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001063 }
1064
Adam Lesinskia40e9722015-11-24 19:11:46 -08001065 std::unique_ptr<io::IData> data = file->openAsData();
1066 if (!data) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001067 mContext->getDiagnostics()->error(DiagMessage(file->getSource())
Adam Lesinskia40e9722015-11-24 19:11:46 -08001068 << "failed to open file");
1069 return false;
1070 }
1071
Adam Lesinski355f2852016-02-13 20:26:45 -08001072 std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(),
1073 data->data(), data->size(),
1074 mContext->getDiagnostics());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001075 if (!table) {
1076 return false;
1077 }
1078
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001079 bool result = false;
1080 if (override) {
1081 result = mTableMerger->mergeOverlay(file->getSource(), table.get());
1082 } else {
1083 result = mTableMerger->merge(file->getSource(), table.get());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001084 }
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001085 return result;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001086 }
1087
Adam Lesinski64587af2016-02-18 18:33:06 -08001088 bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc, bool override) {
Adam Lesinski355f2852016-02-13 20:26:45 -08001089 if (mContext->verbose()) {
Adam Lesinski5eeaadd2016-08-25 12:26:56 -07001090 mContext->getDiagnostics()->note(DiagMessage()
1091 << "merging '" << fileDesc->name
1092 << "' from compiled file "
Adam Lesinski64587af2016-02-18 18:33:06 -08001093 << file->getSource());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001094 }
1095
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001096 bool result = false;
Adam Lesinski64587af2016-02-18 18:33:06 -08001097 if (override) {
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001098 result = mTableMerger->mergeFileOverlay(*fileDesc, file);
Adam Lesinskifb48d292015-11-07 15:52:13 -08001099 } else {
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001100 result = mTableMerger->mergeFile(*fileDesc, file);
Adam Lesinskifb48d292015-11-07 15:52:13 -08001101 }
1102
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001103 if (!result) {
Adam Lesinskifb48d292015-11-07 15:52:13 -08001104 return false;
1105 }
1106
1107 // Add the exports of this file to the table.
Adam Lesinskia40e9722015-11-24 19:11:46 -08001108 for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
Adam Lesinskifb48d292015-11-07 15:52:13 -08001109 if (exportedSymbol.name.package.empty()) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001110 exportedSymbol.name.package = mContext->getCompilationPackage();
Adam Lesinskifb48d292015-11-07 15:52:13 -08001111 }
1112
1113 ResourceNameRef resName = exportedSymbol.name;
1114
Adam Lesinski6a008172016-02-02 17:02:58 -08001115 Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
Adam Lesinskifb48d292015-11-07 15:52:13 -08001116 exportedSymbol.name);
1117 if (mangledName) {
1118 resName = mangledName.value();
1119 }
1120
1121 std::unique_ptr<Id> id = util::make_unique<Id>();
Adam Lesinskia40e9722015-11-24 19:11:46 -08001122 id->setSource(fileDesc->source.withLine(exportedSymbol.line));
Adam Lesinski64587af2016-02-18 18:33:06 -08001123 bool result = mFinalTable.addResourceAllowMangled(
1124 resName, ConfigDescription::defaultConfig(), std::string(), std::move(id),
1125 mContext->getDiagnostics());
Adam Lesinskifb48d292015-11-07 15:52:13 -08001126 if (!result) {
1127 return false;
1128 }
1129 }
Adam Lesinskifb48d292015-11-07 15:52:13 -08001130 return true;
1131 }
1132
Adam Lesinskia40e9722015-11-24 19:11:46 -08001133 /**
Adam Lesinski64587af2016-02-18 18:33:06 -08001134 * Takes a path to load as a ZIP file and merges the files within into the master ResourceTable.
1135 * If override is true, conflicting resources are allowed to override each other, in order of
1136 * last seen.
1137 *
1138 * An io::IFileCollection is created from the ZIP file and added to the set of
1139 * io::IFileCollections that are open.
Adam Lesinskia40e9722015-11-24 19:11:46 -08001140 */
1141 bool mergeArchive(const std::string& input, bool override) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001142 if (mContext->verbose()) {
1143 mContext->getDiagnostics()->note(DiagMessage() << "merging archive " << input);
1144 }
1145
Adam Lesinskia40e9722015-11-24 19:11:46 -08001146 std::string errorStr;
Adam Lesinski64587af2016-02-18 18:33:06 -08001147 std::unique_ptr<io::ZipFileCollection> collection =
1148 io::ZipFileCollection::create(input, &errorStr);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001149 if (!collection) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001150 mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001151 return false;
1152 }
1153
1154 bool error = false;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001155 for (auto iter = collection->iterator(); iter->hasNext(); ) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001156 if (!mergeFile(iter->next(), override)) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001157 error = true;
1158 }
1159 }
1160
1161 // Make sure to move the collection into the set of IFileCollections.
1162 mCollections.push_back(std::move(collection));
1163 return !error;
1164 }
1165
Adam Lesinski64587af2016-02-18 18:33:06 -08001166 /**
1167 * Takes a path to load and merge into the master ResourceTable. If override is true,
1168 * conflicting resources are allowed to override each other, in order of last seen.
1169 *
1170 * If the file path ends with .flata, .jar, .jack, or .zip the file is treated as ZIP archive
1171 * and the files within are merged individually.
1172 *
1173 * Otherwise the files is processed on its own.
1174 */
1175 bool mergePath(const std::string& path, bool override) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001176 if (util::stringEndsWith(path, ".flata") ||
1177 util::stringEndsWith(path, ".jar") ||
1178 util::stringEndsWith(path, ".jack") ||
1179 util::stringEndsWith(path, ".zip")) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001180 return mergeArchive(path, override);
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001181 } else if (util::stringEndsWith(path, ".apk")) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001182 return mergeStaticLibrary(path, override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001183 }
1184
1185 io::IFile* file = mFileCollection->insertFile(path);
Adam Lesinski64587af2016-02-18 18:33:06 -08001186 return mergeFile(file, override);
Adam Lesinskia40e9722015-11-24 19:11:46 -08001187 }
1188
Adam Lesinski64587af2016-02-18 18:33:06 -08001189 /**
1190 * Takes a file to load and merge into the master ResourceTable. If override is true,
1191 * conflicting resources are allowed to override each other, in order of last seen.
1192 *
1193 * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and merged into the
1194 * master ResourceTable. If the file ends with .flat, then it is treated like a compiled file
1195 * and the header data is read and merged into the final ResourceTable.
1196 *
1197 * All other file types are ignored. This is because these files could be coming from a zip,
1198 * where we could have other files like classes.dex.
1199 */
1200 bool mergeFile(io::IFile* file, bool override) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001201 const Source& src = file->getSource();
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001202 if (util::stringEndsWith(src.path, ".arsc.flat")) {
Adam Lesinskia40e9722015-11-24 19:11:46 -08001203 return mergeResourceTable(file, override);
Adam Lesinski64587af2016-02-18 18:33:06 -08001204
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001205 } else if (util::stringEndsWith(src.path, ".flat")){
Adam Lesinskia40e9722015-11-24 19:11:46 -08001206 // Try opening the file and looking for an Export header.
1207 std::unique_ptr<io::IData> data = file->openAsData();
1208 if (!data) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001209 mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open");
Adam Lesinskia40e9722015-11-24 19:11:46 -08001210 return false;
1211 }
1212
Adam Lesinski5eeaadd2016-08-25 12:26:56 -07001213 CompiledFileInputStream inputStream(data->data(), data->size());
1214 uint32_t numFiles = 0;
1215 if (!inputStream.ReadLittleEndian32(&numFiles)) {
1216 mContext->getDiagnostics()->error(DiagMessage(src) << "failed read num files");
1217 return false;
Adam Lesinskia40e9722015-11-24 19:11:46 -08001218 }
Adam Lesinski5eeaadd2016-08-25 12:26:56 -07001219
1220 for (uint32_t i = 0; i < numFiles; i++) {
1221 pb::CompiledFile compiledFile;
1222 if (!inputStream.ReadCompiledFile(&compiledFile)) {
1223 mContext->getDiagnostics()->error(DiagMessage(src)
1224 << "failed to read compiled file header");
1225 return false;
1226 }
1227
1228 uint64_t offset, len;
1229 if (!inputStream.ReadDataMetaData(&offset, &len)) {
1230 mContext->getDiagnostics()->error(DiagMessage(src)
1231 << "failed to read data meta data");
1232 return false;
1233 }
1234
1235 std::unique_ptr<ResourceFile> resourceFile = deserializeCompiledFileFromPb(
1236 compiledFile, file->getSource(), mContext->getDiagnostics());
1237 if (!resourceFile) {
1238 return false;
1239 }
1240
1241 if (!mergeCompiledFile(file->createFileSegment(offset, len), resourceFile.get(),
1242 override)) {
1243 return false;
1244 }
1245 }
1246 return true;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001247 }
Adam Lesinski52364f72016-01-11 13:10:24 -08001248
Adam Lesinskic446a732016-01-21 11:04:46 -08001249 // Ignore non .flat files. This could be classes.dex or something else that happens
1250 // to be in an archive.
1251 return true;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001252 }
1253
Adam Lesinski36c73a52016-08-11 13:39:24 -07001254 std::unique_ptr<xml::XmlResource> generateSplitManifest(const AppInfo& appInfo,
1255 const SplitConstraints& constraints) {
1256 std::unique_ptr<xml::XmlResource> doc = util::make_unique<xml::XmlResource>();
1257
1258 std::unique_ptr<xml::Namespace> namespaceAndroid = util::make_unique<xml::Namespace>();
1259 namespaceAndroid->namespaceUri = xml::kSchemaAndroid;
1260 namespaceAndroid->namespacePrefix = "android";
1261
1262 std::unique_ptr<xml::Element> manifestEl = util::make_unique<xml::Element>();
1263 manifestEl->name = "manifest";
1264 manifestEl->attributes.push_back(
1265 xml::Attribute{ "", "package", appInfo.package });
1266
1267 if (appInfo.versionCode) {
1268 manifestEl->attributes.push_back(xml::Attribute{
1269 xml::kSchemaAndroid,
1270 "versionCode",
1271 std::to_string(appInfo.versionCode.value()) });
1272 }
1273
1274 if (appInfo.revisionCode) {
1275 manifestEl->attributes.push_back(xml::Attribute{
1276 xml::kSchemaAndroid,
1277 "revisionCode", std::to_string(appInfo.revisionCode.value()) });
1278 }
1279
1280 std::stringstream splitName;
1281 splitName << "config." << util::joiner(constraints.configs, "_");
1282
1283 manifestEl->attributes.push_back(
1284 xml::Attribute{ "", "split", splitName.str() });
1285
1286 std::unique_ptr<xml::Element> applicationEl = util::make_unique<xml::Element>();
1287 applicationEl->name = "application";
1288 applicationEl->attributes.push_back(
1289 xml::Attribute{ xml::kSchemaAndroid, "hasCode", "false" });
1290
1291 manifestEl->addChild(std::move(applicationEl));
1292 namespaceAndroid->addChild(std::move(manifestEl));
1293 doc->root = std::move(namespaceAndroid);
1294 return doc;
1295 }
1296
1297 /**
1298 * Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable
1299 * to the IArchiveWriter.
1300 */
1301 bool writeApk(IArchiveWriter* writer, proguard::KeepSet* keepSet, xml::XmlResource* manifest,
1302 ResourceTable* table) {
1303 const bool keepRawValues = mOptions.staticLib;
1304 bool result = flattenXml(manifest, "AndroidManifest.xml", {}, keepRawValues, writer,
1305 mContext);
1306 if (!result) {
1307 return false;
1308 }
1309
1310 ResourceFileFlattenerOptions fileFlattenerOptions;
1311 fileFlattenerOptions.keepRawValues = keepRawValues;
1312 fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything;
1313 fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress;
1314 fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion;
1315 fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors;
Alexandria Cornwalla7cc3f12016-08-16 13:33:32 -07001316 fileFlattenerOptions.noXmlNamespaces = mOptions.noXmlNamespaces;
Adam Lesinski36c73a52016-08-11 13:39:24 -07001317 fileFlattenerOptions.updateProguardSpec =
1318 static_cast<bool>(mOptions.generateProguardRulesPath);
1319
1320 ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, keepSet);
1321
1322 if (!fileFlattener.flatten(table, writer)) {
1323 mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources");
1324 return false;
1325 }
1326
1327 if (mOptions.staticLib) {
1328 if (!flattenTableToPb(table, writer)) {
1329 mContext->getDiagnostics()->error(DiagMessage()
1330 << "failed to write resources.arsc.flat");
1331 return false;
1332 }
1333 } else {
1334 if (!flattenTable(table, writer)) {
1335 mContext->getDiagnostics()->error(DiagMessage()
1336 << "failed to write resources.arsc");
1337 return false;
1338 }
1339 }
1340 return true;
1341 }
1342
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001343 int run(const std::vector<std::string>& inputFiles) {
1344 // Load the AndroidManifest.xml
Adam Lesinskia40e9722015-11-24 19:11:46 -08001345 std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
Adam Lesinski6a008172016-02-02 17:02:58 -08001346 mContext->getDiagnostics());
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001347 if (!manifestXml) {
1348 return 1;
1349 }
1350
Adam Lesinski36c73a52016-08-11 13:39:24 -07001351 // First extract the Package name without modifying it (via --rename-manifest-package).
Adam Lesinskifb6312f2016-06-28 14:40:32 -07001352 if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(),
1353 mContext->getDiagnostics())) {
Adam Lesinski36c73a52016-08-11 13:39:24 -07001354 const AppInfo& appInfo = maybeAppInfo.value();
Adam Lesinskifb6312f2016-06-28 14:40:32 -07001355 mContext->setCompilationPackage(appInfo.package);
Adam Lesinski36c73a52016-08-11 13:39:24 -07001356 }
1357
1358 ManifestFixer manifestFixer(mOptions.manifestFixerOptions);
1359 if (!manifestFixer.consume(mContext, manifestXml.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001360 return 1;
1361 }
1362
Adam Lesinski36c73a52016-08-11 13:39:24 -07001363 Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(),
1364 mContext->getDiagnostics());
1365 if (!maybeAppInfo) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001366 return 1;
1367 }
1368
Adam Lesinski36c73a52016-08-11 13:39:24 -07001369 const AppInfo& appInfo = maybeAppInfo.value();
1370 if (appInfo.minSdkVersion) {
1371 if (Maybe<int> maybeMinSdkVersion =
1372 ResourceUtils::parseSdkVersion(appInfo.minSdkVersion.value())) {
1373 mContext->setMinSdkVersion(maybeMinSdkVersion.value());
1374 }
1375 }
1376
Adam Lesinski64587af2016-02-18 18:33:06 -08001377 mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() });
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001378 if (mContext->getCompilationPackage() == "android") {
Adam Lesinski64587af2016-02-18 18:33:06 -08001379 mContext->setPackageId(0x01);
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001380 } else {
Adam Lesinski64587af2016-02-18 18:33:06 -08001381 mContext->setPackageId(0x7f);
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001382 }
1383
Adam Lesinski64587af2016-02-18 18:33:06 -08001384 if (!loadSymbolsFromIncludePaths()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001385 return 1;
1386 }
1387
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001388 TableMergerOptions tableMergerOptions;
1389 tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
Adam Lesinski6a008172016-02-02 17:02:58 -08001390 mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions);
Adam Lesinskifb48d292015-11-07 15:52:13 -08001391
Adam Lesinski355f2852016-02-13 20:26:45 -08001392 if (mContext->verbose()) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001393 mContext->getDiagnostics()->note(
Adam Lesinski64587af2016-02-18 18:33:06 -08001394 DiagMessage() << "linking package '" << mContext->getCompilationPackage()
1395 << "' with package ID " << std::hex
1396 << (int) mContext->getPackageId());
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001397 }
1398
Adam Lesinskifb48d292015-11-07 15:52:13 -08001399
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001400 for (const std::string& input : inputFiles) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001401 if (!mergePath(input, false)) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001402 mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input");
Adam Lesinski467f1712015-11-16 17:35:44 -08001403 return 1;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001404 }
1405 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001406
Adam Lesinskifb48d292015-11-07 15:52:13 -08001407 for (const std::string& input : mOptions.overlayFiles) {
Adam Lesinski64587af2016-02-18 18:33:06 -08001408 if (!mergePath(input, true)) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001409 mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
Adam Lesinski467f1712015-11-16 17:35:44 -08001410 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001411 }
1412 }
1413
Adam Lesinskifb48d292015-11-07 15:52:13 -08001414 if (!verifyNoExternalPackages()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001415 return 1;
1416 }
1417
1418 if (!mOptions.staticLib) {
1419 PrivateAttributeMover mover;
Adam Lesinski6a008172016-02-02 17:02:58 -08001420 if (!mover.consume(mContext, &mFinalTable)) {
1421 mContext->getDiagnostics()->error(
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001422 DiagMessage() << "failed moving private attributes");
1423 return 1;
1424 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001425
Adam Lesinski64587af2016-02-18 18:33:06 -08001426 // Assign IDs if we are building a regular app.
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001427 IdAssigner idAssigner(&mOptions.stableIdMap);
Adam Lesinski6a008172016-02-02 17:02:58 -08001428 if (!idAssigner.consume(mContext, &mFinalTable)) {
1429 mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001430 return 1;
1431 }
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001432
1433 // Now grab each ID and emit it as a file.
1434 if (mOptions.resourceIdMapPath) {
1435 for (auto& package : mFinalTable.packages) {
1436 for (auto& type : package->types) {
1437 for (auto& entry : type->entries) {
1438 ResourceName name(package->name, type->type, entry->name);
1439 // The IDs are guaranteed to exist.
1440 mOptions.stableIdMap[std::move(name)] = ResourceId(package->id.value(),
1441 type->id.value(),
1442 entry->id.value());
1443 }
1444 }
1445 }
1446
1447 if (!writeStableIdMapToPath(mContext->getDiagnostics(),
1448 mOptions.stableIdMap,
1449 mOptions.resourceIdMapPath.value())) {
1450 return 1;
1451 }
1452 }
Adam Lesinski64587af2016-02-18 18:33:06 -08001453 } else {
1454 // Static libs are merged with other apps, and ID collisions are bad, so verify that
1455 // no IDs have been set.
1456 if (!verifyNoIdsSet()) {
1457 return 1;
1458 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001459 }
1460
Adam Lesinski64587af2016-02-18 18:33:06 -08001461 // Add the names to mangle based on our source merge earlier.
1462 mContext->setNameManglerPolicy(NameManglerPolicy{
1463 mContext->getCompilationPackage(), mTableMerger->getMergedPackages() });
1464
1465 // Add our table to the symbol table.
1466 mContext->getExternalSymbols()->prependSource(
1467 util::make_unique<ResourceTableSymbolSource>(&mFinalTable));
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001468
Adam Lesinski36c73a52016-08-11 13:39:24 -07001469 ReferenceLinker linker;
1470 if (!linker.consume(mContext, &mFinalTable)) {
1471 mContext->getDiagnostics()->error(DiagMessage() << "failed linking references");
1472 return 1;
1473 }
1474
1475 if (mOptions.staticLib) {
1476 if (!mOptions.products.empty()) {
1477 mContext->getDiagnostics()->warn(
1478 DiagMessage() << "can't select products when building static library");
1479 }
1480 } else {
1481 ProductFilter productFilter(mOptions.products);
1482 if (!productFilter.consume(mContext, &mFinalTable)) {
1483 mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001484 return 1;
1485 }
Adam Lesinski36c73a52016-08-11 13:39:24 -07001486 }
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001487
Adam Lesinski36c73a52016-08-11 13:39:24 -07001488 if (!mOptions.noAutoVersion) {
1489 AutoVersioner versioner;
1490 if (!versioner.consume(mContext, &mFinalTable)) {
1491 mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles");
1492 return 1;
1493 }
1494 }
Adam Lesinski355f2852016-02-13 20:26:45 -08001495
Adam Lesinski36c73a52016-08-11 13:39:24 -07001496 if (!mOptions.staticLib && mContext->getMinSdkVersion() > 0) {
1497 if (mContext->verbose()) {
1498 mContext->getDiagnostics()->note(
1499 DiagMessage() << "collapsing resource versions for minimum SDK "
1500 << mContext->getMinSdkVersion());
1501 }
Adam Lesinski355f2852016-02-13 20:26:45 -08001502
Adam Lesinski36c73a52016-08-11 13:39:24 -07001503 VersionCollapser collapser;
1504 if (!collapser.consume(mContext, &mFinalTable)) {
1505 return 1;
Adam Lesinski64587af2016-02-18 18:33:06 -08001506 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001507 }
1508
Alexandria Cornwall77788eb2016-09-06 15:16:49 -07001509 if (!mOptions.noResourceDeduping) {
1510 ResourceDeduper deduper;
1511 if (!deduper.consume(mContext, &mFinalTable)) {
1512 mContext->getDiagnostics()->error(DiagMessage() << "failed deduping resources");
1513 return 1;
1514 }
1515 }
1516
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001517 proguard::KeepSet proguardKeepSet;
Rohit Agrawale49bb302016-04-22 12:27:55 -07001518 proguard::KeepSet proguardMainDexKeepSet;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001519
Adam Lesinski36c73a52016-08-11 13:39:24 -07001520 if (mOptions.staticLib) {
1521 if (mOptions.tableSplitterOptions.configFilter != nullptr ||
1522 mOptions.tableSplitterOptions.preferredDensity) {
1523 mContext->getDiagnostics()->warn(
1524 DiagMessage() << "can't strip resources when building static library");
1525 }
1526 } else {
1527 // Adjust the SplitConstraints so that their SDK version is stripped if it is less
1528 // than or equal to the minSdk. Otherwise the resources that have had their SDK version
1529 // stripped due to minSdk won't ever match.
1530 std::vector<SplitConstraints> adjustedConstraintsList;
1531 adjustedConstraintsList.reserve(mOptions.splitConstraints.size());
1532 for (const SplitConstraints& constraints : mOptions.splitConstraints) {
1533 SplitConstraints adjustedConstraints;
1534 for (const ConfigDescription& config : constraints.configs) {
1535 if (config.sdkVersion <= mContext->getMinSdkVersion()) {
1536 adjustedConstraints.configs.insert(config.copyWithoutSdkVersion());
1537 } else {
1538 adjustedConstraints.configs.insert(config);
1539 }
1540 }
1541 adjustedConstraintsList.push_back(std::move(adjustedConstraints));
1542 }
1543
1544 TableSplitter tableSplitter(adjustedConstraintsList, mOptions.tableSplitterOptions);
1545 if (!tableSplitter.verifySplitConstraints(mContext)) {
1546 return 1;
1547 }
1548 tableSplitter.splitTable(&mFinalTable);
1549
1550 // Now we need to write out the Split APKs.
1551 auto pathIter = mOptions.splitPaths.begin();
1552 auto splitConstraintsIter = adjustedConstraintsList.begin();
1553 for (std::unique_ptr<ResourceTable>& splitTable : tableSplitter.getSplits()) {
1554 if (mContext->verbose()) {
1555 mContext->getDiagnostics()->note(
1556 DiagMessage(*pathIter) << "generating split with configurations '"
1557 << util::joiner(splitConstraintsIter->configs, ", ") << "'");
1558 }
1559
1560 std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(*pathIter);
1561 if (!archiveWriter) {
1562 mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive");
1563 return 1;
1564 }
1565
1566 // Generate an AndroidManifest.xml for each split.
1567 std::unique_ptr<xml::XmlResource> splitManifest =
1568 generateSplitManifest(appInfo, *splitConstraintsIter);
1569
1570 XmlReferenceLinker linker;
1571 if (!linker.consume(mContext, splitManifest.get())) {
1572 mContext->getDiagnostics()->error(
1573 DiagMessage() << "failed to create Split AndroidManifest.xml");
1574 return 1;
1575 }
1576
1577 if (!writeApk(archiveWriter.get(), &proguardKeepSet, splitManifest.get(),
1578 splitTable.get())) {
1579 return 1;
1580 }
1581
1582 ++pathIter;
1583 ++splitConstraintsIter;
1584 }
1585 }
1586
1587 // Start writing the base APK.
1588 std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(mOptions.outputPath);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001589 if (!archiveWriter) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001590 mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive");
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001591 return 1;
1592 }
1593
Adam Lesinski467f1712015-11-16 17:35:44 -08001594 bool error = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001595 {
Adam Lesinski467f1712015-11-16 17:35:44 -08001596 // AndroidManifest.xml has no resource name, but the CallSite is built from the name
1597 // (aka, which package the AndroidManifest.xml is coming from).
1598 // So we give it a package name so it can see local resources.
Adam Lesinski64587af2016-02-18 18:33:06 -08001599 manifestXml->file.name.package = mContext->getCompilationPackage();
Adam Lesinski467f1712015-11-16 17:35:44 -08001600
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001601 XmlReferenceLinker manifestLinker;
Adam Lesinski6a008172016-02-02 17:02:58 -08001602 if (manifestLinker.consume(mContext, manifestXml.get())) {
Rohit Agrawale49bb302016-04-22 12:27:55 -07001603 if (mOptions.generateProguardRulesPath &&
1604 !proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
1605 manifestXml.get(),
1606 &proguardKeepSet)) {
1607 error = true;
1608 }
1609
1610 if (mOptions.generateMainDexProguardRulesPath &&
1611 !proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
1612 manifestXml.get(),
1613 &proguardMainDexKeepSet,
1614 true)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001615 error = true;
1616 }
1617
Adam Lesinskica5638f2015-10-21 14:42:43 -07001618 if (mOptions.generateJavaClassPath) {
1619 if (!writeManifestJavaFile(manifestXml.get())) {
1620 error = true;
1621 }
1622 }
Alexandria Cornwalla7cc3f12016-08-16 13:33:32 -07001623
1624 if (mOptions.noXmlNamespaces) {
1625 // PackageParser will fail if URIs are removed from AndroidManifest.xml.
1626 XmlNamespaceRemover namespaceRemover(true /* keepUris */);
1627 if (!namespaceRemover.consume(mContext, manifestXml.get())) {
1628 error = true;
1629 }
1630 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001631 } else {
1632 error = true;
1633 }
1634 }
1635
Adam Lesinski467f1712015-11-16 17:35:44 -08001636 if (error) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001637 mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest");
Adam Lesinski467f1712015-11-16 17:35:44 -08001638 return 1;
1639 }
1640
Adam Lesinski36c73a52016-08-11 13:39:24 -07001641 if (!writeApk(archiveWriter.get(), &proguardKeepSet, manifestXml.get(), &mFinalTable)) {
Alexandria Cornwall637b4822016-08-11 09:53:16 -07001642 return 1;
1643 }
1644
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001645 if (mOptions.generateJavaClassPath) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001646 JavaClassGeneratorOptions options;
Adam Lesinski52364f72016-01-11 13:10:24 -08001647 options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
Adam Lesinski3524a232016-04-01 19:19:24 -07001648 options.javadocAnnotations = mOptions.javadocAnnotations;
Adam Lesinski52364f72016-01-11 13:10:24 -08001649
Adam Lesinskief9c5012016-01-22 14:09:53 -08001650 if (mOptions.staticLib || mOptions.generateNonFinalIds) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001651 options.useFinal = false;
1652 }
1653
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001654 const StringPiece actualPackage = mContext->getCompilationPackage();
1655 StringPiece outputPackage = mContext->getCompilationPackage();
Adam Lesinski52364f72016-01-11 13:10:24 -08001656 if (mOptions.customJavaPackage) {
1657 // Override the output java package to the custom one.
1658 outputPackage = mOptions.customJavaPackage.value();
1659 }
Adam Lesinski83f22552015-11-07 11:51:23 -08001660
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001661 if (mOptions.privateSymbols) {
1662 // If we defined a private symbols package, we only emit Public symbols
1663 // to the original package, and private and public symbols to the private package.
1664
1665 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
Adam Lesinski6a008172016-02-02 17:02:58 -08001666 if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(),
Adam Lesinski52364f72016-01-11 13:10:24 -08001667 outputPackage, options)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001668 return 1;
1669 }
1670
1671 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
Adam Lesinski83f22552015-11-07 11:51:23 -08001672 outputPackage = mOptions.privateSymbols.value();
1673 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001674
Adam Lesinskifb48d292015-11-07 15:52:13 -08001675 if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
Adam Lesinski83f22552015-11-07 11:51:23 -08001676 return 1;
1677 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001678
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001679 for (const std::string& extraPackage : mOptions.extraJavaPackages) {
Adam Lesinski52364f72016-01-11 13:10:24 -08001680 if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -07001681 return 1;
1682 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001683 }
1684 }
1685
Rohit Agrawale49bb302016-04-22 12:27:55 -07001686 if (!writeProguardFile(mOptions.generateProguardRulesPath, proguardKeepSet)) {
1687 return 1;
1688 }
1689
1690 if (!writeProguardFile(mOptions.generateMainDexProguardRulesPath, proguardMainDexKeepSet)) {
1691 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001692 }
1693
Adam Lesinski355f2852016-02-13 20:26:45 -08001694 if (mContext->verbose()) {
1695 DebugPrintTableOptions debugPrintTableOptions;
1696 debugPrintTableOptions.showSources = true;
1697 Debug::printTable(&mFinalTable, debugPrintTableOptions);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001698 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001699 return 0;
1700 }
Adam Lesinskifb48d292015-11-07 15:52:13 -08001701
1702private:
1703 LinkOptions mOptions;
Adam Lesinski6a008172016-02-02 17:02:58 -08001704 LinkContext* mContext;
Adam Lesinskifb48d292015-11-07 15:52:13 -08001705 ResourceTable mFinalTable;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001706
Adam Lesinskifb48d292015-11-07 15:52:13 -08001707 std::unique_ptr<TableMerger> mTableMerger;
1708
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001709 // A pointer to the FileCollection representing the filesystem (not archives).
Adam Lesinski64587af2016-02-18 18:33:06 -08001710 std::unique_ptr<io::FileCollection> mFileCollection;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001711
1712 // A vector of IFileCollections. This is mainly here to keep ownership of the collections.
Adam Lesinskia40e9722015-11-24 19:11:46 -08001713 std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
Adam Lesinski64587af2016-02-18 18:33:06 -08001714
1715 // A vector of ResourceTables. This is here to retain ownership, so that the SymbolTable
1716 // can use these.
1717 std::vector<std::unique_ptr<ResourceTable>> mStaticTableIncludes;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001718};
1719
1720int link(const std::vector<StringPiece>& args) {
Adam Lesinski355f2852016-02-13 20:26:45 -08001721 LinkContext context;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001722 LinkOptions options;
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001723 std::vector<std::string> overlayArgList;
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001724 std::vector<std::string> extraJavaPackages;
Adam Lesinski6a008172016-02-02 17:02:58 -08001725 Maybe<std::string> configs;
Adam Lesinski355f2852016-02-13 20:26:45 -08001726 Maybe<std::string> preferredDensity;
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001727 Maybe<std::string> productList;
Adam Lesinski8900aa82016-01-25 22:48:15 -08001728 bool legacyXFlag = false;
1729 bool requireLocalization = false;
Adam Lesinski64587af2016-02-18 18:33:06 -08001730 bool verbose = false;
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001731 Maybe<std::string> stableIdFilePath;
Adam Lesinski36c73a52016-08-11 13:39:24 -07001732 std::vector<std::string> splitArgs;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001733 Flags flags = Flags()
1734 .requiredFlag("-o", "Output path", &options.outputPath)
1735 .requiredFlag("--manifest", "Path to the Android manifest to build",
1736 &options.manifestPath)
1737 .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
Adam Lesinski52364f72016-01-11 13:10:24 -08001738 .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n"
Adam Lesinskifb48d292015-11-07 15:52:13 -08001739 "The last conflicting resource given takes precedence.",
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001740 &overlayArgList)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001741 .optionalFlag("--java", "Directory in which to generate R.java",
1742 &options.generateJavaClassPath)
1743 .optionalFlag("--proguard", "Output file for generated Proguard rules",
1744 &options.generateProguardRulesPath)
Rohit Agrawale49bb302016-04-22 12:27:55 -07001745 .optionalFlag("--proguard-main-dex",
1746 "Output file for generated Proguard rules for the main dex",
1747 &options.generateMainDexProguardRulesPath)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001748 .optionalSwitch("--no-auto-version",
1749 "Disables automatic style and layout SDK versioning",
1750 &options.noAutoVersion)
Adam Lesinski64587af2016-02-18 18:33:06 -08001751 .optionalSwitch("--no-version-vectors",
1752 "Disables automatic versioning of vector drawables. Use this only\n"
1753 "when building with vector drawable support library",
1754 &options.noVersionVectors)
Alexandria Cornwall77788eb2016-09-06 15:16:49 -07001755 .optionalSwitch("--no-resource-deduping", "Disables automatic deduping of resources with\n"
1756 "identical values across compatible configurations.",
1757 &options.noResourceDeduping)
Adam Lesinski8900aa82016-01-25 22:48:15 -08001758 .optionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01",
1759 &legacyXFlag)
1760 .optionalSwitch("-z", "Require localization of strings marked 'suggested'",
1761 &requireLocalization)
Adam Lesinski6a008172016-02-02 17:02:58 -08001762 .optionalFlag("-c", "Comma separated list of configurations to include. The default\n"
1763 "is all configurations", &configs)
Adam Lesinski355f2852016-02-13 20:26:45 -08001764 .optionalFlag("--preferred-density",
1765 "Selects the closest matching density and strips out all others.",
1766 &preferredDensity)
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001767 .optionalFlag("--product", "Comma separated list of product names to keep",
1768 &productList)
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001769 .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
1770 "by -o",
1771 &options.outputToDirectory)
Alexandria Cornwalla7cc3f12016-08-16 13:33:32 -07001772 .optionalSwitch("--no-xml-namespaces", "Removes XML namespace prefix and URI "
1773 "information from AndroidManifest.xml\nand XML binaries in res/*.",
1774 &options.noXmlNamespaces)
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001775 .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001776 "AndroidManifest.xml",
1777 &options.manifestFixerOptions.minSdkVersionDefault)
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001778 .optionalFlag("--target-sdk-version", "Default target SDK version to use for "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001779 "AndroidManifest.xml",
1780 &options.manifestFixerOptions.targetSdkVersionDefault)
Adam Lesinski52364f72016-01-11 13:10:24 -08001781 .optionalFlag("--version-code", "Version code (integer) to inject into the "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001782 "AndroidManifest.xml if none is present",
1783 &options.manifestFixerOptions.versionCodeDefault)
Adam Lesinski52364f72016-01-11 13:10:24 -08001784 .optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001785 "if none is present",
1786 &options.manifestFixerOptions.versionNameDefault)
1787 .optionalSwitch("--static-lib", "Generate a static Android library",
1788 &options.staticLib)
Adam Lesinski64587af2016-02-18 18:33:06 -08001789 .optionalSwitch("--no-static-lib-packages",
1790 "Merge all library resources under the app's package",
1791 &options.noStaticLibPackages)
Adam Lesinskief9c5012016-01-22 14:09:53 -08001792 .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n"
1793 "This is implied when --static-lib is specified.",
1794 &options.generateNonFinalIds)
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001795 .optionalFlag("--stable-ids", "File containing a list of name to ID mapping.",
1796 &stableIdFilePath)
1797 .optionalFlag("--emit-ids", "Emit a file at the given path with a list of name to ID\n"
1798 "mappings, suitable for use with --stable-ids.",
1799 &options.resourceIdMapPath)
Adam Lesinski9ba47d82015-10-13 11:37:10 -07001800 .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
Adam Lesinski2ae4a872015-11-02 16:10:55 -08001801 "private symbols.\n"
1802 "If not specified, public and private symbols will use the application's "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001803 "package name",
1804 &options.privateSymbols)
Adam Lesinski52364f72016-01-11 13:10:24 -08001805 .optionalFlag("--custom-package", "Custom Java package under which to generate R.java",
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001806 &options.customJavaPackage)
Adam Lesinski83f22552015-11-07 11:51:23 -08001807 .optionalFlagList("--extra-packages", "Generate the same R.java but with different "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001808 "package names",
1809 &extraJavaPackages)
Adam Lesinski3524a232016-04-01 19:19:24 -07001810 .optionalFlagList("--add-javadoc-annotation", "Adds a JavaDoc annotation to all "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001811 "generated Java classes",
1812 &options.javadocAnnotations)
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001813 .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001814 "overlays without <add-resource> tags",
1815 &options.autoAddOverlay)
Adam Lesinski52364f72016-01-11 13:10:24 -08001816 .optionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml",
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001817 &options.manifestFixerOptions.renameManifestPackage)
Adam Lesinski52364f72016-01-11 13:10:24 -08001818 .optionalFlag("--rename-instrumentation-target-package",
1819 "Changes the name of the target package for instrumentation. Most useful "
1820 "when used\nin conjunction with --rename-manifest-package",
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001821 &options.manifestFixerOptions.renameInstrumentationTargetPackage)
Adam Lesinski52364f72016-01-11 13:10:24 -08001822 .optionalFlagList("-0", "File extensions not to compress",
1823 &options.extensionsToNotCompress)
Adam Lesinski36c73a52016-08-11 13:39:24 -07001824 .optionalFlagList("--split", "Split resources matching a set of configs out to a "
1825 "Split APK.\nSyntax: path/to/output.apk:<config>[,<config>[...]]",
1826 &splitArgs)
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001827 .optionalSwitch("-v", "Enables verbose logging",
1828 &verbose);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001829
1830 if (!flags.parse("aapt2 link", args, &std::cerr)) {
1831 return 1;
1832 }
1833
Adam Lesinskic51562c2016-04-28 11:12:38 -07001834 // Expand all argument-files passed into the command line. These start with '@'.
1835 std::vector<std::string> argList;
1836 for (const std::string& arg : flags.getArgs()) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001837 if (util::stringStartsWith(arg, "@")) {
Adam Lesinskic51562c2016-04-28 11:12:38 -07001838 const std::string path = arg.substr(1, arg.size() - 1);
1839 std::string error;
1840 if (!file::appendArgsFromFile(path, &argList, &error)) {
1841 context.getDiagnostics()->error(DiagMessage(path) << error);
1842 return 1;
1843 }
1844 } else {
1845 argList.push_back(arg);
1846 }
1847 }
1848
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001849 // Expand all argument-files passed to -R.
1850 for (const std::string& arg : overlayArgList) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001851 if (util::stringStartsWith(arg, "@")) {
Adam Lesinski1e21ff02016-06-24 14:57:58 -07001852 const std::string path = arg.substr(1, arg.size() - 1);
1853 std::string error;
1854 if (!file::appendArgsFromFile(path, &options.overlayFiles, &error)) {
1855 context.getDiagnostics()->error(DiagMessage(path) << error);
1856 return 1;
1857 }
1858 } else {
1859 options.overlayFiles.push_back(arg);
1860 }
1861 }
1862
Adam Lesinski64587af2016-02-18 18:33:06 -08001863 if (verbose) {
1864 context.setVerbose(verbose);
1865 }
1866
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001867 // Populate the set of extra packages for which to generate R.java.
1868 for (std::string& extraPackage : extraJavaPackages) {
1869 // A given package can actually be a colon separated list of packages.
1870 for (StringPiece package : util::split(extraPackage, ':')) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001871 options.extraJavaPackages.insert(package.toString());
Adam Lesinskifc9570e62015-11-16 15:07:54 -08001872 }
1873 }
1874
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001875 if (productList) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001876 for (StringPiece product : util::tokenize(productList.value(), ',')) {
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08001877 if (product != "" && product != "default") {
1878 options.products.insert(product.toString());
1879 }
1880 }
1881 }
1882
Adam Lesinski6a008172016-02-02 17:02:58 -08001883 AxisConfigFilter filter;
1884 if (configs) {
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001885 for (const StringPiece& configStr : util::tokenize(configs.value(), ',')) {
Adam Lesinski6a008172016-02-02 17:02:58 -08001886 ConfigDescription config;
1887 LocaleValue lv;
1888 if (lv.initFromFilterString(configStr)) {
1889 lv.writeTo(&config);
1890 } else if (!ConfigDescription::parse(configStr, &config)) {
1891 context.getDiagnostics()->error(
1892 DiagMessage() << "invalid config '" << configStr << "' for -c option");
1893 return 1;
1894 }
1895
1896 if (config.density != 0) {
1897 context.getDiagnostics()->warn(
1898 DiagMessage() << "ignoring density '" << config << "' for -c option");
1899 } else {
1900 filter.addConfig(config);
1901 }
1902 }
1903
Adam Lesinski355f2852016-02-13 20:26:45 -08001904 options.tableSplitterOptions.configFilter = &filter;
1905 }
1906
1907 if (preferredDensity) {
1908 ConfigDescription preferredDensityConfig;
1909 if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) {
1910 context.getDiagnostics()->error(DiagMessage() << "invalid density '"
1911 << preferredDensity.value()
1912 << "' for --preferred-density option");
1913 return 1;
1914 }
1915
1916 // Clear the version that can be automatically added.
1917 preferredDensityConfig.sdkVersion = 0;
1918
1919 if (preferredDensityConfig.diff(ConfigDescription::defaultConfig())
1920 != ConfigDescription::CONFIG_DENSITY) {
1921 context.getDiagnostics()->error(DiagMessage() << "invalid preferred density '"
1922 << preferredDensity.value() << "'. "
1923 << "Preferred density must only be a density value");
1924 return 1;
1925 }
1926 options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density;
Adam Lesinski6a008172016-02-02 17:02:58 -08001927 }
1928
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -07001929 if (!options.staticLib && stableIdFilePath) {
1930 if (!loadStableIdMap(context.getDiagnostics(), stableIdFilePath.value(),
1931 &options.stableIdMap)) {
1932 return 1;
1933 }
1934 }
1935
Adam Lesinski9756dec2016-08-08 12:35:04 -07001936 // Populate some default no-compress extensions that are already compressed.
1937 options.extensionsToNotCompress.insert({
1938 ".jpg", ".jpeg", ".png", ".gif",
1939 ".wav", ".mp2", ".mp3", ".ogg", ".aac",
1940 ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
1941 ".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
1942 ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
1943 ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"});
1944
Adam Lesinski36c73a52016-08-11 13:39:24 -07001945 // Parse the split parameters.
1946 for (const std::string& splitArg : splitArgs) {
1947 options.splitPaths.push_back({});
1948 options.splitConstraints.push_back({});
1949 if (!parseSplitParameter(splitArg, context.getDiagnostics(), &options.splitPaths.back(),
1950 &options.splitConstraints.back())) {
1951 return 1;
1952 }
1953 }
1954
Adam Lesinski626a69f2016-03-03 10:09:26 -08001955 // Turn off auto versioning for static-libs.
1956 if (options.staticLib) {
1957 options.noAutoVersion = true;
1958 options.noVersionVectors = true;
1959 }
1960
Adam Lesinski6a008172016-02-02 17:02:58 -08001961 LinkCommand cmd(&context, options);
Adam Lesinskic51562c2016-04-28 11:12:38 -07001962 return cmd.run(argList);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001963}
1964
1965} // namespace aapt