blob: 6dd34e3e5f11f82c94b4c74ba28c09f955342646 [file] [log] [blame]
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "AppInfo.h"
18#include "Debug.h"
19#include "Flags.h"
Adam Lesinski6a008172016-02-02 17:02:58 -080020#include "Locale.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070021#include "NameMangler.h"
Adam Lesinski59e04c62016-02-04 15:59:23 -080022#include "ResourceUtils.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070023#include "compile/IdAssigner.h"
Adam Lesinski6a008172016-02-02 17:02:58 -080024#include "filter/ConfigFilter.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070025#include "flatten/Archive.h"
26#include "flatten/TableFlattener.h"
27#include "flatten/XmlFlattener.h"
Adam Lesinskia40e9722015-11-24 19:11:46 -080028#include "io/FileSystem.h"
29#include "io/ZipArchive.h"
Adam Lesinskica5638f2015-10-21 14:42:43 -070030#include "java/JavaClassGenerator.h"
31#include "java/ManifestClassGenerator.h"
32#include "java/ProguardRules.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070033#include "link/Linkers.h"
Adam Lesinskicacb28f2016-10-19 12:18:14 -070034#include "link/ManifestFixer.h"
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -080035#include "link/ProductFilter.h"
Adam Lesinski467f1712015-11-16 17:35:44 -080036#include "link/ReferenceLinker.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070037#include "link/TableMerger.h"
38#include "process/IResourceTableConsumer.h"
39#include "process/SymbolTable.h"
Adam Lesinski59e04c62016-02-04 15:59:23 -080040#include "proto/ProtoSerialize.h"
Adam Lesinski355f2852016-02-13 20:26:45 -080041#include "split/TableSplitter.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070042#include "unflatten/BinaryResourceParser.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070043#include "util/Files.h"
44#include "util/StringPiece.h"
Adam Lesinski467f1712015-11-16 17:35:44 -080045#include "xml/XmlDom.h"
Adam Lesinski1ab598f2015-08-14 14:26:04 -070046
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -070047#include <android-base/file.h>
Adam Lesinski59e04c62016-02-04 15:59:23 -080048#include <google/protobuf/io/coded_stream.h>
49
Adam Lesinskicacb28f2016-10-19 12:18:14 -070050#include <sys/stat.h>
Adam Lesinski1ab598f2015-08-14 14:26:04 -070051#include <fstream>
Adam Lesinski5eeaadd2016-08-25 12:26:56 -070052#include <queue>
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -070053#include <unordered_map>
Adam Lesinski1ab598f2015-08-14 14:26:04 -070054#include <vector>
55
Adam Lesinski5eeaadd2016-08-25 12:26:56 -070056using google::protobuf::io::CopyingOutputStreamAdaptor;
57
Adam Lesinski1ab598f2015-08-14 14:26:04 -070058namespace aapt {
59
60struct LinkOptions {
Adam Lesinskicacb28f2016-10-19 12:18:14 -070061 std::string outputPath;
62 std::string manifestPath;
63 std::vector<std::string> includePaths;
64 std::vector<std::string> overlayFiles;
Adam Lesinski36c73a52016-08-11 13:39:24 -070065
Adam Lesinskicacb28f2016-10-19 12:18:14 -070066 // Java/Proguard options.
67 Maybe<std::string> generateJavaClassPath;
68 Maybe<std::string> customJavaPackage;
69 std::set<std::string> extraJavaPackages;
70 Maybe<std::string> generateProguardRulesPath;
71 Maybe<std::string> generateMainDexProguardRulesPath;
Adam Lesinski36c73a52016-08-11 13:39:24 -070072
Adam Lesinskicacb28f2016-10-19 12:18:14 -070073 bool noAutoVersion = false;
74 bool noVersionVectors = false;
75 bool noResourceDeduping = false;
76 bool staticLib = false;
77 bool noStaticLibPackages = false;
78 bool generateNonFinalIds = false;
79 std::vector<std::string> javadocAnnotations;
80 bool outputToDirectory = false;
81 bool noXmlNamespaces = false;
82 bool autoAddOverlay = false;
83 bool doNotCompressAnything = false;
84 std::unordered_set<std::string> extensionsToNotCompress;
85 Maybe<std::string> privateSymbols;
86 ManifestFixerOptions manifestFixerOptions;
87 std::unordered_set<std::string> products;
Adam Lesinski36c73a52016-08-11 13:39:24 -070088
Adam Lesinskicacb28f2016-10-19 12:18:14 -070089 // Split APK options.
90 TableSplitterOptions tableSplitterOptions;
91 std::vector<SplitConstraints> splitConstraints;
92 std::vector<std::string> splitPaths;
Adam Lesinski36c73a52016-08-11 13:39:24 -070093
Adam Lesinskicacb28f2016-10-19 12:18:14 -070094 // Stable ID options.
95 std::unordered_map<ResourceName, ResourceId> stableIdMap;
96 Maybe<std::string> resourceIdMapPath;
Adam Lesinski1ab598f2015-08-14 14:26:04 -070097};
98
Adam Lesinski64587af2016-02-18 18:33:06 -080099class LinkContext : public IAaptContext {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700100 public:
101 LinkContext() : mNameMangler({}) {}
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700102
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700103 IDiagnostics* getDiagnostics() override { return &mDiagnostics; }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700104
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700105 NameMangler* getNameMangler() override { return &mNameMangler; }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700106
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700107 void setNameManglerPolicy(const NameManglerPolicy& policy) {
108 mNameMangler = NameMangler(policy);
109 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800110
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700111 const std::string& getCompilationPackage() override {
112 return mCompilationPackage;
113 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700114
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700115 void setCompilationPackage(const StringPiece& packageName) {
116 mCompilationPackage = packageName.toString();
117 }
Adam Lesinski64587af2016-02-18 18:33:06 -0800118
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700119 uint8_t getPackageId() override { return mPackageId; }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700120
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700121 void setPackageId(uint8_t id) { mPackageId = id; }
Adam Lesinski64587af2016-02-18 18:33:06 -0800122
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700123 SymbolTable* getExternalSymbols() override { return &mSymbols; }
Adam Lesinski355f2852016-02-13 20:26:45 -0800124
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700125 bool verbose() override { return mVerbose; }
Adam Lesinski64587af2016-02-18 18:33:06 -0800126
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700127 void setVerbose(bool val) { mVerbose = val; }
Adam Lesinski64587af2016-02-18 18:33:06 -0800128
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700129 int getMinSdkVersion() override { return mMinSdkVersion; }
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700130
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700131 void setMinSdkVersion(int minSdk) { mMinSdkVersion = minSdk; }
Adam Lesinskifb6312f2016-06-28 14:40:32 -0700132
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700133 private:
134 StdErrDiagnostics mDiagnostics;
135 NameMangler mNameMangler;
136 std::string mCompilationPackage;
137 uint8_t mPackageId = 0x0;
138 SymbolTable mSymbols;
139 bool mVerbose = false;
140 int mMinSdkVersion = 0;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700141};
142
Adam Lesinski355f2852016-02-13 20:26:45 -0800143static bool copyFileToArchive(io::IFile* file, const std::string& outPath,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700144 uint32_t compressionFlags, IArchiveWriter* writer,
145 IAaptContext* context) {
146 std::unique_ptr<io::IData> data = file->openAsData();
147 if (!data) {
148 context->getDiagnostics()->error(DiagMessage(file->getSource())
149 << "failed to open file");
Adam Lesinski355f2852016-02-13 20:26:45 -0800150 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700151 }
152
153 const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data());
154 const size_t bufferSize = data->size();
155
156 if (context->verbose()) {
157 context->getDiagnostics()->note(DiagMessage() << "writing " << outPath
158 << " to archive");
159 }
160
161 if (writer->startEntry(outPath, compressionFlags)) {
162 if (writer->writeEntry(buffer, bufferSize)) {
163 if (writer->finishEntry()) {
164 return true;
165 }
166 }
167 }
168
169 context->getDiagnostics()->error(DiagMessage() << "failed to write file "
170 << outPath);
171 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800172}
173
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700174static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path,
175 Maybe<size_t> maxSdkLevel, bool keepRawValues,
176 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)) {
Adam Lesinski355f2852016-02-13 20:26:45 -0800183 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700184 }
185
186 if (context->verbose()) {
187 DiagMessage msg;
188 msg << "writing " << path << " to archive";
189 if (maxSdkLevel) {
190 msg << " maxSdkLevel=" << maxSdkLevel.value()
191 << " keepRawValues=" << keepRawValues;
192 }
193 context->getDiagnostics()->note(msg);
194 }
195
196 if (writer->startEntry(path, ArchiveEntry::kCompress)) {
197 if (writer->writeEntry(buffer)) {
198 if (writer->finishEntry()) {
199 return true;
200 }
201 }
202 }
203 context->getDiagnostics()->error(DiagMessage() << "failed to write " << path
204 << " to archive");
205 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800206}
207
Adam Lesinski355f2852016-02-13 20:26:45 -0800208static std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700209 const void* data,
210 size_t len,
Adam Lesinski355f2852016-02-13 20:26:45 -0800211 IDiagnostics* diag) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700212 pb::ResourceTable pbTable;
213 if (!pbTable.ParseFromArray(data, len)) {
214 diag->error(DiagMessage(source) << "invalid compiled table");
215 return {};
216 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800217
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700218 std::unique_ptr<ResourceTable> table =
219 deserializeTableFromPb(pbTable, source, diag);
220 if (!table) {
221 return {};
222 }
223 return table;
Adam Lesinski355f2852016-02-13 20:26:45 -0800224}
225
226/**
227 * Inflates an XML file from the source path.
228 */
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700229static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path,
230 IDiagnostics* diag) {
231 std::ifstream fin(path, std::ifstream::binary);
232 if (!fin) {
233 diag->error(DiagMessage(path) << strerror(errno));
234 return {};
235 }
236 return xml::inflate(&fin, diag, Source(path));
Adam Lesinski355f2852016-02-13 20:26:45 -0800237}
238
Adam Lesinski355f2852016-02-13 20:26:45 -0800239struct ResourceFileFlattenerOptions {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700240 bool noAutoVersion = false;
241 bool noVersionVectors = false;
242 bool noXmlNamespaces = false;
243 bool keepRawValues = false;
244 bool doNotCompressAnything = false;
245 bool updateProguardSpec = false;
246 std::unordered_set<std::string> extensionsToNotCompress;
Adam Lesinski355f2852016-02-13 20:26:45 -0800247};
248
249class ResourceFileFlattener {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700250 public:
251 ResourceFileFlattener(const ResourceFileFlattenerOptions& options,
252 IAaptContext* context, proguard::KeepSet* keepSet)
253 : mOptions(options), mContext(context), mKeepSet(keepSet) {}
Adam Lesinski355f2852016-02-13 20:26:45 -0800254
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700255 bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter);
Adam Lesinski355f2852016-02-13 20:26:45 -0800256
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700257 private:
258 struct FileOperation {
259 ConfigDescription config;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700260
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700261 // The entry this file came from.
262 const ResourceEntry* entry;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700263
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700264 // The file to copy as-is.
265 io::IFile* fileToCopy;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700266
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700267 // The XML to process and flatten.
268 std::unique_ptr<xml::XmlResource> xmlToFlatten;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700269
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700270 // The destination to write this file to.
271 std::string dstPath;
272 bool skipVersion = false;
273 };
Adam Lesinski355f2852016-02-13 20:26:45 -0800274
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700275 uint32_t getCompressionFlags(const StringPiece& str);
Adam Lesinski355f2852016-02-13 20:26:45 -0800276
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700277 bool linkAndVersionXmlFile(ResourceTable* table, FileOperation* fileOp,
278 std::queue<FileOperation>* outFileOpQueue);
Adam Lesinski355f2852016-02-13 20:26:45 -0800279
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700280 ResourceFileFlattenerOptions mOptions;
281 IAaptContext* mContext;
282 proguard::KeepSet* mKeepSet;
Adam Lesinski355f2852016-02-13 20:26:45 -0800283};
284
285uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700286 if (mOptions.doNotCompressAnything) {
287 return 0;
288 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800289
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700290 for (const std::string& extension : mOptions.extensionsToNotCompress) {
291 if (util::stringEndsWith(str, extension)) {
292 return 0;
Adam Lesinski355f2852016-02-13 20:26:45 -0800293 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700294 }
295 return ArchiveEntry::kCompress;
Adam Lesinski355f2852016-02-13 20:26:45 -0800296}
297
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700298bool ResourceFileFlattener::linkAndVersionXmlFile(
299 ResourceTable* table, FileOperation* fileOp,
300 std::queue<FileOperation>* outFileOpQueue) {
301 xml::XmlResource* doc = fileOp->xmlToFlatten.get();
302 const Source& src = doc->file.source;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700303
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700304 if (mContext->verbose()) {
305 mContext->getDiagnostics()->note(DiagMessage() << "linking " << src.path);
306 }
307
308 XmlReferenceLinker xmlLinker;
309 if (!xmlLinker.consume(mContext, doc)) {
310 return false;
311 }
312
313 if (mOptions.updateProguardSpec &&
314 !proguard::collectProguardRules(src, doc, mKeepSet)) {
315 return false;
316 }
317
318 if (mOptions.noXmlNamespaces) {
319 XmlNamespaceRemover namespaceRemover;
320 if (!namespaceRemover.consume(mContext, doc)) {
321 return false;
Adam Lesinski355f2852016-02-13 20:26:45 -0800322 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700323 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800324
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700325 if (!mOptions.noAutoVersion) {
326 if (mOptions.noVersionVectors) {
327 // Skip this if it is a vector or animated-vector.
328 xml::Element* el = xml::findRootElement(doc);
329 if (el && el->namespaceUri.empty()) {
330 if (el->name == "vector" || el->name == "animated-vector") {
331 // We are NOT going to version this file.
332 fileOp->skipVersion = true;
333 return true;
Alexandria Cornwalla7cc3f12016-08-16 13:33:32 -0700334 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700335 }
Alexandria Cornwalla7cc3f12016-08-16 13:33:32 -0700336 }
337
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700338 const ConfigDescription& config = fileOp->config;
339
340 // Find the first SDK level used that is higher than this defined config and
341 // not superseded by a lower or equal SDK level resource.
342 const int minSdkVersion = mContext->getMinSdkVersion();
343 for (int sdkLevel : xmlLinker.getSdkLevels()) {
344 if (sdkLevel > minSdkVersion && sdkLevel > config.sdkVersion) {
345 if (!shouldGenerateVersionedResource(fileOp->entry, config, sdkLevel)) {
346 // If we shouldn't generate a versioned resource, stop checking.
347 break;
Adam Lesinski626a69f2016-03-03 10:09:26 -0800348 }
349
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700350 ResourceFile versionedFileDesc = doc->file;
351 versionedFileDesc.config.sdkVersion = (uint16_t)sdkLevel;
Adam Lesinski5eeaadd2016-08-25 12:26:56 -0700352
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700353 FileOperation newFileOp;
354 newFileOp.xmlToFlatten = util::make_unique<xml::XmlResource>(
355 versionedFileDesc, doc->root->clone());
356 newFileOp.config = versionedFileDesc.config;
357 newFileOp.entry = fileOp->entry;
358 newFileOp.dstPath = ResourceUtils::buildResourceFileName(
359 versionedFileDesc, mContext->getNameMangler());
Adam Lesinski355f2852016-02-13 20:26:45 -0800360
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700361 if (mContext->verbose()) {
362 mContext->getDiagnostics()->note(
363 DiagMessage(versionedFileDesc.source)
364 << "auto-versioning resource from config '" << config << "' -> '"
365 << versionedFileDesc.config << "'");
Adam Lesinski355f2852016-02-13 20:26:45 -0800366 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700367
368 bool added = table->addFileReferenceAllowMangled(
369 versionedFileDesc.name, versionedFileDesc.config,
370 versionedFileDesc.source, newFileOp.dstPath, nullptr,
371 mContext->getDiagnostics());
372 if (!added) {
373 return false;
374 }
375
376 outFileOpQueue->push(std::move(newFileOp));
377 break;
378 }
Adam Lesinski355f2852016-02-13 20:26:45 -0800379 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700380 }
381 return true;
Adam Lesinski355f2852016-02-13 20:26:45 -0800382}
383
384/**
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700385 * Do not insert or remove any resources while executing in this function. It
386 * will
Adam Lesinski355f2852016-02-13 20:26:45 -0800387 * corrupt the iteration order.
388 */
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700389bool ResourceFileFlattener::flatten(ResourceTable* table,
390 IArchiveWriter* archiveWriter) {
391 bool error = false;
392 std::map<std::pair<ConfigDescription, StringPiece>, FileOperation>
393 configSortedFiles;
Adam Lesinski355f2852016-02-13 20:26:45 -0800394
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700395 for (auto& pkg : table->packages) {
396 for (auto& type : pkg->types) {
397 // Sort by config and name, so that we get better locality in the zip
398 // file.
399 configSortedFiles.clear();
400 std::queue<FileOperation> fileOperations;
Adam Lesinski355f2852016-02-13 20:26:45 -0800401
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700402 // Populate the queue with all files in the ResourceTable.
403 for (auto& entry : type->entries) {
404 for (auto& configValue : entry->values) {
405 FileReference* fileRef =
406 valueCast<FileReference>(configValue->value.get());
407 if (!fileRef) {
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700408 continue;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700409 }
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700410
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700411 io::IFile* file = fileRef->file;
412 if (!file) {
413 mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource())
414 << "file not found");
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700415 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700416 }
417
418 FileOperation fileOp;
419 fileOp.entry = entry.get();
420 fileOp.dstPath = *fileRef->path;
421 fileOp.config = configValue->config;
422
423 const StringPiece srcPath = file->getSource().path;
424 if (type->type != ResourceType::kRaw &&
425 (util::stringEndsWith(srcPath, ".xml.flat") ||
426 util::stringEndsWith(srcPath, ".xml"))) {
427 std::unique_ptr<io::IData> data = file->openAsData();
428 if (!data) {
429 mContext->getDiagnostics()->error(DiagMessage(file->getSource())
430 << "failed to open file");
431 return false;
432 }
433
434 fileOp.xmlToFlatten =
435 xml::inflate(data->data(), data->size(),
436 mContext->getDiagnostics(), file->getSource());
437
438 if (!fileOp.xmlToFlatten) {
439 return false;
440 }
441
442 fileOp.xmlToFlatten->file.config = configValue->config;
443 fileOp.xmlToFlatten->file.source = fileRef->getSource();
444 fileOp.xmlToFlatten->file.name =
445 ResourceName(pkg->name, type->type, entry->name);
446
447 // Enqueue the XML files to be processed.
448 fileOperations.push(std::move(fileOp));
449 } else {
450 fileOp.fileToCopy = file;
451
452 // NOTE(adamlesinski): Explicitly construct a StringPiece here, or
453 // else
454 // we end up copying the string in the std::make_pair() method, then
455 // creating a StringPiece from the copy, which would cause us to end
456 // up
457 // referencing garbage in the map.
458 const StringPiece entryName(entry->name);
459 configSortedFiles[std::make_pair(configValue->config, entryName)] =
460 std::move(fileOp);
461 }
462 }
463 }
464
465 // Now process the XML queue
466 for (; !fileOperations.empty(); fileOperations.pop()) {
467 FileOperation& fileOp = fileOperations.front();
468
469 if (!linkAndVersionXmlFile(table, &fileOp, &fileOperations)) {
470 error = true;
471 continue;
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700472 }
473
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700474 // NOTE(adamlesinski): Explicitly construct a StringPiece here, or else
475 // we end up copying the string in the std::make_pair() method, then
476 // creating
477 // a StringPiece from the copy, which would cause us to end up
478 // referencing
479 // garbage in the map.
480 const StringPiece entryName(fileOp.entry->name);
481 configSortedFiles[std::make_pair(fileOp.config, entryName)] =
482 std::move(fileOp);
483 }
484
485 if (error) {
486 return false;
487 }
488
489 // Now flatten the sorted values.
490 for (auto& mapEntry : configSortedFiles) {
491 const ConfigDescription& config = mapEntry.first.first;
492 const FileOperation& fileOp = mapEntry.second;
493
494 if (fileOp.xmlToFlatten) {
495 Maybe<size_t> maxSdkLevel;
496 if (!mOptions.noAutoVersion && !fileOp.skipVersion) {
497 maxSdkLevel =
498 std::max<size_t>(std::max<size_t>(config.sdkVersion, 1u),
499 mContext->getMinSdkVersion());
500 }
501
502 bool result =
503 flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel,
504 mOptions.keepRawValues, archiveWriter, mContext);
505 if (!result) {
506 error = true;
507 }
508 } else {
509 bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath,
510 getCompressionFlags(fileOp.dstPath),
511 archiveWriter, mContext);
512 if (!result) {
513 error = true;
514 }
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700515 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700516 }
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700517 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700518 }
519 return !error;
520}
521
522static bool writeStableIdMapToPath(
523 IDiagnostics* diag,
524 const std::unordered_map<ResourceName, ResourceId>& idMap,
525 const std::string& idMapPath) {
526 std::ofstream fout(idMapPath, std::ofstream::binary);
527 if (!fout) {
528 diag->error(DiagMessage(idMapPath) << strerror(errno));
529 return false;
530 }
531
532 for (const auto& entry : idMap) {
533 const ResourceName& name = entry.first;
534 const ResourceId& id = entry.second;
535 fout << name << " = " << id << "\n";
536 }
537
538 if (!fout) {
539 diag->error(DiagMessage(idMapPath) << "failed writing to file: "
540 << strerror(errno));
541 return false;
542 }
543
544 return true;
545}
546
547static bool loadStableIdMap(
548 IDiagnostics* diag, const std::string& path,
549 std::unordered_map<ResourceName, ResourceId>* outIdMap) {
550 std::string content;
551 if (!android::base::ReadFileToString(path, &content)) {
552 diag->error(DiagMessage(path) << "failed reading stable ID file");
553 return false;
554 }
555
556 outIdMap->clear();
557 size_t lineNo = 0;
558 for (StringPiece line : util::tokenize(content, '\n')) {
559 lineNo++;
560 line = util::trimWhitespace(line);
561 if (line.empty()) {
562 continue;
563 }
564
565 auto iter = std::find(line.begin(), line.end(), '=');
566 if (iter == line.end()) {
567 diag->error(DiagMessage(Source(path, lineNo)) << "missing '='");
568 return false;
569 }
570
571 ResourceNameRef name;
572 StringPiece resNameStr =
573 util::trimWhitespace(line.substr(0, std::distance(line.begin(), iter)));
574 if (!ResourceUtils::parseResourceName(resNameStr, &name)) {
575 diag->error(DiagMessage(Source(path, lineNo)) << "invalid resource name '"
576 << resNameStr << "'");
577 return false;
578 }
579
580 const size_t resIdStartIdx = std::distance(line.begin(), iter) + 1;
581 const size_t resIdStrLen = line.size() - resIdStartIdx;
582 StringPiece resIdStr =
583 util::trimWhitespace(line.substr(resIdStartIdx, resIdStrLen));
584
585 Maybe<ResourceId> maybeId = ResourceUtils::parseResourceId(resIdStr);
586 if (!maybeId) {
587 diag->error(DiagMessage(Source(path, lineNo)) << "invalid resource ID '"
588 << resIdStr << "'");
589 return false;
590 }
591
592 (*outIdMap)[name.toResourceName()] = maybeId.value();
593 }
594 return true;
Adam Lesinskibf0bd0f2016-06-01 15:31:50 -0700595}
596
Adam Lesinski36c73a52016-08-11 13:39:24 -0700597static bool parseSplitParameter(const StringPiece& arg, IDiagnostics* diag,
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700598 std::string* outPath,
599 SplitConstraints* outSplit) {
600 std::vector<std::string> parts = util::split(arg, ':');
601 if (parts.size() != 2) {
602 diag->error(DiagMessage() << "invalid split parameter '" << arg << "'");
603 diag->note(
604 DiagMessage()
605 << "should be --split path/to/output.apk:<config>[,<config>...]");
606 return false;
607 }
608 *outPath = parts[0];
609 std::vector<ConfigDescription> configs;
610 for (const StringPiece& configStr : util::tokenize(parts[1], ',')) {
611 configs.push_back({});
612 if (!ConfigDescription::parse(configStr, &configs.back())) {
613 diag->error(DiagMessage() << "invalid config '" << configStr
614 << "' in split parameter '" << arg << "'");
615 return false;
Adam Lesinski36c73a52016-08-11 13:39:24 -0700616 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700617 }
618 outSplit->configs.insert(configs.begin(), configs.end());
619 return true;
Adam Lesinski36c73a52016-08-11 13:39:24 -0700620}
621
Adam Lesinskifb48d292015-11-07 15:52:13 -0800622class LinkCommand {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700623 public:
624 LinkCommand(LinkContext* context, const LinkOptions& options)
625 : mOptions(options),
626 mContext(context),
627 mFinalTable(),
628 mFileCollection(util::make_unique<io::FileCollection>()) {}
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700629
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700630 /**
631 * Creates a SymbolTable that loads symbols from the various APKs and caches
632 * the
633 * results for faster lookup.
634 */
635 bool loadSymbolsFromIncludePaths() {
636 std::unique_ptr<AssetManagerSymbolSource> assetSource =
637 util::make_unique<AssetManagerSymbolSource>();
638 for (const std::string& path : mOptions.includePaths) {
639 if (mContext->verbose()) {
640 mContext->getDiagnostics()->note(DiagMessage(path)
641 << "loading include path");
642 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700643
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700644 // First try to load the file as a static lib.
645 std::string errorStr;
646 std::unique_ptr<ResourceTable> staticInclude =
647 loadStaticLibrary(path, &errorStr);
648 if (staticInclude) {
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700649 if (!mOptions.staticLib) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700650 // Can't include static libraries when not building a static library.
651 mContext->getDiagnostics()->error(
652 DiagMessage(path)
653 << "can't include static library when building app");
654 return false;
655 }
656
657 // If we are using --no-static-lib-packages, we need to rename the
658 // package of this
659 // table to our compilation package.
660 if (mOptions.noStaticLibPackages) {
661 if (ResourceTablePackage* pkg =
662 staticInclude->findPackageById(0x7f)) {
663 pkg->name = mContext->getCompilationPackage();
664 }
665 }
666
667 mContext->getExternalSymbols()->appendSource(
668 util::make_unique<ResourceTableSymbolSource>(staticInclude.get()));
669
670 mStaticTableIncludes.push_back(std::move(staticInclude));
671
672 } else if (!errorStr.empty()) {
673 // We had an error with reading, so fail.
674 mContext->getDiagnostics()->error(DiagMessage(path) << errorStr);
675 return false;
676 }
677
678 if (!assetSource->addAssetPath(path)) {
679 mContext->getDiagnostics()->error(DiagMessage(path)
680 << "failed to load include path");
681 return false;
682 }
683 }
684
685 mContext->getExternalSymbols()->appendSource(std::move(assetSource));
686 return true;
687 }
688
689 Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes,
690 IDiagnostics* diag) {
691 // Make sure the first element is <manifest> with package attribute.
692 if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
693 AppInfo appInfo;
694
695 if (!manifestEl->namespaceUri.empty() || manifestEl->name != "manifest") {
696 diag->error(DiagMessage(xmlRes->file.source)
697 << "root tag must be <manifest>");
698 return {};
699 }
700
701 xml::Attribute* packageAttr = manifestEl->findAttribute({}, "package");
702 if (!packageAttr) {
703 diag->error(DiagMessage(xmlRes->file.source)
704 << "<manifest> must have a 'package' attribute");
705 return {};
706 }
707
708 appInfo.package = packageAttr->value;
709
710 if (xml::Attribute* versionCodeAttr =
711 manifestEl->findAttribute(xml::kSchemaAndroid, "versionCode")) {
712 Maybe<uint32_t> maybeCode =
713 ResourceUtils::parseInt(versionCodeAttr->value);
714 if (!maybeCode) {
715 diag->error(
716 DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber))
717 << "invalid android:versionCode '" << versionCodeAttr->value
718 << "'");
719 return {};
720 }
721 appInfo.versionCode = maybeCode.value();
722 }
723
724 if (xml::Attribute* revisionCodeAttr =
725 manifestEl->findAttribute(xml::kSchemaAndroid, "revisionCode")) {
726 Maybe<uint32_t> maybeCode =
727 ResourceUtils::parseInt(revisionCodeAttr->value);
728 if (!maybeCode) {
729 diag->error(
730 DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber))
731 << "invalid android:revisionCode '" << revisionCodeAttr->value
732 << "'");
733 return {};
734 }
735 appInfo.revisionCode = maybeCode.value();
736 }
737
738 if (xml::Element* usesSdkEl = manifestEl->findChild({}, "uses-sdk")) {
739 if (xml::Attribute* minSdk = usesSdkEl->findAttribute(
740 xml::kSchemaAndroid, "minSdkVersion")) {
741 appInfo.minSdkVersion = minSdk->value;
742 }
743 }
744
745 return appInfo;
746 }
747 return {};
748 }
749
750 /**
751 * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it
752 * linked.
753 * Postcondition: ResourceTable has only one package left. All others are
754 * stripped, or there
755 * is an error and false is returned.
756 */
757 bool verifyNoExternalPackages() {
758 auto isExtPackageFunc =
759 [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool {
760 return mContext->getCompilationPackage() != pkg->name || !pkg->id ||
761 pkg->id.value() != mContext->getPackageId();
762 };
763
764 bool error = false;
765 for (const auto& package : mFinalTable.packages) {
766 if (isExtPackageFunc(package)) {
767 // We have a package that is not related to the one we're building!
768 for (const auto& type : package->types) {
769 for (const auto& entry : type->entries) {
770 ResourceNameRef resName(package->name, type->type, entry->name);
771
772 for (const auto& configValue : entry->values) {
773 // Special case the occurrence of an ID that is being generated
774 // for the
775 // 'android' package. This is due to legacy reasons.
776 if (valueCast<Id>(configValue->value.get()) &&
777 package->name == "android") {
778 mContext->getDiagnostics()->warn(
779 DiagMessage(configValue->value->getSource())
780 << "generated id '" << resName << "' for external package '"
781 << package->name << "'");
782 } else {
Adam Lesinski6a008172016-02-02 17:02:58 -0800783 mContext->getDiagnostics()->error(
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700784 DiagMessage(configValue->value->getSource())
785 << "defined resource '" << resName
786 << "' for external package '" << package->name << "'");
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700787 error = true;
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700788 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700789 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700790 }
791 }
792 }
793 }
794
795 auto newEndIter =
796 std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(),
797 isExtPackageFunc);
798 mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end());
799 return !error;
800 }
801
802 /**
803 * Returns true if no IDs have been set, false otherwise.
804 */
805 bool verifyNoIdsSet() {
806 for (const auto& package : mFinalTable.packages) {
807 for (const auto& type : package->types) {
808 if (type->id) {
809 mContext->getDiagnostics()->error(
810 DiagMessage() << "type " << type->type << " has ID " << std::hex
811 << (int)type->id.value() << std::dec
812 << " assigned");
813 return false;
Adam Lesinski1ab598f2015-08-14 14:26:04 -0700814 }
815
Adam Lesinskicacb28f2016-10-19 12:18:14 -0700816 for (const auto& entry : type->entries) {
817 if (entry->id) {
818 ResourceNameRef resName(package->name, type->type, entry->name);
819 mContext->getDiagnostics()->error(
820 DiagMessage() << "entry " << resName << " has ID " << std::hex
821 << (int)entry->id.value() << std::dec
822 << " assigned");
823 return false;
824 }
825 }
826 }
827 }
828 return true;
829 }
830
831 std::unique_ptr<IArchiveWriter> makeArchiveWriter(const StringPiece& out) {
832 if (mOptions.outputToDirectory) {
833 return createDirectoryArchiveWriter(mContext->getDiagnostics(), out);
834 } else {
835 return createZipFileArchiveWriter(mContext->getDiagnostics(), out);
836 }
837 }
838
839 bool flattenTable(ResourceTable* table, IArchiveWriter* writer) {
840 BigBuffer buffer(1024);
841 TableFlattener flattener(&buffer);
842 if (!flattener.consume(mContext, table)) {
843 return false;
844 }
845
846 if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
847 if (writer->writeEntry(buffer)) {
848 if (writer->finishEntry()) {
849 return true;
850 }
851 }
852 }
853
854 mContext->getDiagnostics()->error(
855 DiagMessage() << "failed to write resources.arsc to archive");
856 return false;
857 }
858
859 bool flattenTableToPb(ResourceTable* table, IArchiveWriter* writer) {
860 // Create the file/zip entry.
861 if (!writer->startEntry("resources.arsc.flat", 0)) {
862 mContext->getDiagnostics()->error(DiagMessage() << "failed to open");
863 return false;
864 }
865
866 // Make sure CopyingOutputStreamAdaptor is deleted before we call
867 // writer->finishEntry().
868 {
869 // Wrap our IArchiveWriter with an adaptor that implements the
870 // ZeroCopyOutputStream
871 // interface.
872 CopyingOutputStreamAdaptor adaptor(writer);
873
874 std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table);
875 if (!pbTable->SerializeToZeroCopyStream(&adaptor)) {
876 mContext->getDiagnostics()->error(DiagMessage() << "failed to write");
877 return false;
878 }
879 }
880
881 if (!writer->finishEntry()) {
882 mContext->getDiagnostics()->error(DiagMessage()
883 << "failed to finish entry");
884 return false;
885 }
886 return true;
887 }
888
889 bool writeJavaFile(ResourceTable* table,
890 const StringPiece& packageNameToGenerate,
891 const StringPiece& outPackage,
892 const JavaClassGeneratorOptions& javaOptions) {
893 if (!mOptions.generateJavaClassPath) {
894 return true;
895 }
896
897 std::string outPath = mOptions.generateJavaClassPath.value();
898 file::appendPath(&outPath, file::packageToPath(outPackage));
899 if (!file::mkdirs(outPath)) {
900 mContext->getDiagnostics()->error(
901 DiagMessage() << "failed to create directory '" << outPath << "'");
902 return false;
903 }
904
905 file::appendPath(&outPath, "R.java");
906
907 std::ofstream fout(outPath, std::ofstream::binary);
908 if (!fout) {
909 mContext->getDiagnostics()->error(DiagMessage()
910 << "failed writing to '" << outPath
911 << "': " << strerror(errno));
912 return false;
913 }
914
915 JavaClassGenerator generator(mContext, table, javaOptions);
916 if (!generator.generate(packageNameToGenerate, outPackage, &fout)) {
917 mContext->getDiagnostics()->error(DiagMessage(outPath)
918 << generator.getError());
919 return false;
920 }
921
922 if (!fout) {
923 mContext->getDiagnostics()->error(DiagMessage()
924 << "failed writing to '" << outPath
925 << "': " << strerror(errno));
926 }
927 return true;
928 }
929
930 bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
931 if (!mOptions.generateJavaClassPath) {
932 return true;
933 }
934
935 std::unique_ptr<ClassDefinition> manifestClass =
936 generateManifestClass(mContext->getDiagnostics(), manifestXml);
937
938 if (!manifestClass) {
939 // Something bad happened, but we already logged it, so exit.
940 return false;
941 }
942
943 if (manifestClass->empty()) {
944 // Empty Manifest class, no need to generate it.
945 return true;
946 }
947
948 // Add any JavaDoc annotations to the generated class.
949 for (const std::string& annotation : mOptions.javadocAnnotations) {
950 std::string properAnnotation = "@";
951 properAnnotation += annotation;
952 manifestClass->getCommentBuilder()->appendComment(properAnnotation);
953 }
954
955 const std::string& packageUtf8 = mContext->getCompilationPackage();
956
957 std::string outPath = mOptions.generateJavaClassPath.value();
958 file::appendPath(&outPath, file::packageToPath(packageUtf8));
959
960 if (!file::mkdirs(outPath)) {
961 mContext->getDiagnostics()->error(
962 DiagMessage() << "failed to create directory '" << outPath << "'");
963 return false;
964 }
965
966 file::appendPath(&outPath, "Manifest.java");
967
968 std::ofstream fout(outPath, std::ofstream::binary);
969 if (!fout) {
970 mContext->getDiagnostics()->error(DiagMessage()
971 << "failed writing to '" << outPath
972 << "': " << strerror(errno));
973 return false;
974 }
975
976 if (!ClassDefinition::writeJavaFile(manifestClass.get(), packageUtf8, true,
977 &fout)) {
978 mContext->getDiagnostics()->error(DiagMessage()
979 << "failed writing to '" << outPath
980 << "': " << strerror(errno));
981 return false;
982 }
983 return true;
984 }
985
986 bool writeProguardFile(const Maybe<std::string>& out,
987 const proguard::KeepSet& keepSet) {
988 if (!out) {
989 return true;
990 }
991
992 const std::string& outPath = out.value();
993 std::ofstream fout(outPath, std::ofstream::binary);
994 if (!fout) {
995 mContext->getDiagnostics()->error(DiagMessage()
996 << "failed to open '" << outPath
997 << "': " << strerror(errno));
998 return false;
999 }
1000
1001 proguard::writeKeepSet(&fout, keepSet);
1002 if (!fout) {
1003 mContext->getDiagnostics()->error(DiagMessage()
1004 << "failed writing to '" << outPath
1005 << "': " << strerror(errno));
1006 return false;
1007 }
1008 return true;
1009 }
1010
1011 std::unique_ptr<ResourceTable> loadStaticLibrary(const std::string& input,
1012 std::string* outError) {
1013 std::unique_ptr<io::ZipFileCollection> collection =
1014 io::ZipFileCollection::create(input, outError);
1015 if (!collection) {
1016 return {};
1017 }
1018 return loadTablePbFromCollection(collection.get());
1019 }
1020
1021 std::unique_ptr<ResourceTable> loadTablePbFromCollection(
1022 io::IFileCollection* collection) {
1023 io::IFile* file = collection->findFile("resources.arsc.flat");
1024 if (!file) {
1025 return {};
1026 }
1027
1028 std::unique_ptr<io::IData> data = file->openAsData();
1029 return loadTableFromPb(file->getSource(), data->data(), data->size(),
1030 mContext->getDiagnostics());
1031 }
1032
1033 bool mergeStaticLibrary(const std::string& input, bool override) {
1034 if (mContext->verbose()) {
1035 mContext->getDiagnostics()->note(DiagMessage()
1036 << "merging static library " << input);
1037 }
1038
1039 std::string errorStr;
1040 std::unique_ptr<io::ZipFileCollection> collection =
1041 io::ZipFileCollection::create(input, &errorStr);
1042 if (!collection) {
1043 mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
1044 return false;
1045 }
1046
1047 std::unique_ptr<ResourceTable> table =
1048 loadTablePbFromCollection(collection.get());
1049 if (!table) {
1050 mContext->getDiagnostics()->error(DiagMessage(input)
1051 << "invalid static library");
1052 return false;
1053 }
1054
1055 ResourceTablePackage* pkg = table->findPackageById(0x7f);
1056 if (!pkg) {
1057 mContext->getDiagnostics()->error(DiagMessage(input)
1058 << "static library has no package");
1059 return false;
1060 }
1061
1062 bool result;
1063 if (mOptions.noStaticLibPackages) {
1064 // Merge all resources as if they were in the compilation package. This is
1065 // the old
1066 // behaviour of aapt.
1067
1068 // Add the package to the set of --extra-packages so we emit an R.java for
1069 // each
1070 // library package.
1071 if (!pkg->name.empty()) {
1072 mOptions.extraJavaPackages.insert(pkg->name);
1073 }
1074
1075 pkg->name = "";
1076 if (override) {
1077 result = mTableMerger->mergeOverlay(Source(input), table.get(),
1078 collection.get());
1079 } else {
1080 result =
1081 mTableMerger->merge(Source(input), table.get(), collection.get());
1082 }
1083
1084 } else {
1085 // This is the proper way to merge libraries, where the package name is
1086 // preserved
1087 // and resource names are mangled.
1088 result = mTableMerger->mergeAndMangle(Source(input), pkg->name,
1089 table.get(), collection.get());
1090 }
1091
1092 if (!result) {
1093 return false;
1094 }
1095
1096 // Make sure to move the collection into the set of IFileCollections.
1097 mCollections.push_back(std::move(collection));
1098 return true;
1099 }
1100
1101 bool mergeResourceTable(io::IFile* file, bool override) {
1102 if (mContext->verbose()) {
1103 mContext->getDiagnostics()->note(
1104 DiagMessage() << "merging resource table " << file->getSource());
1105 }
1106
1107 std::unique_ptr<io::IData> data = file->openAsData();
1108 if (!data) {
1109 mContext->getDiagnostics()->error(DiagMessage(file->getSource())
1110 << "failed to open file");
1111 return false;
1112 }
1113
1114 std::unique_ptr<ResourceTable> table =
1115 loadTableFromPb(file->getSource(), data->data(), data->size(),
1116 mContext->getDiagnostics());
1117 if (!table) {
1118 return false;
1119 }
1120
1121 bool result = false;
1122 if (override) {
1123 result = mTableMerger->mergeOverlay(file->getSource(), table.get());
1124 } else {
1125 result = mTableMerger->merge(file->getSource(), table.get());
1126 }
1127 return result;
1128 }
1129
1130 bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc,
1131 bool override) {
1132 if (mContext->verbose()) {
1133 mContext->getDiagnostics()->note(
1134 DiagMessage() << "merging '" << fileDesc->name
1135 << "' from compiled file " << file->getSource());
1136 }
1137
1138 bool result = false;
1139 if (override) {
1140 result = mTableMerger->mergeFileOverlay(*fileDesc, file);
1141 } else {
1142 result = mTableMerger->mergeFile(*fileDesc, file);
1143 }
1144
1145 if (!result) {
1146 return false;
1147 }
1148
1149 // Add the exports of this file to the table.
1150 for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
1151 if (exportedSymbol.name.package.empty()) {
1152 exportedSymbol.name.package = mContext->getCompilationPackage();
1153 }
1154
1155 ResourceNameRef resName = exportedSymbol.name;
1156
1157 Maybe<ResourceName> mangledName =
1158 mContext->getNameMangler()->mangleName(exportedSymbol.name);
1159 if (mangledName) {
1160 resName = mangledName.value();
1161 }
1162
1163 std::unique_ptr<Id> id = util::make_unique<Id>();
1164 id->setSource(fileDesc->source.withLine(exportedSymbol.line));
1165 bool result = mFinalTable.addResourceAllowMangled(
1166 resName, ConfigDescription::defaultConfig(), std::string(),
1167 std::move(id), mContext->getDiagnostics());
1168 if (!result) {
1169 return false;
1170 }
1171 }
1172 return true;
1173 }
1174
1175 /**
1176 * Takes a path to load as a ZIP file and merges the files within into the
1177 * master ResourceTable.
1178 * If override is true, conflicting resources are allowed to override each
1179 * other, in order of
1180 * last seen.
1181 *
1182 * An io::IFileCollection is created from the ZIP file and added to the set of
1183 * io::IFileCollections that are open.
1184 */
1185 bool mergeArchive(const std::string& input, bool override) {
1186 if (mContext->verbose()) {
1187 mContext->getDiagnostics()->note(DiagMessage() << "merging archive "
1188 << input);
1189 }
1190
1191 std::string errorStr;
1192 std::unique_ptr<io::ZipFileCollection> collection =
1193 io::ZipFileCollection::create(input, &errorStr);
1194 if (!collection) {
1195 mContext->getDiagnostics()->error(DiagMessage(input) << errorStr);
1196 return false;
1197 }
1198
1199 bool error = false;
1200 for (auto iter = collection->iterator(); iter->hasNext();) {
1201 if (!mergeFile(iter->next(), override)) {
1202 error = true;
1203 }
1204 }
1205
1206 // Make sure to move the collection into the set of IFileCollections.
1207 mCollections.push_back(std::move(collection));
1208 return !error;
1209 }
1210
1211 /**
1212 * Takes a path to load and merge into the master ResourceTable. If override
1213 * is true,
1214 * conflicting resources are allowed to override each other, in order of last
1215 * seen.
1216 *
1217 * If the file path ends with .flata, .jar, .jack, or .zip the file is treated
1218 * as ZIP archive
1219 * and the files within are merged individually.
1220 *
1221 * Otherwise the files is processed on its own.
1222 */
1223 bool mergePath(const std::string& path, bool override) {
1224 if (util::stringEndsWith(path, ".flata") ||
1225 util::stringEndsWith(path, ".jar") ||
1226 util::stringEndsWith(path, ".jack") ||
1227 util::stringEndsWith(path, ".zip")) {
1228 return mergeArchive(path, override);
1229 } else if (util::stringEndsWith(path, ".apk")) {
1230 return mergeStaticLibrary(path, override);
1231 }
1232
1233 io::IFile* file = mFileCollection->insertFile(path);
1234 return mergeFile(file, override);
1235 }
1236
1237 /**
1238 * Takes a file to load and merge into the master ResourceTable. If override
1239 * is true,
1240 * conflicting resources are allowed to override each other, in order of last
1241 * seen.
1242 *
1243 * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and
1244 * merged into the
1245 * master ResourceTable. If the file ends with .flat, then it is treated like
1246 * a compiled file
1247 * and the header data is read and merged into the final ResourceTable.
1248 *
1249 * All other file types are ignored. This is because these files could be
1250 * coming from a zip,
1251 * where we could have other files like classes.dex.
1252 */
1253 bool mergeFile(io::IFile* file, bool override) {
1254 const Source& src = file->getSource();
1255 if (util::stringEndsWith(src.path, ".arsc.flat")) {
1256 return mergeResourceTable(file, override);
1257
1258 } else if (util::stringEndsWith(src.path, ".flat")) {
1259 // Try opening the file and looking for an Export header.
1260 std::unique_ptr<io::IData> data = file->openAsData();
1261 if (!data) {
1262 mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open");
1263 return false;
1264 }
1265
1266 CompiledFileInputStream inputStream(data->data(), data->size());
1267 uint32_t numFiles = 0;
1268 if (!inputStream.ReadLittleEndian32(&numFiles)) {
1269 mContext->getDiagnostics()->error(DiagMessage(src)
1270 << "failed read num files");
1271 return false;
1272 }
1273
1274 for (uint32_t i = 0; i < numFiles; i++) {
1275 pb::CompiledFile compiledFile;
1276 if (!inputStream.ReadCompiledFile(&compiledFile)) {
1277 mContext->getDiagnostics()->error(
1278 DiagMessage(src) << "failed to read compiled file header");
1279 return false;
Adam Lesinski467f1712015-11-16 17:35:44 -08001280 }
1281
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001282 uint64_t offset, len;
1283 if (!inputStream.ReadDataMetaData(&offset, &len)) {
1284 mContext->getDiagnostics()->error(DiagMessage(src)
1285 << "failed to read data meta data");
1286 return false;
1287 }
1288
1289 std::unique_ptr<ResourceFile> resourceFile =
1290 deserializeCompiledFileFromPb(compiledFile, file->getSource(),
1291 mContext->getDiagnostics());
1292 if (!resourceFile) {
1293 return false;
1294 }
1295
1296 if (!mergeCompiledFile(file->createFileSegment(offset, len),
1297 resourceFile.get(), override)) {
1298 return false;
1299 }
1300 }
1301 return true;
Adam Lesinski6a396c12016-10-20 14:38:23 -07001302 } else if (util::stringEndsWith(src.path, ".xml") ||
1303 util::stringEndsWith(src.path, ".png")) {
1304 // Since AAPT compiles these file types and appends .flat to them, seeing
1305 // their raw extensions is a sign that they weren't compiled.
1306 const StringPiece fileType =
1307 util::stringEndsWith(src.path, ".xml") ? "XML" : "PNG";
1308 mContext->getDiagnostics()->error(DiagMessage(src)
1309 << "uncompiled " << fileType
1310 << " file passed as argument. Must be "
1311 "compiled first into .flat file.");
1312 return false;
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001313 }
1314
1315 // Ignore non .flat files. This could be classes.dex or something else that
1316 // happens
1317 // to be in an archive.
1318 return true;
1319 }
1320
1321 std::unique_ptr<xml::XmlResource> generateSplitManifest(
1322 const AppInfo& appInfo, const SplitConstraints& constraints) {
1323 std::unique_ptr<xml::XmlResource> doc =
1324 util::make_unique<xml::XmlResource>();
1325
1326 std::unique_ptr<xml::Namespace> namespaceAndroid =
1327 util::make_unique<xml::Namespace>();
1328 namespaceAndroid->namespaceUri = xml::kSchemaAndroid;
1329 namespaceAndroid->namespacePrefix = "android";
1330
1331 std::unique_ptr<xml::Element> manifestEl =
1332 util::make_unique<xml::Element>();
1333 manifestEl->name = "manifest";
1334 manifestEl->attributes.push_back(
1335 xml::Attribute{"", "package", appInfo.package});
1336
1337 if (appInfo.versionCode) {
1338 manifestEl->attributes.push_back(
1339 xml::Attribute{xml::kSchemaAndroid, "versionCode",
1340 std::to_string(appInfo.versionCode.value())});
1341 }
1342
1343 if (appInfo.revisionCode) {
1344 manifestEl->attributes.push_back(
1345 xml::Attribute{xml::kSchemaAndroid, "revisionCode",
1346 std::to_string(appInfo.revisionCode.value())});
1347 }
1348
1349 std::stringstream splitName;
1350 splitName << "config." << util::joiner(constraints.configs, "_");
1351
1352 manifestEl->attributes.push_back(
1353 xml::Attribute{"", "split", splitName.str()});
1354
1355 std::unique_ptr<xml::Element> applicationEl =
1356 util::make_unique<xml::Element>();
1357 applicationEl->name = "application";
1358 applicationEl->attributes.push_back(
1359 xml::Attribute{xml::kSchemaAndroid, "hasCode", "false"});
1360
1361 manifestEl->addChild(std::move(applicationEl));
1362 namespaceAndroid->addChild(std::move(manifestEl));
1363 doc->root = std::move(namespaceAndroid);
1364 return doc;
1365 }
1366
1367 /**
1368 * Writes the AndroidManifest, ResourceTable, and all XML files referenced by
1369 * the ResourceTable
1370 * to the IArchiveWriter.
1371 */
1372 bool writeApk(IArchiveWriter* writer, proguard::KeepSet* keepSet,
1373 xml::XmlResource* manifest, ResourceTable* table) {
1374 const bool keepRawValues = mOptions.staticLib;
1375 bool result = flattenXml(manifest, "AndroidManifest.xml", {}, keepRawValues,
1376 writer, mContext);
1377 if (!result) {
1378 return false;
1379 }
1380
1381 ResourceFileFlattenerOptions fileFlattenerOptions;
1382 fileFlattenerOptions.keepRawValues = keepRawValues;
1383 fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything;
1384 fileFlattenerOptions.extensionsToNotCompress =
1385 mOptions.extensionsToNotCompress;
1386 fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion;
1387 fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors;
1388 fileFlattenerOptions.noXmlNamespaces = mOptions.noXmlNamespaces;
1389 fileFlattenerOptions.updateProguardSpec =
1390 static_cast<bool>(mOptions.generateProguardRulesPath);
1391
1392 ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext,
1393 keepSet);
1394
1395 if (!fileFlattener.flatten(table, writer)) {
1396 mContext->getDiagnostics()->error(DiagMessage()
1397 << "failed linking file resources");
1398 return false;
1399 }
1400
1401 if (mOptions.staticLib) {
1402 if (!flattenTableToPb(table, writer)) {
1403 mContext->getDiagnostics()->error(
1404 DiagMessage() << "failed to write resources.arsc.flat");
1405 return false;
1406 }
1407 } else {
1408 if (!flattenTable(table, writer)) {
1409 mContext->getDiagnostics()->error(DiagMessage()
1410 << "failed to write resources.arsc");
1411 return false;
1412 }
1413 }
1414 return true;
1415 }
1416
1417 int run(const std::vector<std::string>& inputFiles) {
1418 // Load the AndroidManifest.xml
1419 std::unique_ptr<xml::XmlResource> manifestXml =
1420 loadXml(mOptions.manifestPath, mContext->getDiagnostics());
1421 if (!manifestXml) {
1422 return 1;
1423 }
1424
1425 // First extract the Package name without modifying it (via
1426 // --rename-manifest-package).
1427 if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(
1428 manifestXml.get(), mContext->getDiagnostics())) {
1429 const AppInfo& appInfo = maybeAppInfo.value();
1430 mContext->setCompilationPackage(appInfo.package);
1431 }
1432
1433 ManifestFixer manifestFixer(mOptions.manifestFixerOptions);
1434 if (!manifestFixer.consume(mContext, manifestXml.get())) {
1435 return 1;
1436 }
1437
1438 Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(
1439 manifestXml.get(), mContext->getDiagnostics());
1440 if (!maybeAppInfo) {
1441 return 1;
1442 }
1443
1444 const AppInfo& appInfo = maybeAppInfo.value();
1445 if (appInfo.minSdkVersion) {
1446 if (Maybe<int> maybeMinSdkVersion =
1447 ResourceUtils::parseSdkVersion(appInfo.minSdkVersion.value())) {
1448 mContext->setMinSdkVersion(maybeMinSdkVersion.value());
1449 }
1450 }
1451
1452 mContext->setNameManglerPolicy(
1453 NameManglerPolicy{mContext->getCompilationPackage()});
1454 if (mContext->getCompilationPackage() == "android") {
1455 mContext->setPackageId(0x01);
1456 } else {
1457 mContext->setPackageId(0x7f);
1458 }
1459
1460 if (!loadSymbolsFromIncludePaths()) {
1461 return 1;
1462 }
1463
1464 TableMergerOptions tableMergerOptions;
1465 tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay;
1466 mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable,
1467 tableMergerOptions);
1468
1469 if (mContext->verbose()) {
1470 mContext->getDiagnostics()->note(DiagMessage()
1471 << "linking package '"
1472 << mContext->getCompilationPackage()
1473 << "' with package ID " << std::hex
1474 << (int)mContext->getPackageId());
1475 }
1476
1477 for (const std::string& input : inputFiles) {
1478 if (!mergePath(input, false)) {
1479 mContext->getDiagnostics()->error(DiagMessage()
1480 << "failed parsing input");
1481 return 1;
1482 }
1483 }
1484
1485 for (const std::string& input : mOptions.overlayFiles) {
1486 if (!mergePath(input, true)) {
1487 mContext->getDiagnostics()->error(DiagMessage()
1488 << "failed parsing overlays");
1489 return 1;
1490 }
1491 }
1492
1493 if (!verifyNoExternalPackages()) {
1494 return 1;
1495 }
1496
1497 if (!mOptions.staticLib) {
1498 PrivateAttributeMover mover;
1499 if (!mover.consume(mContext, &mFinalTable)) {
1500 mContext->getDiagnostics()->error(
1501 DiagMessage() << "failed moving private attributes");
1502 return 1;
1503 }
1504
1505 // Assign IDs if we are building a regular app.
1506 IdAssigner idAssigner(&mOptions.stableIdMap);
1507 if (!idAssigner.consume(mContext, &mFinalTable)) {
1508 mContext->getDiagnostics()->error(DiagMessage()
1509 << "failed assigning IDs");
1510 return 1;
1511 }
1512
1513 // Now grab each ID and emit it as a file.
1514 if (mOptions.resourceIdMapPath) {
1515 for (auto& package : mFinalTable.packages) {
1516 for (auto& type : package->types) {
1517 for (auto& entry : type->entries) {
1518 ResourceName name(package->name, type->type, entry->name);
1519 // The IDs are guaranteed to exist.
1520 mOptions.stableIdMap[std::move(name)] = ResourceId(
1521 package->id.value(), type->id.value(), entry->id.value());
1522 }
1523 }
1524 }
1525
1526 if (!writeStableIdMapToPath(mContext->getDiagnostics(),
1527 mOptions.stableIdMap,
1528 mOptions.resourceIdMapPath.value())) {
1529 return 1;
1530 }
1531 }
1532 } else {
1533 // Static libs are merged with other apps, and ID collisions are bad, so
1534 // verify that
1535 // no IDs have been set.
1536 if (!verifyNoIdsSet()) {
1537 return 1;
1538 }
1539 }
1540
1541 // Add the names to mangle based on our source merge earlier.
1542 mContext->setNameManglerPolicy(NameManglerPolicy{
1543 mContext->getCompilationPackage(), mTableMerger->getMergedPackages()});
1544
1545 // Add our table to the symbol table.
1546 mContext->getExternalSymbols()->prependSource(
1547 util::make_unique<ResourceTableSymbolSource>(&mFinalTable));
1548
1549 ReferenceLinker linker;
1550 if (!linker.consume(mContext, &mFinalTable)) {
1551 mContext->getDiagnostics()->error(DiagMessage()
1552 << "failed linking references");
1553 return 1;
1554 }
1555
1556 if (mOptions.staticLib) {
1557 if (!mOptions.products.empty()) {
1558 mContext->getDiagnostics()
1559 ->warn(DiagMessage()
1560 << "can't select products when building static library");
1561 }
1562 } else {
1563 ProductFilter productFilter(mOptions.products);
1564 if (!productFilter.consume(mContext, &mFinalTable)) {
1565 mContext->getDiagnostics()->error(DiagMessage()
1566 << "failed stripping products");
1567 return 1;
1568 }
1569 }
1570
1571 if (!mOptions.noAutoVersion) {
1572 AutoVersioner versioner;
1573 if (!versioner.consume(mContext, &mFinalTable)) {
1574 mContext->getDiagnostics()->error(DiagMessage()
1575 << "failed versioning styles");
1576 return 1;
1577 }
1578 }
1579
1580 if (!mOptions.staticLib && mContext->getMinSdkVersion() > 0) {
1581 if (mContext->verbose()) {
1582 mContext->getDiagnostics()->note(
1583 DiagMessage() << "collapsing resource versions for minimum SDK "
1584 << mContext->getMinSdkVersion());
1585 }
1586
1587 VersionCollapser collapser;
1588 if (!collapser.consume(mContext, &mFinalTable)) {
1589 return 1;
1590 }
1591 }
1592
1593 if (!mOptions.noResourceDeduping) {
1594 ResourceDeduper deduper;
1595 if (!deduper.consume(mContext, &mFinalTable)) {
1596 mContext->getDiagnostics()->error(DiagMessage()
1597 << "failed deduping resources");
1598 return 1;
1599 }
1600 }
1601
1602 proguard::KeepSet proguardKeepSet;
1603 proguard::KeepSet proguardMainDexKeepSet;
1604
1605 if (mOptions.staticLib) {
1606 if (mOptions.tableSplitterOptions.configFilter != nullptr ||
1607 mOptions.tableSplitterOptions.preferredDensity) {
1608 mContext->getDiagnostics()
1609 ->warn(DiagMessage()
1610 << "can't strip resources when building static library");
1611 }
1612 } else {
1613 // Adjust the SplitConstraints so that their SDK version is stripped if it
1614 // is less
1615 // than or equal to the minSdk. Otherwise the resources that have had
1616 // their SDK version
1617 // stripped due to minSdk won't ever match.
1618 std::vector<SplitConstraints> adjustedConstraintsList;
1619 adjustedConstraintsList.reserve(mOptions.splitConstraints.size());
1620 for (const SplitConstraints& constraints : mOptions.splitConstraints) {
1621 SplitConstraints adjustedConstraints;
1622 for (const ConfigDescription& config : constraints.configs) {
1623 if (config.sdkVersion <= mContext->getMinSdkVersion()) {
1624 adjustedConstraints.configs.insert(config.copyWithoutSdkVersion());
1625 } else {
1626 adjustedConstraints.configs.insert(config);
1627 }
1628 }
1629 adjustedConstraintsList.push_back(std::move(adjustedConstraints));
1630 }
1631
1632 TableSplitter tableSplitter(adjustedConstraintsList,
1633 mOptions.tableSplitterOptions);
1634 if (!tableSplitter.verifySplitConstraints(mContext)) {
1635 return 1;
1636 }
1637 tableSplitter.splitTable(&mFinalTable);
1638
1639 // Now we need to write out the Split APKs.
1640 auto pathIter = mOptions.splitPaths.begin();
1641 auto splitConstraintsIter = adjustedConstraintsList.begin();
1642 for (std::unique_ptr<ResourceTable>& splitTable :
1643 tableSplitter.getSplits()) {
1644 if (mContext->verbose()) {
1645 mContext->getDiagnostics()->note(
1646 DiagMessage(*pathIter)
1647 << "generating split with configurations '"
1648 << util::joiner(splitConstraintsIter->configs, ", ") << "'");
1649 }
1650
1651 std::unique_ptr<IArchiveWriter> archiveWriter =
1652 makeArchiveWriter(*pathIter);
1653 if (!archiveWriter) {
1654 mContext->getDiagnostics()->error(DiagMessage()
1655 << "failed to create archive");
1656 return 1;
1657 }
1658
1659 // Generate an AndroidManifest.xml for each split.
1660 std::unique_ptr<xml::XmlResource> splitManifest =
1661 generateSplitManifest(appInfo, *splitConstraintsIter);
1662
1663 XmlReferenceLinker linker;
1664 if (!linker.consume(mContext, splitManifest.get())) {
1665 mContext->getDiagnostics()->error(
1666 DiagMessage() << "failed to create Split AndroidManifest.xml");
1667 return 1;
1668 }
1669
1670 if (!writeApk(archiveWriter.get(), &proguardKeepSet,
1671 splitManifest.get(), splitTable.get())) {
1672 return 1;
1673 }
1674
1675 ++pathIter;
1676 ++splitConstraintsIter;
1677 }
1678 }
1679
1680 // Start writing the base APK.
1681 std::unique_ptr<IArchiveWriter> archiveWriter =
1682 makeArchiveWriter(mOptions.outputPath);
1683 if (!archiveWriter) {
1684 mContext->getDiagnostics()->error(DiagMessage()
1685 << "failed to create archive");
1686 return 1;
1687 }
1688
1689 bool error = false;
1690 {
1691 // AndroidManifest.xml has no resource name, but the CallSite is built
1692 // from the name
1693 // (aka, which package the AndroidManifest.xml is coming from).
1694 // So we give it a package name so it can see local resources.
1695 manifestXml->file.name.package = mContext->getCompilationPackage();
1696
1697 XmlReferenceLinker manifestLinker;
1698 if (manifestLinker.consume(mContext, manifestXml.get())) {
1699 if (mOptions.generateProguardRulesPath &&
1700 !proguard::collectProguardRulesForManifest(
1701 Source(mOptions.manifestPath), manifestXml.get(),
1702 &proguardKeepSet)) {
1703 error = true;
1704 }
1705
1706 if (mOptions.generateMainDexProguardRulesPath &&
1707 !proguard::collectProguardRulesForManifest(
1708 Source(mOptions.manifestPath), manifestXml.get(),
1709 &proguardMainDexKeepSet, true)) {
1710 error = true;
Alexandria Cornwall637b4822016-08-11 09:53:16 -07001711 }
1712
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001713 if (mOptions.generateJavaClassPath) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001714 if (!writeManifestJavaFile(manifestXml.get())) {
1715 error = true;
1716 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001717 }
1718
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001719 if (mOptions.noXmlNamespaces) {
1720 // PackageParser will fail if URIs are removed from
1721 // AndroidManifest.xml.
1722 XmlNamespaceRemover namespaceRemover(true /* keepUris */);
1723 if (!namespaceRemover.consume(mContext, manifestXml.get())) {
1724 error = true;
1725 }
Rohit Agrawale49bb302016-04-22 12:27:55 -07001726 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001727 } else {
1728 error = true;
1729 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001730 }
Adam Lesinskifb48d292015-11-07 15:52:13 -08001731
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001732 if (error) {
1733 mContext->getDiagnostics()->error(DiagMessage()
1734 << "failed processing manifest");
1735 return 1;
1736 }
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001737
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001738 if (!writeApk(archiveWriter.get(), &proguardKeepSet, manifestXml.get(),
1739 &mFinalTable)) {
1740 return 1;
1741 }
Adam Lesinskifb48d292015-11-07 15:52:13 -08001742
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001743 if (mOptions.generateJavaClassPath) {
1744 JavaClassGeneratorOptions options;
1745 options.types = JavaClassGeneratorOptions::SymbolTypes::kAll;
1746 options.javadocAnnotations = mOptions.javadocAnnotations;
Adam Lesinskia6fe3452015-12-09 15:20:52 -08001747
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001748 if (mOptions.staticLib || mOptions.generateNonFinalIds) {
1749 options.useFinal = false;
1750 }
Adam Lesinski64587af2016-02-18 18:33:06 -08001751
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001752 const StringPiece actualPackage = mContext->getCompilationPackage();
1753 StringPiece outputPackage = mContext->getCompilationPackage();
1754 if (mOptions.customJavaPackage) {
1755 // Override the output java package to the custom one.
1756 outputPackage = mOptions.customJavaPackage.value();
1757 }
1758
1759 if (mOptions.privateSymbols) {
1760 // If we defined a private symbols package, we only emit Public symbols
1761 // to the original package, and private and public symbols to the
1762 // private package.
1763
1764 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic;
1765 if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(),
1766 outputPackage, options)) {
1767 return 1;
1768 }
1769
1770 options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate;
1771 outputPackage = mOptions.privateSymbols.value();
1772 }
1773
1774 if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) {
1775 return 1;
1776 }
1777
1778 for (const std::string& extraPackage : mOptions.extraJavaPackages) {
1779 if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage,
1780 options)) {
1781 return 1;
1782 }
1783 }
1784 }
1785
1786 if (!writeProguardFile(mOptions.generateProguardRulesPath,
1787 proguardKeepSet)) {
1788 return 1;
1789 }
1790
1791 if (!writeProguardFile(mOptions.generateMainDexProguardRulesPath,
1792 proguardMainDexKeepSet)) {
1793 return 1;
1794 }
1795
1796 if (mContext->verbose()) {
1797 DebugPrintTableOptions debugPrintTableOptions;
1798 debugPrintTableOptions.showSources = true;
1799 Debug::printTable(&mFinalTable, debugPrintTableOptions);
1800 }
1801 return 0;
1802 }
1803
1804 private:
1805 LinkOptions mOptions;
1806 LinkContext* mContext;
1807 ResourceTable mFinalTable;
1808
1809 std::unique_ptr<TableMerger> mTableMerger;
1810
1811 // A pointer to the FileCollection representing the filesystem (not archives).
1812 std::unique_ptr<io::FileCollection> mFileCollection;
1813
1814 // A vector of IFileCollections. This is mainly here to keep ownership of the
1815 // collections.
1816 std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
1817
1818 // A vector of ResourceTables. This is here to retain ownership, so that the
1819 // SymbolTable
1820 // can use these.
1821 std::vector<std::unique_ptr<ResourceTable>> mStaticTableIncludes;
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001822};
1823
1824int link(const std::vector<StringPiece>& args) {
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001825 LinkContext context;
1826 LinkOptions options;
1827 std::vector<std::string> overlayArgList;
1828 std::vector<std::string> extraJavaPackages;
1829 Maybe<std::string> configs;
1830 Maybe<std::string> preferredDensity;
1831 Maybe<std::string> productList;
1832 bool legacyXFlag = false;
1833 bool requireLocalization = false;
1834 bool verbose = false;
1835 Maybe<std::string> stableIdFilePath;
1836 std::vector<std::string> splitArgs;
1837 Flags flags =
1838 Flags()
1839 .requiredFlag("-o", "Output path", &options.outputPath)
1840 .requiredFlag("--manifest", "Path to the Android manifest to build",
1841 &options.manifestPath)
1842 .optionalFlagList("-I", "Adds an Android APK to link against",
1843 &options.includePaths)
1844 .optionalFlagList(
1845 "-R",
1846 "Compilation unit to link, using `overlay` semantics.\n"
1847 "The last conflicting resource given takes precedence.",
1848 &overlayArgList)
1849 .optionalFlag("--java", "Directory in which to generate R.java",
1850 &options.generateJavaClassPath)
1851 .optionalFlag("--proguard",
1852 "Output file for generated Proguard rules",
1853 &options.generateProguardRulesPath)
1854 .optionalFlag(
1855 "--proguard-main-dex",
1856 "Output file for generated Proguard rules for the main dex",
1857 &options.generateMainDexProguardRulesPath)
1858 .optionalSwitch("--no-auto-version",
1859 "Disables automatic style and layout SDK versioning",
1860 &options.noAutoVersion)
1861 .optionalSwitch("--no-version-vectors",
1862 "Disables automatic versioning of vector drawables. "
1863 "Use this only\n"
1864 "when building with vector drawable support library",
1865 &options.noVersionVectors)
1866 .optionalSwitch("--no-resource-deduping",
1867 "Disables automatic deduping of resources with\n"
1868 "identical values across compatible configurations.",
1869 &options.noResourceDeduping)
1870 .optionalSwitch(
1871 "-x",
1872 "Legacy flag that specifies to use the package identifier 0x01",
1873 &legacyXFlag)
1874 .optionalSwitch("-z",
1875 "Require localization of strings marked 'suggested'",
1876 &requireLocalization)
1877 .optionalFlag(
1878 "-c",
1879 "Comma separated list of configurations to include. The default\n"
1880 "is all configurations",
1881 &configs)
1882 .optionalFlag(
1883 "--preferred-density",
1884 "Selects the closest matching density and strips out all others.",
1885 &preferredDensity)
1886 .optionalFlag("--product",
1887 "Comma separated list of product names to keep",
1888 &productList)
1889 .optionalSwitch("--output-to-dir",
1890 "Outputs the APK contents to a directory specified "
1891 "by -o",
1892 &options.outputToDirectory)
1893 .optionalSwitch("--no-xml-namespaces",
1894 "Removes XML namespace prefix and URI "
1895 "information from AndroidManifest.xml\nand XML "
1896 "binaries in res/*.",
1897 &options.noXmlNamespaces)
1898 .optionalFlag("--min-sdk-version",
1899 "Default minimum SDK version to use for "
1900 "AndroidManifest.xml",
1901 &options.manifestFixerOptions.minSdkVersionDefault)
1902 .optionalFlag("--target-sdk-version",
1903 "Default target SDK version to use for "
1904 "AndroidManifest.xml",
1905 &options.manifestFixerOptions.targetSdkVersionDefault)
1906 .optionalFlag("--version-code",
1907 "Version code (integer) to inject into the "
1908 "AndroidManifest.xml if none is present",
1909 &options.manifestFixerOptions.versionCodeDefault)
1910 .optionalFlag("--version-name",
1911 "Version name to inject into the AndroidManifest.xml "
1912 "if none is present",
1913 &options.manifestFixerOptions.versionNameDefault)
1914 .optionalSwitch("--static-lib", "Generate a static Android library",
1915 &options.staticLib)
1916 .optionalSwitch("--no-static-lib-packages",
1917 "Merge all library resources under the app's package",
1918 &options.noStaticLibPackages)
1919 .optionalSwitch("--non-final-ids",
1920 "Generates R.java without the final modifier.\n"
1921 "This is implied when --static-lib is specified.",
1922 &options.generateNonFinalIds)
1923 .optionalFlag("--stable-ids",
1924 "File containing a list of name to ID mapping.",
1925 &stableIdFilePath)
1926 .optionalFlag(
1927 "--emit-ids",
1928 "Emit a file at the given path with a list of name to ID\n"
1929 "mappings, suitable for use with --stable-ids.",
1930 &options.resourceIdMapPath)
1931 .optionalFlag("--private-symbols",
1932 "Package name to use when generating R.java for "
1933 "private symbols.\n"
1934 "If not specified, public and private symbols will use "
1935 "the application's "
1936 "package name",
1937 &options.privateSymbols)
1938 .optionalFlag("--custom-package",
1939 "Custom Java package under which to generate R.java",
1940 &options.customJavaPackage)
1941 .optionalFlagList("--extra-packages",
1942 "Generate the same R.java but with different "
1943 "package names",
1944 &extraJavaPackages)
1945 .optionalFlagList("--add-javadoc-annotation",
1946 "Adds a JavaDoc annotation to all "
Adam Lesinskid0f116b2016-07-08 15:00:32 -07001947 "generated Java classes",
1948 &options.javadocAnnotations)
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001949 .optionalSwitch("--auto-add-overlay",
1950 "Allows the addition of new resources in "
1951 "overlays without <add-resource> tags",
1952 &options.autoAddOverlay)
1953 .optionalFlag("--rename-manifest-package",
1954 "Renames the package in AndroidManifest.xml",
1955 &options.manifestFixerOptions.renameManifestPackage)
1956 .optionalFlag(
1957 "--rename-instrumentation-target-package",
1958 "Changes the name of the target package for instrumentation. "
1959 "Most useful "
1960 "when used\nin conjunction with --rename-manifest-package",
1961 &options.manifestFixerOptions.renameInstrumentationTargetPackage)
1962 .optionalFlagList("-0", "File extensions not to compress",
1963 &options.extensionsToNotCompress)
1964 .optionalFlagList(
1965 "--split",
1966 "Split resources matching a set of configs out to a "
1967 "Split APK.\nSyntax: path/to/output.apk:<config>[,<config>[...]]",
1968 &splitArgs)
1969 .optionalSwitch("-v", "Enables verbose logging", &verbose);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001970
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001971 if (!flags.parse("aapt2 link", args, &std::cerr)) {
1972 return 1;
1973 }
1974
1975 // Expand all argument-files passed into the command line. These start with
1976 // '@'.
1977 std::vector<std::string> argList;
1978 for (const std::string& arg : flags.getArgs()) {
1979 if (util::stringStartsWith(arg, "@")) {
1980 const std::string path = arg.substr(1, arg.size() - 1);
1981 std::string error;
1982 if (!file::appendArgsFromFile(path, &argList, &error)) {
1983 context.getDiagnostics()->error(DiagMessage(path) << error);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07001984 return 1;
Adam Lesinskicacb28f2016-10-19 12:18:14 -07001985 }
1986 } else {
1987 argList.push_back(arg);
1988 }
1989 }
1990
1991 // Expand all argument-files passed to -R.
1992 for (const std::string& arg : overlayArgList) {
1993 if (util::stringStartsWith(arg, "@")) {
1994 const std::string path = arg.substr(1, arg.size() - 1);
1995 std::string error;
1996 if (!file::appendArgsFromFile(path, &options.overlayFiles, &error)) {
1997 context.getDiagnostics()->error(DiagMessage(path) << error);
1998 return 1;
1999 }
2000 } else {
2001 options.overlayFiles.push_back(arg);
2002 }
2003 }
2004
2005 if (verbose) {
2006 context.setVerbose(verbose);
2007 }
2008
2009 // Populate the set of extra packages for which to generate R.java.
2010 for (std::string& extraPackage : extraJavaPackages) {
2011 // A given package can actually be a colon separated list of packages.
2012 for (StringPiece package : util::split(extraPackage, ':')) {
2013 options.extraJavaPackages.insert(package.toString());
2014 }
2015 }
2016
2017 if (productList) {
2018 for (StringPiece product : util::tokenize(productList.value(), ',')) {
2019 if (product != "" && product != "default") {
2020 options.products.insert(product.toString());
2021 }
2022 }
2023 }
2024
2025 AxisConfigFilter filter;
2026 if (configs) {
2027 for (const StringPiece& configStr : util::tokenize(configs.value(), ',')) {
2028 ConfigDescription config;
2029 LocaleValue lv;
2030 if (lv.initFromFilterString(configStr)) {
2031 lv.writeTo(&config);
2032 } else if (!ConfigDescription::parse(configStr, &config)) {
2033 context.getDiagnostics()->error(DiagMessage() << "invalid config '"
2034 << configStr
2035 << "' for -c option");
2036 return 1;
2037 }
2038
2039 if (config.density != 0) {
2040 context.getDiagnostics()->warn(DiagMessage() << "ignoring density '"
2041 << config
2042 << "' for -c option");
2043 } else {
2044 filter.addConfig(config);
2045 }
Adam Lesinski1ab598f2015-08-14 14:26:04 -07002046 }
2047
Adam Lesinskicacb28f2016-10-19 12:18:14 -07002048 options.tableSplitterOptions.configFilter = &filter;
2049 }
2050
2051 if (preferredDensity) {
2052 ConfigDescription preferredDensityConfig;
2053 if (!ConfigDescription::parse(preferredDensity.value(),
2054 &preferredDensityConfig)) {
2055 context.getDiagnostics()->error(
2056 DiagMessage() << "invalid density '" << preferredDensity.value()
2057 << "' for --preferred-density option");
2058 return 1;
Adam Lesinskic51562c2016-04-28 11:12:38 -07002059 }
2060
Adam Lesinskicacb28f2016-10-19 12:18:14 -07002061 // Clear the version that can be automatically added.
2062 preferredDensityConfig.sdkVersion = 0;
2063
2064 if (preferredDensityConfig.diff(ConfigDescription::defaultConfig()) !=
2065 ConfigDescription::CONFIG_DENSITY) {
2066 context.getDiagnostics()->error(
2067 DiagMessage() << "invalid preferred density '"
2068 << preferredDensity.value() << "'. "
2069 << "Preferred density must only be a density value");
2070 return 1;
Adam Lesinski1e21ff02016-06-24 14:57:58 -07002071 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -07002072 options.tableSplitterOptions.preferredDensity =
2073 preferredDensityConfig.density;
2074 }
Adam Lesinski1e21ff02016-06-24 14:57:58 -07002075
Adam Lesinskicacb28f2016-10-19 12:18:14 -07002076 if (!options.staticLib && stableIdFilePath) {
2077 if (!loadStableIdMap(context.getDiagnostics(), stableIdFilePath.value(),
2078 &options.stableIdMap)) {
2079 return 1;
Adam Lesinski64587af2016-02-18 18:33:06 -08002080 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -07002081 }
Adam Lesinski64587af2016-02-18 18:33:06 -08002082
Adam Lesinskicacb28f2016-10-19 12:18:14 -07002083 // Populate some default no-compress extensions that are already compressed.
2084 options.extensionsToNotCompress.insert(
2085 {".jpg", ".jpeg", ".png", ".gif", ".wav", ".mp2", ".mp3", ".ogg",
2086 ".aac", ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet", ".rtttl",
2087 ".imy", ".xmf", ".mp4", ".m4a", ".m4v", ".3gp", ".3gpp", ".3g2",
2088 ".3gpp2", ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"});
2089
2090 // Parse the split parameters.
2091 for (const std::string& splitArg : splitArgs) {
2092 options.splitPaths.push_back({});
2093 options.splitConstraints.push_back({});
2094 if (!parseSplitParameter(splitArg, context.getDiagnostics(),
2095 &options.splitPaths.back(),
2096 &options.splitConstraints.back())) {
2097 return 1;
Adam Lesinskifc9570e62015-11-16 15:07:54 -08002098 }
Adam Lesinskicacb28f2016-10-19 12:18:14 -07002099 }
Adam Lesinskifc9570e62015-11-16 15:07:54 -08002100
Adam Lesinskicacb28f2016-10-19 12:18:14 -07002101 // Turn off auto versioning for static-libs.
2102 if (options.staticLib) {
2103 options.noAutoVersion = true;
2104 options.noVersionVectors = true;
2105 }
Adam Lesinskie4bb9eb2016-02-12 22:18:51 -08002106
Adam Lesinskicacb28f2016-10-19 12:18:14 -07002107 LinkCommand cmd(&context, options);
2108 return cmd.run(argList);
Adam Lesinski1ab598f2015-08-14 14:26:04 -07002109}
2110
Adam Lesinskicacb28f2016-10-19 12:18:14 -07002111} // namespace aapt