blob: 8f8e0c8e4f6d1269f02b63dbf14588ea9e90559f [file] [log] [blame]
Adam Lesinskid0f492d2017-04-03 18:12:45 -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 <memory>
18#include <vector>
19
20#include "androidfw/StringPiece.h"
21
22#include "Diagnostics.h"
23#include "Flags.h"
24#include "LoadedApk.h"
25#include "ResourceUtils.h"
26#include "SdkConstants.h"
27#include "ValueVisitor.h"
28#include "cmd/Util.h"
29#include "flatten/TableFlattener.h"
30#include "flatten/XmlFlattener.h"
31#include "io/BigBufferInputStream.h"
32#include "io/Util.h"
33#include "optimize/ResourceDeduper.h"
34#include "optimize/VersionCollapser.h"
35#include "split/TableSplitter.h"
36
37using android::StringPiece;
38
39namespace aapt {
40
41struct OptimizeOptions {
42 // Path to the output APK.
43 std::string output_path;
44
45 // Details of the app extracted from the AndroidManifest.xml
46 AppInfo app_info;
47
48 // Split APK options.
49 TableSplitterOptions table_splitter_options;
50
51 // List of output split paths. These are in the same order as `split_constraints`.
52 std::vector<std::string> split_paths;
53
54 // List of SplitConstraints governing what resources go into each split. Ordered by `split_paths`.
55 std::vector<SplitConstraints> split_constraints;
56
57 TableFlattenerOptions table_flattener_options;
58};
59
60class OptimizeContext : public IAaptContext {
61 public:
62 IDiagnostics* GetDiagnostics() override {
63 return &diagnostics_;
64 }
65
66 NameMangler* GetNameMangler() override {
67 UNIMPLEMENTED(FATAL);
68 return nullptr;
69 }
70
71 const std::string& GetCompilationPackage() override {
72 static std::string empty;
73 return empty;
74 }
75
76 uint8_t GetPackageId() override {
77 return 0;
78 }
79
80 SymbolTable* GetExternalSymbols() override {
81 UNIMPLEMENTED(FATAL);
82 return nullptr;
83 }
84
85 bool IsVerbose() override {
86 return verbose_;
87 }
88
89 void SetVerbose(bool val) {
90 verbose_ = val;
91 }
92
93 void SetMinSdkVersion(int sdk_version) {
94 sdk_version_ = sdk_version;
95 }
96
97 int GetMinSdkVersion() override {
98 return sdk_version_;
99 }
100
101 private:
102 StdErrDiagnostics diagnostics_;
103 bool verbose_ = false;
104 int sdk_version_ = 0;
105};
106
107class OptimizeCommand {
108 public:
109 OptimizeCommand(OptimizeContext* context, const OptimizeOptions& options)
110 : options_(options), context_(context) {
111 }
112
113 int Run(std::unique_ptr<LoadedApk> apk) {
114 if (context_->IsVerbose()) {
115 context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK...");
116 }
117
118 VersionCollapser collapser;
119 if (!collapser.Consume(context_, apk->GetResourceTable())) {
120 return 1;
121 }
122
123 ResourceDeduper deduper;
124 if (!deduper.Consume(context_, apk->GetResourceTable())) {
125 context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources");
126 return 1;
127 }
128
129 // Adjust the SplitConstraints so that their SDK version is stripped if it is less than or
130 // equal to the minSdk.
131 options_.split_constraints =
132 AdjustSplitConstraintsForMinSdk(context_->GetMinSdkVersion(), options_.split_constraints);
133
134 // Stripping the APK using the TableSplitter. The resource table is modified in place in the
135 // LoadedApk.
136 TableSplitter splitter(options_.split_constraints, options_.table_splitter_options);
137 if (!splitter.VerifySplitConstraints(context_)) {
138 return 1;
139 }
140 splitter.SplitTable(apk->GetResourceTable());
141
142 auto path_iter = options_.split_paths.begin();
143 auto split_constraints_iter = options_.split_constraints.begin();
144 for (std::unique_ptr<ResourceTable>& split_table : splitter.splits()) {
145 if (context_->IsVerbose()) {
146 context_->GetDiagnostics()->Note(
147 DiagMessage(*path_iter) << "generating split with configurations '"
148 << util::Joiner(split_constraints_iter->configs, ", ") << "'");
149 }
150
151 // Generate an AndroidManifest.xml for each split.
152 std::unique_ptr<xml::XmlResource> split_manifest =
153 GenerateSplitManifest(options_.app_info, *split_constraints_iter);
154 std::unique_ptr<IArchiveWriter> split_writer =
155 CreateZipFileArchiveWriter(context_->GetDiagnostics(), *path_iter);
156 if (!split_writer) {
157 return 1;
158 }
159
160 if (!WriteSplitApk(split_table.get(), split_manifest.get(), split_writer.get())) {
161 return 1;
162 }
163
164 ++path_iter;
165 ++split_constraints_iter;
166 }
167
168 std::unique_ptr<IArchiveWriter> writer =
169 CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path);
170 if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) {
171 return 1;
172 }
173
174 return 0;
175 }
176
177 private:
178 bool WriteSplitApk(ResourceTable* table, xml::XmlResource* manifest, IArchiveWriter* writer) {
179 BigBuffer manifest_buffer(4096);
180 XmlFlattener xml_flattener(&manifest_buffer, {});
181 if (!xml_flattener.Consume(context_, manifest)) {
182 return false;
183 }
184
185 io::BigBufferInputStream manifest_buffer_in(&manifest_buffer);
186 if (!io::CopyInputStreamToArchive(context_, &manifest_buffer_in, "AndroidManifest.xml",
187 ArchiveEntry::kCompress, writer)) {
188 return false;
189 }
190
191 std::map<std::pair<ConfigDescription, StringPiece>, FileReference*> config_sorted_files;
192 for (auto& pkg : table->packages) {
193 for (auto& type : pkg->types) {
194 // Sort by config and name, so that we get better locality in the zip file.
195 config_sorted_files.clear();
196
197 for (auto& entry : type->entries) {
198 for (auto& config_value : entry->values) {
199 FileReference* file_ref = ValueCast<FileReference>(config_value->value.get());
200 if (file_ref == nullptr) {
201 continue;
202 }
203
204 if (file_ref->file == nullptr) {
205 ResourceNameRef name(pkg->name, type->type, entry->name);
206 context_->GetDiagnostics()->Error(DiagMessage(file_ref->GetSource())
207 << "file for resource " << name << " with config '"
208 << config_value->config << "' not found");
209 return false;
210 }
211
212 const StringPiece entry_name = entry->name;
213 config_sorted_files[std::make_pair(config_value->config, entry_name)] = file_ref;
214 }
215 }
216
217 for (auto& entry : config_sorted_files) {
218 FileReference* file_ref = entry.second;
219 uint32_t compression_flags =
220 file_ref->file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
221 if (!io::CopyFileToArchive(context_, file_ref->file, *file_ref->path, compression_flags,
222 writer)) {
223 return false;
224 }
225 }
226 }
227 }
228
229 BigBuffer table_buffer(4096);
230 TableFlattener table_flattener(options_.table_flattener_options, &table_buffer);
231 if (!table_flattener.Consume(context_, table)) {
232 return false;
233 }
234
235 io::BigBufferInputStream table_buffer_in(&table_buffer);
236 if (!io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc",
237 ArchiveEntry::kAlign, writer)) {
238 return false;
239 }
240 return true;
241 }
242
243 OptimizeOptions options_;
244 OptimizeContext* context_;
245};
246
247bool ExtractAppDataFromManifest(OptimizeContext* context, LoadedApk* apk,
248 OptimizeOptions* out_options) {
249 io::IFile* manifest_file = apk->GetFileCollection()->FindFile("AndroidManifest.xml");
250 if (manifest_file == nullptr) {
251 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
252 << "missing AndroidManifest.xml");
253 return false;
254 }
255
256 std::unique_ptr<io::IData> data = manifest_file->OpenAsData();
257 if (data == nullptr) {
258 context->GetDiagnostics()->Error(DiagMessage(manifest_file->GetSource())
259 << "failed to open file");
260 return false;
261 }
262
263 std::unique_ptr<xml::XmlResource> manifest = xml::Inflate(
264 data->data(), data->size(), context->GetDiagnostics(), manifest_file->GetSource());
265 if (manifest == nullptr) {
266 context->GetDiagnostics()->Error(DiagMessage() << "failed to read binary AndroidManifest.xml");
267 return false;
268 }
269
270 Maybe<AppInfo> app_info =
271 ExtractAppInfoFromBinaryManifest(manifest.get(), context->GetDiagnostics());
272 if (!app_info) {
273 context->GetDiagnostics()->Error(DiagMessage()
274 << "failed to extract data from AndroidManifest.xml");
275 return false;
276 }
277
278 out_options->app_info = std::move(app_info.value());
279 context->SetMinSdkVersion(out_options->app_info.min_sdk_version.value_or_default(0));
280 return true;
281}
282
283int Optimize(const std::vector<StringPiece>& args) {
284 OptimizeContext context;
285 OptimizeOptions options;
286 Maybe<std::string> target_densities;
287 std::vector<std::string> configs;
288 std::vector<std::string> split_args;
289 bool verbose = false;
290 Flags flags =
291 Flags()
292 .RequiredFlag("-o", "Path to the output APK.", &options.output_path)
293 .OptionalFlag(
294 "--target-densities",
295 "Comma separated list of the screen densities that the APK will be optimized for.\n"
296 "All the resources that would be unused on devices of the given densities will be \n"
297 "removed from the APK.",
298 &target_densities)
299 .OptionalFlagList("-c",
300 "Comma separated list of configurations to include. The default\n"
301 "is all configurations.",
302 &configs)
303 .OptionalFlagList("--split",
304 "Split resources matching a set of configs out to a "
305 "Split APK.\nSyntax: path/to/output.apk:<config>[,<config>[...]].",
306 &split_args)
307 .OptionalSwitch("--enable-sparse-encoding",
308 "Enables encoding sparse entries using a binary search tree.\n"
309 "This decreases APK size at the cost of resource retrieval performance.",
310 &options.table_flattener_options.use_sparse_entries)
311 .OptionalSwitch("-v", "Enables verbose logging", &verbose);
312
313 if (!flags.Parse("aapt2 optimize", args, &std::cerr)) {
314 return 1;
315 }
316
317 if (flags.GetArgs().size() != 1u) {
318 std::cerr << "must have one APK as argument.\n\n";
319 flags.Usage("aapt2 optimize", &std::cerr);
320 return 1;
321 }
322
323 std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[0]);
324 if (!apk) {
325 return 1;
326 }
327
328 context.SetVerbose(verbose);
329
330 if (target_densities) {
331 // Parse the target screen densities.
332 for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
333 Maybe<uint16_t> target_density =
334 ParseTargetDensityParameter(config_str, context.GetDiagnostics());
335 if (!target_density) {
336 return 1;
337 }
338 options.table_splitter_options.preferred_densities.push_back(target_density.value());
339 }
340 }
341
342 std::unique_ptr<IConfigFilter> filter;
343 if (!configs.empty()) {
344 filter = ParseConfigFilterParameters(configs, context.GetDiagnostics());
345 if (filter == nullptr) {
346 return 1;
347 }
348 options.table_splitter_options.config_filter = filter.get();
349 }
350
351 // Parse the split parameters.
352 for (const std::string& split_arg : split_args) {
353 options.split_paths.push_back({});
354 options.split_constraints.push_back({});
355 if (!ParseSplitParameter(split_arg, context.GetDiagnostics(), &options.split_paths.back(),
356 &options.split_constraints.back())) {
357 return 1;
358 }
359 }
360
361 if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) {
362 return 1;
363 }
364
365 OptimizeCommand cmd(&context, options);
366 return cmd.Run(std::move(apk));
367}
368
369} // namespace aapt