blob: 3b90637d9629c394f776dfea9a34cb802d850617 [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
Luke Nicholsonb0643302017-12-01 15:29:03 -080020#include "android-base/file.h"
Shane Farmer57669432017-06-19 12:52:04 -070021#include "android-base/stringprintf.h"
Adam Lesinskid3ffa8442017-09-28 13:34:35 -070022
Shane Farmer0a5b2012017-06-22 12:24:12 -070023#include "androidfw/ResourceTypes.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070024#include "androidfw/StringPiece.h"
25
26#include "Diagnostics.h"
27#include "Flags.h"
28#include "LoadedApk.h"
29#include "ResourceUtils.h"
30#include "SdkConstants.h"
31#include "ValueVisitor.h"
32#include "cmd/Util.h"
Shane Farmer57669432017-06-19 12:52:04 -070033#include "configuration/ConfigurationParser.h"
34#include "filter/AbiFilter.h"
Adam Lesinski46708052017-09-29 14:49:15 -070035#include "format/binary/TableFlattener.h"
36#include "format/binary/XmlFlattener.h"
Adam Lesinski00451162017-10-03 07:44:08 -070037#include "io/BigBufferStream.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070038#include "io/Util.h"
Shane Farmer0a5b2012017-06-22 12:24:12 -070039#include "optimize/MultiApkGenerator.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070040#include "optimize/ResourceDeduper.h"
41#include "optimize/VersionCollapser.h"
42#include "split/TableSplitter.h"
Shane Farmer57669432017-06-19 12:52:04 -070043#include "util/Files.h"
Shane Farmer0a5b2012017-06-22 12:24:12 -070044#include "util/Util.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070045
Shane Farmer57669432017-06-19 12:52:04 -070046using ::aapt::configuration::Abi;
47using ::aapt::configuration::Artifact;
Shane Farmer280be342017-06-21 15:20:15 -070048using ::aapt::configuration::PostProcessingConfiguration;
Shane Farmer0a5b2012017-06-22 12:24:12 -070049using ::android::ResTable_config;
Shane Farmer57669432017-06-19 12:52:04 -070050using ::android::StringPiece;
Luke Nicholsonb0643302017-12-01 15:29:03 -080051using ::android::base::ReadFileToString;
Shane Farmer0a5b2012017-06-22 12:24:12 -070052using ::android::base::StringAppendF;
Shane Farmer57669432017-06-19 12:52:04 -070053using ::android::base::StringPrintf;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070054
55namespace aapt {
56
57struct OptimizeOptions {
58 // Path to the output APK.
Shane Farmer57669432017-06-19 12:52:04 -070059 Maybe<std::string> output_path;
60 // Path to the output APK directory for splits.
61 Maybe<std::string> output_dir;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070062
63 // Details of the app extracted from the AndroidManifest.xml
64 AppInfo app_info;
65
66 // Split APK options.
67 TableSplitterOptions table_splitter_options;
68
69 // List of output split paths. These are in the same order as `split_constraints`.
70 std::vector<std::string> split_paths;
71
72 // List of SplitConstraints governing what resources go into each split. Ordered by `split_paths`.
73 std::vector<SplitConstraints> split_constraints;
74
75 TableFlattenerOptions table_flattener_options;
Shane Farmer57669432017-06-19 12:52:04 -070076
Shane Farmer280be342017-06-21 15:20:15 -070077 Maybe<PostProcessingConfiguration> configuration;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070078};
79
80class OptimizeContext : public IAaptContext {
81 public:
Adam Lesinskib522f042017-04-21 16:57:59 -070082 OptimizeContext() = default;
83
84 PackageType GetPackageType() override {
85 // Not important here. Using anything other than kApp adds EXTRA validation, which we want to
86 // avoid.
87 return PackageType::kApp;
88 }
89
Adam Lesinskid0f492d2017-04-03 18:12:45 -070090 IDiagnostics* GetDiagnostics() override {
91 return &diagnostics_;
92 }
93
94 NameMangler* GetNameMangler() override {
95 UNIMPLEMENTED(FATAL);
96 return nullptr;
97 }
98
99 const std::string& GetCompilationPackage() override {
100 static std::string empty;
101 return empty;
102 }
103
104 uint8_t GetPackageId() override {
105 return 0;
106 }
107
108 SymbolTable* GetExternalSymbols() override {
109 UNIMPLEMENTED(FATAL);
110 return nullptr;
111 }
112
113 bool IsVerbose() override {
114 return verbose_;
115 }
116
117 void SetVerbose(bool val) {
118 verbose_ = val;
119 }
120
121 void SetMinSdkVersion(int sdk_version) {
122 sdk_version_ = sdk_version;
123 }
124
125 int GetMinSdkVersion() override {
126 return sdk_version_;
127 }
128
129 private:
Adam Lesinskib522f042017-04-21 16:57:59 -0700130 DISALLOW_COPY_AND_ASSIGN(OptimizeContext);
131
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700132 StdErrDiagnostics diagnostics_;
133 bool verbose_ = false;
134 int sdk_version_ = 0;
135};
136
137class OptimizeCommand {
138 public:
139 OptimizeCommand(OptimizeContext* context, const OptimizeOptions& options)
140 : options_(options), context_(context) {
141 }
142
143 int Run(std::unique_ptr<LoadedApk> apk) {
144 if (context_->IsVerbose()) {
145 context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK...");
146 }
147
148 VersionCollapser collapser;
149 if (!collapser.Consume(context_, apk->GetResourceTable())) {
150 return 1;
151 }
152
153 ResourceDeduper deduper;
154 if (!deduper.Consume(context_, apk->GetResourceTable())) {
155 context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources");
156 return 1;
157 }
158
159 // Adjust the SplitConstraints so that their SDK version is stripped if it is less than or
160 // equal to the minSdk.
161 options_.split_constraints =
162 AdjustSplitConstraintsForMinSdk(context_->GetMinSdkVersion(), options_.split_constraints);
163
164 // Stripping the APK using the TableSplitter. The resource table is modified in place in the
165 // LoadedApk.
166 TableSplitter splitter(options_.split_constraints, options_.table_splitter_options);
167 if (!splitter.VerifySplitConstraints(context_)) {
168 return 1;
169 }
170 splitter.SplitTable(apk->GetResourceTable());
171
172 auto path_iter = options_.split_paths.begin();
173 auto split_constraints_iter = options_.split_constraints.begin();
174 for (std::unique_ptr<ResourceTable>& split_table : splitter.splits()) {
175 if (context_->IsVerbose()) {
176 context_->GetDiagnostics()->Note(
177 DiagMessage(*path_iter) << "generating split with configurations '"
178 << util::Joiner(split_constraints_iter->configs, ", ") << "'");
179 }
180
181 // Generate an AndroidManifest.xml for each split.
182 std::unique_ptr<xml::XmlResource> split_manifest =
183 GenerateSplitManifest(options_.app_info, *split_constraints_iter);
184 std::unique_ptr<IArchiveWriter> split_writer =
185 CreateZipFileArchiveWriter(context_->GetDiagnostics(), *path_iter);
186 if (!split_writer) {
187 return 1;
188 }
189
190 if (!WriteSplitApk(split_table.get(), split_manifest.get(), split_writer.get())) {
191 return 1;
192 }
193
194 ++path_iter;
195 ++split_constraints_iter;
196 }
197
Shane Farmer57669432017-06-19 12:52:04 -0700198 if (options_.configuration && options_.output_dir) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700199 MultiApkGenerator generator{apk.get(), context_};
Shane Farmerefe45392017-08-21 14:39:28 -0700200 MultiApkGeneratorOptions generator_options = {options_.output_dir.value(),
201 options_.configuration.value(),
202 options_.table_flattener_options};
203 if (!generator.FromBaseApk(generator_options)) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700204 return 1;
Shane Farmer57669432017-06-19 12:52:04 -0700205 }
206 }
207
208 if (options_.output_path) {
209 std::unique_ptr<IArchiveWriter> writer =
210 CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path.value());
211 if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) {
212 return 1;
213 }
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700214 }
215
216 return 0;
217 }
218
219 private:
220 bool WriteSplitApk(ResourceTable* table, xml::XmlResource* manifest, IArchiveWriter* writer) {
221 BigBuffer manifest_buffer(4096);
222 XmlFlattener xml_flattener(&manifest_buffer, {});
223 if (!xml_flattener.Consume(context_, manifest)) {
224 return false;
225 }
226
227 io::BigBufferInputStream manifest_buffer_in(&manifest_buffer);
228 if (!io::CopyInputStreamToArchive(context_, &manifest_buffer_in, "AndroidManifest.xml",
229 ArchiveEntry::kCompress, writer)) {
230 return false;
231 }
232
233 std::map<std::pair<ConfigDescription, StringPiece>, FileReference*> config_sorted_files;
234 for (auto& pkg : table->packages) {
235 for (auto& type : pkg->types) {
236 // Sort by config and name, so that we get better locality in the zip file.
237 config_sorted_files.clear();
238
239 for (auto& entry : type->entries) {
240 for (auto& config_value : entry->values) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700241 auto* file_ref = ValueCast<FileReference>(config_value->value.get());
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700242 if (file_ref == nullptr) {
243 continue;
244 }
245
246 if (file_ref->file == nullptr) {
247 ResourceNameRef name(pkg->name, type->type, entry->name);
Adam Lesinski742888f2017-04-28 15:34:52 -0700248 context_->GetDiagnostics()->Warn(DiagMessage(file_ref->GetSource())
Shane Farmer57669432017-06-19 12:52:04 -0700249 << "file for resource " << name << " with config '"
250 << config_value->config << "' not found");
Adam Lesinski742888f2017-04-28 15:34:52 -0700251 continue;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700252 }
253
254 const StringPiece entry_name = entry->name;
255 config_sorted_files[std::make_pair(config_value->config, entry_name)] = file_ref;
256 }
257 }
258
259 for (auto& entry : config_sorted_files) {
260 FileReference* file_ref = entry.second;
Pierre Lecesned55bef72017-11-10 22:31:01 +0000261 if (!io::CopyFileToArchivePreserveCompression(context_, file_ref->file, *file_ref->path,
262 writer)) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700263 return false;
264 }
265 }
266 }
267 }
268
269 BigBuffer table_buffer(4096);
270 TableFlattener table_flattener(options_.table_flattener_options, &table_buffer);
271 if (!table_flattener.Consume(context_, table)) {
272 return false;
273 }
274
275 io::BigBufferInputStream table_buffer_in(&table_buffer);
Shane Farmer0a5b2012017-06-22 12:24:12 -0700276 return io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc",
277 ArchiveEntry::kAlign, writer);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700278 }
279
280 OptimizeOptions options_;
281 OptimizeContext* context_;
282};
283
Luke Nicholsonb0643302017-12-01 15:29:03 -0800284bool ExtractWhitelistFromConfig(const std::string& path, OptimizeContext* context,
285 OptimizeOptions* options) {
286 std::string contents;
287 if (!ReadFileToString(path, &contents, true)) {
288 context->GetDiagnostics()->Error(DiagMessage()
289 << "failed to parse whitelist from config file: " << path);
290 return false;
291 }
292 for (const StringPiece& resource_name : util::Tokenize(contents, ',')) {
293 options->table_flattener_options.whitelisted_resources.insert(resource_name.to_string());
294 }
295 return true;
296}
297
Adam Lesinski8780eb62017-10-31 17:44:39 -0700298bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk,
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700299 OptimizeOptions* out_options) {
Adam Lesinski8780eb62017-10-31 17:44:39 -0700300 const xml::XmlResource* manifest = apk->GetManifest();
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700301 if (manifest == nullptr) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700302 return false;
303 }
304
Adam Lesinski8780eb62017-10-31 17:44:39 -0700305 Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*manifest, context->GetDiagnostics());
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700306 if (!app_info) {
307 context->GetDiagnostics()->Error(DiagMessage()
308 << "failed to extract data from AndroidManifest.xml");
309 return false;
310 }
311
312 out_options->app_info = std::move(app_info.value());
313 context->SetMinSdkVersion(out_options->app_info.min_sdk_version.value_or_default(0));
314 return true;
315}
316
317int Optimize(const std::vector<StringPiece>& args) {
318 OptimizeContext context;
319 OptimizeOptions options;
Shane Farmer57669432017-06-19 12:52:04 -0700320 Maybe<std::string> config_path;
Luke Nicholsonb0643302017-12-01 15:29:03 -0800321 Maybe<std::string> whitelist_path;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700322 Maybe<std::string> target_densities;
Shane Farmer0a5b2012017-06-22 12:24:12 -0700323 Maybe<std::string> target_abis;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700324 std::vector<std::string> configs;
325 std::vector<std::string> split_args;
326 bool verbose = false;
Shane Farmer9ecc0752017-08-24 15:55:36 -0700327 bool print_only = false;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700328 Flags flags =
329 Flags()
Shane Farmer57669432017-06-19 12:52:04 -0700330 .OptionalFlag("-o", "Path to the output APK.", &options.output_path)
331 .OptionalFlag("-d", "Path to the output directory (for splits).", &options.output_dir)
332 .OptionalFlag("-x", "Path to XML configuration file.", &config_path)
Shane Farmer9ecc0752017-08-24 15:55:36 -0700333 .OptionalSwitch("-p", "Print the multi APK artifacts and exit.", &print_only)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700334 .OptionalFlag(
335 "--target-densities",
336 "Comma separated list of the screen densities that the APK will be optimized for.\n"
337 "All the resources that would be unused on devices of the given densities will be \n"
338 "removed from the APK.",
339 &target_densities)
Luke Nicholsonb0643302017-12-01 15:29:03 -0800340 .OptionalFlag("--whitelist-config-path",
341 "Path to the whitelist.cfg file containing whitelisted resources \n"
342 "whose names should not be altered in final resource tables.",
343 &whitelist_path)
Shane Farmer0a5b2012017-06-22 12:24:12 -0700344 .OptionalFlag(
345 "--target-abis",
346 "Comma separated list of the CPU ABIs that the APK will be optimized for.\n"
347 "All the native libraries that would be unused on devices of the given ABIs will \n"
348 "be removed from the APK.",
349 &target_abis)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700350 .OptionalFlagList("-c",
351 "Comma separated list of configurations to include. The default\n"
352 "is all configurations.",
353 &configs)
354 .OptionalFlagList("--split",
355 "Split resources matching a set of configs out to a "
Adam Lesinskidb091572017-04-13 12:48:56 -0700356 "Split APK.\nSyntax: path/to/output.apk;<config>[,<config>[...]].\n"
357 "On Windows, use a semicolon ';' separator instead.",
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700358 &split_args)
359 .OptionalSwitch("--enable-sparse-encoding",
360 "Enables encoding sparse entries using a binary search tree.\n"
361 "This decreases APK size at the cost of resource retrieval performance.",
362 &options.table_flattener_options.use_sparse_entries)
Luke Nicholsonb0643302017-12-01 15:29:03 -0800363 .OptionalSwitch("--enable-resource-obfuscation",
364 "Enables obfuscation of key string pool to single value",
365 &options.table_flattener_options.collapse_key_stringpool)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700366 .OptionalSwitch("-v", "Enables verbose logging", &verbose);
367
368 if (!flags.Parse("aapt2 optimize", args, &std::cerr)) {
369 return 1;
370 }
371
372 if (flags.GetArgs().size() != 1u) {
373 std::cerr << "must have one APK as argument.\n\n";
374 flags.Usage("aapt2 optimize", &std::cerr);
375 return 1;
376 }
377
Shane Farmer0a5b2012017-06-22 12:24:12 -0700378 const std::string& apk_path = flags.GetArgs()[0];
Adam Lesinski8780eb62017-10-31 17:44:39 -0700379 std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700380 if (!apk) {
381 return 1;
382 }
383
384 context.SetVerbose(verbose);
Shane Farmer9ecc0752017-08-24 15:55:36 -0700385 IDiagnostics* diag = context.GetDiagnostics();
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700386
387 if (target_densities) {
388 // Parse the target screen densities.
389 for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700390 Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700391 if (!target_density) {
392 return 1;
393 }
394 options.table_splitter_options.preferred_densities.push_back(target_density.value());
395 }
396 }
397
398 std::unique_ptr<IConfigFilter> filter;
399 if (!configs.empty()) {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700400 filter = ParseConfigFilterParameters(configs, diag);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700401 if (filter == nullptr) {
402 return 1;
403 }
404 options.table_splitter_options.config_filter = filter.get();
405 }
406
407 // Parse the split parameters.
408 for (const std::string& split_arg : split_args) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700409 options.split_paths.emplace_back();
410 options.split_constraints.emplace_back();
Shane Farmer9ecc0752017-08-24 15:55:36 -0700411 if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(),
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700412 &options.split_constraints.back())) {
413 return 1;
414 }
415 }
416
Shane Farmer57669432017-06-19 12:52:04 -0700417 if (config_path) {
Shane Farmer57669432017-06-19 12:52:04 -0700418 std::string& path = config_path.value();
419 Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path);
420 if (for_path) {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700421 options.configuration = for_path.value().WithDiagnostics(diag).Parse();
Shane Farmer57669432017-06-19 12:52:04 -0700422 } else {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700423 diag->Error(DiagMessage() << "Could not parse config file " << path);
Shane Farmer57669432017-06-19 12:52:04 -0700424 return 1;
425 }
Shane Farmer9ecc0752017-08-24 15:55:36 -0700426
427 if (print_only) {
428 std::vector<std::string> names;
429 const PostProcessingConfiguration& config = options.configuration.value();
430 if (!config.AllArtifactNames(file::GetFilename(apk_path), &names, diag)) {
431 diag->Error(DiagMessage() << "Failed to generate output artifact list");
432 return 1;
433 }
434
435 for (const auto& name : names) {
436 std::cout << name << std::endl;
437 }
438 return 0;
439 }
440
441 // Since we know that we are going to process the APK (not just print targets), make sure we
442 // have somewhere to write them to.
443 if (!options.output_dir) {
444 diag->Error(DiagMessage() << "Output directory is required when using a configuration file");
445 return 1;
446 }
447 } else if (print_only) {
448 diag->Error(DiagMessage() << "Asked to print artifacts without providing a configurations");
449 return 1;
Shane Farmer57669432017-06-19 12:52:04 -0700450 }
451
Luke Nicholsonb0643302017-12-01 15:29:03 -0800452 if (options.table_flattener_options.collapse_key_stringpool) {
453 if (whitelist_path) {
454 std::string& path = whitelist_path.value();
455 if (!ExtractWhitelistFromConfig(path, &context, &options)) {
456 return 1;
457 }
458 }
459 }
460
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700461 if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) {
462 return 1;
463 }
464
465 OptimizeCommand cmd(&context, options);
466 return cmd.Run(std::move(apk));
467}
468
469} // namespace aapt