blob: eaadfd82629e9f5e24fc80af3d645b1c33da8ccf [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;
Shane Farmer666de342017-11-29 16:07:51 -080078
79 // Set of artifacts to keep when generating multi-APK splits. If the list is empty, all artifacts
80 // are kept and will be written as output.
81 std::unordered_set<std::string> kept_artifacts;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070082};
83
84class OptimizeContext : public IAaptContext {
85 public:
Adam Lesinskib522f042017-04-21 16:57:59 -070086 OptimizeContext() = default;
87
88 PackageType GetPackageType() override {
89 // Not important here. Using anything other than kApp adds EXTRA validation, which we want to
90 // avoid.
91 return PackageType::kApp;
92 }
93
Adam Lesinskid0f492d2017-04-03 18:12:45 -070094 IDiagnostics* GetDiagnostics() override {
95 return &diagnostics_;
96 }
97
98 NameMangler* GetNameMangler() override {
99 UNIMPLEMENTED(FATAL);
100 return nullptr;
101 }
102
103 const std::string& GetCompilationPackage() override {
104 static std::string empty;
105 return empty;
106 }
107
108 uint8_t GetPackageId() override {
109 return 0;
110 }
111
112 SymbolTable* GetExternalSymbols() override {
113 UNIMPLEMENTED(FATAL);
114 return nullptr;
115 }
116
117 bool IsVerbose() override {
118 return verbose_;
119 }
120
121 void SetVerbose(bool val) {
122 verbose_ = val;
123 }
124
125 void SetMinSdkVersion(int sdk_version) {
126 sdk_version_ = sdk_version;
127 }
128
129 int GetMinSdkVersion() override {
130 return sdk_version_;
131 }
132
133 private:
Adam Lesinskib522f042017-04-21 16:57:59 -0700134 DISALLOW_COPY_AND_ASSIGN(OptimizeContext);
135
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700136 StdErrDiagnostics diagnostics_;
137 bool verbose_ = false;
138 int sdk_version_ = 0;
139};
140
141class OptimizeCommand {
142 public:
143 OptimizeCommand(OptimizeContext* context, const OptimizeOptions& options)
144 : options_(options), context_(context) {
145 }
146
147 int Run(std::unique_ptr<LoadedApk> apk) {
148 if (context_->IsVerbose()) {
149 context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK...");
150 }
151
152 VersionCollapser collapser;
153 if (!collapser.Consume(context_, apk->GetResourceTable())) {
154 return 1;
155 }
156
157 ResourceDeduper deduper;
158 if (!deduper.Consume(context_, apk->GetResourceTable())) {
159 context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources");
160 return 1;
161 }
162
163 // Adjust the SplitConstraints so that their SDK version is stripped if it is less than or
164 // equal to the minSdk.
165 options_.split_constraints =
166 AdjustSplitConstraintsForMinSdk(context_->GetMinSdkVersion(), options_.split_constraints);
167
168 // Stripping the APK using the TableSplitter. The resource table is modified in place in the
169 // LoadedApk.
170 TableSplitter splitter(options_.split_constraints, options_.table_splitter_options);
171 if (!splitter.VerifySplitConstraints(context_)) {
172 return 1;
173 }
174 splitter.SplitTable(apk->GetResourceTable());
175
176 auto path_iter = options_.split_paths.begin();
177 auto split_constraints_iter = options_.split_constraints.begin();
178 for (std::unique_ptr<ResourceTable>& split_table : splitter.splits()) {
179 if (context_->IsVerbose()) {
180 context_->GetDiagnostics()->Note(
181 DiagMessage(*path_iter) << "generating split with configurations '"
182 << util::Joiner(split_constraints_iter->configs, ", ") << "'");
183 }
184
185 // Generate an AndroidManifest.xml for each split.
186 std::unique_ptr<xml::XmlResource> split_manifest =
187 GenerateSplitManifest(options_.app_info, *split_constraints_iter);
188 std::unique_ptr<IArchiveWriter> split_writer =
189 CreateZipFileArchiveWriter(context_->GetDiagnostics(), *path_iter);
190 if (!split_writer) {
191 return 1;
192 }
193
194 if (!WriteSplitApk(split_table.get(), split_manifest.get(), split_writer.get())) {
195 return 1;
196 }
197
198 ++path_iter;
199 ++split_constraints_iter;
200 }
201
Shane Farmer57669432017-06-19 12:52:04 -0700202 if (options_.configuration && options_.output_dir) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700203 MultiApkGenerator generator{apk.get(), context_};
Shane Farmer666de342017-11-29 16:07:51 -0800204 MultiApkGeneratorOptions generator_options = {
205 options_.output_dir.value(),
206 options_.configuration.value(),
207 options_.table_flattener_options,
208 options_.kept_artifacts,
209 };
Shane Farmerefe45392017-08-21 14:39:28 -0700210 if (!generator.FromBaseApk(generator_options)) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700211 return 1;
Shane Farmer57669432017-06-19 12:52:04 -0700212 }
213 }
214
215 if (options_.output_path) {
216 std::unique_ptr<IArchiveWriter> writer =
217 CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path.value());
218 if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) {
219 return 1;
220 }
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700221 }
222
223 return 0;
224 }
225
226 private:
227 bool WriteSplitApk(ResourceTable* table, xml::XmlResource* manifest, IArchiveWriter* writer) {
228 BigBuffer manifest_buffer(4096);
229 XmlFlattener xml_flattener(&manifest_buffer, {});
230 if (!xml_flattener.Consume(context_, manifest)) {
231 return false;
232 }
233
234 io::BigBufferInputStream manifest_buffer_in(&manifest_buffer);
235 if (!io::CopyInputStreamToArchive(context_, &manifest_buffer_in, "AndroidManifest.xml",
236 ArchiveEntry::kCompress, writer)) {
237 return false;
238 }
239
240 std::map<std::pair<ConfigDescription, StringPiece>, FileReference*> config_sorted_files;
241 for (auto& pkg : table->packages) {
242 for (auto& type : pkg->types) {
243 // Sort by config and name, so that we get better locality in the zip file.
244 config_sorted_files.clear();
245
246 for (auto& entry : type->entries) {
247 for (auto& config_value : entry->values) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700248 auto* file_ref = ValueCast<FileReference>(config_value->value.get());
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700249 if (file_ref == nullptr) {
250 continue;
251 }
252
253 if (file_ref->file == nullptr) {
254 ResourceNameRef name(pkg->name, type->type, entry->name);
Adam Lesinski742888f2017-04-28 15:34:52 -0700255 context_->GetDiagnostics()->Warn(DiagMessage(file_ref->GetSource())
Shane Farmer57669432017-06-19 12:52:04 -0700256 << "file for resource " << name << " with config '"
257 << config_value->config << "' not found");
Adam Lesinski742888f2017-04-28 15:34:52 -0700258 continue;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700259 }
260
261 const StringPiece entry_name = entry->name;
262 config_sorted_files[std::make_pair(config_value->config, entry_name)] = file_ref;
263 }
264 }
265
266 for (auto& entry : config_sorted_files) {
267 FileReference* file_ref = entry.second;
Pierre Lecesned55bef72017-11-10 22:31:01 +0000268 if (!io::CopyFileToArchivePreserveCompression(context_, file_ref->file, *file_ref->path,
269 writer)) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700270 return false;
271 }
272 }
273 }
274 }
275
276 BigBuffer table_buffer(4096);
277 TableFlattener table_flattener(options_.table_flattener_options, &table_buffer);
278 if (!table_flattener.Consume(context_, table)) {
279 return false;
280 }
281
282 io::BigBufferInputStream table_buffer_in(&table_buffer);
Shane Farmer0a5b2012017-06-22 12:24:12 -0700283 return io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc",
284 ArchiveEntry::kAlign, writer);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700285 }
286
287 OptimizeOptions options_;
288 OptimizeContext* context_;
289};
290
Luke Nicholsonb0643302017-12-01 15:29:03 -0800291bool ExtractWhitelistFromConfig(const std::string& path, OptimizeContext* context,
292 OptimizeOptions* options) {
293 std::string contents;
294 if (!ReadFileToString(path, &contents, true)) {
295 context->GetDiagnostics()->Error(DiagMessage()
296 << "failed to parse whitelist from config file: " << path);
297 return false;
298 }
299 for (const StringPiece& resource_name : util::Tokenize(contents, ',')) {
300 options->table_flattener_options.whitelisted_resources.insert(resource_name.to_string());
301 }
302 return true;
303}
304
Adam Lesinski8780eb62017-10-31 17:44:39 -0700305bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk,
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700306 OptimizeOptions* out_options) {
Adam Lesinski8780eb62017-10-31 17:44:39 -0700307 const xml::XmlResource* manifest = apk->GetManifest();
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700308 if (manifest == nullptr) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700309 return false;
310 }
311
Adam Lesinski8780eb62017-10-31 17:44:39 -0700312 Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*manifest, context->GetDiagnostics());
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700313 if (!app_info) {
314 context->GetDiagnostics()->Error(DiagMessage()
315 << "failed to extract data from AndroidManifest.xml");
316 return false;
317 }
318
319 out_options->app_info = std::move(app_info.value());
320 context->SetMinSdkVersion(out_options->app_info.min_sdk_version.value_or_default(0));
321 return true;
322}
323
324int Optimize(const std::vector<StringPiece>& args) {
325 OptimizeContext context;
326 OptimizeOptions options;
Shane Farmer57669432017-06-19 12:52:04 -0700327 Maybe<std::string> config_path;
Luke Nicholsonb0643302017-12-01 15:29:03 -0800328 Maybe<std::string> whitelist_path;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700329 Maybe<std::string> target_densities;
330 std::vector<std::string> configs;
331 std::vector<std::string> split_args;
Shane Farmer666de342017-11-29 16:07:51 -0800332 std::unordered_set<std::string> kept_artifacts;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700333 bool verbose = false;
Shane Farmer9ecc0752017-08-24 15:55:36 -0700334 bool print_only = false;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700335 Flags flags =
336 Flags()
Shane Farmer57669432017-06-19 12:52:04 -0700337 .OptionalFlag("-o", "Path to the output APK.", &options.output_path)
338 .OptionalFlag("-d", "Path to the output directory (for splits).", &options.output_dir)
339 .OptionalFlag("-x", "Path to XML configuration file.", &config_path)
Shane Farmer9ecc0752017-08-24 15:55:36 -0700340 .OptionalSwitch("-p", "Print the multi APK artifacts and exit.", &print_only)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700341 .OptionalFlag(
342 "--target-densities",
343 "Comma separated list of the screen densities that the APK will be optimized for.\n"
344 "All the resources that would be unused on devices of the given densities will be \n"
345 "removed from the APK.",
346 &target_densities)
Luke Nicholsonb0643302017-12-01 15:29:03 -0800347 .OptionalFlag("--whitelist-config-path",
348 "Path to the whitelist.cfg file containing whitelisted resources \n"
349 "whose names should not be altered in final resource tables.",
350 &whitelist_path)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700351 .OptionalFlagList("-c",
352 "Comma separated list of configurations to include. The default\n"
353 "is all configurations.",
354 &configs)
355 .OptionalFlagList("--split",
356 "Split resources matching a set of configs out to a "
Adam Lesinskidb091572017-04-13 12:48:56 -0700357 "Split APK.\nSyntax: path/to/output.apk;<config>[,<config>[...]].\n"
358 "On Windows, use a semicolon ';' separator instead.",
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700359 &split_args)
Shane Farmer666de342017-11-29 16:07:51 -0800360 .OptionalFlagList("--keep-artifacts",
361 "Comma separated list of artifacts to keep. If none are specified,\n"
362 "all artifacts will be kept.",
363 &kept_artifacts)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700364 .OptionalSwitch("--enable-sparse-encoding",
365 "Enables encoding sparse entries using a binary search tree.\n"
366 "This decreases APK size at the cost of resource retrieval performance.",
367 &options.table_flattener_options.use_sparse_entries)
Luke Nicholsonb0643302017-12-01 15:29:03 -0800368 .OptionalSwitch("--enable-resource-obfuscation",
369 "Enables obfuscation of key string pool to single value",
370 &options.table_flattener_options.collapse_key_stringpool)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700371 .OptionalSwitch("-v", "Enables verbose logging", &verbose);
372
373 if (!flags.Parse("aapt2 optimize", args, &std::cerr)) {
374 return 1;
375 }
376
377 if (flags.GetArgs().size() != 1u) {
378 std::cerr << "must have one APK as argument.\n\n";
379 flags.Usage("aapt2 optimize", &std::cerr);
380 return 1;
381 }
382
Shane Farmer0a5b2012017-06-22 12:24:12 -0700383 const std::string& apk_path = flags.GetArgs()[0];
Adam Lesinski8780eb62017-10-31 17:44:39 -0700384 std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700385 if (!apk) {
386 return 1;
387 }
388
389 context.SetVerbose(verbose);
Shane Farmer9ecc0752017-08-24 15:55:36 -0700390 IDiagnostics* diag = context.GetDiagnostics();
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700391
392 if (target_densities) {
393 // Parse the target screen densities.
394 for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700395 Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700396 if (!target_density) {
397 return 1;
398 }
399 options.table_splitter_options.preferred_densities.push_back(target_density.value());
400 }
401 }
402
403 std::unique_ptr<IConfigFilter> filter;
404 if (!configs.empty()) {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700405 filter = ParseConfigFilterParameters(configs, diag);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700406 if (filter == nullptr) {
407 return 1;
408 }
409 options.table_splitter_options.config_filter = filter.get();
410 }
411
412 // Parse the split parameters.
413 for (const std::string& split_arg : split_args) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700414 options.split_paths.emplace_back();
415 options.split_constraints.emplace_back();
Shane Farmer9ecc0752017-08-24 15:55:36 -0700416 if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(),
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700417 &options.split_constraints.back())) {
418 return 1;
419 }
420 }
421
Shane Farmer57669432017-06-19 12:52:04 -0700422 if (config_path) {
Shane Farmer57669432017-06-19 12:52:04 -0700423 std::string& path = config_path.value();
424 Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path);
425 if (for_path) {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700426 options.configuration = for_path.value().WithDiagnostics(diag).Parse();
Shane Farmer57669432017-06-19 12:52:04 -0700427 } else {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700428 diag->Error(DiagMessage() << "Could not parse config file " << path);
Shane Farmer57669432017-06-19 12:52:04 -0700429 return 1;
430 }
Shane Farmer9ecc0752017-08-24 15:55:36 -0700431
432 if (print_only) {
433 std::vector<std::string> names;
434 const PostProcessingConfiguration& config = options.configuration.value();
435 if (!config.AllArtifactNames(file::GetFilename(apk_path), &names, diag)) {
436 diag->Error(DiagMessage() << "Failed to generate output artifact list");
437 return 1;
438 }
439
440 for (const auto& name : names) {
441 std::cout << name << std::endl;
442 }
443 return 0;
444 }
445
Shane Farmer666de342017-11-29 16:07:51 -0800446 if (!kept_artifacts.empty()) {
447 for (const auto& artifact_str : kept_artifacts) {
448 for (const auto& artifact : util::Tokenize(artifact_str, ',')) {
449 options.kept_artifacts.insert(artifact.to_string());
450 }
451 }
452 }
453
Shane Farmer9ecc0752017-08-24 15:55:36 -0700454 // Since we know that we are going to process the APK (not just print targets), make sure we
455 // have somewhere to write them to.
456 if (!options.output_dir) {
457 diag->Error(DiagMessage() << "Output directory is required when using a configuration file");
458 return 1;
459 }
460 } else if (print_only) {
461 diag->Error(DiagMessage() << "Asked to print artifacts without providing a configurations");
462 return 1;
Shane Farmer57669432017-06-19 12:52:04 -0700463 }
464
Luke Nicholsonb0643302017-12-01 15:29:03 -0800465 if (options.table_flattener_options.collapse_key_stringpool) {
466 if (whitelist_path) {
467 std::string& path = whitelist_path.value();
468 if (!ExtractWhitelistFromConfig(path, &context, &options)) {
469 return 1;
470 }
471 }
472 }
473
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700474 if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) {
475 return 1;
476 }
477
478 OptimizeCommand cmd(&context, options);
479 return cmd.Run(std::move(apk));
480}
481
482} // namespace aapt