blob: d80307cd154a5d792fca390097a78fb31546e933 [file] [log] [blame]
Adam Lesinski8780eb62017-10-31 17:44:39 -07001/*
2 * Copyright (C) 2017 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 <vector>
18
19#include "android-base/macros.h"
20#include "androidfw/StringPiece.h"
21
22#include "Flags.h"
23#include "LoadedApk.h"
24#include "ValueVisitor.h"
25#include "cmd/Util.h"
26#include "format/binary/TableFlattener.h"
27#include "format/binary/XmlFlattener.h"
28#include "format/proto/ProtoDeserialize.h"
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000029#include "format/proto/ProtoSerialize.h"
Adam Lesinski8780eb62017-10-31 17:44:39 -070030#include "io/BigBufferStream.h"
31#include "io/Util.h"
32#include "process/IResourceTableConsumer.h"
33#include "process/SymbolTable.h"
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000034#include "util/Util.h"
Adam Lesinski8780eb62017-10-31 17:44:39 -070035
36using ::android::StringPiece;
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000037using ::android::base::StringPrintf;
Adam Lesinski8780eb62017-10-31 17:44:39 -070038using ::std::unique_ptr;
39using ::std::vector;
40
41namespace aapt {
42
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000043class IApkSerializer {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000044 public:
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000045 IApkSerializer(IAaptContext* context, const Source& source) : context_(context), source_(source) {}
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000046
47 virtual bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
48 IArchiveWriter* writer) = 0;
49 virtual bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) = 0;
David Chaloupkab66db4e2018-01-15 12:35:41 +000050 virtual bool SerializeFile(FileReference* file, IArchiveWriter* writer) = 0;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000051
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000052 virtual ~IApkSerializer() = default;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000053
54 protected:
55 IAaptContext* context_;
56 Source source_;
57};
Adam Lesinski8780eb62017-10-31 17:44:39 -070058
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000059bool ConvertApk(IAaptContext* context, unique_ptr<LoadedApk> apk, IApkSerializer* serializer,
60 IArchiveWriter* writer) {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000061 if (!serializer->SerializeXml(apk->GetManifest(), kAndroidManifestPath, true /*utf16*/, writer)) {
62 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
63 << "failed to serialize AndroidManifest.xml");
Adam Lesinski8780eb62017-10-31 17:44:39 -070064 return false;
65 }
66
Tom Dobek725fb122017-12-08 14:19:01 +000067 if (apk->GetResourceTable() != nullptr) {
David Chaloupkab66db4e2018-01-15 12:35:41 +000068 // The table might be modified by below code.
69 auto converted_table = apk->GetResourceTable();
Tom Dobek725fb122017-12-08 14:19:01 +000070
71 // Resources
David Chaloupkab66db4e2018-01-15 12:35:41 +000072 for (const auto& package : converted_table->packages) {
Tom Dobek725fb122017-12-08 14:19:01 +000073 for (const auto& type : package->types) {
74 for (const auto& entry : type->entries) {
75 for (const auto& config_value : entry->values) {
David Chaloupkab66db4e2018-01-15 12:35:41 +000076 FileReference* file = ValueCast<FileReference>(config_value->value.get());
Tom Dobek725fb122017-12-08 14:19:01 +000077 if (file != nullptr) {
78 if (file->file == nullptr) {
79 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
80 << "no file associated with " << *file);
81 return false;
82 }
83
84 if (!serializer->SerializeFile(file, writer)) {
85 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
86 << "failed to serialize file " << *file->path);
87 return false;
88 }
89 } // file
90 } // config_value
91 } // entry
92 } // type
93 } // package
David Chaloupkab66db4e2018-01-15 12:35:41 +000094
95 // Converted resource table
96 if (!serializer->SerializeTable(converted_table, writer)) {
97 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
98 << "failed to serialize the resource table");
99 return false;
100 }
Adam Lesinski8780eb62017-10-31 17:44:39 -0700101 }
102
Pierre Lecesned55bef72017-11-10 22:31:01 +0000103 // Other files
104 std::unique_ptr<io::IFileCollectionIterator> iterator = apk->GetFileCollection()->Iterator();
105 while (iterator->HasNext()) {
106 io::IFile* file = iterator->Next();
107
108 std::string path = file->GetSource().path;
109 // The name of the path has the format "<zip-file-name>@<path-to-file>".
110 path = path.substr(path.find('@') + 1);
111
112 // Manifest, resource table and resources have already been taken care of.
113 if (path == kAndroidManifestPath ||
114 path == kApkResourceTablePath ||
115 path == kProtoResourceTablePath ||
116 path.find("res/") == 0) {
117 continue;
118 }
119
120 if (!io::CopyFileToArchivePreserveCompression(context, file, path, writer)) {
121 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
122 << "failed to copy file " << path);
123 return false;
124 }
125 }
126
Adam Lesinski8780eb62017-10-31 17:44:39 -0700127 return true;
128}
129
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000130
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000131class BinaryApkSerializer : public IApkSerializer {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000132 public:
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000133 BinaryApkSerializer(IAaptContext* context, const Source& source,
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000134 const TableFlattenerOptions& options)
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000135 : IApkSerializer(context, source), tableFlattenerOptions_(options) {}
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000136
137 bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000138 IArchiveWriter* writer) override {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000139 BigBuffer buffer(4096);
140 XmlFlattenerOptions options = {};
141 options.use_utf16 = utf16;
142 XmlFlattener flattener(&buffer, options);
143 if (!flattener.Consume(context_, xml)) {
144 return false;
145 }
146
147 io::BigBufferInputStream input_stream(&buffer);
148 return io::CopyInputStreamToArchive(context_, &input_stream, path, ArchiveEntry::kCompress,
149 writer);
150 }
151
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000152 bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000153 BigBuffer buffer(4096);
154 TableFlattener table_flattener(tableFlattenerOptions_, &buffer);
155 if (!table_flattener.Consume(context_, table)) {
156 return false;
157 }
158
159 io::BigBufferInputStream input_stream(&buffer);
160 return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath,
161 ArchiveEntry::kAlign, writer);
162 }
163
David Chaloupkab66db4e2018-01-15 12:35:41 +0000164 bool SerializeFile(FileReference* file, IArchiveWriter* writer) override {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000165 if (file->type == ResourceFile::Type::kProtoXml) {
166 unique_ptr<io::InputStream> in = file->file->OpenInputStream();
167 if (in == nullptr) {
168 context_->GetDiagnostics()->Error(DiagMessage(source_)
169 << "failed to open file " << *file->path);
170 return false;
171 }
172
173 pb::XmlNode pb_node;
174 io::ZeroCopyInputAdaptor adaptor(in.get());
175 if (!pb_node.ParseFromZeroCopyStream(&adaptor)) {
176 context_->GetDiagnostics()->Error(DiagMessage(source_)
177 << "failed to parse proto XML " << *file->path);
178 return false;
179 }
180
181 std::string error;
182 unique_ptr<xml::XmlResource> xml = DeserializeXmlResourceFromPb(pb_node, &error);
183 if (xml == nullptr) {
184 context_->GetDiagnostics()->Error(DiagMessage(source_)
185 << "failed to deserialize proto XML "
186 << *file->path << ": " << error);
187 return false;
188 }
189
190 if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer)) {
191 context_->GetDiagnostics()->Error(DiagMessage(source_)
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000192 << "failed to serialize to binary XML: " << *file->path);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000193 return false;
194 }
David Chaloupkab66db4e2018-01-15 12:35:41 +0000195
196 file->type = ResourceFile::Type::kBinaryXml;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000197 } else {
Pierre Lecesned55bef72017-11-10 22:31:01 +0000198 if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) {
199 context_->GetDiagnostics()->Error(DiagMessage(source_)
200 << "failed to copy file " << *file->path);
201 return false;
202 }
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000203 }
204
205 return true;
206 }
207
208 private:
209 TableFlattenerOptions tableFlattenerOptions_;
210
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000211 DISALLOW_COPY_AND_ASSIGN(BinaryApkSerializer);
212};
213
214class ProtoApkSerializer : public IApkSerializer {
215 public:
216 ProtoApkSerializer(IAaptContext* context, const Source& source)
217 : IApkSerializer(context, source) {}
218
219 bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
220 IArchiveWriter* writer) override {
221 pb::XmlNode pb_node;
222 SerializeXmlResourceToPb(*xml, &pb_node);
223 return io::CopyProtoToArchive(context_, &pb_node, path, ArchiveEntry::kCompress, writer);
224 }
225
226 bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override {
227 pb::ResourceTable pb_table;
228 SerializeTableToPb(*table, &pb_table);
229 return io::CopyProtoToArchive(context_, &pb_table, kProtoResourceTablePath,
230 ArchiveEntry::kCompress, writer);
231 }
232
David Chaloupkab66db4e2018-01-15 12:35:41 +0000233 bool SerializeFile(FileReference* file, IArchiveWriter* writer) override {
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000234 if (file->type == ResourceFile::Type::kBinaryXml) {
235 std::unique_ptr<io::IData> data = file->file->OpenAsData();
236 if (!data) {
237 context_->GetDiagnostics()->Error(DiagMessage(source_)
238 << "failed to open file " << *file->path);
239 return false;
240 }
241
242 std::string error;
243 std::unique_ptr<xml::XmlResource> xml = xml::Inflate(data->data(), data->size(), &error);
244 if (xml == nullptr) {
245 context_->GetDiagnostics()->Error(DiagMessage(source_) << "failed to parse binary XML: "
246 << error);
247 return false;
248 }
249
250 if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer)) {
251 context_->GetDiagnostics()->Error(DiagMessage(source_)
252 << "failed to serialize to proto XML: " << *file->path);
253 return false;
254 }
David Chaloupkab66db4e2018-01-15 12:35:41 +0000255
256 file->type = ResourceFile::Type::kProtoXml;
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000257 } else {
258 if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) {
259 context_->GetDiagnostics()->Error(DiagMessage(source_)
260 << "failed to copy file " << *file->path);
261 return false;
262 }
263 }
264
265 return true;
266 }
267
268 private:
269 DISALLOW_COPY_AND_ASSIGN(ProtoApkSerializer);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000270};
271
Adam Lesinski8780eb62017-10-31 17:44:39 -0700272class Context : public IAaptContext {
273 public:
274 Context() : mangler_({}), symbols_(&mangler_) {
275 }
276
277 PackageType GetPackageType() override {
278 return PackageType::kApp;
279 }
280
281 SymbolTable* GetExternalSymbols() override {
282 return &symbols_;
283 }
284
285 IDiagnostics* GetDiagnostics() override {
286 return &diag_;
287 }
288
289 const std::string& GetCompilationPackage() override {
290 return package_;
291 }
292
293 uint8_t GetPackageId() override {
294 // Nothing should call this.
295 UNIMPLEMENTED(FATAL) << "PackageID should not be necessary";
296 return 0;
297 }
298
299 NameMangler* GetNameMangler() override {
300 UNIMPLEMENTED(FATAL);
301 return nullptr;
302 }
303
304 bool IsVerbose() override {
305 return verbose_;
306 }
307
308 int GetMinSdkVersion() override {
309 return 0u;
310 }
311
312 bool verbose_ = false;
313 std::string package_;
314
315 private:
316 DISALLOW_COPY_AND_ASSIGN(Context);
317
318 NameMangler mangler_;
319 SymbolTable symbols_;
320 StdErrDiagnostics diag_;
321};
322
323int Convert(const vector<StringPiece>& args) {
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000324
325 static const char* kOutputFormatProto = "proto";
326 static const char* kOutputFormatBinary = "binary";
327
Adam Lesinski8780eb62017-10-31 17:44:39 -0700328 Context context;
329 std::string output_path;
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000330 Maybe<std::string> output_format;
Adam Lesinski8780eb62017-10-31 17:44:39 -0700331 TableFlattenerOptions options;
332 Flags flags =
333 Flags()
334 .RequiredFlag("-o", "Output path", &output_path)
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000335 .OptionalFlag("--output-format", StringPrintf("Format of the output. Accepted values are "
336 "'%s' and '%s'. When not set, defaults to '%s'.", kOutputFormatProto,
337 kOutputFormatBinary, kOutputFormatBinary), &output_format)
Adam Lesinski8780eb62017-10-31 17:44:39 -0700338 .OptionalSwitch("--enable-sparse-encoding",
339 "Enables encoding sparse entries using a binary search tree.\n"
340 "This decreases APK size at the cost of resource retrieval performance.",
341 &options.use_sparse_entries)
342 .OptionalSwitch("-v", "Enables verbose logging", &context.verbose_);
343 if (!flags.Parse("aapt2 convert", args, &std::cerr)) {
344 return 1;
345 }
346
347 if (flags.GetArgs().size() != 1) {
348 std::cerr << "must supply a single proto APK\n";
349 flags.Usage("aapt2 convert", &std::cerr);
350 return 1;
351 }
352
353 const StringPiece& path = flags.GetArgs()[0];
354 unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics());
355 if (apk == nullptr) {
356 context.GetDiagnostics()->Error(DiagMessage(path) << "failed to load APK");
357 return 1;
358 }
359
360 Maybe<AppInfo> app_info =
361 ExtractAppInfoFromBinaryManifest(*apk->GetManifest(), context.GetDiagnostics());
362 if (!app_info) {
363 return 1;
364 }
365
366 context.package_ = app_info.value().package;
367
368 unique_ptr<IArchiveWriter> writer =
369 CreateZipFileArchiveWriter(context.GetDiagnostics(), output_path);
370 if (writer == nullptr) {
371 return 1;
372 }
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000373
374 unique_ptr<IApkSerializer> serializer;
375 if (!output_format || output_format.value() == kOutputFormatBinary) {
376 serializer.reset(new BinaryApkSerializer(&context, apk->GetSource(), options));
377 } else if (output_format.value() == kOutputFormatProto) {
378 serializer.reset(new ProtoApkSerializer(&context, apk->GetSource()));
379 } else {
380 context.GetDiagnostics()->Error(DiagMessage(path)
381 << "Invalid value for flag --output-format: "
382 << output_format.value());
383 return 1;
384 }
385
386
387 return ConvertApk(&context, std::move(apk), serializer.get(), writer.get()) ? 0 : 1;
Adam Lesinski8780eb62017-10-31 17:44:39 -0700388}
389
390} // namespace aapt