blob: 33a1d3a704f1c58f96c2796355c68810da2d8c98 [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
Shane Farmer57669432017-06-19 12:52:04 -070020#include "android-base/stringprintf.h"
Shane Farmer0a5b2012017-06-22 12:24:12 -070021#include "androidfw/ResourceTypes.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070022#include "androidfw/StringPiece.h"
23
24#include "Diagnostics.h"
25#include "Flags.h"
26#include "LoadedApk.h"
27#include "ResourceUtils.h"
28#include "SdkConstants.h"
29#include "ValueVisitor.h"
30#include "cmd/Util.h"
Shane Farmer57669432017-06-19 12:52:04 -070031#include "configuration/ConfigurationParser.h"
32#include "filter/AbiFilter.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070033#include "flatten/TableFlattener.h"
34#include "flatten/XmlFlattener.h"
35#include "io/BigBufferInputStream.h"
36#include "io/Util.h"
Shane Farmer0a5b2012017-06-22 12:24:12 -070037#include "optimize/MultiApkGenerator.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070038#include "optimize/ResourceDeduper.h"
39#include "optimize/VersionCollapser.h"
40#include "split/TableSplitter.h"
Shane Farmer57669432017-06-19 12:52:04 -070041#include "util/Files.h"
Shane Farmer0a5b2012017-06-22 12:24:12 -070042#include "util/Util.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070043
Shane Farmer57669432017-06-19 12:52:04 -070044using ::aapt::configuration::Abi;
45using ::aapt::configuration::Artifact;
Shane Farmer280be342017-06-21 15:20:15 -070046using ::aapt::configuration::PostProcessingConfiguration;
Shane Farmer0a5b2012017-06-22 12:24:12 -070047using ::android::ResTable_config;
Shane Farmer57669432017-06-19 12:52:04 -070048using ::android::StringPiece;
Shane Farmer0a5b2012017-06-22 12:24:12 -070049using ::android::base::StringAppendF;
Shane Farmer57669432017-06-19 12:52:04 -070050using ::android::base::StringPrintf;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070051
52namespace aapt {
53
54struct OptimizeOptions {
55 // Path to the output APK.
Shane Farmer57669432017-06-19 12:52:04 -070056 Maybe<std::string> output_path;
57 // Path to the output APK directory for splits.
58 Maybe<std::string> output_dir;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070059
60 // Details of the app extracted from the AndroidManifest.xml
61 AppInfo app_info;
62
63 // Split APK options.
64 TableSplitterOptions table_splitter_options;
65
66 // List of output split paths. These are in the same order as `split_constraints`.
67 std::vector<std::string> split_paths;
68
69 // List of SplitConstraints governing what resources go into each split. Ordered by `split_paths`.
70 std::vector<SplitConstraints> split_constraints;
71
72 TableFlattenerOptions table_flattener_options;
Shane Farmer57669432017-06-19 12:52:04 -070073
Shane Farmer280be342017-06-21 15:20:15 -070074 Maybe<PostProcessingConfiguration> configuration;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070075};
76
77class OptimizeContext : public IAaptContext {
78 public:
Adam Lesinskib522f042017-04-21 16:57:59 -070079 OptimizeContext() = default;
80
81 PackageType GetPackageType() override {
82 // Not important here. Using anything other than kApp adds EXTRA validation, which we want to
83 // avoid.
84 return PackageType::kApp;
85 }
86
Adam Lesinskid0f492d2017-04-03 18:12:45 -070087 IDiagnostics* GetDiagnostics() override {
88 return &diagnostics_;
89 }
90
91 NameMangler* GetNameMangler() override {
92 UNIMPLEMENTED(FATAL);
93 return nullptr;
94 }
95
96 const std::string& GetCompilationPackage() override {
97 static std::string empty;
98 return empty;
99 }
100
101 uint8_t GetPackageId() override {
102 return 0;
103 }
104
105 SymbolTable* GetExternalSymbols() override {
106 UNIMPLEMENTED(FATAL);
107 return nullptr;
108 }
109
110 bool IsVerbose() override {
111 return verbose_;
112 }
113
114 void SetVerbose(bool val) {
115 verbose_ = val;
116 }
117
118 void SetMinSdkVersion(int sdk_version) {
119 sdk_version_ = sdk_version;
120 }
121
122 int GetMinSdkVersion() override {
123 return sdk_version_;
124 }
125
126 private:
Adam Lesinskib522f042017-04-21 16:57:59 -0700127 DISALLOW_COPY_AND_ASSIGN(OptimizeContext);
128
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700129 StdErrDiagnostics diagnostics_;
130 bool verbose_ = false;
131 int sdk_version_ = 0;
132};
133
134class OptimizeCommand {
135 public:
136 OptimizeCommand(OptimizeContext* context, const OptimizeOptions& options)
137 : options_(options), context_(context) {
138 }
139
140 int Run(std::unique_ptr<LoadedApk> apk) {
141 if (context_->IsVerbose()) {
142 context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK...");
143 }
144
145 VersionCollapser collapser;
146 if (!collapser.Consume(context_, apk->GetResourceTable())) {
147 return 1;
148 }
149
150 ResourceDeduper deduper;
151 if (!deduper.Consume(context_, apk->GetResourceTable())) {
152 context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources");
153 return 1;
154 }
155
156 // Adjust the SplitConstraints so that their SDK version is stripped if it is less than or
157 // equal to the minSdk.
158 options_.split_constraints =
159 AdjustSplitConstraintsForMinSdk(context_->GetMinSdkVersion(), options_.split_constraints);
160
161 // Stripping the APK using the TableSplitter. The resource table is modified in place in the
162 // LoadedApk.
163 TableSplitter splitter(options_.split_constraints, options_.table_splitter_options);
164 if (!splitter.VerifySplitConstraints(context_)) {
165 return 1;
166 }
167 splitter.SplitTable(apk->GetResourceTable());
168
169 auto path_iter = options_.split_paths.begin();
170 auto split_constraints_iter = options_.split_constraints.begin();
171 for (std::unique_ptr<ResourceTable>& split_table : splitter.splits()) {
172 if (context_->IsVerbose()) {
173 context_->GetDiagnostics()->Note(
174 DiagMessage(*path_iter) << "generating split with configurations '"
175 << util::Joiner(split_constraints_iter->configs, ", ") << "'");
176 }
177
178 // Generate an AndroidManifest.xml for each split.
179 std::unique_ptr<xml::XmlResource> split_manifest =
180 GenerateSplitManifest(options_.app_info, *split_constraints_iter);
181 std::unique_ptr<IArchiveWriter> split_writer =
182 CreateZipFileArchiveWriter(context_->GetDiagnostics(), *path_iter);
183 if (!split_writer) {
184 return 1;
185 }
186
187 if (!WriteSplitApk(split_table.get(), split_manifest.get(), split_writer.get())) {
188 return 1;
189 }
190
191 ++path_iter;
192 ++split_constraints_iter;
193 }
194
Shane Farmer57669432017-06-19 12:52:04 -0700195 if (options_.configuration && options_.output_dir) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700196 MultiApkGenerator generator{apk.get(), context_};
Shane Farmerefe45392017-08-21 14:39:28 -0700197 MultiApkGeneratorOptions generator_options = {options_.output_dir.value(),
198 options_.configuration.value(),
199 options_.table_flattener_options};
200 if (!generator.FromBaseApk(generator_options)) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700201 return 1;
Shane Farmer57669432017-06-19 12:52:04 -0700202 }
203 }
204
205 if (options_.output_path) {
206 std::unique_ptr<IArchiveWriter> writer =
207 CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path.value());
208 if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) {
209 return 1;
210 }
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700211 }
212
213 return 0;
214 }
215
216 private:
217 bool WriteSplitApk(ResourceTable* table, xml::XmlResource* manifest, IArchiveWriter* writer) {
218 BigBuffer manifest_buffer(4096);
219 XmlFlattener xml_flattener(&manifest_buffer, {});
220 if (!xml_flattener.Consume(context_, manifest)) {
221 return false;
222 }
223
224 io::BigBufferInputStream manifest_buffer_in(&manifest_buffer);
225 if (!io::CopyInputStreamToArchive(context_, &manifest_buffer_in, "AndroidManifest.xml",
226 ArchiveEntry::kCompress, writer)) {
227 return false;
228 }
229
230 std::map<std::pair<ConfigDescription, StringPiece>, FileReference*> config_sorted_files;
231 for (auto& pkg : table->packages) {
232 for (auto& type : pkg->types) {
233 // Sort by config and name, so that we get better locality in the zip file.
234 config_sorted_files.clear();
235
236 for (auto& entry : type->entries) {
237 for (auto& config_value : entry->values) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700238 auto* file_ref = ValueCast<FileReference>(config_value->value.get());
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700239 if (file_ref == nullptr) {
240 continue;
241 }
242
243 if (file_ref->file == nullptr) {
244 ResourceNameRef name(pkg->name, type->type, entry->name);
Adam Lesinski742888f2017-04-28 15:34:52 -0700245 context_->GetDiagnostics()->Warn(DiagMessage(file_ref->GetSource())
Shane Farmer57669432017-06-19 12:52:04 -0700246 << "file for resource " << name << " with config '"
247 << config_value->config << "' not found");
Adam Lesinski742888f2017-04-28 15:34:52 -0700248 continue;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700249 }
250
251 const StringPiece entry_name = entry->name;
252 config_sorted_files[std::make_pair(config_value->config, entry_name)] = file_ref;
253 }
254 }
255
256 for (auto& entry : config_sorted_files) {
257 FileReference* file_ref = entry.second;
258 uint32_t compression_flags =
259 file_ref->file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
260 if (!io::CopyFileToArchive(context_, file_ref->file, *file_ref->path, compression_flags,
261 writer)) {
262 return false;
263 }
264 }
265 }
266 }
267
268 BigBuffer table_buffer(4096);
269 TableFlattener table_flattener(options_.table_flattener_options, &table_buffer);
270 if (!table_flattener.Consume(context_, table)) {
271 return false;
272 }
273
274 io::BigBufferInputStream table_buffer_in(&table_buffer);
Shane Farmer0a5b2012017-06-22 12:24:12 -0700275 return io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc",
276 ArchiveEntry::kAlign, writer);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700277 }
278
279 OptimizeOptions options_;
280 OptimizeContext* context_;
281};
282
283bool ExtractAppDataFromManifest(OptimizeContext* context, LoadedApk* apk,
284 OptimizeOptions* out_options) {
285 io::IFile* manifest_file = apk->GetFileCollection()->FindFile("AndroidManifest.xml");
286 if (manifest_file == nullptr) {
287 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
288 << "missing AndroidManifest.xml");
289 return false;
290 }
291
292 std::unique_ptr<io::IData> data = manifest_file->OpenAsData();
293 if (data == nullptr) {
294 context->GetDiagnostics()->Error(DiagMessage(manifest_file->GetSource())
295 << "failed to open file");
296 return false;
297 }
298
299 std::unique_ptr<xml::XmlResource> manifest = xml::Inflate(
300 data->data(), data->size(), context->GetDiagnostics(), manifest_file->GetSource());
301 if (manifest == nullptr) {
302 context->GetDiagnostics()->Error(DiagMessage() << "failed to read binary AndroidManifest.xml");
303 return false;
304 }
305
306 Maybe<AppInfo> app_info =
307 ExtractAppInfoFromBinaryManifest(manifest.get(), context->GetDiagnostics());
308 if (!app_info) {
309 context->GetDiagnostics()->Error(DiagMessage()
310 << "failed to extract data from AndroidManifest.xml");
311 return false;
312 }
313
314 out_options->app_info = std::move(app_info.value());
315 context->SetMinSdkVersion(out_options->app_info.min_sdk_version.value_or_default(0));
316 return true;
317}
318
319int Optimize(const std::vector<StringPiece>& args) {
320 OptimizeContext context;
321 OptimizeOptions options;
Shane Farmer57669432017-06-19 12:52:04 -0700322 Maybe<std::string> config_path;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700323 Maybe<std::string> target_densities;
Shane Farmer0a5b2012017-06-22 12:24:12 -0700324 Maybe<std::string> target_abis;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700325 std::vector<std::string> configs;
326 std::vector<std::string> split_args;
327 bool verbose = false;
Shane Farmer9ecc0752017-08-24 15:55:36 -0700328 bool print_only = false;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700329 Flags flags =
330 Flags()
Shane Farmer57669432017-06-19 12:52:04 -0700331 .OptionalFlag("-o", "Path to the output APK.", &options.output_path)
332 .OptionalFlag("-d", "Path to the output directory (for splits).", &options.output_dir)
333 .OptionalFlag("-x", "Path to XML configuration file.", &config_path)
Shane Farmer9ecc0752017-08-24 15:55:36 -0700334 .OptionalSwitch("-p", "Print the multi APK artifacts and exit.", &print_only)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700335 .OptionalFlag(
336 "--target-densities",
337 "Comma separated list of the screen densities that the APK will be optimized for.\n"
338 "All the resources that would be unused on devices of the given densities will be \n"
339 "removed from the APK.",
340 &target_densities)
Shane Farmer0a5b2012017-06-22 12:24:12 -0700341 .OptionalFlag(
342 "--target-abis",
343 "Comma separated list of the CPU ABIs that the APK will be optimized for.\n"
344 "All the native libraries that would be unused on devices of the given ABIs will \n"
345 "be removed from the APK.",
346 &target_abis)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700347 .OptionalFlagList("-c",
348 "Comma separated list of configurations to include. The default\n"
349 "is all configurations.",
350 &configs)
351 .OptionalFlagList("--split",
352 "Split resources matching a set of configs out to a "
Adam Lesinskidb091572017-04-13 12:48:56 -0700353 "Split APK.\nSyntax: path/to/output.apk;<config>[,<config>[...]].\n"
354 "On Windows, use a semicolon ';' separator instead.",
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700355 &split_args)
356 .OptionalSwitch("--enable-sparse-encoding",
357 "Enables encoding sparse entries using a binary search tree.\n"
358 "This decreases APK size at the cost of resource retrieval performance.",
359 &options.table_flattener_options.use_sparse_entries)
360 .OptionalSwitch("-v", "Enables verbose logging", &verbose);
361
362 if (!flags.Parse("aapt2 optimize", args, &std::cerr)) {
363 return 1;
364 }
365
366 if (flags.GetArgs().size() != 1u) {
367 std::cerr << "must have one APK as argument.\n\n";
368 flags.Usage("aapt2 optimize", &std::cerr);
369 return 1;
370 }
371
Shane Farmer0a5b2012017-06-22 12:24:12 -0700372 const std::string& apk_path = flags.GetArgs()[0];
373 std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(&context, apk_path);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700374 if (!apk) {
375 return 1;
376 }
377
378 context.SetVerbose(verbose);
Shane Farmer9ecc0752017-08-24 15:55:36 -0700379 IDiagnostics* diag = context.GetDiagnostics();
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700380
381 if (target_densities) {
382 // Parse the target screen densities.
383 for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700384 Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700385 if (!target_density) {
386 return 1;
387 }
388 options.table_splitter_options.preferred_densities.push_back(target_density.value());
389 }
390 }
391
392 std::unique_ptr<IConfigFilter> filter;
393 if (!configs.empty()) {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700394 filter = ParseConfigFilterParameters(configs, diag);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700395 if (filter == nullptr) {
396 return 1;
397 }
398 options.table_splitter_options.config_filter = filter.get();
399 }
400
401 // Parse the split parameters.
402 for (const std::string& split_arg : split_args) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700403 options.split_paths.emplace_back();
404 options.split_constraints.emplace_back();
Shane Farmer9ecc0752017-08-24 15:55:36 -0700405 if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(),
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700406 &options.split_constraints.back())) {
407 return 1;
408 }
409 }
410
Shane Farmer57669432017-06-19 12:52:04 -0700411 if (config_path) {
Shane Farmer57669432017-06-19 12:52:04 -0700412 std::string& path = config_path.value();
413 Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path);
414 if (for_path) {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700415 options.configuration = for_path.value().WithDiagnostics(diag).Parse();
Shane Farmer57669432017-06-19 12:52:04 -0700416 } else {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700417 diag->Error(DiagMessage() << "Could not parse config file " << path);
Shane Farmer57669432017-06-19 12:52:04 -0700418 return 1;
419 }
Shane Farmer9ecc0752017-08-24 15:55:36 -0700420
421 if (print_only) {
422 std::vector<std::string> names;
423 const PostProcessingConfiguration& config = options.configuration.value();
424 if (!config.AllArtifactNames(file::GetFilename(apk_path), &names, diag)) {
425 diag->Error(DiagMessage() << "Failed to generate output artifact list");
426 return 1;
427 }
428
429 for (const auto& name : names) {
430 std::cout << name << std::endl;
431 }
432 return 0;
433 }
434
435 // Since we know that we are going to process the APK (not just print targets), make sure we
436 // have somewhere to write them to.
437 if (!options.output_dir) {
438 diag->Error(DiagMessage() << "Output directory is required when using a configuration file");
439 return 1;
440 }
441 } else if (print_only) {
442 diag->Error(DiagMessage() << "Asked to print artifacts without providing a configurations");
443 return 1;
Shane Farmer57669432017-06-19 12:52:04 -0700444 }
445
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700446 if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) {
447 return 1;
448 }
449
450 OptimizeCommand cmd(&context, options);
451 return cmd.Run(std::move(apk));
452}
453
454} // namespace aapt