blob: 9850ae5cf57b01a9f0d15b2306a6f24e39f48125 [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 Lesinskica5638f2015-10-21 14:42:43 -070025#include "java/JavaClassGenerator.h"
26#include "java/ManifestClassGenerator.h"
27#include "java/ProguardRules.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070028#include "link/Linkers.h"
Adam Lesinski467f1712015-11-16 17:35:44 -080029#include "link/ReferenceLinker.h"
Adam Lesinski2ae4a872015-11-02 16:10:55 -080030#include "link/ManifestFixer.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070031#include "link/TableMerger.h"
32#include "process/IResourceTableConsumer.h"
33#include "process/SymbolTable.h"
34#include "unflatten/BinaryResourceParser.h"
35#include "unflatten/FileExportHeaderReader.h"
36#include "util/Files.h"
37#include "util/StringPiece.h"
Adam Lesinski467f1712015-11-16 17:35:44 -080038#include "xml/XmlDom.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070039
40#include <fstream>
41#include <sys/stat.h>
42#include <utils/FileMap.h>
43#include <vector>
44
45namespace aapt {
46
47struct LinkOptions {
48 std::string outputPath;
49 std::string manifestPath;
50 std::vector<std::string> includePaths;
Adam Lesinskifb48d292015-11-07 15:52:13 -080051 std::vector<std::string> overlayFiles;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070052 Maybe<std::string> generateJavaClassPath;
Adam Lesinskifc9570e62015-11-16 15:07:54 -080053 std::set<std::string> extraJavaPackages;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070054 Maybe<std::string> generateProguardRulesPath;
55 bool noAutoVersion = false;
56 bool staticLib = false;
57 bool verbose = false;
58 bool outputToDirectory = false;
Adam Lesinski9e10ac72015-10-16 14:37:48 -070059 Maybe<std::u16string> privateSymbols;
Adam Lesinski2ae4a872015-11-02 16:10:55 -080060 Maybe<std::u16string> minSdkVersionDefault;
61 Maybe<std::u16string> targetSdkVersionDefault;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070062};
63
64struct LinkContext : public IAaptContext {
65 StdErrDiagnostics mDiagnostics;
66 std::unique_ptr<NameMangler> mNameMangler;
67 std::u16string mCompilationPackage;
68 uint8_t mPackageId;
69 std::unique_ptr<ISymbolTable> mSymbols;
70
71 IDiagnostics* getDiagnostics() override {
72 return &mDiagnostics;
73 }
74
75 NameMangler* getNameMangler() override {
76 return mNameMangler.get();
77 }
78
79 StringPiece16 getCompilationPackage() override {
80 return mCompilationPackage;
81 }
82
83 uint8_t getPackageId() override {
84 return mPackageId;
85 }
86
87 ISymbolTable* getExternalSymbols() override {
88 return mSymbols.get();
89 }
90};
91
Adam Lesinskifb48d292015-11-07 15:52:13 -080092class LinkCommand {
93public:
94 LinkCommand(const LinkOptions& options) :
95 mOptions(options), mContext(), mFinalTable() {
96 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -070097
98 std::string buildResourceFileName(const ResourceFile& resFile) {
99 std::stringstream out;
100 out << "res/" << resFile.name.type;
101 if (resFile.config != ConfigDescription{}) {
102 out << "-" << resFile.config;
103 }
104 out << "/";
105
106 if (mContext.getNameMangler()->shouldMangle(resFile.name.package)) {
107 out << NameMangler::mangleEntry(resFile.name.package, resFile.name.entry);
108 } else {
109 out << resFile.name.entry;
110 }
111 out << file::getExtension(resFile.source.path);
112 return out.str();
113 }
114
115 /**
116 * Creates a SymbolTable that loads symbols from the various APKs and caches the
117 * results for faster lookup.
118 */
119 std::unique_ptr<ISymbolTable> createSymbolTableFromIncludePaths() {
120 AssetManagerSymbolTableBuilder builder;
121 for (const std::string& path : mOptions.includePaths) {
122 if (mOptions.verbose) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800123 mContext.getDiagnostics()->note(DiagMessage(path) << "loading include path");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700124 }
125
126 std::unique_ptr<android::AssetManager> assetManager =
127 util::make_unique<android::AssetManager>();
128 int32_t cookie = 0;
129 if (!assetManager->addAssetPath(android::String8(path.data(), path.size()), &cookie)) {
130 mContext.getDiagnostics()->error(
Adam Lesinskifb48d292015-11-07 15:52:13 -0800131 DiagMessage(path) << "failed to load include path");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700132 return {};
133 }
134 builder.add(std::move(assetManager));
135 }
136 return builder.build();
137 }
138
139 /**
140 * Loads the resource table (not inside an apk) at the given path.
141 */
142 std::unique_ptr<ResourceTable> loadTable(const std::string& input) {
143 std::string errorStr;
144 Maybe<android::FileMap> map = file::mmapPath(input, &errorStr);
145 if (!map) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800146 mContext.getDiagnostics()->error(DiagMessage(input) << errorStr);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700147 return {};
148 }
149
150 std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
Adam Lesinskifb48d292015-11-07 15:52:13 -0800151 BinaryResourceParser parser(&mContext, table.get(), Source(input),
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700152 map.value().getDataPtr(), map.value().getDataLength());
153 if (!parser.parse()) {
154 return {};
155 }
156 return table;
157 }
158
159 /**
160 * Inflates an XML file from the source path.
161 */
Adam Lesinski467f1712015-11-16 17:35:44 -0800162 std::unique_ptr<xml::XmlResource> loadXml(const std::string& path) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700163 std::ifstream fin(path, std::ifstream::binary);
164 if (!fin) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800165 mContext.getDiagnostics()->error(DiagMessage(path) << strerror(errno));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700166 return {};
167 }
168
Adam Lesinskifb48d292015-11-07 15:52:13 -0800169 return xml::inflate(&fin, mContext.getDiagnostics(), Source(path));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700170 }
171
172 /**
173 * Inflates a binary XML file from the source path.
174 */
Adam Lesinski467f1712015-11-16 17:35:44 -0800175 std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const std::string& path) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700176 // Read header for symbol info and export info.
177 std::string errorStr;
178 Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
179 if (!maybeF) {
180 mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
181 return {};
182 }
183
184 ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(),
185 maybeF.value().getDataLength(), &errorStr);
186 if (offset < 0) {
187 mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
188 return {};
189 }
190
Adam Lesinski467f1712015-11-16 17:35:44 -0800191 std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700192 (const uint8_t*) maybeF.value().getDataPtr() + (size_t) offset,
193 maybeF.value().getDataLength() - offset,
194 mContext.getDiagnostics(), Source(path));
195 if (!xmlRes) {
196 return {};
197 }
198 return xmlRes;
199 }
200
201 Maybe<ResourceFile> loadFileExportHeader(const std::string& path) {
202 // Read header for symbol info and export info.
203 std::string errorStr;
204 Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
205 if (!maybeF) {
206 mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
207 return {};
208 }
209
210 ResourceFile resFile;
211 ssize_t offset = unwrapFileExportHeader(maybeF.value().getDataPtr(),
212 maybeF.value().getDataLength(),
213 &resFile, &errorStr);
214 if (offset < 0) {
215 mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
216 return {};
217 }
218 return std::move(resFile);
219 }
220
221 bool copyFileToArchive(const std::string& path, const std::string& outPath, uint32_t flags,
222 IArchiveWriter* writer) {
223 std::string errorStr;
224 Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
225 if (!maybeF) {
226 mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
227 return false;
228 }
229
230 ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(),
231 maybeF.value().getDataLength(),
232 &errorStr);
233 if (offset < 0) {
234 mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
235 return false;
236 }
237
238 ArchiveEntry* entry = writer->writeEntry(outPath, flags, &maybeF.value(),
239 offset, maybeF.value().getDataLength() - offset);
240 if (!entry) {
241 mContext.getDiagnostics()->error(
242 DiagMessage(mOptions.outputPath) << "failed to write file " << outPath);
243 return false;
244 }
245 return true;
246 }
247
Adam Lesinski467f1712015-11-16 17:35:44 -0800248 Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700249 // Make sure the first element is <manifest> with package attribute.
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800250 if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700251 if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") {
252 if (xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package")) {
253 return AppInfo{ packageAttr->value };
254 }
255 }
256 }
257 return {};
258 }
259
Adam Lesinskifb48d292015-11-07 15:52:13 -0800260 bool verifyNoExternalPackages() {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700261 bool error = false;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800262 for (const auto& package : mFinalTable.packages) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700263 if (mContext.getCompilationPackage() != package->name ||
264 !package->id || package->id.value() != mContext.getPackageId()) {
265 // We have a package that is not related to the one we're building!
266 for (const auto& type : package->types) {
267 for (const auto& entry : type->entries) {
268 for (const auto& configValue : entry->values) {
Adam Lesinskie78fd612015-10-22 12:48:43 -0700269 mContext.getDiagnostics()->error(
270 DiagMessage(configValue.value->getSource())
271 << "defined resource '"
272 << ResourceNameRef(package->name,
273 type->type,
274 entry->name)
275 << "' for external package '"
276 << package->name << "'");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700277 error = true;
278 }
279 }
280 }
281 }
282 }
283 return !error;
284 }
285
286 std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
287 if (mOptions.outputToDirectory) {
288 return createDirectoryArchiveWriter(mOptions.outputPath);
289 } else {
290 return createZipFileArchiveWriter(mOptions.outputPath);
291 }
292 }
293
294 bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
295 BigBuffer buffer(1024);
296 TableFlattenerOptions options = {};
297 options.useExtendedChunks = mOptions.staticLib;
298 TableFlattener flattener(&buffer, options);
299 if (!flattener.consume(&mContext, table)) {
300 return false;
301 }
302
303 ArchiveEntry* entry = writer->writeEntry("resources.arsc", ArchiveEntry::kAlign, buffer);
304 if (!entry) {
305 mContext.getDiagnostics()->error(
306 DiagMessage() << "failed to write resources.arsc to archive");
307 return false;
308 }
309 return true;
310 }
311
Adam Lesinski467f1712015-11-16 17:35:44 -0800312 bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700313 IArchiveWriter* writer) {
314 BigBuffer buffer(1024);
315 XmlFlattenerOptions options = {};
316 options.keepRawValues = mOptions.staticLib;
317 options.maxSdkLevel = maxSdkLevel;
318 XmlFlattener flattener(&buffer, options);
319 if (!flattener.consume(&mContext, xmlRes)) {
320 return false;
321 }
322
323 ArchiveEntry* entry = writer->writeEntry(path, ArchiveEntry::kCompress, buffer);
324 if (!entry) {
325 mContext.getDiagnostics()->error(
326 DiagMessage() << "failed to write " << path << " to archive");
327 return false;
328 }
329 return true;
330 }
331
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700332 bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
333 const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700334 if (!mOptions.generateJavaClassPath) {
335 return true;
336 }
337
338 std::string outPath = mOptions.generateJavaClassPath.value();
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700339 file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(outPackage)));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700340 file::mkdirs(outPath);
341 file::appendPath(&outPath, "R.java");
342
343 std::ofstream fout(outPath, std::ofstream::binary);
344 if (!fout) {
345 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
346 return false;
347 }
348
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700349 JavaClassGenerator generator(table, javaOptions);
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700350 if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700351 mContext.getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
352 return false;
353 }
354 return true;
355 }
356
Adam Lesinski467f1712015-11-16 17:35:44 -0800357 bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
Adam Lesinskica5638f2015-10-21 14:42:43 -0700358 if (!mOptions.generateJavaClassPath) {
359 return true;
360 }
361
362 std::string outPath = mOptions.generateJavaClassPath.value();
363 file::appendPath(&outPath,
364 file::packageToPath(util::utf16ToUtf8(mContext.getCompilationPackage())));
365 file::mkdirs(outPath);
366 file::appendPath(&outPath, "Manifest.java");
367
368 std::ofstream fout(outPath, std::ofstream::binary);
369 if (!fout) {
370 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
371 return false;
372 }
373
374 ManifestClassGenerator generator;
375 if (!generator.generate(mContext.getDiagnostics(), mContext.getCompilationPackage(),
376 manifestXml, &fout)) {
377 return false;
378 }
379
380 if (!fout) {
381 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
382 return false;
383 }
384 return true;
385 }
386
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700387 bool writeProguardFile(const proguard::KeepSet& keepSet) {
388 if (!mOptions.generateProguardRulesPath) {
389 return true;
390 }
391
392 std::ofstream fout(mOptions.generateProguardRulesPath.value(), std::ofstream::binary);
393 if (!fout) {
394 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
395 return false;
396 }
397
398 proguard::writeKeepSet(&fout, keepSet);
399 if (!fout) {
400 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
401 return false;
402 }
403 return true;
404 }
405
Adam Lesinskifb48d292015-11-07 15:52:13 -0800406 bool mergeStaticLibrary(const std::string& input) {
407 // TODO(adamlesinski): Load resources from a static library APK and merge the table into
408 // TableMerger.
409 mContext.getDiagnostics()->warn(DiagMessage()
410 << "linking static libraries not supported yet: "
411 << input);
412 return true;
413 }
414
415 bool mergeResourceTable(const std::string& input, bool override) {
416 if (mOptions.verbose) {
417 mContext.getDiagnostics()->note(DiagMessage() << "linking " << input);
418 }
419
420 std::unique_ptr<ResourceTable> table = loadTable(input);
421 if (!table) {
422 return false;
423 }
424
425 if (!mTableMerger->merge(Source(input), table.get(), override)) {
426 return false;
427 }
428 return true;
429 }
430
431 bool mergeCompiledFile(const std::string& input, ResourceFile&& file, bool override) {
432 if (file.name.package.empty()) {
433 file.name.package = mContext.getCompilationPackage().toString();
434 }
435
436 ResourceNameRef resName = file.name;
437
438 Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(file.name);
439 if (mangledName) {
440 resName = mangledName.value();
441 }
442
443 std::function<int(Value*,Value*)> resolver;
444 if (override) {
445 resolver = [](Value* a, Value* b) -> int {
446 int result = ResourceTable::resolveValueCollision(a, b);
447 if (result == 0) {
448 // Always accept the new value if it would normally conflict (override).
449 result = 1;
450 }
451 return result;
452 };
453 } else {
454 // Otherwise use the default resolution.
455 resolver = ResourceTable::resolveValueCollision;
456 }
457
458 // Add this file to the table.
459 if (!mFinalTable.addFileReference(resName, file.config, file.source,
460 util::utf8ToUtf16(buildResourceFileName(file)),
461 resolver, mContext.getDiagnostics())) {
462 return false;
463 }
464
465 // Add the exports of this file to the table.
466 for (SourcedResourceName& exportedSymbol : file.exportedSymbols) {
467 if (exportedSymbol.name.package.empty()) {
468 exportedSymbol.name.package = mContext.getCompilationPackage().toString();
469 }
470
471 ResourceNameRef resName = exportedSymbol.name;
472
473 Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(
474 exportedSymbol.name);
475 if (mangledName) {
476 resName = mangledName.value();
477 }
478
479 std::unique_ptr<Id> id = util::make_unique<Id>();
480 id->setSource(file.source.withLine(exportedSymbol.line));
481 bool result = mFinalTable.addResourceAllowMangled(resName, {}, std::move(id),
482 mContext.getDiagnostics());
483 if (!result) {
484 return false;
485 }
486 }
487
Adam Lesinskifc9570e62015-11-16 15:07:54 -0800488 mFilesToProcess.insert(FileToProcess{ std::move(file), Source(input) });
Adam Lesinskifb48d292015-11-07 15:52:13 -0800489 return true;
490 }
491
492 bool processFile(const std::string& input, bool override) {
493 if (util::stringEndsWith<char>(input, ".apk")) {
494 return mergeStaticLibrary(input);
495 } else if (util::stringEndsWith<char>(input, ".arsc.flat")) {
496 return mergeResourceTable(input, override);
497 } else if (Maybe<ResourceFile> maybeF = loadFileExportHeader(input)) {
498 return mergeCompiledFile(input, std::move(maybeF.value()), override);
499 }
500 return false;
501 }
502
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700503 int run(const std::vector<std::string>& inputFiles) {
504 // Load the AndroidManifest.xml
Adam Lesinski467f1712015-11-16 17:35:44 -0800505 std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700506 if (!manifestXml) {
507 return 1;
508 }
509
510 if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) {
511 mContext.mCompilationPackage = maybeAppInfo.value().package;
512 } else {
513 mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
514 << "no package specified in <manifest> tag");
515 return 1;
516 }
517
518 if (!util::isJavaPackageName(mContext.mCompilationPackage)) {
519 mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
520 << "invalid package name '"
521 << mContext.mCompilationPackage
522 << "'");
523 return 1;
524 }
525
526 mContext.mNameMangler = util::make_unique<NameMangler>(
527 NameManglerPolicy{ mContext.mCompilationPackage });
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700528
529 if (mContext.mCompilationPackage == u"android") {
530 mContext.mPackageId = 0x01;
531 } else {
532 mContext.mPackageId = 0x7f;
533 }
534
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700535 mContext.mSymbols = createSymbolTableFromIncludePaths();
536 if (!mContext.mSymbols) {
537 return 1;
538 }
539
Adam Lesinskifb48d292015-11-07 15:52:13 -0800540 mTableMerger = util::make_unique<TableMerger>(&mContext, &mFinalTable);
541
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700542 if (mOptions.verbose) {
543 mContext.getDiagnostics()->note(
544 DiagMessage() << "linking package '" << mContext.mCompilationPackage << "' "
545 << "with package ID " << std::hex << (int) mContext.mPackageId);
546 }
547
Adam Lesinskifb48d292015-11-07 15:52:13 -0800548
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700549 for (const std::string& input : inputFiles) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800550 if (!processFile(input, false)) {
Adam Lesinski467f1712015-11-16 17:35:44 -0800551 mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input");
552 return 1;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800553 }
554 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700555
Adam Lesinskifb48d292015-11-07 15:52:13 -0800556 for (const std::string& input : mOptions.overlayFiles) {
557 if (!processFile(input, true)) {
Adam Lesinski467f1712015-11-16 17:35:44 -0800558 mContext.getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
559 return 1;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700560 }
561 }
562
Adam Lesinskifb48d292015-11-07 15:52:13 -0800563 if (!verifyNoExternalPackages()) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700564 return 1;
565 }
566
567 if (!mOptions.staticLib) {
568 PrivateAttributeMover mover;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800569 if (!mover.consume(&mContext, &mFinalTable)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700570 mContext.getDiagnostics()->error(
571 DiagMessage() << "failed moving private attributes");
572 return 1;
573 }
574 }
575
576 {
577 IdAssigner idAssigner;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800578 if (!idAssigner.consume(&mContext, &mFinalTable)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700579 mContext.getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
580 return 1;
581 }
582 }
583
Adam Lesinskifb48d292015-11-07 15:52:13 -0800584 mContext.mNameMangler = util::make_unique<NameMangler>(NameManglerPolicy{
585 mContext.mCompilationPackage, mTableMerger->getMergedPackages() });
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700586 mContext.mSymbols = JoinedSymbolTableBuilder()
Adam Lesinskifb48d292015-11-07 15:52:13 -0800587 .addSymbolTable(util::make_unique<SymbolTableWrapper>(&mFinalTable))
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700588 .addSymbolTable(std::move(mContext.mSymbols))
589 .build();
590
591 {
592 ReferenceLinker linker;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800593 if (!linker.consume(&mContext, &mFinalTable)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700594 mContext.getDiagnostics()->error(DiagMessage() << "failed linking references");
595 return 1;
596 }
597 }
598
599 proguard::KeepSet proguardKeepSet;
600
601 std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter();
602 if (!archiveWriter) {
603 mContext.getDiagnostics()->error(DiagMessage() << "failed to create archive");
604 return 1;
605 }
606
Adam Lesinski467f1712015-11-16 17:35:44 -0800607 bool error = false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700608 {
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800609 ManifestFixerOptions manifestFixerOptions;
610 manifestFixerOptions.minSdkVersionDefault = mOptions.minSdkVersionDefault;
611 manifestFixerOptions.targetSdkVersionDefault = mOptions.targetSdkVersionDefault;
612 ManifestFixer manifestFixer(manifestFixerOptions);
613 if (!manifestFixer.consume(&mContext, manifestXml.get())) {
614 error = true;
615 }
616
Adam Lesinski467f1712015-11-16 17:35:44 -0800617 // AndroidManifest.xml has no resource name, but the CallSite is built from the name
618 // (aka, which package the AndroidManifest.xml is coming from).
619 // So we give it a package name so it can see local resources.
620 manifestXml->file.name.package = mContext.getCompilationPackage().toString();
621
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700622 XmlReferenceLinker manifestLinker;
623 if (manifestLinker.consume(&mContext, manifestXml.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700624 if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
625 manifestXml.get(),
626 &proguardKeepSet)) {
627 error = true;
628 }
629
Adam Lesinskica5638f2015-10-21 14:42:43 -0700630 if (mOptions.generateJavaClassPath) {
631 if (!writeManifestJavaFile(manifestXml.get())) {
632 error = true;
633 }
634 }
635
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700636 if (!flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
637 archiveWriter.get())) {
638 error = true;
639 }
640 } else {
641 error = true;
642 }
643 }
644
Adam Lesinski467f1712015-11-16 17:35:44 -0800645 if (error) {
646 mContext.getDiagnostics()->error(DiagMessage() << "failed processing manifest");
647 return 1;
648 }
649
Adam Lesinskifc9570e62015-11-16 15:07:54 -0800650 for (const FileToProcess& file : mFilesToProcess) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800651 if (file.file.name.type != ResourceType::kRaw &&
652 util::stringEndsWith<char>(file.source.path, ".xml.flat")) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700653 if (mOptions.verbose) {
Adam Lesinski467f1712015-11-16 17:35:44 -0800654 mContext.getDiagnostics()->note(DiagMessage()
655 << "linking " << file.source.path);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700656 }
657
Adam Lesinski467f1712015-11-16 17:35:44 -0800658 std::unique_ptr<xml::XmlResource> xmlRes = loadBinaryXmlSkipFileExport(
659 file.source.path);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700660 if (!xmlRes) {
661 return 1;
662 }
663
Adam Lesinskifb48d292015-11-07 15:52:13 -0800664 xmlRes->file = std::move(file.file);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700665
666 XmlReferenceLinker xmlLinker;
667 if (xmlLinker.consume(&mContext, xmlRes.get())) {
668 if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(),
669 &proguardKeepSet)) {
670 error = true;
671 }
672
673 Maybe<size_t> maxSdkLevel;
674 if (!mOptions.noAutoVersion) {
675 maxSdkLevel = std::max<size_t>(xmlRes->file.config.sdkVersion, 1u);
676 }
677
678 if (!flattenXml(xmlRes.get(), buildResourceFileName(xmlRes->file), maxSdkLevel,
679 archiveWriter.get())) {
680 error = true;
681 }
682
683 if (!mOptions.noAutoVersion) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800684 Maybe<ResourceTable::SearchResult> result = mFinalTable.findResource(
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700685 xmlRes->file.name);
686 for (int sdkLevel : xmlLinker.getSdkLevels()) {
687 if (sdkLevel > xmlRes->file.config.sdkVersion &&
688 shouldGenerateVersionedResource(result.value().entry,
689 xmlRes->file.config,
690 sdkLevel)) {
691 xmlRes->file.config.sdkVersion = sdkLevel;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800692 if (!mFinalTable.addFileReference(xmlRes->file.name,
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700693 xmlRes->file.config,
694 xmlRes->file.source,
695 util::utf8ToUtf16(
696 buildResourceFileName(xmlRes->file)),
697 mContext.getDiagnostics())) {
698 error = true;
699 continue;
700 }
701
702 if (!flattenXml(xmlRes.get(), buildResourceFileName(xmlRes->file),
703 sdkLevel, archiveWriter.get())) {
704 error = true;
705 }
706 }
707 }
708 }
709
710 } else {
711 error = true;
712 }
713 } else {
714 if (mOptions.verbose) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800715 mContext.getDiagnostics()->note(DiagMessage() << "copying "
716 << file.source.path);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700717 }
718
Adam Lesinskifb48d292015-11-07 15:52:13 -0800719 if (!copyFileToArchive(file.source.path, buildResourceFileName(file.file), 0,
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700720 archiveWriter.get())) {
721 error = true;
722 }
723 }
724 }
725
726 if (error) {
727 mContext.getDiagnostics()->error(DiagMessage() << "failed linking file resources");
728 return 1;
729 }
730
731 if (!mOptions.noAutoVersion) {
732 AutoVersioner versioner;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800733 if (!versioner.consume(&mContext, &mFinalTable)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700734 mContext.getDiagnostics()->error(DiagMessage() << "failed versioning styles");
735 return 1;
736 }
737 }
738
Adam Lesinskifb48d292015-11-07 15:52:13 -0800739 if (!flattenTable(&mFinalTable, archiveWriter.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700740 mContext.getDiagnostics()->error(DiagMessage() << "failed to write resources.arsc");
741 return 1;
742 }
743
744 if (mOptions.generateJavaClassPath) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700745 JavaClassGeneratorOptions options;
746 if (mOptions.staticLib) {
747 options.useFinal = false;
748 }
749
Adam Lesinski83f22552015-11-07 11:51:23 -0800750 StringPiece16 actualPackage = mContext.getCompilationPackage();
751 StringPiece16 outputPackage = mContext.getCompilationPackage();
752
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700753 if (mOptions.privateSymbols) {
754 // If we defined a private symbols package, we only emit Public symbols
755 // to the original package, and private and public symbols to the private package.
756
757 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800758 if (!writeJavaFile(&mFinalTable, mContext.getCompilationPackage(),
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700759 mContext.getCompilationPackage(), options)) {
760 return 1;
761 }
762
763 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
Adam Lesinski83f22552015-11-07 11:51:23 -0800764 outputPackage = mOptions.privateSymbols.value();
765 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700766
Adam Lesinskifb48d292015-11-07 15:52:13 -0800767 if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
Adam Lesinski83f22552015-11-07 11:51:23 -0800768 return 1;
769 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700770
Adam Lesinskifc9570e62015-11-16 15:07:54 -0800771 for (const std::string& extraPackage : mOptions.extraJavaPackages) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800772 if (!writeJavaFile(&mFinalTable, actualPackage, util::utf8ToUtf16(extraPackage),
Adam Lesinski83f22552015-11-07 11:51:23 -0800773 options)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700774 return 1;
775 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700776 }
777 }
778
779 if (mOptions.generateProguardRulesPath) {
780 if (!writeProguardFile(proguardKeepSet)) {
781 return 1;
782 }
783 }
784
785 if (mOptions.verbose) {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800786 Debug::printTable(&mFinalTable);
787 for (; !mTableMerger->getFileMergeQueue()->empty();
788 mTableMerger->getFileMergeQueue()->pop()) {
789 const FileToMerge& f = mTableMerger->getFileMergeQueue()->front();
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700790 mContext.getDiagnostics()->note(
791 DiagMessage() << f.srcPath << " -> " << f.dstPath << " from (0x"
792 << std::hex << (uintptr_t) f.srcTable << std::dec);
793 }
794 }
795
796 return 0;
797 }
Adam Lesinskifb48d292015-11-07 15:52:13 -0800798
799private:
800 LinkOptions mOptions;
801 LinkContext mContext;
802 ResourceTable mFinalTable;
803 std::unique_ptr<TableMerger> mTableMerger;
804
805 struct FileToProcess {
Adam Lesinskifb48d292015-11-07 15:52:13 -0800806 ResourceFile file;
Adam Lesinskifc9570e62015-11-16 15:07:54 -0800807 Source source;
Adam Lesinskifb48d292015-11-07 15:52:13 -0800808 };
Adam Lesinskifc9570e62015-11-16 15:07:54 -0800809
810 struct FileToProcessComparator {
811 bool operator()(const FileToProcess& a, const FileToProcess& b) {
812 return std::tie(a.file.name, a.file.config) < std::tie(b.file.name, b.file.config);
813 }
814 };
815
816 std::set<FileToProcess, FileToProcessComparator> mFilesToProcess;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700817};
818
819int link(const std::vector<StringPiece>& args) {
820 LinkOptions options;
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700821 Maybe<std::string> privateSymbolsPackage;
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800822 Maybe<std::string> minSdkVersion, targetSdkVersion;
Adam Lesinskifc9570e62015-11-16 15:07:54 -0800823 std::vector<std::string> extraJavaPackages;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700824 Flags flags = Flags()
825 .requiredFlag("-o", "Output path", &options.outputPath)
826 .requiredFlag("--manifest", "Path to the Android manifest to build",
827 &options.manifestPath)
828 .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
Adam Lesinskifb48d292015-11-07 15:52:13 -0800829 .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics. "
830 "The last conflicting resource given takes precedence.",
831 &options.overlayFiles)
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700832 .optionalFlag("--java", "Directory in which to generate R.java",
833 &options.generateJavaClassPath)
834 .optionalFlag("--proguard", "Output file for generated Proguard rules",
835 &options.generateProguardRulesPath)
836 .optionalSwitch("--no-auto-version",
837 "Disables automatic style and layout SDK versioning",
838 &options.noAutoVersion)
839 .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
840 "by -o",
841 &options.outputToDirectory)
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800842 .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
843 "AndroidManifest.xml", &minSdkVersion)
844 .optionalFlag("--target-sdk-version", "Default target SDK version to use for "
845 "AndroidManifest.xml", &targetSdkVersion)
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700846 .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700847 .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800848 "private symbols.\n"
849 "If not specified, public and private symbols will use the application's "
850 "package name", &privateSymbolsPackage)
Adam Lesinski83f22552015-11-07 11:51:23 -0800851 .optionalFlagList("--extra-packages", "Generate the same R.java but with different "
Adam Lesinskifc9570e62015-11-16 15:07:54 -0800852 "package names", &extraJavaPackages)
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700853 .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
854
855 if (!flags.parse("aapt2 link", args, &std::cerr)) {
856 return 1;
857 }
858
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700859 if (privateSymbolsPackage) {
860 options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
861 }
862
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800863 if (minSdkVersion) {
864 options.minSdkVersionDefault = util::utf8ToUtf16(minSdkVersion.value());
865 }
866
867 if (targetSdkVersion) {
868 options.targetSdkVersionDefault = util::utf8ToUtf16(targetSdkVersion.value());
869 }
870
Adam Lesinskifc9570e62015-11-16 15:07:54 -0800871 // Populate the set of extra packages for which to generate R.java.
872 for (std::string& extraPackage : extraJavaPackages) {
873 // A given package can actually be a colon separated list of packages.
874 for (StringPiece package : util::split(extraPackage, ':')) {
875 options.extraJavaPackages.insert(package.toString());
876 }
877 }
878
Adam Lesinskifb48d292015-11-07 15:52:13 -0800879 LinkCommand cmd(options);
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700880 return cmd.run(flags.getArgs());
881}
882
883} // namespace aapt