blob: da32010226f0f313fdb994cbf3ded900b7f24d3c [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;
Shane Farmercb6c3f92017-11-27 13:19:36 -080047using ::aapt::configuration::OutputArtifact;
Shane Farmer0a5b2012017-06-22 12:24:12 -070048using ::android::ResTable_config;
Shane Farmer57669432017-06-19 12:52:04 -070049using ::android::StringPiece;
Luke Nicholsonb0643302017-12-01 15:29:03 -080050using ::android::base::ReadFileToString;
Shane Farmer0a5b2012017-06-22 12:24:12 -070051using ::android::base::StringAppendF;
Shane Farmer57669432017-06-19 12:52:04 -070052using ::android::base::StringPrintf;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070053
54namespace aapt {
55
56struct OptimizeOptions {
57 // Path to the output APK.
Shane Farmer57669432017-06-19 12:52:04 -070058 Maybe<std::string> output_path;
59 // Path to the output APK directory for splits.
60 Maybe<std::string> output_dir;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070061
62 // Details of the app extracted from the AndroidManifest.xml
63 AppInfo app_info;
64
65 // Split APK options.
66 TableSplitterOptions table_splitter_options;
67
68 // List of output split paths. These are in the same order as `split_constraints`.
69 std::vector<std::string> split_paths;
70
71 // List of SplitConstraints governing what resources go into each split. Ordered by `split_paths`.
72 std::vector<SplitConstraints> split_constraints;
73
74 TableFlattenerOptions table_flattener_options;
Shane Farmer57669432017-06-19 12:52:04 -070075
Shane Farmercb6c3f92017-11-27 13:19:36 -080076 Maybe<std::vector<OutputArtifact>> apk_artifacts;
Shane Farmer666de342017-11-29 16:07:51 -080077
78 // Set of artifacts to keep when generating multi-APK splits. If the list is empty, all artifacts
79 // are kept and will be written as output.
80 std::unordered_set<std::string> kept_artifacts;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070081};
82
83class OptimizeContext : public IAaptContext {
84 public:
Adam Lesinskib522f042017-04-21 16:57:59 -070085 OptimizeContext() = default;
86
87 PackageType GetPackageType() override {
88 // Not important here. Using anything other than kApp adds EXTRA validation, which we want to
89 // avoid.
90 return PackageType::kApp;
91 }
92
Adam Lesinskid0f492d2017-04-03 18:12:45 -070093 IDiagnostics* GetDiagnostics() override {
94 return &diagnostics_;
95 }
96
97 NameMangler* GetNameMangler() override {
98 UNIMPLEMENTED(FATAL);
99 return nullptr;
100 }
101
102 const std::string& GetCompilationPackage() override {
103 static std::string empty;
104 return empty;
105 }
106
107 uint8_t GetPackageId() override {
108 return 0;
109 }
110
111 SymbolTable* GetExternalSymbols() override {
112 UNIMPLEMENTED(FATAL);
113 return nullptr;
114 }
115
116 bool IsVerbose() override {
117 return verbose_;
118 }
119
120 void SetVerbose(bool val) {
121 verbose_ = val;
122 }
123
124 void SetMinSdkVersion(int sdk_version) {
125 sdk_version_ = sdk_version;
126 }
127
128 int GetMinSdkVersion() override {
129 return sdk_version_;
130 }
131
Chris Warrington481f0272018-02-06 14:03:39 +0000132 bool IsAutoNamespace() override {
133 return false;
134 }
135
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700136 private:
Adam Lesinskib522f042017-04-21 16:57:59 -0700137 DISALLOW_COPY_AND_ASSIGN(OptimizeContext);
138
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700139 StdErrDiagnostics diagnostics_;
140 bool verbose_ = false;
141 int sdk_version_ = 0;
142};
143
144class OptimizeCommand {
145 public:
146 OptimizeCommand(OptimizeContext* context, const OptimizeOptions& options)
147 : options_(options), context_(context) {
148 }
149
150 int Run(std::unique_ptr<LoadedApk> apk) {
151 if (context_->IsVerbose()) {
152 context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK...");
153 }
154
155 VersionCollapser collapser;
156 if (!collapser.Consume(context_, apk->GetResourceTable())) {
157 return 1;
158 }
159
160 ResourceDeduper deduper;
161 if (!deduper.Consume(context_, apk->GetResourceTable())) {
162 context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources");
163 return 1;
164 }
165
166 // Adjust the SplitConstraints so that their SDK version is stripped if it is less than or
167 // equal to the minSdk.
168 options_.split_constraints =
169 AdjustSplitConstraintsForMinSdk(context_->GetMinSdkVersion(), options_.split_constraints);
170
171 // Stripping the APK using the TableSplitter. The resource table is modified in place in the
172 // LoadedApk.
173 TableSplitter splitter(options_.split_constraints, options_.table_splitter_options);
174 if (!splitter.VerifySplitConstraints(context_)) {
175 return 1;
176 }
177 splitter.SplitTable(apk->GetResourceTable());
178
179 auto path_iter = options_.split_paths.begin();
180 auto split_constraints_iter = options_.split_constraints.begin();
181 for (std::unique_ptr<ResourceTable>& split_table : splitter.splits()) {
182 if (context_->IsVerbose()) {
183 context_->GetDiagnostics()->Note(
184 DiagMessage(*path_iter) << "generating split with configurations '"
185 << util::Joiner(split_constraints_iter->configs, ", ") << "'");
186 }
187
188 // Generate an AndroidManifest.xml for each split.
189 std::unique_ptr<xml::XmlResource> split_manifest =
190 GenerateSplitManifest(options_.app_info, *split_constraints_iter);
191 std::unique_ptr<IArchiveWriter> split_writer =
192 CreateZipFileArchiveWriter(context_->GetDiagnostics(), *path_iter);
193 if (!split_writer) {
194 return 1;
195 }
196
197 if (!WriteSplitApk(split_table.get(), split_manifest.get(), split_writer.get())) {
198 return 1;
199 }
200
201 ++path_iter;
202 ++split_constraints_iter;
203 }
204
Shane Farmercb6c3f92017-11-27 13:19:36 -0800205 if (options_.apk_artifacts && options_.output_dir) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700206 MultiApkGenerator generator{apk.get(), context_};
Shane Farmer666de342017-11-29 16:07:51 -0800207 MultiApkGeneratorOptions generator_options = {
Shane Farmercb6c3f92017-11-27 13:19:36 -0800208 options_.output_dir.value(), options_.apk_artifacts.value(),
209 options_.table_flattener_options, options_.kept_artifacts};
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 Lesinskid0f492d2017-04-03 18:12:45 -0700384
385 context.SetVerbose(verbose);
Shane Farmer9ecc0752017-08-24 15:55:36 -0700386 IDiagnostics* diag = context.GetDiagnostics();
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700387
Shane Farmer57669432017-06-19 12:52:04 -0700388 if (config_path) {
Shane Farmer57669432017-06-19 12:52:04 -0700389 std::string& path = config_path.value();
390 Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path);
391 if (for_path) {
Shane Farmercb6c3f92017-11-27 13:19:36 -0800392 options.apk_artifacts = for_path.value().WithDiagnostics(diag).Parse(apk_path);
393 if (!options.apk_artifacts) {
394 diag->Error(DiagMessage() << "Failed to parse the output artifact list");
395 return 1;
396 }
397
Shane Farmer57669432017-06-19 12:52:04 -0700398 } else {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700399 diag->Error(DiagMessage() << "Could not parse config file " << path);
Shane Farmer57669432017-06-19 12:52:04 -0700400 return 1;
401 }
Shane Farmer9ecc0752017-08-24 15:55:36 -0700402
403 if (print_only) {
Shane Farmercb6c3f92017-11-27 13:19:36 -0800404 for (const OutputArtifact& artifact : options.apk_artifacts.value()) {
405 std::cout << artifact.name << std::endl;
Shane Farmer9ecc0752017-08-24 15:55:36 -0700406 }
407 return 0;
408 }
409
Shane Farmer666de342017-11-29 16:07:51 -0800410 if (!kept_artifacts.empty()) {
Shane Farmercb6c3f92017-11-27 13:19:36 -0800411 for (const std::string& artifact_str : kept_artifacts) {
412 for (const StringPiece& artifact : util::Tokenize(artifact_str, ',')) {
Shane Farmer666de342017-11-29 16:07:51 -0800413 options.kept_artifacts.insert(artifact.to_string());
414 }
415 }
416 }
417
Shane Farmer9ecc0752017-08-24 15:55:36 -0700418 // Since we know that we are going to process the APK (not just print targets), make sure we
419 // have somewhere to write them to.
420 if (!options.output_dir) {
421 diag->Error(DiagMessage() << "Output directory is required when using a configuration file");
422 return 1;
423 }
424 } else if (print_only) {
425 diag->Error(DiagMessage() << "Asked to print artifacts without providing a configurations");
426 return 1;
Shane Farmer57669432017-06-19 12:52:04 -0700427 }
428
Shane Farmer2c122412017-12-15 16:55:54 -0800429 std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
430 if (!apk) {
431 return 1;
432 }
433
434 if (target_densities) {
435 // Parse the target screen densities.
436 for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
437 Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
438 if (!target_density) {
439 return 1;
440 }
441 options.table_splitter_options.preferred_densities.push_back(target_density.value());
442 }
443 }
444
445 std::unique_ptr<IConfigFilter> filter;
446 if (!configs.empty()) {
447 filter = ParseConfigFilterParameters(configs, diag);
448 if (filter == nullptr) {
449 return 1;
450 }
451 options.table_splitter_options.config_filter = filter.get();
452 }
453
454 // Parse the split parameters.
455 for (const std::string& split_arg : split_args) {
456 options.split_paths.emplace_back();
457 options.split_constraints.emplace_back();
458 if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(),
459 &options.split_constraints.back())) {
460 return 1;
461 }
462 }
463
Luke Nicholsonb0643302017-12-01 15:29:03 -0800464 if (options.table_flattener_options.collapse_key_stringpool) {
465 if (whitelist_path) {
466 std::string& path = whitelist_path.value();
467 if (!ExtractWhitelistFromConfig(path, &context, &options)) {
468 return 1;
469 }
470 }
471 }
472
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700473 if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) {
474 return 1;
475 }
476
477 OptimizeCommand cmd(&context, options);
478 return cmd.Run(std::move(apk));
479}
480
481} // namespace aapt