Link.cpp revision 8900aa8b44f2dae3ee7b0f53422a4dbcb4fd2903
1/*
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"
20#include "NameMangler.h"
21#include "compile/IdAssigner.h"
22#include "flatten/Archive.h"
23#include "flatten/TableFlattener.h"
24#include "flatten/XmlFlattener.h"
25#include "io/FileSystem.h"
26#include "io/ZipArchive.h"
27#include "java/JavaClassGenerator.h"
28#include "java/ManifestClassGenerator.h"
29#include "java/ProguardRules.h"
30#include "link/Linkers.h"
31#include "link/ReferenceLinker.h"
32#include "link/ManifestFixer.h"
33#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"
40#include "xml/XmlDom.h"
41
42#include <fstream>
43#include <sys/stat.h>
44#include <vector>
45
46namespace aapt {
47
48struct LinkOptions {
49    std::string outputPath;
50    std::string manifestPath;
51    std::vector<std::string> includePaths;
52    std::vector<std::string> overlayFiles;
53    Maybe<std::string> generateJavaClassPath;
54    Maybe<std::u16string> customJavaPackage;
55    std::set<std::u16string> extraJavaPackages;
56    Maybe<std::string> generateProguardRulesPath;
57    bool noAutoVersion = false;
58    bool staticLib = false;
59    bool generateNonFinalIds = false;
60    bool verbose = false;
61    bool outputToDirectory = false;
62    bool autoAddOverlay = false;
63    bool doNotCompressAnything = false;
64    std::vector<std::string> extensionsToNotCompress;
65    Maybe<std::u16string> privateSymbols;
66    ManifestFixerOptions manifestFixerOptions;
67
68};
69
70struct LinkContext : public IAaptContext {
71    StdErrDiagnostics mDiagnostics;
72    std::unique_ptr<NameMangler> mNameMangler;
73    std::u16string mCompilationPackage;
74    uint8_t mPackageId;
75    std::unique_ptr<ISymbolTable> mSymbols;
76
77    IDiagnostics* getDiagnostics() override {
78        return &mDiagnostics;
79    }
80
81    NameMangler* getNameMangler() override {
82        return mNameMangler.get();
83    }
84
85    StringPiece16 getCompilationPackage() override {
86        return mCompilationPackage;
87    }
88
89    uint8_t getPackageId() override {
90        return mPackageId;
91    }
92
93    ISymbolTable* getExternalSymbols() override {
94        return mSymbols.get();
95    }
96};
97
98class LinkCommand {
99public:
100    LinkCommand(const LinkOptions& options) :
101            mOptions(options), mContext(), mFinalTable(), mFileCollection(nullptr) {
102        std::unique_ptr<io::FileCollection> fileCollection =
103                util::make_unique<io::FileCollection>();
104
105        // Get a pointer to the FileCollection for convenience, but it will be owned by the vector.
106        mFileCollection = fileCollection.get();
107
108        // Move it to the collection.
109        mCollections.push_back(std::move(fileCollection));
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(DiagMessage(path) << "loading include path");
121            }
122
123            std::unique_ptr<android::AssetManager> assetManager =
124                    util::make_unique<android::AssetManager>();
125            int32_t cookie = 0;
126            if (!assetManager->addAssetPath(android::String8(path.data(), path.size()), &cookie)) {
127                mContext.getDiagnostics()->error(
128                        DiagMessage(path) << "failed to load include path");
129                return {};
130            }
131            builder.add(std::move(assetManager));
132        }
133        return builder.build();
134    }
135
136    std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len) {
137        std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
138        BinaryResourceParser parser(&mContext, table.get(), source, data, len);
139        if (!parser.parse()) {
140            return {};
141        }
142        return table;
143    }
144
145    /**
146     * Inflates an XML file from the source path.
147     */
148    static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
149        std::ifstream fin(path, std::ifstream::binary);
150        if (!fin) {
151            diag->error(DiagMessage(path) << strerror(errno));
152            return {};
153        }
154
155        return xml::inflate(&fin, diag, Source(path));
156    }
157
158    static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(
159            const Source& source,
160            const void* data, size_t len,
161            IDiagnostics* diag) {
162        std::string errorStr;
163        ssize_t offset = getWrappedDataOffset(data, len, &errorStr);
164        if (offset < 0) {
165            diag->error(DiagMessage(source) << errorStr);
166            return {};
167        }
168
169        std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(
170                reinterpret_cast<const uint8_t*>(data) + static_cast<size_t>(offset),
171                len - static_cast<size_t>(offset),
172                diag,
173                source);
174        if (!xmlRes) {
175            return {};
176        }
177        return xmlRes;
178    }
179
180    static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
181                                                              const void* data, size_t len,
182                                                              IDiagnostics* diag) {
183        std::unique_ptr<ResourceFile> resFile = util::make_unique<ResourceFile>();
184        std::string errorStr;
185        ssize_t offset = unwrapFileExportHeader(data, len, resFile.get(), &errorStr);
186        if (offset < 0) {
187            diag->error(DiagMessage(source) << errorStr);
188            return {};
189        }
190        return resFile;
191    }
192
193    uint32_t getCompressionFlags(const StringPiece& str) {
194        if (mOptions.doNotCompressAnything) {
195            return 0;
196        }
197
198        for (const std::string& extension : mOptions.extensionsToNotCompress) {
199            if (util::stringEndsWith<char>(str, extension)) {
200                return 0;
201            }
202        }
203        return ArchiveEntry::kCompress;
204    }
205
206    bool copyFileToArchive(io::IFile* file, const std::string& outPath,
207                           IArchiveWriter* writer) {
208        std::unique_ptr<io::IData> data = file->openAsData();
209        if (!data) {
210            mContext.getDiagnostics()->error(DiagMessage(file->getSource())
211                                             << "failed to open file");
212            return false;
213        }
214
215        std::string errorStr;
216        ssize_t offset = getWrappedDataOffset(data->data(), data->size(), &errorStr);
217        if (offset < 0) {
218            mContext.getDiagnostics()->error(DiagMessage(file->getSource()) << errorStr);
219            return false;
220        }
221
222        if (writer->startEntry(outPath, getCompressionFlags(outPath))) {
223            if (writer->writeEntry(reinterpret_cast<const uint8_t*>(data->data()) + offset,
224                                   data->size() - static_cast<size_t>(offset))) {
225                if (writer->finishEntry()) {
226                    return true;
227                }
228            }
229        }
230
231        mContext.getDiagnostics()->error(
232                DiagMessage(mOptions.outputPath) << "failed to write file " << outPath);
233        return false;
234    }
235
236    Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) {
237        // Make sure the first element is <manifest> with package attribute.
238        if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
239            if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") {
240                if (xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package")) {
241                    return AppInfo{ packageAttr->value };
242                }
243            }
244        }
245        return {};
246    }
247
248    /**
249     * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked.
250     * Postcondition: ResourceTable has only one package left. All others are stripped, or there
251     *                is an error and false is returned.
252     */
253    bool verifyNoExternalPackages() {
254        auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool {
255            return mContext.getCompilationPackage() != pkg->name ||
256                    !pkg->id ||
257                    pkg->id.value() != mContext.getPackageId();
258        };
259
260        bool error = false;
261        for (const auto& package : mFinalTable.packages) {
262            if (isExtPackageFunc(package)) {
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                        ResourceNameRef resName(package->name, type->type, entry->name);
267
268                        for (const auto& configValue : entry->values) {
269                            // Special case the occurrence of an ID that is being generated for the
270                            // 'android' package. This is due to legacy reasons.
271                            if (valueCast<Id>(configValue.value.get()) &&
272                                    package->name == u"android") {
273                                mContext.getDiagnostics()->warn(
274                                        DiagMessage(configValue.value->getSource())
275                                        << "generated id '" << resName
276                                        << "' for external package '" << package->name
277                                        << "'");
278                            } else {
279                                mContext.getDiagnostics()->error(
280                                        DiagMessage(configValue.value->getSource())
281                                        << "defined resource '" << resName
282                                        << "' for external package '" << package->name
283                                        << "'");
284                                error = true;
285                            }
286                        }
287                    }
288                }
289            }
290        }
291
292        auto newEndIter = std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(),
293                                         isExtPackageFunc);
294        mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end());
295        return !error;
296    }
297
298    std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
299        if (mOptions.outputToDirectory) {
300            return createDirectoryArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath);
301        } else {
302            return createZipFileArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath);
303        }
304    }
305
306    bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
307        BigBuffer buffer(1024);
308        TableFlattenerOptions options = {};
309        options.useExtendedChunks = mOptions.staticLib;
310        TableFlattener flattener(&buffer, options);
311        if (!flattener.consume(&mContext, table)) {
312            return false;
313        }
314
315        if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
316            if (writer->writeEntry(buffer)) {
317                if (writer->finishEntry()) {
318                    return true;
319                }
320            }
321        }
322
323        mContext.getDiagnostics()->error(
324                DiagMessage() << "failed to write resources.arsc to archive");
325        return false;
326    }
327
328    bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
329                    IArchiveWriter* writer) {
330        BigBuffer buffer(1024);
331        XmlFlattenerOptions options = {};
332        options.keepRawValues = mOptions.staticLib;
333        options.maxSdkLevel = maxSdkLevel;
334        XmlFlattener flattener(&buffer, options);
335        if (!flattener.consume(&mContext, xmlRes)) {
336            return false;
337        }
338
339        if (writer->startEntry(path, ArchiveEntry::kCompress)) {
340            if (writer->writeEntry(buffer)) {
341                if (writer->finishEntry()) {
342                    return true;
343                }
344            }
345        }
346        mContext.getDiagnostics()->error(
347                DiagMessage() << "failed to write " << path << " to archive");
348        return false;
349    }
350
351    bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
352                       const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) {
353        if (!mOptions.generateJavaClassPath) {
354            return true;
355        }
356
357        std::string outPath = mOptions.generateJavaClassPath.value();
358        file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(outPackage)));
359        file::mkdirs(outPath);
360        file::appendPath(&outPath, "R.java");
361
362        std::ofstream fout(outPath, std::ofstream::binary);
363        if (!fout) {
364            mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
365            return false;
366        }
367
368        JavaClassGenerator generator(table, javaOptions);
369        if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
370            mContext.getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
371            return false;
372        }
373        return true;
374    }
375
376    bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
377        if (!mOptions.generateJavaClassPath) {
378            return true;
379        }
380
381        std::string outPath = mOptions.generateJavaClassPath.value();
382        file::appendPath(&outPath,
383                         file::packageToPath(util::utf16ToUtf8(mContext.getCompilationPackage())));
384        file::mkdirs(outPath);
385        file::appendPath(&outPath, "Manifest.java");
386
387        std::ofstream fout(outPath, std::ofstream::binary);
388        if (!fout) {
389            mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
390            return false;
391        }
392
393        ManifestClassGenerator generator;
394        if (!generator.generate(mContext.getDiagnostics(), mContext.getCompilationPackage(),
395                                manifestXml, &fout)) {
396            return false;
397        }
398
399        if (!fout) {
400            mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
401            return false;
402        }
403        return true;
404    }
405
406    bool writeProguardFile(const proguard::KeepSet& keepSet) {
407        if (!mOptions.generateProguardRulesPath) {
408            return true;
409        }
410
411        std::ofstream fout(mOptions.generateProguardRulesPath.value(), std::ofstream::binary);
412        if (!fout) {
413            mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
414            return false;
415        }
416
417        proguard::writeKeepSet(&fout, keepSet);
418        if (!fout) {
419            mContext.getDiagnostics()->error(DiagMessage() << strerror(errno));
420            return false;
421        }
422        return true;
423    }
424
425    bool mergeStaticLibrary(const std::string& input) {
426        // TODO(adamlesinski): Load resources from a static library APK and merge the table into
427        // TableMerger.
428        mContext.getDiagnostics()->warn(DiagMessage()
429                                        << "linking static libraries not supported yet: "
430                                        << input);
431        return true;
432    }
433
434    bool mergeResourceTable(io::IFile* file, bool override) {
435        if (mOptions.verbose) {
436            mContext.getDiagnostics()->note(DiagMessage() << "linking " << file->getSource());
437        }
438
439        std::unique_ptr<io::IData> data = file->openAsData();
440        if (!data) {
441            mContext.getDiagnostics()->error(DiagMessage(file->getSource())
442                                             << "failed to open file");
443            return false;
444        }
445
446        std::unique_ptr<ResourceTable> table = loadTable(file->getSource(), data->data(),
447                                                         data->size());
448        if (!table) {
449            return false;
450        }
451
452        bool result = false;
453        if (override) {
454            result = mTableMerger->mergeOverlay(file->getSource(), table.get());
455        } else {
456            result = mTableMerger->merge(file->getSource(), table.get());
457        }
458        return result;
459    }
460
461    bool mergeCompiledFile(io::IFile* file, std::unique_ptr<ResourceFile> fileDesc, bool overlay) {
462        if (mOptions.verbose) {
463            mContext.getDiagnostics()->note(DiagMessage() << "adding " << file->getSource());
464        }
465
466        bool result = false;
467        if (overlay) {
468            result = mTableMerger->mergeFileOverlay(*fileDesc, file);
469        } else {
470            result = mTableMerger->mergeFile(*fileDesc, file);
471        }
472
473        if (!result) {
474            return false;
475        }
476
477        // Add the exports of this file to the table.
478        for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
479            if (exportedSymbol.name.package.empty()) {
480                exportedSymbol.name.package = mContext.getCompilationPackage().toString();
481            }
482
483            ResourceNameRef resName = exportedSymbol.name;
484
485            Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(
486                    exportedSymbol.name);
487            if (mangledName) {
488                resName = mangledName.value();
489            }
490
491            std::unique_ptr<Id> id = util::make_unique<Id>();
492            id->setSource(fileDesc->source.withLine(exportedSymbol.line));
493            bool result = mFinalTable.addResourceAllowMangled(resName, {}, std::move(id),
494                                                              mContext.getDiagnostics());
495            if (!result) {
496                return false;
497            }
498        }
499        return true;
500    }
501
502    /**
503     * Creates an io::IFileCollection from the ZIP archive and processes the files within.
504     */
505    bool mergeArchive(const std::string& input, bool override) {
506        std::string errorStr;
507        std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
508                input, &errorStr);
509        if (!collection) {
510            mContext.getDiagnostics()->error(DiagMessage(input) << errorStr);
511            return false;
512        }
513
514        bool error = false;
515        for (auto iter = collection->iterator(); iter->hasNext(); ) {
516            if (!processFile(iter->next(), override)) {
517                error = true;
518            }
519        }
520
521        // Make sure to move the collection into the set of IFileCollections.
522        mCollections.push_back(std::move(collection));
523        return !error;
524    }
525
526    bool processFile(const std::string& path, bool override) {
527        if (util::stringEndsWith<char>(path, ".flata") ||
528                util::stringEndsWith<char>(path, ".jar") ||
529                util::stringEndsWith<char>(path, ".jack") ||
530                util::stringEndsWith<char>(path, ".zip")) {
531            return mergeArchive(path, override);
532        }
533
534        io::IFile* file = mFileCollection->insertFile(path);
535        return processFile(file, override);
536    }
537
538    bool processFile(io::IFile* file, bool override) {
539        const Source& src = file->getSource();
540        if (util::stringEndsWith<char>(src.path, ".arsc.flat")) {
541            return mergeResourceTable(file, override);
542        } else if (util::stringEndsWith<char>(src.path, ".flat")){
543            // Try opening the file and looking for an Export header.
544            std::unique_ptr<io::IData> data = file->openAsData();
545            if (!data) {
546                mContext.getDiagnostics()->error(DiagMessage(src) << "failed to open");
547                return false;
548            }
549
550            std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader(
551                    src, data->data(), data->size(), mContext.getDiagnostics());
552            if (resourceFile) {
553                return mergeCompiledFile(file, std::move(resourceFile), override);
554            }
555
556            return false;
557        }
558
559        // Ignore non .flat files. This could be classes.dex or something else that happens
560        // to be in an archive.
561        return true;
562    }
563
564    int run(const std::vector<std::string>& inputFiles) {
565        // Load the AndroidManifest.xml
566        std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
567                                                                mContext.getDiagnostics());
568        if (!manifestXml) {
569            return 1;
570        }
571
572        if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) {
573            mContext.mCompilationPackage = maybeAppInfo.value().package;
574        } else {
575            mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
576                                             << "no package specified in <manifest> tag");
577            return 1;
578        }
579
580        if (!util::isJavaPackageName(mContext.mCompilationPackage)) {
581            mContext.getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
582                                             << "invalid package name '"
583                                             << mContext.mCompilationPackage
584                                             << "'");
585            return 1;
586        }
587
588        mContext.mNameMangler = util::make_unique<NameMangler>(
589                NameManglerPolicy{ mContext.mCompilationPackage });
590
591        if (mContext.mCompilationPackage == u"android") {
592            mContext.mPackageId = 0x01;
593        } else {
594            mContext.mPackageId = 0x7f;
595        }
596
597        mContext.mSymbols = createSymbolTableFromIncludePaths();
598        if (!mContext.mSymbols) {
599            return 1;
600        }
601
602        TableMergerOptions tableMergerOptions;
603        tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
604        mTableMerger = util::make_unique<TableMerger>(&mContext, &mFinalTable, tableMergerOptions);
605
606        if (mOptions.verbose) {
607            mContext.getDiagnostics()->note(
608                    DiagMessage() << "linking package '" << mContext.mCompilationPackage << "' "
609                                  << "with package ID " << std::hex << (int) mContext.mPackageId);
610        }
611
612
613        for (const std::string& input : inputFiles) {
614            if (!processFile(input, false)) {
615                mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input");
616                return 1;
617            }
618        }
619
620        for (const std::string& input : mOptions.overlayFiles) {
621            if (!processFile(input, true)) {
622                mContext.getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
623                return 1;
624            }
625        }
626
627        if (!verifyNoExternalPackages()) {
628            return 1;
629        }
630
631        if (!mOptions.staticLib) {
632            PrivateAttributeMover mover;
633            if (!mover.consume(&mContext, &mFinalTable)) {
634                mContext.getDiagnostics()->error(
635                        DiagMessage() << "failed moving private attributes");
636                return 1;
637            }
638        }
639
640        {
641            IdAssigner idAssigner;
642            if (!idAssigner.consume(&mContext, &mFinalTable)) {
643                mContext.getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
644                return 1;
645            }
646        }
647
648        mContext.mNameMangler = util::make_unique<NameMangler>(NameManglerPolicy{
649                mContext.mCompilationPackage, mTableMerger->getMergedPackages() });
650        mContext.mSymbols = JoinedSymbolTableBuilder()
651                .addSymbolTable(util::make_unique<SymbolTableWrapper>(&mFinalTable))
652                .addSymbolTable(std::move(mContext.mSymbols))
653                .build();
654
655        {
656            ReferenceLinker linker;
657            if (!linker.consume(&mContext, &mFinalTable)) {
658                mContext.getDiagnostics()->error(DiagMessage() << "failed linking references");
659                return 1;
660            }
661        }
662
663        proguard::KeepSet proguardKeepSet;
664
665        std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter();
666        if (!archiveWriter) {
667            mContext.getDiagnostics()->error(DiagMessage() << "failed to create archive");
668            return 1;
669        }
670
671        bool error = false;
672        {
673            ManifestFixer manifestFixer(mOptions.manifestFixerOptions);
674            if (!manifestFixer.consume(&mContext, manifestXml.get())) {
675                error = true;
676            }
677
678            // AndroidManifest.xml has no resource name, but the CallSite is built from the name
679            // (aka, which package the AndroidManifest.xml is coming from).
680            // So we give it a package name so it can see local resources.
681            manifestXml->file.name.package = mContext.getCompilationPackage().toString();
682
683            XmlReferenceLinker manifestLinker;
684            if (manifestLinker.consume(&mContext, manifestXml.get())) {
685                if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
686                                                               manifestXml.get(),
687                                                               &proguardKeepSet)) {
688                    error = true;
689                }
690
691                if (mOptions.generateJavaClassPath) {
692                    if (!writeManifestJavaFile(manifestXml.get())) {
693                        error = true;
694                    }
695                }
696
697                if (!flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
698                                archiveWriter.get())) {
699                    error = true;
700                }
701            } else {
702                error = true;
703            }
704        }
705
706        if (error) {
707            mContext.getDiagnostics()->error(DiagMessage() << "failed processing manifest");
708            return 1;
709        }
710
711        for (auto& mergeEntry : mTableMerger->getFilesToMerge()) {
712            const ResourceKeyRef& key = mergeEntry.first;
713            const FileToMerge& fileToMerge = mergeEntry.second;
714
715            const StringPiece path = fileToMerge.file->getSource().path;
716
717            if (key.name.type != ResourceType::kRaw &&
718                    (util::stringEndsWith<char>(path, ".xml.flat") ||
719                    util::stringEndsWith<char>(path, ".xml"))) {
720                if (mOptions.verbose) {
721                    mContext.getDiagnostics()->note(DiagMessage() << "linking " << path);
722                }
723
724                io::IFile* file = fileToMerge.file;
725                std::unique_ptr<io::IData> data = file->openAsData();
726                if (!data) {
727                    mContext.getDiagnostics()->error(DiagMessage(file->getSource())
728                                                     << "failed to open file");
729                    return 1;
730                }
731
732                std::unique_ptr<xml::XmlResource> xmlRes;
733                if (util::stringEndsWith<char>(path, ".flat")) {
734                    xmlRes = loadBinaryXmlSkipFileExport(file->getSource(),
735                                                         data->data(), data->size(),
736                                                         mContext.getDiagnostics());
737                } else {
738                    xmlRes = xml::inflate(data->data(), data->size(), mContext.getDiagnostics(),
739                                          file->getSource());
740                }
741
742                if (!xmlRes) {
743                    return 1;
744                }
745
746                // Create the file description header.
747                xmlRes->file = ResourceFile{
748                        key.name.toResourceName(),
749                        key.config,
750                        fileToMerge.originalSource,
751                };
752
753                XmlReferenceLinker xmlLinker;
754                if (xmlLinker.consume(&mContext, xmlRes.get())) {
755                    if (!proguard::collectProguardRules(xmlRes->file.source, xmlRes.get(),
756                                                        &proguardKeepSet)) {
757                        error = true;
758                    }
759
760                    Maybe<size_t> maxSdkLevel;
761                    if (!mOptions.noAutoVersion) {
762                        maxSdkLevel = std::max<size_t>(xmlRes->file.config.sdkVersion, 1u);
763                    }
764
765                    if (!flattenXml(xmlRes.get(), fileToMerge.dstPath, maxSdkLevel,
766                                    archiveWriter.get())) {
767                        error = true;
768                    }
769
770                    if (!mOptions.noAutoVersion) {
771                        Maybe<ResourceTable::SearchResult> result = mFinalTable.findResource(
772                                xmlRes->file.name);
773                        for (int sdkLevel : xmlLinker.getSdkLevels()) {
774                            if (sdkLevel > xmlRes->file.config.sdkVersion &&
775                                    shouldGenerateVersionedResource(result.value().entry,
776                                                                    xmlRes->file.config,
777                                                                    sdkLevel)) {
778                                xmlRes->file.config.sdkVersion = sdkLevel;
779
780                                std::string genResourcePath = ResourceUtils::buildResourceFileName(
781                                        xmlRes->file, mContext.getNameMangler());
782
783                                bool added = mFinalTable.addFileReference(
784                                        xmlRes->file.name,
785                                        xmlRes->file.config,
786                                        xmlRes->file.source,
787                                        util::utf8ToUtf16(genResourcePath),
788                                        mContext.getDiagnostics());
789                                if (!added) {
790                                    error = true;
791                                    continue;
792                                }
793
794                                if (!flattenXml(xmlRes.get(), genResourcePath, sdkLevel,
795                                                archiveWriter.get())) {
796                                    error = true;
797                                }
798                            }
799                        }
800                    }
801
802                } else {
803                    error = true;
804                }
805            } else {
806                if (mOptions.verbose) {
807                    mContext.getDiagnostics()->note(DiagMessage() << "copying " << path);
808                }
809
810                if (!copyFileToArchive(fileToMerge.file, fileToMerge.dstPath,
811                                       archiveWriter.get())) {
812                    error = true;
813                }
814            }
815        }
816
817        if (error) {
818            mContext.getDiagnostics()->error(DiagMessage() << "failed linking file resources");
819            return 1;
820        }
821
822        if (!mOptions.noAutoVersion) {
823            AutoVersioner versioner;
824            if (!versioner.consume(&mContext, &mFinalTable)) {
825                mContext.getDiagnostics()->error(DiagMessage() << "failed versioning styles");
826                return 1;
827            }
828        }
829
830        if (!flattenTable(&mFinalTable, archiveWriter.get())) {
831            mContext.getDiagnostics()->error(DiagMessage() << "failed to write resources.arsc");
832            return 1;
833        }
834
835        if (mOptions.generateJavaClassPath) {
836            JavaClassGeneratorOptions options;
837            options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
838
839            if (mOptions.staticLib || mOptions.generateNonFinalIds) {
840                options.useFinal = false;
841            }
842
843            const StringPiece16 actualPackage = mContext.getCompilationPackage();
844            StringPiece16 outputPackage = mContext.getCompilationPackage();
845            if (mOptions.customJavaPackage) {
846                // Override the output java package to the custom one.
847                outputPackage = mOptions.customJavaPackage.value();
848            }
849
850            if (mOptions.privateSymbols) {
851                // If we defined a private symbols package, we only emit Public symbols
852                // to the original package, and private and public symbols to the private package.
853
854                options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
855                if (!writeJavaFile(&mFinalTable, mContext.getCompilationPackage(),
856                                   outputPackage, options)) {
857                    return 1;
858                }
859
860                options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
861                outputPackage = mOptions.privateSymbols.value();
862            }
863
864            if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
865                return 1;
866            }
867
868            for (const std::u16string& extraPackage : mOptions.extraJavaPackages) {
869                if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) {
870                    return 1;
871                }
872            }
873        }
874
875        if (mOptions.generateProguardRulesPath) {
876            if (!writeProguardFile(proguardKeepSet)) {
877                return 1;
878            }
879        }
880
881        if (mOptions.verbose) {
882            Debug::printTable(&mFinalTable);
883        }
884        return 0;
885    }
886
887private:
888    LinkOptions mOptions;
889    LinkContext mContext;
890    ResourceTable mFinalTable;
891
892    ResourceTable mLocalFileTable;
893    std::unique_ptr<TableMerger> mTableMerger;
894
895    // A pointer to the FileCollection representing the filesystem (not archives).
896    io::FileCollection* mFileCollection;
897
898    // A vector of IFileCollections. This is mainly here to keep ownership of the collections.
899    std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
900};
901
902int link(const std::vector<StringPiece>& args) {
903    LinkOptions options;
904    Maybe<std::string> privateSymbolsPackage;
905    Maybe<std::string> minSdkVersion, targetSdkVersion;
906    Maybe<std::string> renameManifestPackage, renameInstrumentationTargetPackage;
907    Maybe<std::string> versionCode, versionName;
908    Maybe<std::string> customJavaPackage;
909    std::vector<std::string> extraJavaPackages;
910    bool legacyXFlag = false;
911    bool requireLocalization = false;
912    Flags flags = Flags()
913            .requiredFlag("-o", "Output path", &options.outputPath)
914            .requiredFlag("--manifest", "Path to the Android manifest to build",
915                          &options.manifestPath)
916            .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
917            .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n"
918                              "The last conflicting resource given takes precedence.",
919                              &options.overlayFiles)
920            .optionalFlag("--java", "Directory in which to generate R.java",
921                          &options.generateJavaClassPath)
922            .optionalFlag("--proguard", "Output file for generated Proguard rules",
923                          &options.generateProguardRulesPath)
924            .optionalSwitch("--no-auto-version",
925                            "Disables automatic style and layout SDK versioning",
926                            &options.noAutoVersion)
927            .optionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01",
928                            &legacyXFlag)
929            .optionalSwitch("-z", "Require localization of strings marked 'suggested'",
930                            &requireLocalization)
931            .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
932                            "by -o",
933                            &options.outputToDirectory)
934            .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
935                          "AndroidManifest.xml", &minSdkVersion)
936            .optionalFlag("--target-sdk-version", "Default target SDK version to use for "
937                          "AndroidManifest.xml", &targetSdkVersion)
938            .optionalFlag("--version-code", "Version code (integer) to inject into the "
939                          "AndroidManifest.xml if none is present", &versionCode)
940            .optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml "
941                          "if none is present", &versionName)
942            .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
943            .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n"
944                            "This is implied when --static-lib is specified.",
945                            &options.generateNonFinalIds)
946            .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
947                          "private symbols.\n"
948                          "If not specified, public and private symbols will use the application's "
949                          "package name", &privateSymbolsPackage)
950            .optionalFlag("--custom-package", "Custom Java package under which to generate R.java",
951                          &customJavaPackage)
952            .optionalFlagList("--extra-packages", "Generate the same R.java but with different "
953                              "package names", &extraJavaPackages)
954            .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in "
955                            "overlays without <add-resource> tags", &options.autoAddOverlay)
956            .optionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml",
957                          &renameManifestPackage)
958            .optionalFlag("--rename-instrumentation-target-package",
959                          "Changes the name of the target package for instrumentation. Most useful "
960                          "when used\nin conjunction with --rename-manifest-package",
961                          &renameInstrumentationTargetPackage)
962            .optionalFlagList("-0", "File extensions not to compress",
963                              &options.extensionsToNotCompress)
964            .optionalSwitch("-v", "Enables verbose logging", &options.verbose);
965
966    if (!flags.parse("aapt2 link", args, &std::cerr)) {
967        return 1;
968    }
969
970    if (privateSymbolsPackage) {
971        options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
972    }
973
974    if (minSdkVersion) {
975        options.manifestFixerOptions.minSdkVersionDefault =
976                util::utf8ToUtf16(minSdkVersion.value());
977    }
978
979    if (targetSdkVersion) {
980        options.manifestFixerOptions.targetSdkVersionDefault =
981                util::utf8ToUtf16(targetSdkVersion.value());
982    }
983
984    if (renameManifestPackage) {
985        options.manifestFixerOptions.renameManifestPackage =
986                util::utf8ToUtf16(renameManifestPackage.value());
987    }
988
989    if (renameInstrumentationTargetPackage) {
990        options.manifestFixerOptions.renameInstrumentationTargetPackage =
991                util::utf8ToUtf16(renameInstrumentationTargetPackage.value());
992    }
993
994    if (versionCode) {
995        options.manifestFixerOptions.versionCodeDefault = util::utf8ToUtf16(versionCode.value());
996    }
997
998    if (versionName) {
999        options.manifestFixerOptions.versionNameDefault = util::utf8ToUtf16(versionName.value());
1000    }
1001
1002    if (customJavaPackage) {
1003        options.customJavaPackage = util::utf8ToUtf16(customJavaPackage.value());
1004    }
1005
1006    // Populate the set of extra packages for which to generate R.java.
1007    for (std::string& extraPackage : extraJavaPackages) {
1008        // A given package can actually be a colon separated list of packages.
1009        for (StringPiece package : util::split(extraPackage, ':')) {
1010            options.extraJavaPackages.insert(util::utf8ToUtf16(package));
1011        }
1012    }
1013
1014    LinkCommand cmd(options);
1015    return cmd.run(flags.getArgs());
1016}
1017
1018} // namespace aapt
1019