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