blob: 652e52fa0a67a792b00edca846f17d5498242101 [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 Lesinski1ab598f2015-08-14 14:26:04 -070020#include "NameMangler.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070021#include "compile/IdAssigner.h"
22#include "flatten/Archive.h"
23#include "flatten/TableFlattener.h"
24#include "flatten/XmlFlattener.h"
Adam Lesinskia40e9722015-11-24 19:11:46 -080025#include "io/FileSystem.h"
26#include "io/ZipArchive.h"
Adam Lesinskica5638f2015-10-21 14:42:43 -070027#include "java/JavaClassGenerator.h"
28#include "java/ManifestClassGenerator.h"
29#include "java/ProguardRules.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070030#include "link/Linkers.h"
Adam Lesinski467f1712015-11-16 17:35:44 -080031#include "link/ReferenceLinker.h"
Adam Lesinski2ae4a872015-11-02 16:10:55 -080032#include "link/ManifestFixer.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070033#include "link/TableMerger.h"
34#include "process/IResourceTableConsumer.h"
35#include "process/SymbolTable.h"
36#include "unflatten/BinaryResourceParser.h"
37#include "unflatten/FileExportHeaderReader.h"
38#include "util/Files.h"
39#include "util/StringPiece.h"
Adam Lesinski467f1712015-11-16 17:35:44 -080040#include "xml/XmlDom.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070041
42#include <fstream>
43#include <sys/stat.h>
Adam Lesinski1ab598f2015-08-14 14:26:04 -070044#include <vector>
45
46namespace aapt {
47
48struct LinkOptions {
49 std::string outputPath;
50 std::string manifestPath;
51 std::vector<std::string> includePaths;
Adam Lesinskifb48d292015-11-07 15:52:13 -080052 std::vector<std::string> overlayFiles;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070053 Maybe<std::string> generateJavaClassPath;
Adam Lesinskifc9570e62015-11-16 15:07:54 -080054 std::set<std::string> extraJavaPackages;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070055 Maybe<std::string> generateProguardRulesPath;
56 bool noAutoVersion = false;
57 bool staticLib = false;
58 bool verbose = false;
59 bool outputToDirectory = false;
Adam Lesinskia6fe3452015-12-09 15:20:52 -080060 bool autoAddOverlay = false;
Adam Lesinski9e10ac72015-10-16 14:37:48 -070061 Maybe<std::u16string> privateSymbols;
Adam Lesinski2ae4a872015-11-02 16:10:55 -080062 Maybe<std::u16string> minSdkVersionDefault;
63 Maybe<std::u16string> targetSdkVersionDefault;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070064};
65
66struct LinkContext : public IAaptContext {
67 StdErrDiagnostics mDiagnostics;
68 std::unique_ptr<NameMangler> mNameMangler;
69 std::u16string mCompilationPackage;
70 uint8_t mPackageId;
71 std::unique_ptr<ISymbolTable> mSymbols;
72
73 IDiagnostics* getDiagnostics() override {
74 return &mDiagnostics;
75 }
76
77 NameMangler* getNameMangler() override {
78 return mNameMangler.get();
79 }
80
81 StringPiece16 getCompilationPackage() override {
82 return mCompilationPackage;
83 }
84
85 uint8_t getPackageId() override {
86 return mPackageId;
87 }
88
89 ISymbolTable* getExternalSymbols() override {
90 return mSymbols.get();
91 }
92};
93
Adam Lesinskifb48d292015-11-07 15:52:13 -080094class LinkCommand {
95public:
96 LinkCommand(const LinkOptions& options) :
Adam Lesinskia40e9722015-11-24 19:11:46 -080097 mOptions(options), mContext(), mFinalTable(), mFileCollection(nullptr) {
98 std::unique_ptr<io::FileCollection> fileCollection =
99 util::make_unique<io::FileCollection>();
100
101 // Get a pointer to the FileCollection for convenience, but it will be owned by the vector.
102 mFileCollection = fileCollection.get();
103
104 // Move it to the collection.
105 mCollections.push_back(std::move(fileCollection));
Adam Lesinskifb48d292015-11-07 15:52:13 -0800106 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700107
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700108 /**
109 * Creates a SymbolTable that loads symbols from the various APKs and caches the
110 * results for faster lookup.
111 */
112 std::unique_ptr<ISymbolTable> createSymbolTableFromIncludePaths() {
113 AssetManagerSymbolTableBuilder builder;
114 for (const std::string& path : mOptions.includePaths) {
115 if (mOptions.verbose) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800116 mContext.getDiagnostics()->note(DiagMessage(path) << "loading include path");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700117 }
118
119 std::unique_ptr<android::AssetManager> assetManager =
120 util::make_unique<android::AssetManager>();
121 int32_t cookie = 0;
122 if (!assetManager->addAssetPath(android::String8(path.data(), path.size()), &cookie)) {
123 mContext.getDiagnostics()->error(
Adam Lesinskifb48d292015-11-07 15:52:13 -0800124 DiagMessage(path) << "failed to load include path");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700125 return {};
126 }
127 builder.add(std::move(assetManager));
128 }
129 return builder.build();
130 }
131
Adam Lesinskia40e9722015-11-24 19:11:46 -0800132 std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700133 std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
Adam Lesinskia40e9722015-11-24 19:11:46 -0800134 BinaryResourceParser parser(&mContext, table.get(), source, data, len);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700135 if (!parser.parse()) {
136 return {};
137 }
138 return table;
139 }
140
141 /**
142 * Inflates an XML file from the source path.
143 */
Adam Lesinskia40e9722015-11-24 19:11:46 -0800144 static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700145 std::ifstream fin(path, std::ifstream::binary);
146 if (!fin) {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800147 diag->error(DiagMessage(path) << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700148 return {};
149 }
150
Adam Lesinskia40e9722015-11-24 19:11:46 -0800151 return xml::inflate(&fin, diag, Source(path));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700152 }
153
Adam Lesinskia40e9722015-11-24 19:11:46 -0800154 static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(
155 const Source& source,
156 const void* data, size_t len,
157 IDiagnostics* diag) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700158 std::string errorStr;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800159 ssize_t offset = getWrappedDataOffset(data, len, &errorStr);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700160 if (offset < 0) {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800161 diag->error(DiagMessage(source) << errorStr);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700162 return {};
163 }
164
Adam Lesinski467f1712015-11-16 17:35:44 -0800165 std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(
Adam Lesinskia40e9722015-11-24 19:11:46 -0800166 reinterpret_cast<const uint8_t*>(data) + static_cast<size_t>(offset),
167 len - static_cast<size_t>(offset),
168 diag,
169 source);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700170 if (!xmlRes) {
171 return {};
172 }
173 return xmlRes;
174 }
175
Adam Lesinskia40e9722015-11-24 19:11:46 -0800176 static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
177 const void* data, size_t len,
178 IDiagnostics* diag) {
179 std::unique_ptr<ResourceFile> resFile = util::make_unique<ResourceFile>();
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700180 std::string errorStr;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800181 ssize_t offset = unwrapFileExportHeader(data, len, resFile.get(), &errorStr);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700182 if (offset < 0) {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800183 diag->error(DiagMessage(source) << errorStr);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700184 return {};
185 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800186 return resFile;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700187 }
188
Adam Lesinskia40e9722015-11-24 19:11:46 -0800189 bool copyFileToArchive(io::IFile* file, const std::string& outPath, uint32_t flags,
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700190 IArchiveWriter* writer) {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800191 std::unique_ptr<io::IData> data = file->openAsData();
192 if (!data) {
193 mContext.getDiagnostics()->error(DiagMessage(file->getSource())
194 << "failed to open file");
195 return false;
196 }
197
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700198 std::string errorStr;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800199 ssize_t offset = getWrappedDataOffset(data->data(), data->size(), &errorStr);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700200 if (offset < 0) {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800201 mContext.getDiagnostics()->error(DiagMessage(file->getSource()) << errorStr);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700202 return false;
203 }
204
Adam Lesinskia40e9722015-11-24 19:11:46 -0800205 if (writer->startEntry(outPath, flags)) {
206 if (writer->writeEntry(reinterpret_cast<const uint8_t*>(data->data()) + offset,
207 data->size() - static_cast<size_t>(offset))) {
208 if (writer->finishEntry()) {
209 return true;
210 }
211 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700212 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800213
214 mContext.getDiagnostics()->error(
215 DiagMessage(mOptions.outputPath) << "failed to write file " << outPath);
216 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700217 }
218
Adam Lesinski467f1712015-11-16 17:35:44 -0800219 Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700220 // Make sure the first element is <manifest> with package attribute.
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800221 if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700222 if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") {
223 if (xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package")) {
224 return AppInfo{ packageAttr->value };
225 }
226 }
227 }
228 return {};
229 }
230
Adam Lesinskifb48d292015-11-07 15:52:13 -0800231 bool verifyNoExternalPackages() {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700232 bool error = false;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800233 for (const auto& package : mFinalTable.packages) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700234 if (mContext.getCompilationPackage() != package->name ||
235 !package->id || package->id.value() != mContext.getPackageId()) {
236 // We have a package that is not related to the one we're building!
237 for (const auto& type : package->types) {
238 for (const auto& entry : type->entries) {
239 for (const auto& configValue : entry->values) {
Adam Lesinskie78fd612015-10-22 12:48:43 -0700240 mContext.getDiagnostics()->error(
241 DiagMessage(configValue.value->getSource())
242 << "defined resource '"
243 << ResourceNameRef(package->name,
244 type->type,
245 entry->name)
246 << "' for external package '"
247 << package->name << "'");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700248 error = true;
249 }
250 }
251 }
252 }
253 }
254 return !error;
255 }
256
257 std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
258 if (mOptions.outputToDirectory) {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800259 return createDirectoryArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700260 } else {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800261 return createZipFileArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700262 }
263 }
264
265 bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
266 BigBuffer buffer(1024);
267 TableFlattenerOptions options = {};
268 options.useExtendedChunks = mOptions.staticLib;
269 TableFlattener flattener(&buffer, options);
270 if (!flattener.consume(&mContext, table)) {
271 return false;
272 }
273
Adam Lesinskia40e9722015-11-24 19:11:46 -0800274 if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
275 if (writer->writeEntry(buffer)) {
276 if (writer->finishEntry()) {
277 return true;
278 }
279 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700280 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800281
282 mContext.getDiagnostics()->error(
283 DiagMessage() << "failed to write resources.arsc to archive");
284 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700285 }
286
Adam Lesinski467f1712015-11-16 17:35:44 -0800287 bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700288 IArchiveWriter* writer) {
289 BigBuffer buffer(1024);
290 XmlFlattenerOptions options = {};
291 options.keepRawValues = mOptions.staticLib;
292 options.maxSdkLevel = maxSdkLevel;
293 XmlFlattener flattener(&buffer, options);
294 if (!flattener.consume(&mContext, xmlRes)) {
295 return false;
296 }
297
Adam Lesinskia40e9722015-11-24 19:11:46 -0800298
299 if (writer->startEntry(path, ArchiveEntry::kCompress)) {
300 if (writer->writeEntry(buffer)) {
301 if (writer->finishEntry()) {
302 return true;
303 }
304 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700305 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800306 mContext.getDiagnostics()->error(
307 DiagMessage() << "failed to write " << path << " to archive");
308 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700309 }
310
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700311 bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
312 const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700313 if (!mOptions.generateJavaClassPath) {
314 return true;
315 }
316
317 std::string outPath = mOptions.generateJavaClassPath.value();
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700318 file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(outPackage)));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700319 file::mkdirs(outPath);
320 file::appendPath(&outPath, "R.java");
321
322 std::ofstream fout(outPath, std::ofstream::binary);
323 if (!fout) {
324 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
325 return false;
326 }
327
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700328 JavaClassGenerator generator(table, javaOptions);
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700329 if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700330 mContext.getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
331 return false;
332 }
333 return true;
334 }
335
Adam Lesinski467f1712015-11-16 17:35:44 -0800336 bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
Adam Lesinskica5638f2015-10-21 14:42:43 -0700337 if (!mOptions.generateJavaClassPath) {
338 return true;
339 }
340
341 std::string outPath = mOptions.generateJavaClassPath.value();
342 file::appendPath(&outPath,
343 file::packageToPath(util::utf16ToUtf8(mContext.getCompilationPackage())));
344 file::mkdirs(outPath);
345 file::appendPath(&outPath, "Manifest.java");
346
347 std::ofstream fout(outPath, std::ofstream::binary);
348 if (!fout) {
349 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
350 return false;
351 }
352
353 ManifestClassGenerator generator;
354 if (!generator.generate(mContext.getDiagnostics(), mContext.getCompilationPackage(),
355 manifestXml, &fout)) {
356 return false;
357 }
358
359 if (!fout) {
360 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
361 return false;
362 }
363 return true;
364 }
365
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700366 bool writeProguardFile(const proguard::KeepSet& keepSet) {
367 if (!mOptions.generateProguardRulesPath) {
368 return true;
369 }
370
371 std::ofstream fout(mOptions.generateProguardRulesPath.value(), std::ofstream::binary);
372 if (!fout) {
373 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
374 return false;
375 }
376
377 proguard::writeKeepSet(&fout, keepSet);
378 if (!fout) {
379 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
380 return false;
381 }
382 return true;
383 }
384
Adam Lesinskifb48d292015-11-07 15:52:13 -0800385 bool mergeStaticLibrary(const std::string& input) {
386 // TODO(adamlesinski): Load resources from a static library APK and merge the table into
387 // TableMerger.
388 mContext.getDiagnostics()->warn(DiagMessage()
389 << "linking static libraries not supported yet: "
390 << input);
391 return true;
392 }
393
Adam Lesinskia40e9722015-11-24 19:11:46 -0800394 bool mergeResourceTable(io::IFile* file, bool override) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800395 if (mOptions.verbose) {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800396 mContext.getDiagnostics()->note(DiagMessage() << "linking " << file->getSource());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800397 }
398
Adam Lesinskia40e9722015-11-24 19:11:46 -0800399 std::unique_ptr<io::IData> data = file->openAsData();
400 if (!data) {
401 mContext.getDiagnostics()->error(DiagMessage(file->getSource())
402 << "failed to open file");
403 return false;
404 }
405
406 std::unique_ptr<ResourceTable> table = loadTable(file->getSource(), data->data(),
407 data->size());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800408 if (!table) {
409 return false;
410 }
411
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800412 bool result = false;
413 if (override) {
414 result = mTableMerger->mergeOverlay(file->getSource(), table.get());
415 } else {
416 result = mTableMerger->merge(file->getSource(), table.get());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800417 }
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800418 return result;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800419 }
420
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800421 bool mergeCompiledFile(io::IFile* file, std::unique_ptr<ResourceFile> fileDesc, bool overlay) {
422 if (mOptions.verbose) {
423 mContext.getDiagnostics()->note(DiagMessage() << "adding " << file->getSource());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800424 }
425
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800426 bool result = false;
427 if (overlay) {
428 result = mTableMerger->mergeFileOverlay(*fileDesc, file);
Adam Lesinskifb48d292015-11-07 15:52:13 -0800429 } else {
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800430 result = mTableMerger->mergeFile(*fileDesc, file);
Adam Lesinskifb48d292015-11-07 15:52:13 -0800431 }
432
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800433 if (!result) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800434 return false;
435 }
436
437 // Add the exports of this file to the table.
Adam Lesinskia40e9722015-11-24 19:11:46 -0800438 for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800439 if (exportedSymbol.name.package.empty()) {
440 exportedSymbol.name.package = mContext.getCompilationPackage().toString();
441 }
442
443 ResourceNameRef resName = exportedSymbol.name;
444
445 Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(
446 exportedSymbol.name);
447 if (mangledName) {
448 resName = mangledName.value();
449 }
450
451 std::unique_ptr<Id> id = util::make_unique<Id>();
Adam Lesinskia40e9722015-11-24 19:11:46 -0800452 id->setSource(fileDesc->source.withLine(exportedSymbol.line));
Adam Lesinskifb48d292015-11-07 15:52:13 -0800453 bool result = mFinalTable.addResourceAllowMangled(resName, {}, std::move(id),
Adam Lesinskia40e9722015-11-24 19:11:46 -0800454 mContext.getDiagnostics());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800455 if (!result) {
456 return false;
457 }
458 }
Adam Lesinskifb48d292015-11-07 15:52:13 -0800459 return true;
460 }
461
Adam Lesinskia40e9722015-11-24 19:11:46 -0800462 /**
463 * Creates an io::IFileCollection from the ZIP archive and processes the files within.
464 */
465 bool mergeArchive(const std::string& input, bool override) {
466 std::string errorStr;
467 std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
468 input, &errorStr);
469 if (!collection) {
470 mContext.getDiagnostics()->error(DiagMessage(input) << errorStr);
471 return false;
472 }
473
474 bool error = false;
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800475 for (auto iter = collection->iterator(); iter->hasNext(); ) {
476 if (!processFile(iter->next(), override)) {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800477 error = true;
478 }
479 }
480
481 // Make sure to move the collection into the set of IFileCollections.
482 mCollections.push_back(std::move(collection));
483 return !error;
484 }
485
486 bool processFile(const std::string& path, bool override) {
487 if (util::stringEndsWith<char>(path, ".flata")) {
488 return mergeArchive(path, override);
489 }
490
491 io::IFile* file = mFileCollection->insertFile(path);
492 return processFile(file, override);
493 }
494
495 bool processFile(io::IFile* file, bool override) {
496 const Source& src = file->getSource();
497 if (util::stringEndsWith<char>(src.path, ".arsc.flat")) {
498 return mergeResourceTable(file, override);
499 } else {
500 // Try opening the file and looking for an Export header.
501 std::unique_ptr<io::IData> data = file->openAsData();
502 if (!data) {
503 mContext.getDiagnostics()->error(DiagMessage(src) << "failed to open");
504 return false;
505 }
506
507 std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader(
508 src, data->data(), data->size(), mContext.getDiagnostics());
509 if (resourceFile) {
510 return mergeCompiledFile(file, std::move(resourceFile), override);
511 }
Adam Lesinskifb48d292015-11-07 15:52:13 -0800512 }
513 return false;
514 }
515
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700516 int run(const std::vector<std::string>& inputFiles) {
517 // Load the AndroidManifest.xml
Adam Lesinskia40e9722015-11-24 19:11:46 -0800518 std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
519 mContext.getDiagnostics());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700520 if (!manifestXml) {
521 return 1;
522 }
523
524 if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) {
525 mContext.mCompilationPackage = maybeAppInfo.value().package;
526 } else {
527 mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
528 << "no package specified in <manifest> tag");
529 return 1;
530 }
531
532 if (!util::isJavaPackageName(mContext.mCompilationPackage)) {
533 mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
534 << "invalid package name '"
535 << mContext.mCompilationPackage
536 << "'");
537 return 1;
538 }
539
540 mContext.mNameMangler = util::make_unique<NameMangler>(
541 NameManglerPolicy{ mContext.mCompilationPackage });
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700542
543 if (mContext.mCompilationPackage == u"android") {
544 mContext.mPackageId = 0x01;
545 } else {
546 mContext.mPackageId = 0x7f;
547 }
548
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700549 mContext.mSymbols = createSymbolTableFromIncludePaths();
550 if (!mContext.mSymbols) {
551 return 1;
552 }
553
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800554 TableMergerOptions tableMergerOptions;
555 tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
556 mTableMerger = util::make_unique<TableMerger>(&mContext, &mFinalTable, tableMergerOptions);
Adam Lesinskifb48d292015-11-07 15:52:13 -0800557
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700558 if (mOptions.verbose) {
559 mContext.getDiagnostics()->note(
560 DiagMessage() << "linking package '" << mContext.mCompilationPackage << "' "
561 << "with package ID " << std::hex << (int) mContext.mPackageId);
562 }
563
Adam Lesinskifb48d292015-11-07 15:52:13 -0800564
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700565 for (const std::string& input : inputFiles) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800566 if (!processFile(input, false)) {
Adam Lesinski467f1712015-11-16 17:35:44 -0800567 mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input");
568 return 1;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800569 }
570 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700571
Adam Lesinskifb48d292015-11-07 15:52:13 -0800572 for (const std::string& input : mOptions.overlayFiles) {
573 if (!processFile(input, true)) {
Adam Lesinski467f1712015-11-16 17:35:44 -0800574 mContext.getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
575 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700576 }
577 }
578
Adam Lesinskifb48d292015-11-07 15:52:13 -0800579 if (!verifyNoExternalPackages()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700580 return 1;
581 }
582
583 if (!mOptions.staticLib) {
584 PrivateAttributeMover mover;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800585 if (!mover.consume(&mContext, &mFinalTable)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700586 mContext.getDiagnostics()->error(
587 DiagMessage() << "failed moving private attributes");
588 return 1;
589 }
590 }
591
592 {
593 IdAssigner idAssigner;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800594 if (!idAssigner.consume(&mContext, &mFinalTable)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700595 mContext.getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
596 return 1;
597 }
598 }
599
Adam Lesinskifb48d292015-11-07 15:52:13 -0800600 mContext.mNameMangler = util::make_unique<NameMangler>(NameManglerPolicy{
601 mContext.mCompilationPackage, mTableMerger->getMergedPackages() });
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700602 mContext.mSymbols = JoinedSymbolTableBuilder()
Adam Lesinskifb48d292015-11-07 15:52:13 -0800603 .addSymbolTable(util::make_unique<SymbolTableWrapper>(&mFinalTable))
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700604 .addSymbolTable(std::move(mContext.mSymbols))
605 .build();
606
607 {
608 ReferenceLinker linker;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800609 if (!linker.consume(&mContext, &mFinalTable)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700610 mContext.getDiagnostics()->error(DiagMessage() << "failed linking references");
611 return 1;
612 }
613 }
614
615 proguard::KeepSet proguardKeepSet;
616
617 std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter();
618 if (!archiveWriter) {
619 mContext.getDiagnostics()->error(DiagMessage() << "failed to create archive");
620 return 1;
621 }
622
Adam Lesinski467f1712015-11-16 17:35:44 -0800623 bool error = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700624 {
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800625 ManifestFixerOptions manifestFixerOptions;
626 manifestFixerOptions.minSdkVersionDefault = mOptions.minSdkVersionDefault;
627 manifestFixerOptions.targetSdkVersionDefault = mOptions.targetSdkVersionDefault;
628 ManifestFixer manifestFixer(manifestFixerOptions);
629 if (!manifestFixer.consume(&mContext, manifestXml.get())) {
630 error = true;
631 }
632
Adam Lesinski467f1712015-11-16 17:35:44 -0800633 // AndroidManifest.xml has no resource name, but the CallSite is built from the name
634 // (aka, which package the AndroidManifest.xml is coming from).
635 // So we give it a package name so it can see local resources.
636 manifestXml->file.name.package = mContext.getCompilationPackage().toString();
637
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700638 XmlReferenceLinker manifestLinker;
639 if (manifestLinker.consume(&mContext, manifestXml.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700640 if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
641 manifestXml.get(),
642 &proguardKeepSet)) {
643 error = true;
644 }
645
Adam Lesinskica5638f2015-10-21 14:42:43 -0700646 if (mOptions.generateJavaClassPath) {
647 if (!writeManifestJavaFile(manifestXml.get())) {
648 error = true;
649 }
650 }
651
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700652 if (!flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
653 archiveWriter.get())) {
654 error = true;
655 }
656 } else {
657 error = true;
658 }
659 }
660
Adam Lesinski467f1712015-11-16 17:35:44 -0800661 if (error) {
662 mContext.getDiagnostics()->error(DiagMessage() << "failed processing manifest");
663 return 1;
664 }
665
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800666 for (auto& mergeEntry : mTableMerger->getFilesToMerge()) {
667 const ResourceKeyRef& key = mergeEntry.first;
668 const FileToMerge& fileToMerge = mergeEntry.second;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800669
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800670 const StringPiece path = fileToMerge.file->getSource().path;
671
672 if (key.name.type != ResourceType::kRaw &&
673 (util::stringEndsWith<char>(path, ".xml.flat") ||
674 util::stringEndsWith<char>(path, ".xml"))) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700675 if (mOptions.verbose) {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800676 mContext.getDiagnostics()->note(DiagMessage() << "linking " << path);
677 }
678
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800679 io::IFile* file = fileToMerge.file;
680 std::unique_ptr<io::IData> data = file->openAsData();
Adam Lesinskia40e9722015-11-24 19:11:46 -0800681 if (!data) {
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800682 mContext.getDiagnostics()->error(DiagMessage(file->getSource())
Adam Lesinskia40e9722015-11-24 19:11:46 -0800683 << "failed to open file");
684 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700685 }
686
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800687 std::unique_ptr<xml::XmlResource> xmlRes;
688 if (util::stringEndsWith<char>(path, ".flat")) {
689 xmlRes = loadBinaryXmlSkipFileExport(file->getSource(),
690 data->data(), data->size(),
691 mContext.getDiagnostics());
692 } else {
693 xmlRes = xml::inflate(data->data(), data->size(), mContext.getDiagnostics(),
694 file->getSource());
695 }
696
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700697 if (!xmlRes) {
698 return 1;
699 }
700
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800701 // Create the file description header.
702 xmlRes->file = ResourceFile{
703 key.name.toResourceName(),
704 key.config,
705 fileToMerge.originalSource,
706 };
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700707
708 XmlReferenceLinker xmlLinker;
709 if (xmlLinker.consume(&mContext, xmlRes.get())) {
710 if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(),
711 &proguardKeepSet)) {
712 error = true;
713 }
714
715 Maybe<size_t> maxSdkLevel;
716 if (!mOptions.noAutoVersion) {
717 maxSdkLevel = std::max<size_t>(xmlRes->file.config.sdkVersion, 1u);
718 }
719
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800720 if (!flattenXml(xmlRes.get(), fileToMerge.dstPath, maxSdkLevel,
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700721 archiveWriter.get())) {
722 error = true;
723 }
724
725 if (!mOptions.noAutoVersion) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800726 Maybe<ResourceTable::SearchResult> result = mFinalTable.findResource(
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700727 xmlRes->file.name);
728 for (int sdkLevel : xmlLinker.getSdkLevels()) {
729 if (sdkLevel > xmlRes->file.config.sdkVersion &&
730 shouldGenerateVersionedResource(result.value().entry,
731 xmlRes->file.config,
732 sdkLevel)) {
733 xmlRes->file.config.sdkVersion = sdkLevel;
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800734
735 std::string genResourcePath = ResourceUtils::buildResourceFileName(
736 xmlRes->file, mContext.getNameMangler());
737
Adam Lesinskia40e9722015-11-24 19:11:46 -0800738 bool added = mFinalTable.addFileReference(
739 xmlRes->file.name,
740 xmlRes->file.config,
741 xmlRes->file.source,
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800742 util::utf8ToUtf16(genResourcePath),
Adam Lesinskia40e9722015-11-24 19:11:46 -0800743 mContext.getDiagnostics());
744 if (!added) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700745 error = true;
746 continue;
747 }
748
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800749 if (!flattenXml(xmlRes.get(), genResourcePath, sdkLevel,
750 archiveWriter.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700751 error = true;
752 }
753 }
754 }
755 }
756
757 } else {
758 error = true;
759 }
760 } else {
761 if (mOptions.verbose) {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800762 mContext.getDiagnostics()->note(DiagMessage() << "copying " << path);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700763 }
764
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800765 if (!copyFileToArchive(fileToMerge.file, fileToMerge.dstPath, 0,
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700766 archiveWriter.get())) {
767 error = true;
768 }
769 }
770 }
771
772 if (error) {
773 mContext.getDiagnostics()->error(DiagMessage() << "failed linking file resources");
774 return 1;
775 }
776
777 if (!mOptions.noAutoVersion) {
778 AutoVersioner versioner;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800779 if (!versioner.consume(&mContext, &mFinalTable)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700780 mContext.getDiagnostics()->error(DiagMessage() << "failed versioning styles");
781 return 1;
782 }
783 }
784
Adam Lesinskifb48d292015-11-07 15:52:13 -0800785 if (!flattenTable(&mFinalTable, archiveWriter.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700786 mContext.getDiagnostics()->error(DiagMessage() << "failed to write resources.arsc");
787 return 1;
788 }
789
790 if (mOptions.generateJavaClassPath) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700791 JavaClassGeneratorOptions options;
792 if (mOptions.staticLib) {
793 options.useFinal = false;
794 }
795
Adam Lesinski83f22552015-11-07 11:51:23 -0800796 StringPiece16 actualPackage = mContext.getCompilationPackage();
797 StringPiece16 outputPackage = mContext.getCompilationPackage();
798
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700799 if (mOptions.privateSymbols) {
800 // If we defined a private symbols package, we only emit Public symbols
801 // to the original package, and private and public symbols to the private package.
802
803 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800804 if (!writeJavaFile(&mFinalTable, mContext.getCompilationPackage(),
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700805 mContext.getCompilationPackage(), options)) {
806 return 1;
807 }
808
809 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
Adam Lesinski83f22552015-11-07 11:51:23 -0800810 outputPackage = mOptions.privateSymbols.value();
811 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700812
Adam Lesinskifb48d292015-11-07 15:52:13 -0800813 if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
Adam Lesinski83f22552015-11-07 11:51:23 -0800814 return 1;
815 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700816
Adam Lesinskifc9570e62015-11-16 15:07:54 -0800817 for (const std::string& extraPackage : mOptions.extraJavaPackages) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800818 if (!writeJavaFile(&mFinalTable, actualPackage, util::utf8ToUtf16(extraPackage),
Adam Lesinski83f22552015-11-07 11:51:23 -0800819 options)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700820 return 1;
821 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700822 }
823 }
824
825 if (mOptions.generateProguardRulesPath) {
826 if (!writeProguardFile(proguardKeepSet)) {
827 return 1;
828 }
829 }
830
831 if (mOptions.verbose) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800832 Debug::printTable(&mFinalTable);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700833 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700834 return 0;
835 }
Adam Lesinskifb48d292015-11-07 15:52:13 -0800836
837private:
838 LinkOptions mOptions;
839 LinkContext mContext;
840 ResourceTable mFinalTable;
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800841
842 ResourceTable mLocalFileTable;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800843 std::unique_ptr<TableMerger> mTableMerger;
844
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800845 // A pointer to the FileCollection representing the filesystem (not archives).
Adam Lesinskia40e9722015-11-24 19:11:46 -0800846 io::FileCollection* mFileCollection;
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800847
848 // A vector of IFileCollections. This is mainly here to keep ownership of the collections.
Adam Lesinskia40e9722015-11-24 19:11:46 -0800849 std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700850};
851
852int link(const std::vector<StringPiece>& args) {
853 LinkOptions options;
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700854 Maybe<std::string> privateSymbolsPackage;
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800855 Maybe<std::string> minSdkVersion, targetSdkVersion;
Adam Lesinskifc9570e62015-11-16 15:07:54 -0800856 std::vector<std::string> extraJavaPackages;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700857 Flags flags = Flags()
858 .requiredFlag("-o", "Output path", &options.outputPath)
859 .requiredFlag("--manifest", "Path to the Android manifest to build",
860 &options.manifestPath)
861 .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
Adam Lesinskifb48d292015-11-07 15:52:13 -0800862 .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics. "
863 "The last conflicting resource given takes precedence.",
864 &options.overlayFiles)
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700865 .optionalFlag("--java", "Directory in which to generate R.java",
866 &options.generateJavaClassPath)
867 .optionalFlag("--proguard", "Output file for generated Proguard rules",
868 &options.generateProguardRulesPath)
869 .optionalSwitch("--no-auto-version",
870 "Disables automatic style and layout SDK versioning",
871 &options.noAutoVersion)
872 .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
873 "by -o",
874 &options.outputToDirectory)
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800875 .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
876 "AndroidManifest.xml", &minSdkVersion)
877 .optionalFlag("--target-sdk-version", "Default target SDK version to use for "
878 "AndroidManifest.xml", &targetSdkVersion)
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700879 .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700880 .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800881 "private symbols.\n"
882 "If not specified, public and private symbols will use the application's "
883 "package name", &privateSymbolsPackage)
Adam Lesinski83f22552015-11-07 11:51:23 -0800884 .optionalFlagList("--extra-packages", "Generate the same R.java but with different "
Adam Lesinskifc9570e62015-11-16 15:07:54 -0800885 "package names", &extraJavaPackages)
Adam Lesinskia6fe3452015-12-09 15:20:52 -0800886 .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in "
887 "overlays without <add-resource> tags", &options.autoAddOverlay)
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700888 .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
889
890 if (!flags.parse("aapt2 link", args, &std::cerr)) {
891 return 1;
892 }
893
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700894 if (privateSymbolsPackage) {
895 options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
896 }
897
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800898 if (minSdkVersion) {
899 options.minSdkVersionDefault = util::utf8ToUtf16(minSdkVersion.value());
900 }
901
902 if (targetSdkVersion) {
903 options.targetSdkVersionDefault = util::utf8ToUtf16(targetSdkVersion.value());
904 }
905
Adam Lesinskifc9570e62015-11-16 15:07:54 -0800906 // Populate the set of extra packages for which to generate R.java.
907 for (std::string& extraPackage : extraJavaPackages) {
908 // A given package can actually be a colon separated list of packages.
909 for (StringPiece package : util::split(extraPackage, ':')) {
910 options.extraJavaPackages.insert(package.toString());
911 }
912 }
913
Adam Lesinskifb48d292015-11-07 15:52:13 -0800914 LinkCommand cmd(options);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700915 return cmd.run(flags.getArgs());
916}
917
918} // namespace aapt