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