• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "Locale.h"
21 #include "NameMangler.h"
22 #include "ResourceUtils.h"
23 #include "compile/IdAssigner.h"
24 #include "filter/ConfigFilter.h"
25 #include "flatten/Archive.h"
26 #include "flatten/TableFlattener.h"
27 #include "flatten/XmlFlattener.h"
28 #include "io/FileSystem.h"
29 #include "io/ZipArchive.h"
30 #include "java/JavaClassGenerator.h"
31 #include "java/ManifestClassGenerator.h"
32 #include "java/ProguardRules.h"
33 #include "link/Linkers.h"
34 #include "link/ProductFilter.h"
35 #include "link/ReferenceLinker.h"
36 #include "link/ManifestFixer.h"
37 #include "link/TableMerger.h"
38 #include "process/IResourceTableConsumer.h"
39 #include "process/SymbolTable.h"
40 #include "proto/ProtoSerialize.h"
41 #include "split/TableSplitter.h"
42 #include "unflatten/BinaryResourceParser.h"
43 #include "util/Files.h"
44 #include "util/StringPiece.h"
45 #include "xml/XmlDom.h"
46 
47 #include <google/protobuf/io/coded_stream.h>
48 
49 #include <fstream>
50 #include <sys/stat.h>
51 #include <vector>
52 
53 namespace aapt {
54 
55 struct LinkOptions {
56     std::string outputPath;
57     std::string manifestPath;
58     std::vector<std::string> includePaths;
59     std::vector<std::string> overlayFiles;
60     Maybe<std::string> generateJavaClassPath;
61     Maybe<std::u16string> customJavaPackage;
62     std::set<std::u16string> extraJavaPackages;
63     Maybe<std::string> generateProguardRulesPath;
64     bool noAutoVersion = false;
65     bool noVersionVectors = false;
66     bool staticLib = false;
67     bool noStaticLibPackages = false;
68     bool generateNonFinalIds = false;
69     std::vector<std::string> javadocAnnotations;
70     bool outputToDirectory = false;
71     bool autoAddOverlay = false;
72     bool doNotCompressAnything = false;
73     std::vector<std::string> extensionsToNotCompress;
74     Maybe<std::u16string> privateSymbols;
75     ManifestFixerOptions manifestFixerOptions;
76     std::unordered_set<std::string> products;
77     TableSplitterOptions tableSplitterOptions;
78 };
79 
80 class LinkContext : public IAaptContext {
81 public:
LinkContext()82     LinkContext() : mNameMangler({}) {
83     }
84 
getDiagnostics()85     IDiagnostics* getDiagnostics() override {
86         return &mDiagnostics;
87     }
88 
getNameMangler()89     NameMangler* getNameMangler() override {
90         return &mNameMangler;
91     }
92 
setNameManglerPolicy(const NameManglerPolicy & policy)93     void setNameManglerPolicy(const NameManglerPolicy& policy) {
94         mNameMangler = NameMangler(policy);
95     }
96 
getCompilationPackage()97     const std::u16string& getCompilationPackage() override {
98         return mCompilationPackage;
99     }
100 
setCompilationPackage(const StringPiece16 & packageName)101     void setCompilationPackage(const StringPiece16& packageName) {
102         mCompilationPackage = packageName.toString();
103     }
104 
getPackageId()105     uint8_t getPackageId() override {
106         return mPackageId;
107     }
108 
setPackageId(uint8_t id)109     void setPackageId(uint8_t id) {
110         mPackageId = id;
111     }
112 
getExternalSymbols()113     SymbolTable* getExternalSymbols() override {
114         return &mSymbols;
115     }
116 
verbose()117     bool verbose() override {
118         return mVerbose;
119     }
120 
setVerbose(bool val)121     void setVerbose(bool val) {
122         mVerbose = val;
123     }
124 
125 private:
126     StdErrDiagnostics mDiagnostics;
127     NameMangler mNameMangler;
128     std::u16string mCompilationPackage;
129     uint8_t mPackageId = 0x0;
130     SymbolTable mSymbols;
131     bool mVerbose = false;
132 };
133 
copyFileToArchive(io::IFile * file,const std::string & outPath,uint32_t compressionFlags,IArchiveWriter * writer,IAaptContext * context)134 static bool copyFileToArchive(io::IFile* file, const std::string& outPath,
135                               uint32_t compressionFlags,
136                               IArchiveWriter* writer, IAaptContext* context) {
137     std::unique_ptr<io::IData> data = file->openAsData();
138     if (!data) {
139         context->getDiagnostics()->error(DiagMessage(file->getSource())
140                                          << "failed to open file");
141         return false;
142     }
143 
144     const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data());
145     size_t bufferSize = data->size();
146 
147     // If the file ends with .flat, we must strip off the CompiledFileHeader from it.
148     if (util::stringEndsWith<char>(file->getSource().path, ".flat")) {
149         CompiledFileInputStream inputStream(data->data(), data->size());
150         if (!inputStream.CompiledFile()) {
151             context->getDiagnostics()->error(DiagMessage(file->getSource())
152                                              << "invalid compiled file header");
153             return false;
154         }
155         buffer = reinterpret_cast<const uint8_t*>(inputStream.data());
156         bufferSize = inputStream.size();
157     }
158 
159     if (context->verbose()) {
160         context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive");
161     }
162 
163     if (writer->startEntry(outPath, compressionFlags)) {
164         if (writer->writeEntry(buffer, bufferSize)) {
165             if (writer->finishEntry()) {
166                 return true;
167             }
168         }
169     }
170 
171     context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath);
172     return false;
173 }
174 
flattenXml(xml::XmlResource * xmlRes,const StringPiece & path,Maybe<size_t> maxSdkLevel,bool keepRawValues,IArchiveWriter * writer,IAaptContext * context)175 static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
176                        bool keepRawValues, IArchiveWriter* writer, IAaptContext* context) {
177     BigBuffer buffer(1024);
178     XmlFlattenerOptions options = {};
179     options.keepRawValues = keepRawValues;
180     options.maxSdkLevel = maxSdkLevel;
181     XmlFlattener flattener(&buffer, options);
182     if (!flattener.consume(context, xmlRes)) {
183         return false;
184     }
185 
186     if (context->verbose()) {
187         DiagMessage msg;
188         msg << "writing " << path << " to archive";
189         if (maxSdkLevel) {
190             msg << " maxSdkLevel=" << maxSdkLevel.value() << " keepRawValues=" << keepRawValues;
191         }
192         context->getDiagnostics()->note(msg);
193     }
194 
195     if (writer->startEntry(path, ArchiveEntry::kCompress)) {
196         if (writer->writeEntry(buffer)) {
197             if (writer->finishEntry()) {
198                 return true;
199             }
200         }
201     }
202     context->getDiagnostics()->error(DiagMessage() << "failed to write " << path << " to archive");
203     return false;
204 }
205 
206 /*static std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len,
207                                                 IDiagnostics* diag) {
208     std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
209     BinaryResourceParser parser(diag, table.get(), source, data, len);
210     if (!parser.parse()) {
211         return {};
212     }
213     return table;
214 }*/
215 
loadTableFromPb(const Source & source,const void * data,size_t len,IDiagnostics * diag)216 static std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
217                                                       const void* data, size_t len,
218                                                       IDiagnostics* diag) {
219     pb::ResourceTable pbTable;
220     if (!pbTable.ParseFromArray(data, len)) {
221         diag->error(DiagMessage(source) << "invalid compiled table");
222         return {};
223     }
224 
225     std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source, diag);
226     if (!table) {
227         return {};
228     }
229     return table;
230 }
231 
232 /**
233  * Inflates an XML file from the source path.
234  */
loadXml(const std::string & path,IDiagnostics * diag)235 static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
236     std::ifstream fin(path, std::ifstream::binary);
237     if (!fin) {
238         diag->error(DiagMessage(path) << strerror(errno));
239         return {};
240     }
241     return xml::inflate(&fin, diag, Source(path));
242 }
243 
loadBinaryXmlSkipFileExport(const Source & source,const void * data,size_t len,IDiagnostics * diag)244 static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(const Source& source,
245                                                                      const void* data, size_t len,
246                                                                      IDiagnostics* diag) {
247     CompiledFileInputStream inputStream(data, len);
248     if (!inputStream.CompiledFile()) {
249         diag->error(DiagMessage(source) << "invalid compiled file header");
250         return {};
251     }
252 
253     const uint8_t* xmlData = reinterpret_cast<const uint8_t*>(inputStream.data());
254     const size_t xmlDataLen = inputStream.size();
255 
256     std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(xmlData, xmlDataLen, diag, source);
257     if (!xmlRes) {
258         return {};
259     }
260     return xmlRes;
261 }
262 
loadFileExportHeader(const Source & source,const void * data,size_t len,IDiagnostics * diag)263 static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
264                                                           const void* data, size_t len,
265                                                           IDiagnostics* diag) {
266     CompiledFileInputStream inputStream(data, len);
267     const pb::CompiledFile* pbFile = inputStream.CompiledFile();
268     if (!pbFile) {
269         diag->error(DiagMessage(source) << "invalid compiled file header");
270         return {};
271     }
272 
273     std::unique_ptr<ResourceFile> resFile = deserializeCompiledFileFromPb(*pbFile, source, diag);
274     if (!resFile) {
275         return {};
276     }
277     return resFile;
278 }
279 
280 struct ResourceFileFlattenerOptions {
281     bool noAutoVersion = false;
282     bool noVersionVectors = false;
283     bool keepRawValues = false;
284     bool doNotCompressAnything = false;
285     std::vector<std::string> extensionsToNotCompress;
286 };
287 
288 class ResourceFileFlattener {
289 public:
ResourceFileFlattener(const ResourceFileFlattenerOptions & options,IAaptContext * context,proguard::KeepSet * keepSet)290     ResourceFileFlattener(const ResourceFileFlattenerOptions& options,
291                           IAaptContext* context, proguard::KeepSet* keepSet) :
292             mOptions(options), mContext(context), mKeepSet(keepSet) {
293     }
294 
295     bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter);
296 
297 private:
298     struct FileOperation {
299         io::IFile* fileToCopy;
300         std::unique_ptr<xml::XmlResource> xmlToFlatten;
301         std::string dstPath;
302         bool skipVersion = false;
303     };
304 
305     uint32_t getCompressionFlags(const StringPiece& str);
306 
307     bool linkAndVersionXmlFile(const ResourceEntry* entry, const ResourceFile& fileDesc,
308                                io::IFile* file, ResourceTable* table, FileOperation* outFileOp);
309 
310     ResourceFileFlattenerOptions mOptions;
311     IAaptContext* mContext;
312     proguard::KeepSet* mKeepSet;
313 };
314 
getCompressionFlags(const StringPiece & str)315 uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) {
316     if (mOptions.doNotCompressAnything) {
317         return 0;
318     }
319 
320     for (const std::string& extension : mOptions.extensionsToNotCompress) {
321         if (util::stringEndsWith<char>(str, extension)) {
322             return 0;
323         }
324     }
325     return ArchiveEntry::kCompress;
326 }
327 
linkAndVersionXmlFile(const ResourceEntry * entry,const ResourceFile & fileDesc,io::IFile * file,ResourceTable * table,FileOperation * outFileOp)328 bool ResourceFileFlattener::linkAndVersionXmlFile(const ResourceEntry* entry,
329                                                   const ResourceFile& fileDesc,
330                                                   io::IFile* file,
331                                                   ResourceTable* table,
332                                                   FileOperation* outFileOp) {
333     const StringPiece srcPath = file->getSource().path;
334     if (mContext->verbose()) {
335         mContext->getDiagnostics()->note(DiagMessage() << "linking " << srcPath);
336     }
337 
338     std::unique_ptr<io::IData> data = file->openAsData();
339     if (!data) {
340         mContext->getDiagnostics()->error(DiagMessage(file->getSource()) << "failed to open file");
341         return false;
342     }
343 
344     if (util::stringEndsWith<char>(srcPath, ".flat")) {
345         outFileOp->xmlToFlatten = loadBinaryXmlSkipFileExport(file->getSource(),
346                                                               data->data(), data->size(),
347                                                               mContext->getDiagnostics());
348     } else {
349         outFileOp->xmlToFlatten = xml::inflate(data->data(), data->size(),
350                                                mContext->getDiagnostics(),
351                                                file->getSource());
352     }
353 
354     if (!outFileOp->xmlToFlatten) {
355         return false;
356     }
357 
358     // Copy the the file description header.
359     outFileOp->xmlToFlatten->file = fileDesc;
360 
361     XmlReferenceLinker xmlLinker;
362     if (!xmlLinker.consume(mContext, outFileOp->xmlToFlatten.get())) {
363         return false;
364     }
365 
366     if (!proguard::collectProguardRules(outFileOp->xmlToFlatten->file.source,
367                                         outFileOp->xmlToFlatten.get(), mKeepSet)) {
368         return false;
369     }
370 
371     if (!mOptions.noAutoVersion) {
372         if (mOptions.noVersionVectors) {
373             // Skip this if it is a vector or animated-vector.
374             xml::Element* el = xml::findRootElement(outFileOp->xmlToFlatten.get());
375             if (el && el->namespaceUri.empty()) {
376                 if (el->name == u"vector" || el->name == u"animated-vector") {
377                     // We are NOT going to version this file.
378                     outFileOp->skipVersion = true;
379                     return true;
380                 }
381             }
382         }
383 
384         // Find the first SDK level used that is higher than this defined config and
385         // not superseded by a lower or equal SDK level resource.
386         for (int sdkLevel : xmlLinker.getSdkLevels()) {
387             if (sdkLevel > outFileOp->xmlToFlatten->file.config.sdkVersion) {
388                 if (!shouldGenerateVersionedResource(entry, outFileOp->xmlToFlatten->file.config,
389                                                      sdkLevel)) {
390                     // If we shouldn't generate a versioned resource, stop checking.
391                     break;
392                 }
393 
394                 ResourceFile versionedFileDesc = outFileOp->xmlToFlatten->file;
395                 versionedFileDesc.config.sdkVersion = (uint16_t) sdkLevel;
396 
397                 if (mContext->verbose()) {
398                     mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source)
399                                                      << "auto-versioning resource from config '"
400                                                      << outFileOp->xmlToFlatten->file.config
401                                                      << "' -> '"
402                                                      << versionedFileDesc.config << "'");
403                 }
404 
405                 std::u16string genPath = util::utf8ToUtf16(ResourceUtils::buildResourceFileName(
406                         versionedFileDesc, mContext->getNameMangler()));
407 
408                 bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name,
409                                                                  versionedFileDesc.config,
410                                                                  versionedFileDesc.source,
411                                                                  genPath,
412                                                                  file,
413                                                                  mContext->getDiagnostics());
414                 if (!added) {
415                     return false;
416                 }
417                 break;
418             }
419         }
420     }
421     return true;
422 }
423 
424 /**
425  * Do not insert or remove any resources while executing in this function. It will
426  * corrupt the iteration order.
427  */
flatten(ResourceTable * table,IArchiveWriter * archiveWriter)428 bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiveWriter) {
429     bool error = false;
430     std::map<std::pair<ConfigDescription, StringPiece16>, FileOperation> configSortedFiles;
431 
432     for (auto& pkg : table->packages) {
433         for (auto& type : pkg->types) {
434             // Sort by config and name, so that we get better locality in the zip file.
435             configSortedFiles.clear();
436             for (auto& entry : type->entries) {
437                 // Iterate via indices because auto generated values can be inserted ahead of
438                 // the value being processed.
439                 for (size_t i = 0; i < entry->values.size(); i++) {
440                     ResourceConfigValue* configValue = entry->values[i].get();
441 
442                     FileReference* fileRef = valueCast<FileReference>(configValue->value.get());
443                     if (!fileRef) {
444                         continue;
445                     }
446 
447                     io::IFile* file = fileRef->file;
448                     if (!file) {
449                         mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource())
450                                                           << "file not found");
451                         return false;
452                     }
453 
454                     FileOperation fileOp;
455                     fileOp.dstPath = util::utf16ToUtf8(*fileRef->path);
456 
457                     const StringPiece srcPath = file->getSource().path;
458                     if (type->type != ResourceType::kRaw &&
459                             (util::stringEndsWith<char>(srcPath, ".xml.flat") ||
460                             util::stringEndsWith<char>(srcPath, ".xml"))) {
461                         ResourceFile fileDesc;
462                         fileDesc.config = configValue->config;
463                         fileDesc.name = ResourceName(pkg->name, type->type, entry->name);
464                         fileDesc.source = fileRef->getSource();
465                         if (!linkAndVersionXmlFile(entry.get(), fileDesc, file, table, &fileOp)) {
466                             error = true;
467                             continue;
468                         }
469 
470                     } else {
471                         fileOp.fileToCopy = file;
472                     }
473 
474                     // NOTE(adamlesinski): Explicitly construct a StringPiece16 here, or else
475                     // we end up copying the string in the std::make_pair() method, then creating
476                     // a StringPiece16 from the copy, which would cause us to end up referencing
477                     // garbage in the map.
478                     const StringPiece16 entryName(entry->name);
479                     configSortedFiles[std::make_pair(configValue->config, entryName)] =
480                                       std::move(fileOp);
481                 }
482             }
483 
484             if (error) {
485                 return false;
486             }
487 
488             // Now flatten the sorted values.
489             for (auto& mapEntry : configSortedFiles) {
490                 const ConfigDescription& config = mapEntry.first.first;
491                 const FileOperation& fileOp = mapEntry.second;
492 
493                 if (fileOp.xmlToFlatten) {
494                     Maybe<size_t> maxSdkLevel;
495                     if (!mOptions.noAutoVersion && !fileOp.skipVersion) {
496                         maxSdkLevel = std::max<size_t>(config.sdkVersion, 1u);
497                     }
498 
499                     bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel,
500                                              mOptions.keepRawValues,
501                                              archiveWriter, mContext);
502                     if (!result) {
503                         error = true;
504                     }
505                 } else {
506                     bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath,
507                                                     getCompressionFlags(fileOp.dstPath),
508                                                     archiveWriter, mContext);
509                     if (!result) {
510                         error = true;
511                     }
512                 }
513             }
514         }
515     }
516     return !error;
517 }
518 
519 class LinkCommand {
520 public:
LinkCommand(LinkContext * context,const LinkOptions & options)521     LinkCommand(LinkContext* context, const LinkOptions& options) :
522             mOptions(options), mContext(context), mFinalTable(),
523             mFileCollection(util::make_unique<io::FileCollection>()) {
524     }
525 
526     /**
527      * Creates a SymbolTable that loads symbols from the various APKs and caches the
528      * results for faster lookup.
529      */
loadSymbolsFromIncludePaths()530     bool loadSymbolsFromIncludePaths() {
531         std::unique_ptr<AssetManagerSymbolSource> assetSource =
532                 util::make_unique<AssetManagerSymbolSource>();
533         for (const std::string& path : mOptions.includePaths) {
534             if (mContext->verbose()) {
535                 mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path");
536             }
537 
538             // First try to load the file as a static lib.
539             std::string errorStr;
540             std::unique_ptr<ResourceTable> staticInclude = loadStaticLibrary(path, &errorStr);
541             if (staticInclude) {
542                 if (!mOptions.staticLib) {
543                     // Can't include static libraries when not building a static library.
544                     mContext->getDiagnostics()->error(
545                             DiagMessage(path) << "can't include static library when building app");
546                     return false;
547                 }
548 
549                 // If we are using --no-static-lib-packages, we need to rename the package of this
550                 // table to our compilation package.
551                 if (mOptions.noStaticLibPackages) {
552                     if (ResourceTablePackage* pkg = staticInclude->findPackageById(0x7f)) {
553                         pkg->name = mContext->getCompilationPackage();
554                     }
555                 }
556 
557                 mContext->getExternalSymbols()->appendSource(
558                         util::make_unique<ResourceTableSymbolSource>(staticInclude.get()));
559 
560                 mStaticTableIncludes.push_back(std::move(staticInclude));
561 
562             } else if (!errorStr.empty()) {
563                 // We had an error with reading, so fail.
564                 mContext->getDiagnostics()->error(DiagMessage(path) << errorStr);
565                 return false;
566             }
567 
568             if (!assetSource->addAssetPath(path)) {
569                 mContext->getDiagnostics()->error(
570                         DiagMessage(path) << "failed to load include path");
571                 return false;
572             }
573         }
574 
575         mContext->getExternalSymbols()->appendSource(std::move(assetSource));
576         return true;
577     }
578 
extractAppInfoFromManifest(xml::XmlResource * xmlRes)579     Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) {
580         // Make sure the first element is <manifest> with package attribute.
581         if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
582             if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") {
583                 if (xml::Attribute* packageAttr = manifestEl->findAttribute({}, u"package")) {
584                     return AppInfo{ packageAttr->value };
585                 }
586             }
587         }
588         return {};
589     }
590 
591     /**
592      * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked.
593      * Postcondition: ResourceTable has only one package left. All others are stripped, or there
594      *                is an error and false is returned.
595      */
verifyNoExternalPackages()596     bool verifyNoExternalPackages() {
597         auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool {
598             return mContext->getCompilationPackage() != pkg->name ||
599                     !pkg->id ||
600                     pkg->id.value() != mContext->getPackageId();
601         };
602 
603         bool error = false;
604         for (const auto& package : mFinalTable.packages) {
605             if (isExtPackageFunc(package)) {
606                 // We have a package that is not related to the one we're building!
607                 for (const auto& type : package->types) {
608                     for (const auto& entry : type->entries) {
609                         ResourceNameRef resName(package->name, type->type, entry->name);
610 
611                         for (const auto& configValue : entry->values) {
612                             // Special case the occurrence of an ID that is being generated for the
613                             // 'android' package. This is due to legacy reasons.
614                             if (valueCast<Id>(configValue->value.get()) &&
615                                     package->name == u"android") {
616                                 mContext->getDiagnostics()->warn(
617                                         DiagMessage(configValue->value->getSource())
618                                         << "generated id '" << resName
619                                         << "' for external package '" << package->name
620                                         << "'");
621                             } else {
622                                 mContext->getDiagnostics()->error(
623                                         DiagMessage(configValue->value->getSource())
624                                         << "defined resource '" << resName
625                                         << "' for external package '" << package->name
626                                         << "'");
627                                 error = true;
628                             }
629                         }
630                     }
631                 }
632             }
633         }
634 
635         auto newEndIter = std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(),
636                                          isExtPackageFunc);
637         mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end());
638         return !error;
639     }
640 
641     /**
642      * Returns true if no IDs have been set, false otherwise.
643      */
verifyNoIdsSet()644     bool verifyNoIdsSet() {
645         for (const auto& package : mFinalTable.packages) {
646             for (const auto& type : package->types) {
647                 if (type->id) {
648                     mContext->getDiagnostics()->error(DiagMessage() << "type " << type->type
649                                                       << " has ID " << std::hex
650                                                       << (int) type->id.value()
651                                                       << std::dec << " assigned");
652                     return false;
653                 }
654 
655                 for (const auto& entry : type->entries) {
656                     if (entry->id) {
657                         ResourceNameRef resName(package->name, type->type, entry->name);
658                         mContext->getDiagnostics()->error(DiagMessage() << "entry " << resName
659                                                           << " has ID " << std::hex
660                                                           << (int) entry->id.value()
661                                                           << std::dec << " assigned");
662                         return false;
663                     }
664                 }
665             }
666         }
667         return true;
668     }
669 
makeArchiveWriter()670     std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
671         if (mOptions.outputToDirectory) {
672             return createDirectoryArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath);
673         } else {
674             return createZipFileArchiveWriter(mContext->getDiagnostics(), mOptions.outputPath);
675         }
676     }
677 
flattenTable(ResourceTable * table,IArchiveWriter * writer)678     bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
679         BigBuffer buffer(1024);
680         TableFlattener flattener(&buffer);
681         if (!flattener.consume(mContext, table)) {
682             return false;
683         }
684 
685         if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
686             if (writer->writeEntry(buffer)) {
687                 if (writer->finishEntry()) {
688                     return true;
689                 }
690             }
691         }
692 
693         mContext->getDiagnostics()->error(
694                 DiagMessage() << "failed to write resources.arsc to archive");
695         return false;
696     }
697 
flattenTableToPb(ResourceTable * table,IArchiveWriter * writer)698     bool flattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
699         // Create the file/zip entry.
700         if (!writer->startEntry("resources.arsc.flat", 0)) {
701             mContext->getDiagnostics()->error(DiagMessage() << "failed to open");
702             return false;
703         }
704 
705         std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table);
706 
707         // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream
708         // interface.
709         {
710             google::protobuf::io::CopyingOutputStreamAdaptor adaptor(writer);
711 
712             if (!pbTable->SerializeToZeroCopyStream(&adaptor)) {
713                 mContext->getDiagnostics()->error(DiagMessage() << "failed to write");
714                 return false;
715             }
716         }
717 
718         if (!writer->finishEntry()) {
719             mContext->getDiagnostics()->error(DiagMessage() << "failed to finish entry");
720             return false;
721         }
722         return true;
723     }
724 
writeJavaFile(ResourceTable * table,const StringPiece16 & packageNameToGenerate,const StringPiece16 & outPackage,JavaClassGeneratorOptions javaOptions)725     bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
726                        const StringPiece16& outPackage, JavaClassGeneratorOptions javaOptions) {
727         if (!mOptions.generateJavaClassPath) {
728             return true;
729         }
730 
731         std::string outPath = mOptions.generateJavaClassPath.value();
732         file::appendPath(&outPath, file::packageToPath(util::utf16ToUtf8(outPackage)));
733         if (!file::mkdirs(outPath)) {
734             mContext->getDiagnostics()->error(
735                     DiagMessage() << "failed to create directory '" << outPath << "'");
736             return false;
737         }
738 
739         file::appendPath(&outPath, "R.java");
740 
741         std::ofstream fout(outPath, std::ofstream::binary);
742         if (!fout) {
743             mContext->getDiagnostics()->error(
744                     DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
745             return false;
746         }
747 
748         JavaClassGenerator generator(mContext, table, javaOptions);
749         if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
750             mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError());
751             return false;
752         }
753 
754         if (!fout) {
755             mContext->getDiagnostics()->error(
756                     DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
757         }
758         return true;
759     }
760 
writeManifestJavaFile(xml::XmlResource * manifestXml)761     bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
762         if (!mOptions.generateJavaClassPath) {
763             return true;
764         }
765 
766         std::unique_ptr<ClassDefinition> manifestClass = generateManifestClass(
767                 mContext->getDiagnostics(), manifestXml);
768 
769         if (!manifestClass) {
770             // Something bad happened, but we already logged it, so exit.
771             return false;
772         }
773 
774         if (manifestClass->empty()) {
775             // Empty Manifest class, no need to generate it.
776             return true;
777         }
778 
779         // Add any JavaDoc annotations to the generated class.
780         for (const std::string& annotation : mOptions.javadocAnnotations) {
781             std::string properAnnotation = "@";
782             properAnnotation += annotation;
783             manifestClass->getCommentBuilder()->appendComment(properAnnotation);
784         }
785 
786         const std::string packageUtf8 = util::utf16ToUtf8(mContext->getCompilationPackage());
787 
788         std::string outPath = mOptions.generateJavaClassPath.value();
789         file::appendPath(&outPath, file::packageToPath(packageUtf8));
790 
791         if (!file::mkdirs(outPath)) {
792             mContext->getDiagnostics()->error(
793                     DiagMessage() << "failed to create directory '" << outPath << "'");
794             return false;
795         }
796 
797         file::appendPath(&outPath, "Manifest.java");
798 
799         std::ofstream fout(outPath, std::ofstream::binary);
800         if (!fout) {
801             mContext->getDiagnostics()->error(
802                     DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
803             return false;
804         }
805 
806         if (!ClassDefinition::writeJavaFile(manifestClass.get(), packageUtf8, true, &fout)) {
807             mContext->getDiagnostics()->error(
808                     DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
809             return false;
810         }
811         return true;
812     }
813 
writeProguardFile(const proguard::KeepSet & keepSet)814     bool writeProguardFile(const proguard::KeepSet& keepSet) {
815         if (!mOptions.generateProguardRulesPath) {
816             return true;
817         }
818 
819         const std::string& outPath = mOptions.generateProguardRulesPath.value();
820         std::ofstream fout(outPath, std::ofstream::binary);
821         if (!fout) {
822             mContext->getDiagnostics()->error(
823                     DiagMessage() << "failed to open '" << outPath << "': " << strerror(errno));
824             return false;
825         }
826 
827         proguard::writeKeepSet(&fout, keepSet);
828         if (!fout) {
829             mContext->getDiagnostics()->error(
830                     DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno));
831             return false;
832         }
833         return true;
834     }
835 
loadStaticLibrary(const std::string & input,std::string * outError)836     std::unique_ptr<ResourceTable> loadStaticLibrary(const std::string& input,
837                                                      std::string* outError) {
838         std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
839                 input, outError);
840         if (!collection) {
841             return {};
842         }
843         return loadTablePbFromCollection(collection.get());
844     }
845 
loadTablePbFromCollection(io::IFileCollection * collection)846     std::unique_ptr<ResourceTable> loadTablePbFromCollection(io::IFileCollection* collection) {
847         io::IFile* file = collection->findFile("resources.arsc.flat");
848         if (!file) {
849             return {};
850         }
851 
852         std::unique_ptr<io::IData> data = file->openAsData();
853         return loadTableFromPb(file->getSource(), data->data(), data->size(),
854                                mContext->getDiagnostics());
855     }
856 
mergeStaticLibrary(const std::string & input,bool override)857     bool mergeStaticLibrary(const std::string& input, bool override) {
858         if (mContext->verbose()) {
859             mContext->getDiagnostics()->note(DiagMessage() << "merging static library " << input);
860         }
861 
862         std::string errorStr;
863         std::unique_ptr<io::ZipFileCollection> collection =
864                 io::ZipFileCollection::create(input, &errorStr);
865         if (!collection) {
866             mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
867             return false;
868         }
869 
870         std::unique_ptr<ResourceTable> table = loadTablePbFromCollection(collection.get());
871         if (!table) {
872             mContext->getDiagnostics()->error(DiagMessage(input) << "invalid static library");
873             return false;
874         }
875 
876         ResourceTablePackage* pkg = table->findPackageById(0x7f);
877         if (!pkg) {
878             mContext->getDiagnostics()->error(DiagMessage(input)
879                                               << "static library has no package");
880             return false;
881         }
882 
883         bool result;
884         if (mOptions.noStaticLibPackages) {
885             // Merge all resources as if they were in the compilation package. This is the old
886             // behaviour of aapt.
887 
888             // Add the package to the set of --extra-packages so we emit an R.java for each
889             // library package.
890             if (!pkg->name.empty()) {
891                 mOptions.extraJavaPackages.insert(pkg->name);
892             }
893 
894             pkg->name = u"";
895             if (override) {
896                 result = mTableMerger->mergeOverlay(Source(input), table.get(), collection.get());
897             } else {
898                 result = mTableMerger->merge(Source(input), table.get(), collection.get());
899             }
900 
901         } else {
902             // This is the proper way to merge libraries, where the package name is preserved
903             // and resource names are mangled.
904             result = mTableMerger->mergeAndMangle(Source(input), pkg->name, table.get(),
905                                                   collection.get());
906         }
907 
908         if (!result) {
909             return false;
910         }
911 
912         // Make sure to move the collection into the set of IFileCollections.
913         mCollections.push_back(std::move(collection));
914         return true;
915     }
916 
mergeResourceTable(io::IFile * file,bool override)917     bool mergeResourceTable(io::IFile* file, bool override) {
918         if (mContext->verbose()) {
919             mContext->getDiagnostics()->note(DiagMessage() << "merging resource table "
920                                              << file->getSource());
921         }
922 
923         std::unique_ptr<io::IData> data = file->openAsData();
924         if (!data) {
925             mContext->getDiagnostics()->error(DiagMessage(file->getSource())
926                                              << "failed to open file");
927             return false;
928         }
929 
930         std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(),
931                                                                data->data(), data->size(),
932                                                                mContext->getDiagnostics());
933         if (!table) {
934             return false;
935         }
936 
937         bool result = false;
938         if (override) {
939             result = mTableMerger->mergeOverlay(file->getSource(), table.get());
940         } else {
941             result = mTableMerger->merge(file->getSource(), table.get());
942         }
943         return result;
944     }
945 
mergeCompiledFile(io::IFile * file,ResourceFile * fileDesc,bool override)946     bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc, bool override) {
947         if (mContext->verbose()) {
948             mContext->getDiagnostics()->note(DiagMessage() << "merging compiled file "
949                                              << file->getSource());
950         }
951 
952         bool result = false;
953         if (override) {
954             result = mTableMerger->mergeFileOverlay(*fileDesc, file);
955         } else {
956             result = mTableMerger->mergeFile(*fileDesc, file);
957         }
958 
959         if (!result) {
960             return false;
961         }
962 
963         // Add the exports of this file to the table.
964         for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
965             if (exportedSymbol.name.package.empty()) {
966                 exportedSymbol.name.package = mContext->getCompilationPackage();
967             }
968 
969             ResourceNameRef resName = exportedSymbol.name;
970 
971             Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
972                     exportedSymbol.name);
973             if (mangledName) {
974                 resName = mangledName.value();
975             }
976 
977             std::unique_ptr<Id> id = util::make_unique<Id>();
978             id->setSource(fileDesc->source.withLine(exportedSymbol.line));
979             bool result = mFinalTable.addResourceAllowMangled(
980                     resName, ConfigDescription::defaultConfig(), std::string(), std::move(id),
981                     mContext->getDiagnostics());
982             if (!result) {
983                 return false;
984             }
985         }
986         return true;
987     }
988 
989     /**
990      * Takes a path to load as a ZIP file and merges the files within into the master ResourceTable.
991      * If override is true, conflicting resources are allowed to override each other, in order of
992      * last seen.
993      *
994      * An io::IFileCollection is created from the ZIP file and added to the set of
995      * io::IFileCollections that are open.
996      */
mergeArchive(const std::string & input,bool override)997     bool mergeArchive(const std::string& input, bool override) {
998         if (mContext->verbose()) {
999             mContext->getDiagnostics()->note(DiagMessage() << "merging archive " << input);
1000         }
1001 
1002         std::string errorStr;
1003         std::unique_ptr<io::ZipFileCollection> collection =
1004                 io::ZipFileCollection::create(input, &errorStr);
1005         if (!collection) {
1006             mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
1007             return false;
1008         }
1009 
1010         bool error = false;
1011         for (auto iter = collection->iterator(); iter->hasNext(); ) {
1012             if (!mergeFile(iter->next(), override)) {
1013                 error = true;
1014             }
1015         }
1016 
1017         // Make sure to move the collection into the set of IFileCollections.
1018         mCollections.push_back(std::move(collection));
1019         return !error;
1020     }
1021 
1022     /**
1023      * Takes a path to load and merge into the master ResourceTable. If override is true,
1024      * conflicting resources are allowed to override each other, in order of last seen.
1025      *
1026      * If the file path ends with .flata, .jar, .jack, or .zip the file is treated as ZIP archive
1027      * and the files within are merged individually.
1028      *
1029      * Otherwise the files is processed on its own.
1030      */
mergePath(const std::string & path,bool override)1031     bool mergePath(const std::string& path, bool override) {
1032         if (util::stringEndsWith<char>(path, ".flata") ||
1033                 util::stringEndsWith<char>(path, ".jar") ||
1034                 util::stringEndsWith<char>(path, ".jack") ||
1035                 util::stringEndsWith<char>(path, ".zip")) {
1036             return mergeArchive(path, override);
1037         } else if (util::stringEndsWith<char>(path, ".apk")) {
1038             return mergeStaticLibrary(path, override);
1039         }
1040 
1041         io::IFile* file = mFileCollection->insertFile(path);
1042         return mergeFile(file, override);
1043     }
1044 
1045     /**
1046      * Takes a file to load and merge into the master ResourceTable. If override is true,
1047      * conflicting resources are allowed to override each other, in order of last seen.
1048      *
1049      * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and merged into the
1050      * master ResourceTable. If the file ends with .flat, then it is treated like a compiled file
1051      * and the header data is read and merged into the final ResourceTable.
1052      *
1053      * All other file types are ignored. This is because these files could be coming from a zip,
1054      * where we could have other files like classes.dex.
1055      */
mergeFile(io::IFile * file,bool override)1056     bool mergeFile(io::IFile* file, bool override) {
1057         const Source& src = file->getSource();
1058         if (util::stringEndsWith<char>(src.path, ".arsc.flat")) {
1059             return mergeResourceTable(file, override);
1060 
1061         } else if (util::stringEndsWith<char>(src.path, ".flat")){
1062             // Try opening the file and looking for an Export header.
1063             std::unique_ptr<io::IData> data = file->openAsData();
1064             if (!data) {
1065                 mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open");
1066                 return false;
1067             }
1068 
1069             std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader(
1070                     src, data->data(), data->size(), mContext->getDiagnostics());
1071             if (resourceFile) {
1072                 return mergeCompiledFile(file, resourceFile.get(), override);
1073             }
1074             return false;
1075         }
1076 
1077         // Ignore non .flat files. This could be classes.dex or something else that happens
1078         // to be in an archive.
1079         return true;
1080     }
1081 
run(const std::vector<std::string> & inputFiles)1082     int run(const std::vector<std::string>& inputFiles) {
1083         // Load the AndroidManifest.xml
1084         std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
1085                                                                 mContext->getDiagnostics());
1086         if (!manifestXml) {
1087             return 1;
1088         }
1089 
1090         if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get())) {
1091             mContext->setCompilationPackage(maybeAppInfo.value().package);
1092         } else {
1093             mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
1094                                              << "no package specified in <manifest> tag");
1095             return 1;
1096         }
1097 
1098         if (!util::isJavaPackageName(mContext->getCompilationPackage())) {
1099             mContext->getDiagnostics()->error(DiagMessage(mOptions.manifestPath)
1100                                              << "invalid package name '"
1101                                              << mContext->getCompilationPackage()
1102                                              << "'");
1103             return 1;
1104         }
1105 
1106         mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() });
1107 
1108         if (mContext->getCompilationPackage() == u"android") {
1109             mContext->setPackageId(0x01);
1110         } else {
1111             mContext->setPackageId(0x7f);
1112         }
1113 
1114         if (!loadSymbolsFromIncludePaths()) {
1115             return 1;
1116         }
1117 
1118         TableMergerOptions tableMergerOptions;
1119         tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
1120         mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions);
1121 
1122         if (mContext->verbose()) {
1123             mContext->getDiagnostics()->note(
1124                     DiagMessage() << "linking package '" << mContext->getCompilationPackage()
1125                                   << "' with package ID " << std::hex
1126                                   << (int) mContext->getPackageId());
1127         }
1128 
1129 
1130         for (const std::string& input : inputFiles) {
1131             if (!mergePath(input, false)) {
1132                 mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input");
1133                 return 1;
1134             }
1135         }
1136 
1137         for (const std::string& input : mOptions.overlayFiles) {
1138             if (!mergePath(input, true)) {
1139                 mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
1140                 return 1;
1141             }
1142         }
1143 
1144         if (!verifyNoExternalPackages()) {
1145             return 1;
1146         }
1147 
1148         if (!mOptions.staticLib) {
1149             PrivateAttributeMover mover;
1150             if (!mover.consume(mContext, &mFinalTable)) {
1151                 mContext->getDiagnostics()->error(
1152                         DiagMessage() << "failed moving private attributes");
1153                 return 1;
1154             }
1155         }
1156 
1157         if (!mOptions.staticLib) {
1158             // Assign IDs if we are building a regular app.
1159             IdAssigner idAssigner;
1160             if (!idAssigner.consume(mContext, &mFinalTable)) {
1161                 mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs");
1162                 return 1;
1163             }
1164         } else {
1165             // Static libs are merged with other apps, and ID collisions are bad, so verify that
1166             // no IDs have been set.
1167             if (!verifyNoIdsSet()) {
1168                 return 1;
1169             }
1170         }
1171 
1172         // Add the names to mangle based on our source merge earlier.
1173         mContext->setNameManglerPolicy(NameManglerPolicy{
1174                 mContext->getCompilationPackage(), mTableMerger->getMergedPackages() });
1175 
1176         // Add our table to the symbol table.
1177         mContext->getExternalSymbols()->prependSource(
1178                         util::make_unique<ResourceTableSymbolSource>(&mFinalTable));
1179 
1180         {
1181             ReferenceLinker linker;
1182             if (!linker.consume(mContext, &mFinalTable)) {
1183                 mContext->getDiagnostics()->error(DiagMessage() << "failed linking references");
1184                 return 1;
1185             }
1186 
1187             if (mOptions.staticLib) {
1188                 if (!mOptions.products.empty()) {
1189                     mContext->getDiagnostics()->warn(
1190                             DiagMessage() << "can't select products when building static library");
1191                 }
1192 
1193                 if (mOptions.tableSplitterOptions.configFilter != nullptr ||
1194                         mOptions.tableSplitterOptions.preferredDensity) {
1195                     mContext->getDiagnostics()->warn(
1196                             DiagMessage() << "can't strip resources when building static library");
1197                 }
1198             } else {
1199                 ProductFilter productFilter(mOptions.products);
1200                 if (!productFilter.consume(mContext, &mFinalTable)) {
1201                     mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products");
1202                     return 1;
1203                 }
1204 
1205                 // TODO(adamlesinski): Actually pass in split constraints and handle splits at the file
1206                 // level.
1207                 TableSplitter tableSplitter({}, mOptions.tableSplitterOptions);
1208                 if (!tableSplitter.verifySplitConstraints(mContext)) {
1209                     return 1;
1210                 }
1211                 tableSplitter.splitTable(&mFinalTable);
1212             }
1213         }
1214 
1215         proguard::KeepSet proguardKeepSet;
1216 
1217         std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter();
1218         if (!archiveWriter) {
1219             mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive");
1220             return 1;
1221         }
1222 
1223         bool error = false;
1224         {
1225             ManifestFixer manifestFixer(mOptions.manifestFixerOptions);
1226             if (!manifestFixer.consume(mContext, manifestXml.get())) {
1227                 error = true;
1228             }
1229 
1230             // AndroidManifest.xml has no resource name, but the CallSite is built from the name
1231             // (aka, which package the AndroidManifest.xml is coming from).
1232             // So we give it a package name so it can see local resources.
1233             manifestXml->file.name.package = mContext->getCompilationPackage();
1234 
1235             XmlReferenceLinker manifestLinker;
1236             if (manifestLinker.consume(mContext, manifestXml.get())) {
1237                 if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
1238                                                                manifestXml.get(),
1239                                                                &proguardKeepSet)) {
1240                     error = true;
1241                 }
1242 
1243                 if (mOptions.generateJavaClassPath) {
1244                     if (!writeManifestJavaFile(manifestXml.get())) {
1245                         error = true;
1246                     }
1247                 }
1248 
1249                 const bool keepRawValues = mOptions.staticLib;
1250                 bool result = flattenXml(manifestXml.get(), "AndroidManifest.xml", {},
1251                                          keepRawValues, archiveWriter.get(), mContext);
1252                 if (!result) {
1253                     error = true;
1254                 }
1255             } else {
1256                 error = true;
1257             }
1258         }
1259 
1260         if (error) {
1261             mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest");
1262             return 1;
1263         }
1264 
1265         ResourceFileFlattenerOptions fileFlattenerOptions;
1266         fileFlattenerOptions.keepRawValues = mOptions.staticLib;
1267         fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything;
1268         fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress;
1269         fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion;
1270         fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors;
1271         ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, &proguardKeepSet);
1272 
1273         if (!fileFlattener.flatten(&mFinalTable, archiveWriter.get())) {
1274             mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources");
1275             return 1;
1276         }
1277 
1278         if (!mOptions.noAutoVersion) {
1279             AutoVersioner versioner;
1280             if (!versioner.consume(mContext, &mFinalTable)) {
1281                 mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles");
1282                 return 1;
1283             }
1284         }
1285 
1286         if (mOptions.staticLib) {
1287             if (!flattenTableToPb(&mFinalTable, archiveWriter.get())) {
1288                 mContext->getDiagnostics()->error(DiagMessage()
1289                                                   << "failed to write resources.arsc.flat");
1290                 return 1;
1291             }
1292         } else {
1293             if (!flattenTable(&mFinalTable, archiveWriter.get())) {
1294                 mContext->getDiagnostics()->error(DiagMessage()
1295                                                   << "failed to write resources.arsc");
1296                 return 1;
1297             }
1298         }
1299 
1300         if (mOptions.generateJavaClassPath) {
1301             JavaClassGeneratorOptions options;
1302             options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
1303             options.javadocAnnotations = mOptions.javadocAnnotations;
1304 
1305             if (mOptions.staticLib || mOptions.generateNonFinalIds) {
1306                 options.useFinal = false;
1307             }
1308 
1309             const StringPiece16 actualPackage = mContext->getCompilationPackage();
1310             StringPiece16 outputPackage = mContext->getCompilationPackage();
1311             if (mOptions.customJavaPackage) {
1312                 // Override the output java package to the custom one.
1313                 outputPackage = mOptions.customJavaPackage.value();
1314             }
1315 
1316             if (mOptions.privateSymbols) {
1317                 // If we defined a private symbols package, we only emit Public symbols
1318                 // to the original package, and private and public symbols to the private package.
1319 
1320                 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
1321                 if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(),
1322                                    outputPackage, options)) {
1323                     return 1;
1324                 }
1325 
1326                 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
1327                 outputPackage = mOptions.privateSymbols.value();
1328             }
1329 
1330             if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
1331                 return 1;
1332             }
1333 
1334             for (const std::u16string& extraPackage : mOptions.extraJavaPackages) {
1335                 if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) {
1336                     return 1;
1337                 }
1338             }
1339         }
1340 
1341         if (mOptions.generateProguardRulesPath) {
1342             if (!writeProguardFile(proguardKeepSet)) {
1343                 return 1;
1344             }
1345         }
1346 
1347         if (mContext->verbose()) {
1348             DebugPrintTableOptions debugPrintTableOptions;
1349             debugPrintTableOptions.showSources = true;
1350             Debug::printTable(&mFinalTable, debugPrintTableOptions);
1351         }
1352         return 0;
1353     }
1354 
1355 private:
1356     LinkOptions mOptions;
1357     LinkContext* mContext;
1358     ResourceTable mFinalTable;
1359 
1360     std::unique_ptr<TableMerger> mTableMerger;
1361 
1362     // A pointer to the FileCollection representing the filesystem (not archives).
1363     std::unique_ptr<io::FileCollection> mFileCollection;
1364 
1365     // A vector of IFileCollections. This is mainly here to keep ownership of the collections.
1366     std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
1367 
1368     // A vector of ResourceTables. This is here to retain ownership, so that the SymbolTable
1369     // can use these.
1370     std::vector<std::unique_ptr<ResourceTable>> mStaticTableIncludes;
1371 };
1372 
link(const std::vector<StringPiece> & args)1373 int link(const std::vector<StringPiece>& args) {
1374     LinkContext context;
1375     LinkOptions options;
1376     Maybe<std::string> privateSymbolsPackage;
1377     Maybe<std::string> minSdkVersion, targetSdkVersion;
1378     Maybe<std::string> renameManifestPackage, renameInstrumentationTargetPackage;
1379     Maybe<std::string> versionCode, versionName;
1380     Maybe<std::string> customJavaPackage;
1381     std::vector<std::string> extraJavaPackages;
1382     Maybe<std::string> configs;
1383     Maybe<std::string> preferredDensity;
1384     Maybe<std::string> productList;
1385     bool legacyXFlag = false;
1386     bool requireLocalization = false;
1387     bool verbose = false;
1388     Flags flags = Flags()
1389             .requiredFlag("-o", "Output path", &options.outputPath)
1390             .requiredFlag("--manifest", "Path to the Android manifest to build",
1391                           &options.manifestPath)
1392             .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths)
1393             .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n"
1394                               "The last conflicting resource given takes precedence.",
1395                               &options.overlayFiles)
1396             .optionalFlag("--java", "Directory in which to generate R.java",
1397                           &options.generateJavaClassPath)
1398             .optionalFlag("--proguard", "Output file for generated Proguard rules",
1399                           &options.generateProguardRulesPath)
1400             .optionalSwitch("--no-auto-version",
1401                             "Disables automatic style and layout SDK versioning",
1402                             &options.noAutoVersion)
1403             .optionalSwitch("--no-version-vectors",
1404                             "Disables automatic versioning of vector drawables. Use this only\n"
1405                             "when building with vector drawable support library",
1406                             &options.noVersionVectors)
1407             .optionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01",
1408                             &legacyXFlag)
1409             .optionalSwitch("-z", "Require localization of strings marked 'suggested'",
1410                             &requireLocalization)
1411             .optionalFlag("-c", "Comma separated list of configurations to include. The default\n"
1412                                 "is all configurations", &configs)
1413             .optionalFlag("--preferred-density",
1414                           "Selects the closest matching density and strips out all others.",
1415                           &preferredDensity)
1416             .optionalFlag("--product", "Comma separated list of product names to keep",
1417                           &productList)
1418             .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
1419                             "by -o",
1420                             &options.outputToDirectory)
1421             .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for "
1422                           "AndroidManifest.xml", &minSdkVersion)
1423             .optionalFlag("--target-sdk-version", "Default target SDK version to use for "
1424                           "AndroidManifest.xml", &targetSdkVersion)
1425             .optionalFlag("--version-code", "Version code (integer) to inject into the "
1426                           "AndroidManifest.xml if none is present", &versionCode)
1427             .optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml "
1428                           "if none is present", &versionName)
1429             .optionalSwitch("--static-lib", "Generate a static Android library", &options.staticLib)
1430             .optionalSwitch("--no-static-lib-packages",
1431                             "Merge all library resources under the app's package",
1432                             &options.noStaticLibPackages)
1433             .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n"
1434                             "This is implied when --static-lib is specified.",
1435                             &options.generateNonFinalIds)
1436             .optionalFlag("--private-symbols", "Package name to use when generating R.java for "
1437                           "private symbols.\n"
1438                           "If not specified, public and private symbols will use the application's "
1439                           "package name", &privateSymbolsPackage)
1440             .optionalFlag("--custom-package", "Custom Java package under which to generate R.java",
1441                           &customJavaPackage)
1442             .optionalFlagList("--extra-packages", "Generate the same R.java but with different "
1443                               "package names", &extraJavaPackages)
1444             .optionalFlagList("--add-javadoc-annotation", "Adds a JavaDoc annotation to all "
1445                             "generated Java classes", &options.javadocAnnotations)
1446             .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in "
1447                             "overlays without <add-resource> tags", &options.autoAddOverlay)
1448             .optionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml",
1449                           &renameManifestPackage)
1450             .optionalFlag("--rename-instrumentation-target-package",
1451                           "Changes the name of the target package for instrumentation. Most useful "
1452                           "when used\nin conjunction with --rename-manifest-package",
1453                           &renameInstrumentationTargetPackage)
1454             .optionalFlagList("-0", "File extensions not to compress",
1455                               &options.extensionsToNotCompress)
1456             .optionalSwitch("-v", "Enables verbose logging", &verbose);
1457 
1458     if (!flags.parse("aapt2 link", args, &std::cerr)) {
1459         return 1;
1460     }
1461 
1462     // Expand all argument-files passed into the command line. These start with '@'.
1463     std::vector<std::string> argList;
1464     for (const std::string& arg : flags.getArgs()) {
1465         if (util::stringStartsWith<char>(arg, "@")) {
1466             const std::string path = arg.substr(1, arg.size() - 1);
1467             std::string error;
1468             if (!file::appendArgsFromFile(path, &argList, &error)) {
1469                 context.getDiagnostics()->error(DiagMessage(path) << error);
1470                 return 1;
1471             }
1472         } else {
1473             argList.push_back(arg);
1474         }
1475     }
1476 
1477     if (verbose) {
1478         context.setVerbose(verbose);
1479     }
1480 
1481     if (privateSymbolsPackage) {
1482         options.privateSymbols = util::utf8ToUtf16(privateSymbolsPackage.value());
1483     }
1484 
1485     if (minSdkVersion) {
1486         options.manifestFixerOptions.minSdkVersionDefault =
1487                 util::utf8ToUtf16(minSdkVersion.value());
1488     }
1489 
1490     if (targetSdkVersion) {
1491         options.manifestFixerOptions.targetSdkVersionDefault =
1492                 util::utf8ToUtf16(targetSdkVersion.value());
1493     }
1494 
1495     if (renameManifestPackage) {
1496         options.manifestFixerOptions.renameManifestPackage =
1497                 util::utf8ToUtf16(renameManifestPackage.value());
1498     }
1499 
1500     if (renameInstrumentationTargetPackage) {
1501         options.manifestFixerOptions.renameInstrumentationTargetPackage =
1502                 util::utf8ToUtf16(renameInstrumentationTargetPackage.value());
1503     }
1504 
1505     if (versionCode) {
1506         options.manifestFixerOptions.versionCodeDefault = util::utf8ToUtf16(versionCode.value());
1507     }
1508 
1509     if (versionName) {
1510         options.manifestFixerOptions.versionNameDefault = util::utf8ToUtf16(versionName.value());
1511     }
1512 
1513     if (customJavaPackage) {
1514         options.customJavaPackage = util::utf8ToUtf16(customJavaPackage.value());
1515     }
1516 
1517     // Populate the set of extra packages for which to generate R.java.
1518     for (std::string& extraPackage : extraJavaPackages) {
1519         // A given package can actually be a colon separated list of packages.
1520         for (StringPiece package : util::split(extraPackage, ':')) {
1521             options.extraJavaPackages.insert(util::utf8ToUtf16(package));
1522         }
1523     }
1524 
1525     if (productList) {
1526         for (StringPiece product : util::tokenize<char>(productList.value(), ',')) {
1527             if (product != "" && product != "default") {
1528                 options.products.insert(product.toString());
1529             }
1530         }
1531     }
1532 
1533     AxisConfigFilter filter;
1534     if (configs) {
1535         for (const StringPiece& configStr : util::tokenize<char>(configs.value(), ',')) {
1536             ConfigDescription config;
1537             LocaleValue lv;
1538             if (lv.initFromFilterString(configStr)) {
1539                 lv.writeTo(&config);
1540             } else if (!ConfigDescription::parse(configStr, &config)) {
1541                 context.getDiagnostics()->error(
1542                         DiagMessage() << "invalid config '" << configStr << "' for -c option");
1543                 return 1;
1544             }
1545 
1546             if (config.density != 0) {
1547                 context.getDiagnostics()->warn(
1548                         DiagMessage() << "ignoring density '" << config << "' for -c option");
1549             } else {
1550                 filter.addConfig(config);
1551             }
1552         }
1553 
1554         options.tableSplitterOptions.configFilter = &filter;
1555     }
1556 
1557     if (preferredDensity) {
1558         ConfigDescription preferredDensityConfig;
1559         if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) {
1560             context.getDiagnostics()->error(DiagMessage() << "invalid density '"
1561                                             << preferredDensity.value()
1562                                             << "' for --preferred-density option");
1563             return 1;
1564         }
1565 
1566         // Clear the version that can be automatically added.
1567         preferredDensityConfig.sdkVersion = 0;
1568 
1569         if (preferredDensityConfig.diff(ConfigDescription::defaultConfig())
1570                 != ConfigDescription::CONFIG_DENSITY) {
1571             context.getDiagnostics()->error(DiagMessage() << "invalid preferred density '"
1572                                             << preferredDensity.value() << "'. "
1573                                             << "Preferred density must only be a density value");
1574             return 1;
1575         }
1576         options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density;
1577     }
1578 
1579     // Turn off auto versioning for static-libs.
1580     if (options.staticLib) {
1581         options.noAutoVersion = true;
1582         options.noVersionVectors = true;
1583     }
1584 
1585     LinkCommand cmd(&context, options);
1586     return cmd.run(argList);
1587 }
1588 
1589 } // namespace aapt
1590