blob: 9ce3734f1914bd90a38f75fbac7bedc34186d5c0 [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 "XmlDom.h"
22
23#include "compile/IdAssigner.h"
24#include "flatten/Archive.h"
25#include "flatten/TableFlattener.h"
26#include "flatten/XmlFlattener.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 Lesinski2ae4a872015-11-02 16:10:55 -080031#include "link/ManifestFixer.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070032#include "link/TableMerger.h"
33#include "process/IResourceTableConsumer.h"
34#include "process/SymbolTable.h"
35#include "unflatten/BinaryResourceParser.h"
36#include "unflatten/FileExportHeaderReader.h"
37#include "util/Files.h"
38#include "util/StringPiece.h"
39
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;
51 Maybe<std::string> generateJavaClassPath;
Adam Lesinski83f22552015-11-07 11:51:23 -080052 std::vector<std::string> extraJavaPackages;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070053 Maybe<std::string> generateProguardRulesPath;
54 bool noAutoVersion = false;
55 bool staticLib = false;
56 bool verbose = false;
57 bool outputToDirectory = false;
Adam Lesinski9e10ac72015-10-16 14:37:48 -070058 Maybe<std::u16string> privateSymbols;
Adam Lesinski2ae4a872015-11-02 16:10:55 -080059 Maybe<std::u16string> minSdkVersionDefault;
60 Maybe<std::u16string> targetSdkVersionDefault;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070061};
62
63struct LinkContext : public IAaptContext {
64 StdErrDiagnostics mDiagnostics;
65 std::unique_ptr<NameMangler> mNameMangler;
66 std::u16string mCompilationPackage;
67 uint8_t mPackageId;
68 std::unique_ptr<ISymbolTable> mSymbols;
69
70 IDiagnostics* getDiagnostics() override {
71 return &mDiagnostics;
72 }
73
74 NameMangler* getNameMangler() override {
75 return mNameMangler.get();
76 }
77
78 StringPiece16 getCompilationPackage() override {
79 return mCompilationPackage;
80 }
81
82 uint8_t getPackageId() override {
83 return mPackageId;
84 }
85
86 ISymbolTable* getExternalSymbols() override {
87 return mSymbols.get();
88 }
89};
90
91struct LinkCommand {
92 LinkOptions mOptions;
93 LinkContext mContext;
94
95 std::string buildResourceFileName(const ResourceFile& resFile) {
96 std::stringstream out;
97 out << "res/" << resFile.name.type;
98 if (resFile.config != ConfigDescription{}) {
99 out << "-" << resFile.config;
100 }
101 out << "/";
102
103 if (mContext.getNameMangler()->shouldMangle(resFile.name.package)) {
104 out << NameMangler::mangleEntry(resFile.name.package, resFile.name.entry);
105 } else {
106 out << resFile.name.entry;
107 }
108 out << file::getExtension(resFile.source.path);
109 return out.str();
110 }
111
112 /**
113 * Creates a SymbolTable that loads symbols from the various APKs and caches the
114 * results for faster lookup.
115 */
116 std::unique_ptr<ISymbolTable> createSymbolTableFromIncludePaths() {
117 AssetManagerSymbolTableBuilder builder;
118 for (const std::string& path : mOptions.includePaths) {
119 if (mOptions.verbose) {
120 mContext.getDiagnostics()->note(
121 DiagMessage(Source{ path }) << "loading include path");
122 }
123
124 std::unique_ptr<android::AssetManager> assetManager =
125 util::make_unique<android::AssetManager>();
126 int32_t cookie = 0;
127 if (!assetManager->addAssetPath(android::String8(path.data(), path.size()), &cookie)) {
128 mContext.getDiagnostics()->error(
129 DiagMessage(Source{ path }) << "failed to load include path");
130 return {};
131 }
132 builder.add(std::move(assetManager));
133 }
134 return builder.build();
135 }
136
137 /**
138 * Loads the resource table (not inside an apk) at the given path.
139 */
140 std::unique_ptr<ResourceTable> loadTable(const std::string& input) {
141 std::string errorStr;
142 Maybe<android::FileMap> map = file::mmapPath(input, &errorStr);
143 if (!map) {
144 mContext.getDiagnostics()->error(DiagMessage(Source{ input }) << errorStr);
145 return {};
146 }
147
148 std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
149 BinaryResourceParser parser(&mContext, table.get(), Source{ input },
150 map.value().getDataPtr(), map.value().getDataLength());
151 if (!parser.parse()) {
152 return {};
153 }
154 return table;
155 }
156
157 /**
158 * Inflates an XML file from the source path.
159 */
160 std::unique_ptr<XmlResource> loadXml(const std::string& path) {
161 std::ifstream fin(path, std::ifstream::binary);
162 if (!fin) {
163 mContext.getDiagnostics()->error(DiagMessage(Source{ path }) << strerror(errno));
164 return {};
165 }
166
167 return xml::inflate(&fin, mContext.getDiagnostics(), Source{ path });
168 }
169
170 /**
171 * Inflates a binary XML file from the source path.
172 */
173 std::unique_ptr<XmlResource> loadBinaryXmlSkipFileExport(const std::string& path) {
174 // Read header for symbol info and export info.
175 std::string errorStr;
176 Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
177 if (!maybeF) {
178 mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
179 return {};
180 }
181
182 ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(),
183 maybeF.value().getDataLength(), &errorStr);
184 if (offset < 0) {
185 mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
186 return {};
187 }
188
189 std::unique_ptr<XmlResource> xmlRes = xml::inflate(
190 (const uint8_t*) maybeF.value().getDataPtr() + (size_t) offset,
191 maybeF.value().getDataLength() - offset,
192 mContext.getDiagnostics(), Source(path));
193 if (!xmlRes) {
194 return {};
195 }
196 return xmlRes;
197 }
198
199 Maybe<ResourceFile> loadFileExportHeader(const std::string& path) {
200 // Read header for symbol info and export info.
201 std::string errorStr;
202 Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
203 if (!maybeF) {
204 mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
205 return {};
206 }
207
208 ResourceFile resFile;
209 ssize_t offset = unwrapFileExportHeader(maybeF.value().getDataPtr(),
210 maybeF.value().getDataLength(),
211 &resFile, &errorStr);
212 if (offset < 0) {
213 mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
214 return {};
215 }
216 return std::move(resFile);
217 }
218
219 bool copyFileToArchive(const std::string& path, const std::string& outPath, uint32_t flags,
220 IArchiveWriter* writer) {
221 std::string errorStr;
222 Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
223 if (!maybeF) {
224 mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
225 return false;
226 }
227
228 ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(),
229 maybeF.value().getDataLength(),
230 &errorStr);
231 if (offset < 0) {
232 mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
233 return false;
234 }
235
236 ArchiveEntry* entry = writer->writeEntry(outPath, flags, &maybeF.value(),
237 offset, maybeF.value().getDataLength() - offset);
238 if (!entry) {
239 mContext.getDiagnostics()->error(
240 DiagMessage(mOptions.outputPath) << "failed to write file " << outPath);
241 return false;
242 }
243 return true;
244 }
245
246 Maybe<AppInfo> extractAppInfoFromManifest(XmlResource* xmlRes) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700247 // Make sure the first element is <manifest> with package attribute.
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800248 if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700249 if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") {
250 if (xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package")) {
251 return AppInfo{ packageAttr->value };
252 }
253 }
254 }
255 return {};
256 }
257
258 bool verifyNoExternalPackages(ResourceTable* table) {
259 bool error = false;
260 for (const auto& package : table->packages) {
261 if (mContext.getCompilationPackage() != package->name ||
262 !package->id || package->id.value() != mContext.getPackageId()) {
263 // We have a package that is not related to the one we're building!
264 for (const auto& type : package->types) {
265 for (const auto& entry : type->entries) {
266 for (const auto& configValue : entry->values) {
Adam Lesinskie78fd612015-10-22 12:48:43 -0700267 mContext.getDiagnostics()->error(
268 DiagMessage(configValue.value->getSource())
269 << "defined resource '"
270 << ResourceNameRef(package->name,
271 type->type,
272 entry->name)
273 << "' for external package '"
274 << package->name << "'");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700275 error = true;
276 }
277 }
278 }
279 }
280 }
281 return !error;
282 }
283
284 std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
285 if (mOptions.outputToDirectory) {
286 return createDirectoryArchiveWriter(mOptions.outputPath);
287 } else {
288 return createZipFileArchiveWriter(mOptions.outputPath);
289 }
290 }
291
292 bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
293 BigBuffer buffer(1024);
294 TableFlattenerOptions options = {};
295 options.useExtendedChunks = mOptions.staticLib;
296 TableFlattener flattener(&buffer, options);
297 if (!flattener.consume(&mContext, table)) {
298 return false;
299 }
300
301 ArchiveEntry* entry = writer->writeEntry("resources.arsc", ArchiveEntry::kAlign, buffer);
302 if (!entry) {
303 mContext.getDiagnostics()->error(
304 DiagMessage() << "failed to write resources.arsc to archive");
305 return false;
306 }
307 return true;
308 }
309
310 bool flattenXml(XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
311 IArchiveWriter* writer) {
312 BigBuffer buffer(1024);
313 XmlFlattenerOptions options = {};
314 options.keepRawValues = mOptions.staticLib;
315 options.maxSdkLevel = maxSdkLevel;
316 XmlFlattener flattener(&buffer, options);
317 if (!flattener.consume(&mContext, xmlRes)) {
318 return false;
319 }
320
321 ArchiveEntry* entry = writer->writeEntry(path, ArchiveEntry::kCompress, buffer);
322 if (!entry) {
323 mContext.getDiagnostics()->error(
324 DiagMessage() << "failed to write " << path << " to archive");
325 return false;
326 }
327 return true;
328 }
329
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700330 bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
331 const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700332 if (!mOptions.generateJavaClassPath) {
333 return true;
334 }
335
336 std::string outPath = mOptions.generateJavaClassPath.value();
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700337 file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(outPackage)));
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700338 file::mkdirs(outPath);
339 file::appendPath(&outPath, "R.java");
340
341 std::ofstream fout(outPath, std::ofstream::binary);
342 if (!fout) {
343 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
344 return false;
345 }
346
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700347 JavaClassGenerator generator(table, javaOptions);
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700348 if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700349 mContext.getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
350 return false;
351 }
352 return true;
353 }
354
Adam Lesinskica5638f2015-10-21 14:42:43 -0700355 bool writeManifestJavaFile(XmlResource* manifestXml) {
356 if (!mOptions.generateJavaClassPath) {
357 return true;
358 }
359
360 std::string outPath = mOptions.generateJavaClassPath.value();
361 file::appendPath(&outPath,
362 file::packageToPath(util::utf16ToUtf8(mContext.getCompilationPackage())));
363 file::mkdirs(outPath);
364 file::appendPath(&outPath, "Manifest.java");
365
366 std::ofstream fout(outPath, std::ofstream::binary);
367 if (!fout) {
368 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
369 return false;
370 }
371
372 ManifestClassGenerator generator;
373 if (!generator.generate(mContext.getDiagnostics(), mContext.getCompilationPackage(),
374 manifestXml, &fout)) {
375 return false;
376 }
377
378 if (!fout) {
379 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
380 return false;
381 }
382 return true;
383 }
384
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700385 bool writeProguardFile(const proguard::KeepSet& keepSet) {
386 if (!mOptions.generateProguardRulesPath) {
387 return true;
388 }
389
390 std::ofstream fout(mOptions.generateProguardRulesPath.value(), std::ofstream::binary);
391 if (!fout) {
392 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
393 return false;
394 }
395
396 proguard::writeKeepSet(&fout, keepSet);
397 if (!fout) {
398 mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
399 return false;
400 }
401 return true;
402 }
403
404 int run(const std::vector<std::string>& inputFiles) {
405 // Load the AndroidManifest.xml
406 std::unique_ptr<XmlResource> manifestXml = loadXml(mOptions.manifestPath);
407 if (!manifestXml) {
408 return 1;
409 }
410
411 if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) {
412 mContext.mCompilationPackage = maybeAppInfo.value().package;
413 } else {
414 mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
415 << "no package specified in <manifest> tag");
416 return 1;
417 }
418
419 if (!util::isJavaPackageName(mContext.mCompilationPackage)) {
420 mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
421 << "invalid package name '"
422 << mContext.mCompilationPackage
423 << "'");
424 return 1;
425 }
426
427 mContext.mNameMangler = util::make_unique<NameMangler>(
428 NameManglerPolicy{ mContext.mCompilationPackage });
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700429
430 if (mContext.mCompilationPackage == u"android") {
431 mContext.mPackageId = 0x01;
432 } else {
433 mContext.mPackageId = 0x7f;
434 }
435
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700436 mContext.mSymbols = createSymbolTableFromIncludePaths();
437 if (!mContext.mSymbols) {
438 return 1;
439 }
440
441 if (mOptions.verbose) {
442 mContext.getDiagnostics()->note(
443 DiagMessage() << "linking package '" << mContext.mCompilationPackage << "' "
444 << "with package ID " << std::hex << (int) mContext.mPackageId);
445 }
446
447 ResourceTable mergedTable;
448 TableMerger tableMerger(&mContext, &mergedTable);
449
450 struct FilesToProcess {
451 Source source;
452 ResourceFile file;
453 };
454
455 bool error = false;
456 std::queue<FilesToProcess> filesToProcess;
457 for (const std::string& input : inputFiles) {
458 if (util::stringEndsWith<char>(input, ".apk")) {
459 // TODO(adamlesinski): Load resources from a static library APK
460 // Merge the table into TableMerger.
461
462 } else if (util::stringEndsWith<char>(input, ".arsc.flat")) {
463 if (mOptions.verbose) {
464 mContext.getDiagnostics()->note(DiagMessage() << "linking " << input);
465 }
466
467 std::unique_ptr<ResourceTable> table = loadTable(input);
468 if (!table) {
469 return 1;
470 }
471
472 if (!tableMerger.merge(Source(input), table.get())) {
473 return 1;
474 }
475
476 } else {
477 // Extract the exported IDs here so we can build the resource table.
478 if (Maybe<ResourceFile> maybeF = loadFileExportHeader(input)) {
479 ResourceFile& f = maybeF.value();
480
481 if (f.name.package.empty()) {
482 f.name.package = mContext.getCompilationPackage().toString();
483 }
484
485 Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(f.name);
486
487 // Add this file to the table.
488 if (!mergedTable.addFileReference(mangledName ? mangledName.value() : f.name,
489 f.config, f.source,
490 util::utf8ToUtf16(buildResourceFileName(f)),
491 mContext.getDiagnostics())) {
492 error = true;
493 }
494
495 // Add the exports of this file to the table.
496 for (SourcedResourceName& exportedSymbol : f.exportedSymbols) {
497 if (exportedSymbol.name.package.empty()) {
498 exportedSymbol.name.package = mContext.getCompilationPackage()
499 .toString();
500 }
501
502 Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(
503 exportedSymbol.name);
Adam Lesinskie78fd612015-10-22 12:48:43 -0700504 std::unique_ptr<Id> id = util::make_unique<Id>();
505 id->setSource(f.source.withLine(exportedSymbol.line));
506 if (!mergedTable.addResourceAllowMangled(
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700507 mangledName ? mangledName.value() : exportedSymbol.name,
Adam Lesinskie78fd612015-10-22 12:48:43 -0700508 {}, std::move(id), mContext.getDiagnostics())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700509 error = true;
510 }
511 }
512
513 filesToProcess.push(FilesToProcess{ Source(input), std::move(f) });
514 } else {
515 return 1;
516 }
517 }
518 }
519
520 if (error) {
521 mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input");
522 return 1;
523 }
524
525 if (!verifyNoExternalPackages(&mergedTable)) {
526 return 1;
527 }
528
529 if (!mOptions.staticLib) {
530 PrivateAttributeMover mover;
531 if (!mover.consume(&mContext, &mergedTable)) {
532 mContext.getDiagnostics()->error(
533 DiagMessage() << "failed moving private attributes");
534 return 1;
535 }
536 }
537
538 {
539 IdAssigner idAssigner;
540 if (!idAssigner.consume(&mContext, &mergedTable)) {
541 mContext.getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
542 return 1;
543 }
544 }
545
546 mContext.mNameMangler = util::make_unique<NameMangler>(
547 NameManglerPolicy{ mContext.mCompilationPackage, tableMerger.getMergedPackages() });
548 mContext.mSymbols = JoinedSymbolTableBuilder()
549 .addSymbolTable(util::make_unique<SymbolTableWrapper>(&mergedTable))
550 .addSymbolTable(std::move(mContext.mSymbols))
551 .build();
552
553 {
554 ReferenceLinker linker;
555 if (!linker.consume(&mContext, &mergedTable)) {
556 mContext.getDiagnostics()->error(DiagMessage() << "failed linking references");
557 return 1;
558 }
559 }
560
561 proguard::KeepSet proguardKeepSet;
562
563 std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter();
564 if (!archiveWriter) {
565 mContext.getDiagnostics()->error(DiagMessage() << "failed to create archive");
566 return 1;
567 }
568
569 {
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800570 ManifestFixerOptions manifestFixerOptions;
571 manifestFixerOptions.minSdkVersionDefault = mOptions.minSdkVersionDefault;
572 manifestFixerOptions.targetSdkVersionDefault = mOptions.targetSdkVersionDefault;
573 ManifestFixer manifestFixer(manifestFixerOptions);
574 if (!manifestFixer.consume(&mContext, manifestXml.get())) {
575 error = true;
576 }
577
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700578 XmlReferenceLinker manifestLinker;
579 if (manifestLinker.consume(&mContext, manifestXml.get())) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700580 if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
581 manifestXml.get(),
582 &proguardKeepSet)) {
583 error = true;
584 }
585
Adam Lesinskica5638f2015-10-21 14:42:43 -0700586 if (mOptions.generateJavaClassPath) {
587 if (!writeManifestJavaFile(manifestXml.get())) {
588 error = true;
589 }
590 }
591
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700592 if (!flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
593 archiveWriter.get())) {
594 error = true;
595 }
596 } else {
597 error = true;
598 }
599 }
600
601 for (; !filesToProcess.empty(); filesToProcess.pop()) {
602 FilesToProcess& f = filesToProcess.front();
603 if (f.file.name.type != ResourceType::kRaw &&
604 util::stringEndsWith<char>(f.source.path, ".xml.flat")) {
605 if (mOptions.verbose) {
606 mContext.getDiagnostics()->note(DiagMessage() << "linking " << f.source.path);
607 }
608
609 std::unique_ptr<XmlResource> xmlRes = loadBinaryXmlSkipFileExport(f.source.path);
610 if (!xmlRes) {
611 return 1;
612 }
613
614 xmlRes->file = std::move(f.file);
615
616 XmlReferenceLinker xmlLinker;
617 if (xmlLinker.consume(&mContext, xmlRes.get())) {
618 if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(),
619 &proguardKeepSet)) {
620 error = true;
621 }
622
623 Maybe<size_t> maxSdkLevel;
624 if (!mOptions.noAutoVersion) {
625 maxSdkLevel = std::max<size_t>(xmlRes->file.config.sdkVersion, 1u);
626 }
627
628 if (!flattenXml(xmlRes.get(), buildResourceFileName(xmlRes->file), maxSdkLevel,
629 archiveWriter.get())) {
630 error = true;
631 }
632
633 if (!mOptions.noAutoVersion) {
634 Maybe<ResourceTable::SearchResult> result = mergedTable.findResource(
635 xmlRes->file.name);
636 for (int sdkLevel : xmlLinker.getSdkLevels()) {
637 if (sdkLevel > xmlRes->file.config.sdkVersion &&
638 shouldGenerateVersionedResource(result.value().entry,
639 xmlRes->file.config,
640 sdkLevel)) {
641 xmlRes->file.config.sdkVersion = sdkLevel;
642 if (!mergedTable.addFileReference(xmlRes->file.name,
643 xmlRes->file.config,
644 xmlRes->file.source,
645 util::utf8ToUtf16(
646 buildResourceFileName(xmlRes->file)),
647 mContext.getDiagnostics())) {
648 error = true;
649 continue;
650 }
651
652 if (!flattenXml(xmlRes.get(), buildResourceFileName(xmlRes->file),
653 sdkLevel, archiveWriter.get())) {
654 error = true;
655 }
656 }
657 }
658 }
659
660 } else {
661 error = true;
662 }
663 } else {
664 if (mOptions.verbose) {
665 mContext.getDiagnostics()->note(DiagMessage() << "copying " << f.source.path);
666 }
667
668 if (!copyFileToArchive(f.source.path, buildResourceFileName(f.file), 0,
669 archiveWriter.get())) {
670 error = true;
671 }
672 }
673 }
674
675 if (error) {
676 mContext.getDiagnostics()->error(DiagMessage() << "failed linking file resources");
677 return 1;
678 }
679
680 if (!mOptions.noAutoVersion) {
681 AutoVersioner versioner;
682 if (!versioner.consume(&mContext, &mergedTable)) {
683 mContext.getDiagnostics()->error(DiagMessage() << "failed versioning styles");
684 return 1;
685 }
686 }
687
688 if (!flattenTable(&mergedTable, archiveWriter.get())) {
689 mContext.getDiagnostics()->error(DiagMessage() << "failed to write resources.arsc");
690 return 1;
691 }
692
693 if (mOptions.generateJavaClassPath) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700694 JavaClassGeneratorOptions options;
695 if (mOptions.staticLib) {
696 options.useFinal = false;
697 }
698
Adam Lesinski83f22552015-11-07 11:51:23 -0800699 StringPiece16 actualPackage = mContext.getCompilationPackage();
700 StringPiece16 outputPackage = mContext.getCompilationPackage();
701
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700702 if (mOptions.privateSymbols) {
703 // If we defined a private symbols package, we only emit Public symbols
704 // to the original package, and private and public symbols to the private package.
705
706 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
707 if (!writeJavaFile(&mergedTable, mContext.getCompilationPackage(),
708 mContext.getCompilationPackage(), options)) {
709 return 1;
710 }
711
712 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
Adam Lesinski83f22552015-11-07 11:51:23 -0800713 outputPackage = mOptions.privateSymbols.value();
714 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700715
Adam Lesinski83f22552015-11-07 11:51:23 -0800716 if (!writeJavaFile(&mergedTable, actualPackage, outputPackage, options)) {
717 return 1;
718 }
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700719
Adam Lesinski83f22552015-11-07 11:51:23 -0800720 for (std::string& extraPackage : mOptions.extraJavaPackages) {
721 if (!writeJavaFile(&mergedTable, actualPackage, util::utf8ToUtf16(extraPackage),
722 options)) {
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700723 return 1;
724 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700725 }
726 }
727
728 if (mOptions.generateProguardRulesPath) {
729 if (!writeProguardFile(proguardKeepSet)) {
730 return 1;
731 }
732 }
733
734 if (mOptions.verbose) {
735 Debug::printTable(&mergedTable);
736 for (; !tableMerger.getFileMergeQueue()->empty();
737 tableMerger.getFileMergeQueue()->pop()) {
738 const FileToMerge& f = tableMerger.getFileMergeQueue()->front();
739 mContext.getDiagnostics()->note(
740 DiagMessage() << f.srcPath << " -> " << f.dstPath << " from (0x"
741 << std::hex << (uintptr_t) f.srcTable << std::dec);
742 }
743 }
744
745 return 0;
746 }
747};
748
749int link(const std::vector<StringPiece>& args) {
750 LinkOptions options;
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700751 Maybe<std::string> privateSymbolsPackage;
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800752 Maybe<std::string> minSdkVersion, targetSdkVersion;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700753 Flags flags = Flags()
754 .requiredFlag("-o", "Output path", &options.outputPath)
755 .requiredFlag("--manifest", "Path to the Android manifest to build",
756 &options.manifestPath)
757 .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
758 .optionalFlag("--java", "Directory in which to generate R.java",
759 &options.generateJavaClassPath)
760 .optionalFlag("--proguard", "Output file for generated Proguard rules",
761 &options.generateProguardRulesPath)
762 .optionalSwitch("--no-auto-version",
763 "Disables automatic style and layout SDK versioning",
764 &options.noAutoVersion)
765 .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
766 "by -o",
767 &options.outputToDirectory)
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800768 .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
769 "AndroidManifest.xml", &minSdkVersion)
770 .optionalFlag("--target-sdk-version", "Default target SDK version to use for "
771 "AndroidManifest.xml", &targetSdkVersion)
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700772 .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
Adam Lesinski9ba47d82015-10-13 11:37:10 -0700773 .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800774 "private symbols.\n"
775 "If not specified, public and private symbols will use the application's "
776 "package name", &privateSymbolsPackage)
Adam Lesinski83f22552015-11-07 11:51:23 -0800777 .optionalFlagList("--extra-packages", "Generate the same R.java but with different "
778 "package names", &options.extraJavaPackages)
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700779 .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
780
781 if (!flags.parse("aapt2 link", args, &std::cerr)) {
782 return 1;
783 }
784
Adam Lesinski9e10ac72015-10-16 14:37:48 -0700785 if (privateSymbolsPackage) {
786 options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
787 }
788
Adam Lesinski2ae4a872015-11-02 16:10:55 -0800789 if (minSdkVersion) {
790 options.minSdkVersionDefault = util::utf8ToUtf16(minSdkVersion.value());
791 }
792
793 if (targetSdkVersion) {
794 options.targetSdkVersionDefault = util::utf8ToUtf16(targetSdkVersion.value());
795 }
796
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700797 LinkCommand cmd = { options };
798 return cmd.run(flags.getArgs());
799}
800
801} // namespace aapt