blob: 33d9272b39fdb92d13228475eec28e90f7454442 [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 Lesinski9e10ac72015-10-16 14:37:48 -070060 Maybe<std::u16string> privateSymbols;
Adam Lesinski2ae4a872015-11-02 16:10:55 -080061 Maybe<std::u16string> minSdkVersionDefault;
62 Maybe<std::u16string> targetSdkVersionDefault;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070063};
64
65struct LinkContext : public IAaptContext {
66 StdErrDiagnostics mDiagnostics;
67 std::unique_ptr<NameMangler> mNameMangler;
68 std::u16string mCompilationPackage;
69 uint8_t mPackageId;
70 std::unique_ptr<ISymbolTable> mSymbols;
71
72 IDiagnostics* getDiagnostics() override {
73 return &mDiagnostics;
74 }
75
76 NameMangler* getNameMangler() override {
77 return mNameMangler.get();
78 }
79
80 StringPiece16 getCompilationPackage() override {
81 return mCompilationPackage;
82 }
83
84 uint8_t getPackageId() override {
85 return mPackageId;
86 }
87
88 ISymbolTable* getExternalSymbols() override {
89 return mSymbols.get();
90 }
91};
92
Adam Lesinskifb48d292015-11-07 15:52:13 -080093class LinkCommand {
94public:
95 LinkCommand(const LinkOptions& options) :
Adam Lesinskia40e9722015-11-24 19:11:46 -080096 mOptions(options), mContext(), mFinalTable(), mFileCollection(nullptr) {
97 std::unique_ptr<io::FileCollection> fileCollection =
98 util::make_unique<io::FileCollection>();
99
100 // Get a pointer to the FileCollection for convenience, but it will be owned by the vector.
101 mFileCollection = fileCollection.get();
102
103 // Move it to the collection.
104 mCollections.push_back(std::move(fileCollection));
Adam Lesinskifb48d292015-11-07 15:52:13 -0800105 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700106
107 std::string buildResourceFileName(const ResourceFile& resFile) {
108 std::stringstream out;
109 out << "res/" << resFile.name.type;
110 if (resFile.config != ConfigDescription{}) {
111 out << "-" << resFile.config;
112 }
113 out << "/";
114
115 if (mContext.getNameMangler()->shouldMangle(resFile.name.package)) {
116 out << NameMangler::mangleEntry(resFile.name.package, resFile.name.entry);
117 } else {
118 out << resFile.name.entry;
119 }
120 out << file::getExtension(resFile.source.path);
121 return out.str();
122 }
123
124 /**
125 * Creates a SymbolTable that loads symbols from the various APKs and caches the
126 * results for faster lookup.
127 */
128 std::unique_ptr<ISymbolTable> createSymbolTableFromIncludePaths() {
129 AssetManagerSymbolTableBuilder builder;
130 for (const std::string& path : mOptions.includePaths) {
131 if (mOptions.verbose) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800132 mContext.getDiagnostics()->note(DiagMessage(path) << "loading include path");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700133 }
134
135 std::unique_ptr<android::AssetManager> assetManager =
136 util::make_unique<android::AssetManager>();
137 int32_t cookie = 0;
138 if (!assetManager->addAssetPath(android::String8(path.data(), path.size()), &cookie)) {
139 mContext.getDiagnostics()->error(
Adam Lesinskifb48d292015-11-07 15:52:13 -0800140 DiagMessage(path) << "failed to load include path");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700141 return {};
142 }
143 builder.add(std::move(assetManager));
144 }
145 return builder.build();
146 }
147
Adam Lesinskia40e9722015-11-24 19:11:46 -0800148 std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700149 std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
Adam Lesinskia40e9722015-11-24 19:11:46 -0800150 BinaryResourceParser parser(&mContext, table.get(), source, data, len);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700151 if (!parser.parse()) {
152 return {};
153 }
154 return table;
155 }
156
157 /**
158 * Inflates an XML file from the source path.
159 */
Adam Lesinskia40e9722015-11-24 19:11:46 -0800160 static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700161 std::ifstream fin(path, std::ifstream::binary);
162 if (!fin) {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800163 diag->error(DiagMessage(path) << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700164 return {};
165 }
166
Adam Lesinskia40e9722015-11-24 19:11:46 -0800167 return xml::inflate(&fin, diag, Source(path));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700168 }
169
Adam Lesinskia40e9722015-11-24 19:11:46 -0800170 static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(
171 const Source& source,
172 const void* data, size_t len,
173 IDiagnostics* diag) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700174 std::string errorStr;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800175 ssize_t offset = getWrappedDataOffset(data, len, &errorStr);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700176 if (offset < 0) {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800177 diag->error(DiagMessage(source) << errorStr);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700178 return {};
179 }
180
Adam Lesinski467f1712015-11-16 17:35:44 -0800181 std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(
Adam Lesinskia40e9722015-11-24 19:11:46 -0800182 reinterpret_cast<const uint8_t*>(data) + static_cast<size_t>(offset),
183 len - static_cast<size_t>(offset),
184 diag,
185 source);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700186 if (!xmlRes) {
187 return {};
188 }
189 return xmlRes;
190 }
191
Adam Lesinskia40e9722015-11-24 19:11:46 -0800192 static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
193 const void* data, size_t len,
194 IDiagnostics* diag) {
195 std::unique_ptr<ResourceFile> resFile = util::make_unique<ResourceFile>();
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700196 std::string errorStr;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800197 ssize_t offset = unwrapFileExportHeader(data, len, resFile.get(), &errorStr);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700198 if (offset < 0) {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800199 diag->error(DiagMessage(source) << errorStr);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700200 return {};
201 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800202 return resFile;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700203 }
204
Adam Lesinskia40e9722015-11-24 19:11:46 -0800205 bool copyFileToArchive(io::IFile* file, const std::string& outPath, uint32_t flags,
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700206 IArchiveWriter* writer) {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800207 std::unique_ptr<io::IData> data = file->openAsData();
208 if (!data) {
209 mContext.getDiagnostics()->error(DiagMessage(file->getSource())
210 << "failed to open file");
211 return false;
212 }
213
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700214 std::string errorStr;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800215 ssize_t offset = getWrappedDataOffset(data->data(), data->size(), &errorStr);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700216 if (offset < 0) {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800217 mContext.getDiagnostics()->error(DiagMessage(file->getSource()) << errorStr);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700218 return false;
219 }
220
Adam Lesinskia40e9722015-11-24 19:11:46 -0800221 if (writer->startEntry(outPath, flags)) {
222 if (writer->writeEntry(reinterpret_cast<const uint8_t*>(data->data()) + offset,
223 data->size() - static_cast<size_t>(offset))) {
224 if (writer->finishEntry()) {
225 return true;
226 }
227 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700228 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800229
230 mContext.getDiagnostics()->error(
231 DiagMessage(mOptions.outputPath) << "failed to write file " << outPath);
232 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700233 }
234
Adam Lesinski467f1712015-11-16 17:35:44 -0800235 Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700236 // Make sure the first element is <manifest> with package attribute.
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800237 if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700238 if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") {
239 if (xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package")) {
240 return AppInfo{ packageAttr->value };
241 }
242 }
243 }
244 return {};
245 }
246
Adam Lesinskifb48d292015-11-07 15:52:13 -0800247 bool verifyNoExternalPackages() {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700248 bool error = false;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800249 for (const auto& package : mFinalTable.packages) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700250 if (mContext.getCompilationPackage() != package->name ||
251 !package->id || package->id.value() != mContext.getPackageId()) {
252 // We have a package that is not related to the one we're building!
253 for (const auto& type : package->types) {
254 for (const auto& entry : type->entries) {
255 for (const auto& configValue : entry->values) {
Adam Lesinskie78fd612015-10-22 12:48:43 -0700256 mContext.getDiagnostics()->error(
257 DiagMessage(configValue.value->getSource())
258 << "defined resource '"
259 << ResourceNameRef(package->name,
260 type->type,
261 entry->name)
262 << "' for external package '"
263 << package->name << "'");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700264 error = true;
265 }
266 }
267 }
268 }
269 }
270 return !error;
271 }
272
273 std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
274 if (mOptions.outputToDirectory) {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800275 return createDirectoryArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700276 } else {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800277 return createZipFileArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700278 }
279 }
280
281 bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
282 BigBuffer buffer(1024);
283 TableFlattenerOptions options = {};
284 options.useExtendedChunks = mOptions.staticLib;
285 TableFlattener flattener(&buffer, options);
286 if (!flattener.consume(&mContext, table)) {
287 return false;
288 }
289
Adam Lesinskia40e9722015-11-24 19:11:46 -0800290 if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
291 if (writer->writeEntry(buffer)) {
292 if (writer->finishEntry()) {
293 return true;
294 }
295 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700296 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800297
298 mContext.getDiagnostics()->error(
299 DiagMessage() << "failed to write resources.arsc to archive");
300 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700301 }
302
Adam Lesinski467f1712015-11-16 17:35:44 -0800303 bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700304 IArchiveWriter* writer) {
305 BigBuffer buffer(1024);
306 XmlFlattenerOptions options = {};
307 options.keepRawValues = mOptions.staticLib;
308 options.maxSdkLevel = maxSdkLevel;
309 XmlFlattener flattener(&buffer, options);
310 if (!flattener.consume(&mContext, xmlRes)) {
311 return false;
312 }
313
Adam Lesinskia40e9722015-11-24 19:11:46 -0800314
315 if (writer->startEntry(path, ArchiveEntry::kCompress)) {
316 if (writer->writeEntry(buffer)) {
317 if (writer->finishEntry()) {
318 return true;
319 }
320 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700321 }
Adam Lesinskia40e9722015-11-24 19:11:46 -0800322 mContext.getDiagnostics()->error(
323 DiagMessage() << "failed to write " << path << " to archive");
324 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700325 }
326
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700327 bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
328 const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700329 if (!mOptions.generateJavaClassPath) {
330 return true;
331 }
332
333 std::string outPath = mOptions.generateJavaClassPath.value();
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700334 file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(outPackage)));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700335 file::mkdirs(outPath);
336 file::appendPath(&outPath, "R.java");
337
338 std::ofstream fout(outPath, std::ofstream::binary);
339 if (!fout) {
340 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
341 return false;
342 }
343
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700344 JavaClassGenerator generator(table, javaOptions);
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700345 if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700346 mContext.getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
347 return false;
348 }
349 return true;
350 }
351
Adam Lesinski467f1712015-11-16 17:35:44 -0800352 bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
Adam Lesinskica5638f2015-10-21 14:42:43 -0700353 if (!mOptions.generateJavaClassPath) {
354 return true;
355 }
356
357 std::string outPath = mOptions.generateJavaClassPath.value();
358 file::appendPath(&outPath,
359 file::packageToPath(util::utf16ToUtf8(mContext.getCompilationPackage())));
360 file::mkdirs(outPath);
361 file::appendPath(&outPath, "Manifest.java");
362
363 std::ofstream fout(outPath, std::ofstream::binary);
364 if (!fout) {
365 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
366 return false;
367 }
368
369 ManifestClassGenerator generator;
370 if (!generator.generate(mContext.getDiagnostics(), mContext.getCompilationPackage(),
371 manifestXml, &fout)) {
372 return false;
373 }
374
375 if (!fout) {
376 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
377 return false;
378 }
379 return true;
380 }
381
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700382 bool writeProguardFile(const proguard::KeepSet& keepSet) {
383 if (!mOptions.generateProguardRulesPath) {
384 return true;
385 }
386
387 std::ofstream fout(mOptions.generateProguardRulesPath.value(), std::ofstream::binary);
388 if (!fout) {
389 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
390 return false;
391 }
392
393 proguard::writeKeepSet(&fout, keepSet);
394 if (!fout) {
395 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
396 return false;
397 }
398 return true;
399 }
400
Adam Lesinskifb48d292015-11-07 15:52:13 -0800401 bool mergeStaticLibrary(const std::string& input) {
402 // TODO(adamlesinski): Load resources from a static library APK and merge the table into
403 // TableMerger.
404 mContext.getDiagnostics()->warn(DiagMessage()
405 << "linking static libraries not supported yet: "
406 << input);
407 return true;
408 }
409
Adam Lesinskia40e9722015-11-24 19:11:46 -0800410 bool mergeResourceTable(io::IFile* file, bool override) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800411 if (mOptions.verbose) {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800412 mContext.getDiagnostics()->note(DiagMessage() << "linking " << file->getSource());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800413 }
414
Adam Lesinskia40e9722015-11-24 19:11:46 -0800415 std::unique_ptr<io::IData> data = file->openAsData();
416 if (!data) {
417 mContext.getDiagnostics()->error(DiagMessage(file->getSource())
418 << "failed to open file");
419 return false;
420 }
421
422 std::unique_ptr<ResourceTable> table = loadTable(file->getSource(), data->data(),
423 data->size());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800424 if (!table) {
425 return false;
426 }
427
Adam Lesinskia40e9722015-11-24 19:11:46 -0800428 if (!mTableMerger->merge(file->getSource(), table.get(), override)) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800429 return false;
430 }
431 return true;
432 }
433
Adam Lesinskia40e9722015-11-24 19:11:46 -0800434 bool mergeCompiledFile(io::IFile* file, std::unique_ptr<ResourceFile> fileDesc, bool override) {
435 // Apply the package name used for this compilation phase if none was specified.
436 if (fileDesc->name.package.empty()) {
437 fileDesc->name.package = mContext.getCompilationPackage().toString();
Adam Lesinskifb48d292015-11-07 15:52:13 -0800438 }
439
Adam Lesinskia40e9722015-11-24 19:11:46 -0800440 // Mangle the name if necessary.
441 ResourceNameRef resName = fileDesc->name;
442 Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(fileDesc->name);
Adam Lesinskifb48d292015-11-07 15:52:13 -0800443 if (mangledName) {
444 resName = mangledName.value();
445 }
446
Adam Lesinskia40e9722015-11-24 19:11:46 -0800447 // If we are overriding resources, we supply a custom resolver function.
Adam Lesinskifb48d292015-11-07 15:52:13 -0800448 std::function<int(Value*,Value*)> resolver;
449 if (override) {
450 resolver = [](Value* a, Value* b) -> int {
451 int result = ResourceTable::resolveValueCollision(a, b);
452 if (result == 0) {
453 // Always accept the new value if it would normally conflict (override).
454 result = 1;
455 }
456 return result;
457 };
458 } else {
459 // Otherwise use the default resolution.
460 resolver = ResourceTable::resolveValueCollision;
461 }
462
463 // Add this file to the table.
Adam Lesinskia40e9722015-11-24 19:11:46 -0800464 if (!mFinalTable.addFileReference(resName, fileDesc->config, fileDesc->source,
465 util::utf8ToUtf16(buildResourceFileName(*fileDesc)),
Adam Lesinskifb48d292015-11-07 15:52:13 -0800466 resolver, mContext.getDiagnostics())) {
467 return false;
468 }
469
470 // Add the exports of this file to the table.
Adam Lesinskia40e9722015-11-24 19:11:46 -0800471 for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800472 if (exportedSymbol.name.package.empty()) {
473 exportedSymbol.name.package = mContext.getCompilationPackage().toString();
474 }
475
476 ResourceNameRef resName = exportedSymbol.name;
477
478 Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(
479 exportedSymbol.name);
480 if (mangledName) {
481 resName = mangledName.value();
482 }
483
484 std::unique_ptr<Id> id = util::make_unique<Id>();
Adam Lesinskia40e9722015-11-24 19:11:46 -0800485 id->setSource(fileDesc->source.withLine(exportedSymbol.line));
Adam Lesinskifb48d292015-11-07 15:52:13 -0800486 bool result = mFinalTable.addResourceAllowMangled(resName, {}, std::move(id),
Adam Lesinskia40e9722015-11-24 19:11:46 -0800487 mContext.getDiagnostics());
Adam Lesinskifb48d292015-11-07 15:52:13 -0800488 if (!result) {
489 return false;
490 }
491 }
492
Adam Lesinskia40e9722015-11-24 19:11:46 -0800493 // Now add this file for later processing. Once the table is assigned IDs, we can compile
494 // this file.
495 mFilesToProcess.insert(FileToProcess{ std::move(fileDesc), file });
Adam Lesinskifb48d292015-11-07 15:52:13 -0800496 return true;
497 }
498
Adam Lesinskia40e9722015-11-24 19:11:46 -0800499 /**
500 * Creates an io::IFileCollection from the ZIP archive and processes the files within.
501 */
502 bool mergeArchive(const std::string& input, bool override) {
503 std::string errorStr;
504 std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
505 input, &errorStr);
506 if (!collection) {
507 mContext.getDiagnostics()->error(DiagMessage(input) << errorStr);
508 return false;
509 }
510
511 bool error = false;
512 for (const std::unique_ptr<io::IFile>& file : *collection) {
513 if (!processFile(file.get(), override)) {
514 error = true;
515 }
516 }
517
518 // Make sure to move the collection into the set of IFileCollections.
519 mCollections.push_back(std::move(collection));
520 return !error;
521 }
522
523 bool processFile(const std::string& path, bool override) {
524 if (util::stringEndsWith<char>(path, ".flata")) {
525 return mergeArchive(path, override);
526 }
527
528 io::IFile* file = mFileCollection->insertFile(path);
529 return processFile(file, override);
530 }
531
532 bool processFile(io::IFile* file, bool override) {
533 const Source& src = file->getSource();
534 if (util::stringEndsWith<char>(src.path, ".arsc.flat")) {
535 return mergeResourceTable(file, override);
536 } else {
537 // Try opening the file and looking for an Export header.
538 std::unique_ptr<io::IData> data = file->openAsData();
539 if (!data) {
540 mContext.getDiagnostics()->error(DiagMessage(src) << "failed to open");
541 return false;
542 }
543
544 std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader(
545 src, data->data(), data->size(), mContext.getDiagnostics());
546 if (resourceFile) {
547 return mergeCompiledFile(file, std::move(resourceFile), override);
548 }
Adam Lesinskifb48d292015-11-07 15:52:13 -0800549 }
550 return false;
551 }
552
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700553 int run(const std::vector<std::string>& inputFiles) {
554 // Load the AndroidManifest.xml
Adam Lesinskia40e9722015-11-24 19:11:46 -0800555 std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
556 mContext.getDiagnostics());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700557 if (!manifestXml) {
558 return 1;
559 }
560
561 if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) {
562 mContext.mCompilationPackage = maybeAppInfo.value().package;
563 } else {
564 mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
565 << "no package specified in <manifest> tag");
566 return 1;
567 }
568
569 if (!util::isJavaPackageName(mContext.mCompilationPackage)) {
570 mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
571 << "invalid package name '"
572 << mContext.mCompilationPackage
573 << "'");
574 return 1;
575 }
576
577 mContext.mNameMangler = util::make_unique<NameMangler>(
578 NameManglerPolicy{ mContext.mCompilationPackage });
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700579
580 if (mContext.mCompilationPackage == u"android") {
581 mContext.mPackageId = 0x01;
582 } else {
583 mContext.mPackageId = 0x7f;
584 }
585
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700586 mContext.mSymbols = createSymbolTableFromIncludePaths();
587 if (!mContext.mSymbols) {
588 return 1;
589 }
590
Adam Lesinskifb48d292015-11-07 15:52:13 -0800591 mTableMerger = util::make_unique<TableMerger>(&mContext, &mFinalTable);
592
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700593 if (mOptions.verbose) {
594 mContext.getDiagnostics()->note(
595 DiagMessage() << "linking package '" << mContext.mCompilationPackage << "' "
596 << "with package ID " << std::hex << (int) mContext.mPackageId);
597 }
598
Adam Lesinskifb48d292015-11-07 15:52:13 -0800599
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700600 for (const std::string& input : inputFiles) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800601 if (!processFile(input, false)) {
Adam Lesinski467f1712015-11-16 17:35:44 -0800602 mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input");
603 return 1;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800604 }
605 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700606
Adam Lesinskifb48d292015-11-07 15:52:13 -0800607 for (const std::string& input : mOptions.overlayFiles) {
608 if (!processFile(input, true)) {
Adam Lesinski467f1712015-11-16 17:35:44 -0800609 mContext.getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
610 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700611 }
612 }
613
Adam Lesinskifb48d292015-11-07 15:52:13 -0800614 if (!verifyNoExternalPackages()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700615 return 1;
616 }
617
618 if (!mOptions.staticLib) {
619 PrivateAttributeMover mover;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800620 if (!mover.consume(&mContext, &mFinalTable)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700621 mContext.getDiagnostics()->error(
622 DiagMessage() << "failed moving private attributes");
623 return 1;
624 }
625 }
626
627 {
628 IdAssigner idAssigner;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800629 if (!idAssigner.consume(&mContext, &mFinalTable)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700630 mContext.getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
631 return 1;
632 }
633 }
634
Adam Lesinskifb48d292015-11-07 15:52:13 -0800635 mContext.mNameMangler = util::make_unique<NameMangler>(NameManglerPolicy{
636 mContext.mCompilationPackage, mTableMerger->getMergedPackages() });
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700637 mContext.mSymbols = JoinedSymbolTableBuilder()
Adam Lesinskifb48d292015-11-07 15:52:13 -0800638 .addSymbolTable(util::make_unique<SymbolTableWrapper>(&mFinalTable))
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700639 .addSymbolTable(std::move(mContext.mSymbols))
640 .build();
641
642 {
643 ReferenceLinker linker;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800644 if (!linker.consume(&mContext, &mFinalTable)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700645 mContext.getDiagnostics()->error(DiagMessage() << "failed linking references");
646 return 1;
647 }
648 }
649
650 proguard::KeepSet proguardKeepSet;
651
652 std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter();
653 if (!archiveWriter) {
654 mContext.getDiagnostics()->error(DiagMessage() << "failed to create archive");
655 return 1;
656 }
657
Adam Lesinski467f1712015-11-16 17:35:44 -0800658 bool error = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700659 {
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800660 ManifestFixerOptions manifestFixerOptions;
661 manifestFixerOptions.minSdkVersionDefault = mOptions.minSdkVersionDefault;
662 manifestFixerOptions.targetSdkVersionDefault = mOptions.targetSdkVersionDefault;
663 ManifestFixer manifestFixer(manifestFixerOptions);
664 if (!manifestFixer.consume(&mContext, manifestXml.get())) {
665 error = true;
666 }
667
Adam Lesinski467f1712015-11-16 17:35:44 -0800668 // AndroidManifest.xml has no resource name, but the CallSite is built from the name
669 // (aka, which package the AndroidManifest.xml is coming from).
670 // So we give it a package name so it can see local resources.
671 manifestXml->file.name.package = mContext.getCompilationPackage().toString();
672
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700673 XmlReferenceLinker manifestLinker;
674 if (manifestLinker.consume(&mContext, manifestXml.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700675 if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
676 manifestXml.get(),
677 &proguardKeepSet)) {
678 error = true;
679 }
680
Adam Lesinskica5638f2015-10-21 14:42:43 -0700681 if (mOptions.generateJavaClassPath) {
682 if (!writeManifestJavaFile(manifestXml.get())) {
683 error = true;
684 }
685 }
686
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700687 if (!flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
688 archiveWriter.get())) {
689 error = true;
690 }
691 } else {
692 error = true;
693 }
694 }
695
Adam Lesinski467f1712015-11-16 17:35:44 -0800696 if (error) {
697 mContext.getDiagnostics()->error(DiagMessage() << "failed processing manifest");
698 return 1;
699 }
700
Adam Lesinskifc9570e62015-11-16 15:07:54 -0800701 for (const FileToProcess& file : mFilesToProcess) {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800702 const StringPiece path = file.file->getSource().path;
703
704 if (file.fileExport->name.type != ResourceType::kRaw &&
705 util::stringEndsWith<char>(path, ".xml.flat")) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700706 if (mOptions.verbose) {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800707 mContext.getDiagnostics()->note(DiagMessage() << "linking " << path);
708 }
709
710 std::unique_ptr<io::IData> data = file.file->openAsData();
711 if (!data) {
712 mContext.getDiagnostics()->error(DiagMessage(file.file->getSource())
713 << "failed to open file");
714 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700715 }
716
Adam Lesinski467f1712015-11-16 17:35:44 -0800717 std::unique_ptr<xml::XmlResource> xmlRes = loadBinaryXmlSkipFileExport(
Adam Lesinskia40e9722015-11-24 19:11:46 -0800718 file.file->getSource(), data->data(), data->size(),
719 mContext.getDiagnostics());
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700720 if (!xmlRes) {
721 return 1;
722 }
723
Adam Lesinskia40e9722015-11-24 19:11:46 -0800724 // Move the file description over.
725 xmlRes->file = std::move(*file.fileExport);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700726
727 XmlReferenceLinker xmlLinker;
728 if (xmlLinker.consume(&mContext, xmlRes.get())) {
729 if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(),
730 &proguardKeepSet)) {
731 error = true;
732 }
733
734 Maybe<size_t> maxSdkLevel;
735 if (!mOptions.noAutoVersion) {
736 maxSdkLevel = std::max<size_t>(xmlRes->file.config.sdkVersion, 1u);
737 }
738
739 if (!flattenXml(xmlRes.get(), buildResourceFileName(xmlRes->file), maxSdkLevel,
740 archiveWriter.get())) {
741 error = true;
742 }
743
744 if (!mOptions.noAutoVersion) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800745 Maybe<ResourceTable::SearchResult> result = mFinalTable.findResource(
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700746 xmlRes->file.name);
747 for (int sdkLevel : xmlLinker.getSdkLevels()) {
748 if (sdkLevel > xmlRes->file.config.sdkVersion &&
749 shouldGenerateVersionedResource(result.value().entry,
750 xmlRes->file.config,
751 sdkLevel)) {
752 xmlRes->file.config.sdkVersion = sdkLevel;
Adam Lesinskia40e9722015-11-24 19:11:46 -0800753 bool added = mFinalTable.addFileReference(
754 xmlRes->file.name,
755 xmlRes->file.config,
756 xmlRes->file.source,
757 util::utf8ToUtf16(buildResourceFileName(xmlRes->file)),
758 mContext.getDiagnostics());
759 if (!added) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700760 error = true;
761 continue;
762 }
763
764 if (!flattenXml(xmlRes.get(), buildResourceFileName(xmlRes->file),
765 sdkLevel, archiveWriter.get())) {
766 error = true;
767 }
768 }
769 }
770 }
771
772 } else {
773 error = true;
774 }
775 } else {
776 if (mOptions.verbose) {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800777 mContext.getDiagnostics()->note(DiagMessage() << "copying " << path);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700778 }
779
Adam Lesinskia40e9722015-11-24 19:11:46 -0800780 if (!copyFileToArchive(file.file, buildResourceFileName(*file.fileExport), 0,
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700781 archiveWriter.get())) {
782 error = true;
783 }
784 }
785 }
786
787 if (error) {
788 mContext.getDiagnostics()->error(DiagMessage() << "failed linking file resources");
789 return 1;
790 }
791
792 if (!mOptions.noAutoVersion) {
793 AutoVersioner versioner;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800794 if (!versioner.consume(&mContext, &mFinalTable)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700795 mContext.getDiagnostics()->error(DiagMessage() << "failed versioning styles");
796 return 1;
797 }
798 }
799
Adam Lesinskifb48d292015-11-07 15:52:13 -0800800 if (!flattenTable(&mFinalTable, archiveWriter.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700801 mContext.getDiagnostics()->error(DiagMessage() << "failed to write resources.arsc");
802 return 1;
803 }
804
805 if (mOptions.generateJavaClassPath) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700806 JavaClassGeneratorOptions options;
807 if (mOptions.staticLib) {
808 options.useFinal = false;
809 }
810
Adam Lesinski83f22552015-11-07 11:51:23 -0800811 StringPiece16 actualPackage = mContext.getCompilationPackage();
812 StringPiece16 outputPackage = mContext.getCompilationPackage();
813
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700814 if (mOptions.privateSymbols) {
815 // If we defined a private symbols package, we only emit Public symbols
816 // to the original package, and private and public symbols to the private package.
817
818 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800819 if (!writeJavaFile(&mFinalTable, mContext.getCompilationPackage(),
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700820 mContext.getCompilationPackage(), options)) {
821 return 1;
822 }
823
824 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
Adam Lesinski83f22552015-11-07 11:51:23 -0800825 outputPackage = mOptions.privateSymbols.value();
826 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700827
Adam Lesinskifb48d292015-11-07 15:52:13 -0800828 if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
Adam Lesinski83f22552015-11-07 11:51:23 -0800829 return 1;
830 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700831
Adam Lesinskifc9570e62015-11-16 15:07:54 -0800832 for (const std::string& extraPackage : mOptions.extraJavaPackages) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800833 if (!writeJavaFile(&mFinalTable, actualPackage, util::utf8ToUtf16(extraPackage),
Adam Lesinski83f22552015-11-07 11:51:23 -0800834 options)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700835 return 1;
836 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700837 }
838 }
839
840 if (mOptions.generateProguardRulesPath) {
841 if (!writeProguardFile(proguardKeepSet)) {
842 return 1;
843 }
844 }
845
846 if (mOptions.verbose) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800847 Debug::printTable(&mFinalTable);
848 for (; !mTableMerger->getFileMergeQueue()->empty();
849 mTableMerger->getFileMergeQueue()->pop()) {
850 const FileToMerge& f = mTableMerger->getFileMergeQueue()->front();
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700851 mContext.getDiagnostics()->note(
852 DiagMessage() << f.srcPath << " -> " << f.dstPath << " from (0x"
853 << std::hex << (uintptr_t) f.srcTable << std::dec);
854 }
855 }
856
857 return 0;
858 }
Adam Lesinskifb48d292015-11-07 15:52:13 -0800859
860private:
861 LinkOptions mOptions;
862 LinkContext mContext;
863 ResourceTable mFinalTable;
864 std::unique_ptr<TableMerger> mTableMerger;
865
Adam Lesinskia40e9722015-11-24 19:11:46 -0800866 io::FileCollection* mFileCollection;
867 std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
868
Adam Lesinskifb48d292015-11-07 15:52:13 -0800869 struct FileToProcess {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800870 std::unique_ptr<ResourceFile> fileExport;
871 io::IFile* file;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800872 };
Adam Lesinskifc9570e62015-11-16 15:07:54 -0800873
874 struct FileToProcessComparator {
875 bool operator()(const FileToProcess& a, const FileToProcess& b) {
Adam Lesinskia40e9722015-11-24 19:11:46 -0800876 return std::tie(a.fileExport->name, a.fileExport->config) <
877 std::tie(b.fileExport->name, b.fileExport->config);
Adam Lesinskifc9570e62015-11-16 15:07:54 -0800878 }
879 };
880
881 std::set<FileToProcess, FileToProcessComparator> mFilesToProcess;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700882};
883
884int link(const std::vector<StringPiece>& args) {
885 LinkOptions options;
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700886 Maybe<std::string> privateSymbolsPackage;
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800887 Maybe<std::string> minSdkVersion, targetSdkVersion;
Adam Lesinskifc9570e62015-11-16 15:07:54 -0800888 std::vector<std::string> extraJavaPackages;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700889 Flags flags = Flags()
890 .requiredFlag("-o", "Output path", &options.outputPath)
891 .requiredFlag("--manifest", "Path to the Android manifest to build",
892 &options.manifestPath)
893 .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
Adam Lesinskifb48d292015-11-07 15:52:13 -0800894 .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics. "
895 "The last conflicting resource given takes precedence.",
896 &options.overlayFiles)
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700897 .optionalFlag("--java", "Directory in which to generate R.java",
898 &options.generateJavaClassPath)
899 .optionalFlag("--proguard", "Output file for generated Proguard rules",
900 &options.generateProguardRulesPath)
901 .optionalSwitch("--no-auto-version",
902 "Disables automatic style and layout SDK versioning",
903 &options.noAutoVersion)
904 .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
905 "by -o",
906 &options.outputToDirectory)
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800907 .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
908 "AndroidManifest.xml", &minSdkVersion)
909 .optionalFlag("--target-sdk-version", "Default target SDK version to use for "
910 "AndroidManifest.xml", &targetSdkVersion)
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700911 .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700912 .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800913 "private symbols.\n"
914 "If not specified, public and private symbols will use the application's "
915 "package name", &privateSymbolsPackage)
Adam Lesinski83f22552015-11-07 11:51:23 -0800916 .optionalFlagList("--extra-packages", "Generate the same R.java but with different "
Adam Lesinskifc9570e62015-11-16 15:07:54 -0800917 "package names", &extraJavaPackages)
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700918 .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
919
920 if (!flags.parse("aapt2 link", args, &std::cerr)) {
921 return 1;
922 }
923
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700924 if (privateSymbolsPackage) {
925 options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
926 }
927
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800928 if (minSdkVersion) {
929 options.minSdkVersionDefault = util::utf8ToUtf16(minSdkVersion.value());
930 }
931
932 if (targetSdkVersion) {
933 options.targetSdkVersionDefault = util::utf8ToUtf16(targetSdkVersion.value());
934 }
935
Adam Lesinskifc9570e62015-11-16 15:07:54 -0800936 // Populate the set of extra packages for which to generate R.java.
937 for (std::string& extraPackage : extraJavaPackages) {
938 // A given package can actually be a colon separated list of packages.
939 for (StringPiece package : util::split(extraPackage, ':')) {
940 options.extraJavaPackages.insert(package.toString());
941 }
942 }
943
Adam Lesinskifb48d292015-11-07 15:52:13 -0800944 LinkCommand cmd(options);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700945 return cmd.run(flags.getArgs());
946}
947
948} // namespace aapt