blob: 964dacfeafef8405b8630ca57a8d69748c20f250 [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;
50 virtual bool SerializeFile(const FileReference* file, IArchiveWriter* writer) = 0;
51
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) {
68 // Resource table
69 if (!serializer->SerializeTable(apk->GetResourceTable(), writer)) {
70 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
71 << "failed to serialize the resource table");
72 return false;
73 }
74
75 // Resources
76 for (const auto& package : apk->GetResourceTable()->packages) {
77 for (const auto& type : package->types) {
78 for (const auto& entry : type->entries) {
79 for (const auto& config_value : entry->values) {
80 const FileReference* file = ValueCast<FileReference>(config_value->value.get());
81 if (file != nullptr) {
82 if (file->file == nullptr) {
83 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
84 << "no file associated with " << *file);
85 return false;
86 }
87
88 if (!serializer->SerializeFile(file, writer)) {
89 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
90 << "failed to serialize file " << *file->path);
91 return false;
92 }
93 } // file
94 } // config_value
95 } // entry
96 } // type
97 } // package
Adam Lesinski8780eb62017-10-31 17:44:39 -070098 }
99
Pierre Lecesned55bef72017-11-10 22:31:01 +0000100 // Other files
101 std::unique_ptr<io::IFileCollectionIterator> iterator = apk->GetFileCollection()->Iterator();
102 while (iterator->HasNext()) {
103 io::IFile* file = iterator->Next();
104
105 std::string path = file->GetSource().path;
106 // The name of the path has the format "<zip-file-name>@<path-to-file>".
107 path = path.substr(path.find('@') + 1);
108
109 // Manifest, resource table and resources have already been taken care of.
110 if (path == kAndroidManifestPath ||
111 path == kApkResourceTablePath ||
112 path == kProtoResourceTablePath ||
113 path.find("res/") == 0) {
114 continue;
115 }
116
117 if (!io::CopyFileToArchivePreserveCompression(context, file, path, writer)) {
118 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
119 << "failed to copy file " << path);
120 return false;
121 }
122 }
123
Adam Lesinski8780eb62017-10-31 17:44:39 -0700124 return true;
125}
126
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000127
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000128class BinaryApkSerializer : public IApkSerializer {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000129 public:
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000130 BinaryApkSerializer(IAaptContext* context, const Source& source,
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000131 const TableFlattenerOptions& options)
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000132 : IApkSerializer(context, source), tableFlattenerOptions_(options) {}
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000133
134 bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000135 IArchiveWriter* writer) override {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000136 BigBuffer buffer(4096);
137 XmlFlattenerOptions options = {};
138 options.use_utf16 = utf16;
139 XmlFlattener flattener(&buffer, options);
140 if (!flattener.Consume(context_, xml)) {
141 return false;
142 }
143
144 io::BigBufferInputStream input_stream(&buffer);
145 return io::CopyInputStreamToArchive(context_, &input_stream, path, ArchiveEntry::kCompress,
146 writer);
147 }
148
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000149 bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000150 BigBuffer buffer(4096);
151 TableFlattener table_flattener(tableFlattenerOptions_, &buffer);
152 if (!table_flattener.Consume(context_, table)) {
153 return false;
154 }
155
156 io::BigBufferInputStream input_stream(&buffer);
157 return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath,
158 ArchiveEntry::kAlign, writer);
159 }
160
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000161 bool SerializeFile(const FileReference* file, IArchiveWriter* writer) override {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000162 if (file->type == ResourceFile::Type::kProtoXml) {
163 unique_ptr<io::InputStream> in = file->file->OpenInputStream();
164 if (in == nullptr) {
165 context_->GetDiagnostics()->Error(DiagMessage(source_)
166 << "failed to open file " << *file->path);
167 return false;
168 }
169
170 pb::XmlNode pb_node;
171 io::ZeroCopyInputAdaptor adaptor(in.get());
172 if (!pb_node.ParseFromZeroCopyStream(&adaptor)) {
173 context_->GetDiagnostics()->Error(DiagMessage(source_)
174 << "failed to parse proto XML " << *file->path);
175 return false;
176 }
177
178 std::string error;
179 unique_ptr<xml::XmlResource> xml = DeserializeXmlResourceFromPb(pb_node, &error);
180 if (xml == nullptr) {
181 context_->GetDiagnostics()->Error(DiagMessage(source_)
182 << "failed to deserialize proto XML "
183 << *file->path << ": " << error);
184 return false;
185 }
186
187 if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer)) {
188 context_->GetDiagnostics()->Error(DiagMessage(source_)
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000189 << "failed to serialize to binary XML: " << *file->path);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000190 return false;
191 }
192 } else {
Pierre Lecesned55bef72017-11-10 22:31:01 +0000193 if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) {
194 context_->GetDiagnostics()->Error(DiagMessage(source_)
195 << "failed to copy file " << *file->path);
196 return false;
197 }
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000198 }
199
200 return true;
201 }
202
203 private:
204 TableFlattenerOptions tableFlattenerOptions_;
205
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000206 DISALLOW_COPY_AND_ASSIGN(BinaryApkSerializer);
207};
208
209class ProtoApkSerializer : public IApkSerializer {
210 public:
211 ProtoApkSerializer(IAaptContext* context, const Source& source)
212 : IApkSerializer(context, source) {}
213
214 bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
215 IArchiveWriter* writer) override {
216 pb::XmlNode pb_node;
217 SerializeXmlResourceToPb(*xml, &pb_node);
218 return io::CopyProtoToArchive(context_, &pb_node, path, ArchiveEntry::kCompress, writer);
219 }
220
221 bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override {
222 pb::ResourceTable pb_table;
223 SerializeTableToPb(*table, &pb_table);
224 return io::CopyProtoToArchive(context_, &pb_table, kProtoResourceTablePath,
225 ArchiveEntry::kCompress, writer);
226 }
227
228 bool SerializeFile(const FileReference* file, IArchiveWriter* writer) override {
229 if (file->type == ResourceFile::Type::kBinaryXml) {
230 std::unique_ptr<io::IData> data = file->file->OpenAsData();
231 if (!data) {
232 context_->GetDiagnostics()->Error(DiagMessage(source_)
233 << "failed to open file " << *file->path);
234 return false;
235 }
236
237 std::string error;
238 std::unique_ptr<xml::XmlResource> xml = xml::Inflate(data->data(), data->size(), &error);
239 if (xml == nullptr) {
240 context_->GetDiagnostics()->Error(DiagMessage(source_) << "failed to parse binary XML: "
241 << error);
242 return false;
243 }
244
245 if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer)) {
246 context_->GetDiagnostics()->Error(DiagMessage(source_)
247 << "failed to serialize to proto XML: " << *file->path);
248 return false;
249 }
250 } else {
251 if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) {
252 context_->GetDiagnostics()->Error(DiagMessage(source_)
253 << "failed to copy file " << *file->path);
254 return false;
255 }
256 }
257
258 return true;
259 }
260
261 private:
262 DISALLOW_COPY_AND_ASSIGN(ProtoApkSerializer);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000263};
264
Adam Lesinski8780eb62017-10-31 17:44:39 -0700265class Context : public IAaptContext {
266 public:
267 Context() : mangler_({}), symbols_(&mangler_) {
268 }
269
270 PackageType GetPackageType() override {
271 return PackageType::kApp;
272 }
273
274 SymbolTable* GetExternalSymbols() override {
275 return &symbols_;
276 }
277
278 IDiagnostics* GetDiagnostics() override {
279 return &diag_;
280 }
281
282 const std::string& GetCompilationPackage() override {
283 return package_;
284 }
285
286 uint8_t GetPackageId() override {
287 // Nothing should call this.
288 UNIMPLEMENTED(FATAL) << "PackageID should not be necessary";
289 return 0;
290 }
291
292 NameMangler* GetNameMangler() override {
293 UNIMPLEMENTED(FATAL);
294 return nullptr;
295 }
296
297 bool IsVerbose() override {
298 return verbose_;
299 }
300
301 int GetMinSdkVersion() override {
302 return 0u;
303 }
304
305 bool verbose_ = false;
306 std::string package_;
307
308 private:
309 DISALLOW_COPY_AND_ASSIGN(Context);
310
311 NameMangler mangler_;
312 SymbolTable symbols_;
313 StdErrDiagnostics diag_;
314};
315
316int Convert(const vector<StringPiece>& args) {
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000317
318 static const char* kOutputFormatProto = "proto";
319 static const char* kOutputFormatBinary = "binary";
320
Adam Lesinski8780eb62017-10-31 17:44:39 -0700321 Context context;
322 std::string output_path;
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000323 Maybe<std::string> output_format;
Adam Lesinski8780eb62017-10-31 17:44:39 -0700324 TableFlattenerOptions options;
325 Flags flags =
326 Flags()
327 .RequiredFlag("-o", "Output path", &output_path)
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000328 .OptionalFlag("--output-format", StringPrintf("Format of the output. Accepted values are "
329 "'%s' and '%s'. When not set, defaults to '%s'.", kOutputFormatProto,
330 kOutputFormatBinary, kOutputFormatBinary), &output_format)
Adam Lesinski8780eb62017-10-31 17:44:39 -0700331 .OptionalSwitch("--enable-sparse-encoding",
332 "Enables encoding sparse entries using a binary search tree.\n"
333 "This decreases APK size at the cost of resource retrieval performance.",
334 &options.use_sparse_entries)
335 .OptionalSwitch("-v", "Enables verbose logging", &context.verbose_);
336 if (!flags.Parse("aapt2 convert", args, &std::cerr)) {
337 return 1;
338 }
339
340 if (flags.GetArgs().size() != 1) {
341 std::cerr << "must supply a single proto APK\n";
342 flags.Usage("aapt2 convert", &std::cerr);
343 return 1;
344 }
345
346 const StringPiece& path = flags.GetArgs()[0];
347 unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics());
348 if (apk == nullptr) {
349 context.GetDiagnostics()->Error(DiagMessage(path) << "failed to load APK");
350 return 1;
351 }
352
353 Maybe<AppInfo> app_info =
354 ExtractAppInfoFromBinaryManifest(*apk->GetManifest(), context.GetDiagnostics());
355 if (!app_info) {
356 return 1;
357 }
358
359 context.package_ = app_info.value().package;
360
361 unique_ptr<IArchiveWriter> writer =
362 CreateZipFileArchiveWriter(context.GetDiagnostics(), output_path);
363 if (writer == nullptr) {
364 return 1;
365 }
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000366
367 unique_ptr<IApkSerializer> serializer;
368 if (!output_format || output_format.value() == kOutputFormatBinary) {
369 serializer.reset(new BinaryApkSerializer(&context, apk->GetSource(), options));
370 } else if (output_format.value() == kOutputFormatProto) {
371 serializer.reset(new ProtoApkSerializer(&context, apk->GetSource()));
372 } else {
373 context.GetDiagnostics()->Error(DiagMessage(path)
374 << "Invalid value for flag --output-format: "
375 << output_format.value());
376 return 1;
377 }
378
379
380 return ConvertApk(&context, std::move(apk), serializer.get(), writer.get()) ? 0 : 1;
Adam Lesinski8780eb62017-10-31 17:44:39 -0700381}
382
383} // namespace aapt