blob: 45297a7f8997781c751de6f5206f6d02bba9451e [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
Mårten Kongstad5c541f62018-06-20 08:46:41 +020023#include "androidfw/ConfigDescription.h"
Shane Farmer0a5b2012017-06-22 12:24:12 -070024#include "androidfw/ResourceTypes.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070025#include "androidfw/StringPiece.h"
26
27#include "Diagnostics.h"
28#include "Flags.h"
29#include "LoadedApk.h"
30#include "ResourceUtils.h"
31#include "SdkConstants.h"
32#include "ValueVisitor.h"
33#include "cmd/Util.h"
Shane Farmer57669432017-06-19 12:52:04 -070034#include "configuration/ConfigurationParser.h"
35#include "filter/AbiFilter.h"
Adam Lesinski46708052017-09-29 14:49:15 -070036#include "format/binary/TableFlattener.h"
37#include "format/binary/XmlFlattener.h"
Adam Lesinski00451162017-10-03 07:44:08 -070038#include "io/BigBufferStream.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070039#include "io/Util.h"
Shane Farmer0a5b2012017-06-22 12:24:12 -070040#include "optimize/MultiApkGenerator.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070041#include "optimize/ResourceDeduper.h"
42#include "optimize/VersionCollapser.h"
43#include "split/TableSplitter.h"
Shane Farmer57669432017-06-19 12:52:04 -070044#include "util/Files.h"
Shane Farmer0a5b2012017-06-22 12:24:12 -070045#include "util/Util.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070046
Shane Farmer57669432017-06-19 12:52:04 -070047using ::aapt::configuration::Abi;
Shane Farmercb6c3f92017-11-27 13:19:36 -080048using ::aapt::configuration::OutputArtifact;
Mårten Kongstad5c541f62018-06-20 08:46:41 +020049using ::android::ConfigDescription;
Shane Farmer0a5b2012017-06-22 12:24:12 -070050using ::android::ResTable_config;
Shane Farmer57669432017-06-19 12:52:04 -070051using ::android::StringPiece;
Luke Nicholsonb0643302017-12-01 15:29:03 -080052using ::android::base::ReadFileToString;
Shane Farmer0a5b2012017-06-22 12:24:12 -070053using ::android::base::StringAppendF;
Shane Farmer57669432017-06-19 12:52:04 -070054using ::android::base::StringPrintf;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070055
56namespace aapt {
57
58struct OptimizeOptions {
59 // Path to the output APK.
Shane Farmer57669432017-06-19 12:52:04 -070060 Maybe<std::string> output_path;
61 // Path to the output APK directory for splits.
62 Maybe<std::string> output_dir;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070063
64 // Details of the app extracted from the AndroidManifest.xml
65 AppInfo app_info;
66
67 // Split APK options.
68 TableSplitterOptions table_splitter_options;
69
70 // List of output split paths. These are in the same order as `split_constraints`.
71 std::vector<std::string> split_paths;
72
73 // List of SplitConstraints governing what resources go into each split. Ordered by `split_paths`.
74 std::vector<SplitConstraints> split_constraints;
75
76 TableFlattenerOptions table_flattener_options;
Shane Farmer57669432017-06-19 12:52:04 -070077
Shane Farmercb6c3f92017-11-27 13:19:36 -080078 Maybe<std::vector<OutputArtifact>> apk_artifacts;
Shane Farmer666de342017-11-29 16:07:51 -080079
80 // Set of artifacts to keep when generating multi-APK splits. If the list is empty, all artifacts
81 // are kept and will be written as output.
82 std::unordered_set<std::string> kept_artifacts;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070083};
84
85class OptimizeContext : public IAaptContext {
86 public:
Adam Lesinskib522f042017-04-21 16:57:59 -070087 OptimizeContext() = default;
88
89 PackageType GetPackageType() override {
90 // Not important here. Using anything other than kApp adds EXTRA validation, which we want to
91 // avoid.
92 return PackageType::kApp;
93 }
94
Adam Lesinskid0f492d2017-04-03 18:12:45 -070095 IDiagnostics* GetDiagnostics() override {
96 return &diagnostics_;
97 }
98
99 NameMangler* GetNameMangler() override {
100 UNIMPLEMENTED(FATAL);
101 return nullptr;
102 }
103
104 const std::string& GetCompilationPackage() override {
105 static std::string empty;
106 return empty;
107 }
108
109 uint8_t GetPackageId() override {
110 return 0;
111 }
112
113 SymbolTable* GetExternalSymbols() override {
114 UNIMPLEMENTED(FATAL);
115 return nullptr;
116 }
117
118 bool IsVerbose() override {
119 return verbose_;
120 }
121
122 void SetVerbose(bool val) {
123 verbose_ = val;
124 }
125
126 void SetMinSdkVersion(int sdk_version) {
127 sdk_version_ = sdk_version;
128 }
129
130 int GetMinSdkVersion() override {
131 return sdk_version_;
132 }
133
134 private:
Adam Lesinskib522f042017-04-21 16:57:59 -0700135 DISALLOW_COPY_AND_ASSIGN(OptimizeContext);
136
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700137 StdErrDiagnostics diagnostics_;
138 bool verbose_ = false;
139 int sdk_version_ = 0;
140};
141
142class OptimizeCommand {
143 public:
144 OptimizeCommand(OptimizeContext* context, const OptimizeOptions& options)
145 : options_(options), context_(context) {
146 }
147
148 int Run(std::unique_ptr<LoadedApk> apk) {
149 if (context_->IsVerbose()) {
150 context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK...");
151 }
152
153 VersionCollapser collapser;
154 if (!collapser.Consume(context_, apk->GetResourceTable())) {
155 return 1;
156 }
157
158 ResourceDeduper deduper;
159 if (!deduper.Consume(context_, apk->GetResourceTable())) {
160 context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources");
161 return 1;
162 }
163
164 // Adjust the SplitConstraints so that their SDK version is stripped if it is less than or
165 // equal to the minSdk.
166 options_.split_constraints =
167 AdjustSplitConstraintsForMinSdk(context_->GetMinSdkVersion(), options_.split_constraints);
168
169 // Stripping the APK using the TableSplitter. The resource table is modified in place in the
170 // LoadedApk.
171 TableSplitter splitter(options_.split_constraints, options_.table_splitter_options);
172 if (!splitter.VerifySplitConstraints(context_)) {
173 return 1;
174 }
175 splitter.SplitTable(apk->GetResourceTable());
176
177 auto path_iter = options_.split_paths.begin();
178 auto split_constraints_iter = options_.split_constraints.begin();
179 for (std::unique_ptr<ResourceTable>& split_table : splitter.splits()) {
180 if (context_->IsVerbose()) {
181 context_->GetDiagnostics()->Note(
182 DiagMessage(*path_iter) << "generating split with configurations '"
183 << util::Joiner(split_constraints_iter->configs, ", ") << "'");
184 }
185
186 // Generate an AndroidManifest.xml for each split.
187 std::unique_ptr<xml::XmlResource> split_manifest =
188 GenerateSplitManifest(options_.app_info, *split_constraints_iter);
189 std::unique_ptr<IArchiveWriter> split_writer =
190 CreateZipFileArchiveWriter(context_->GetDiagnostics(), *path_iter);
191 if (!split_writer) {
192 return 1;
193 }
194
195 if (!WriteSplitApk(split_table.get(), split_manifest.get(), split_writer.get())) {
196 return 1;
197 }
198
199 ++path_iter;
200 ++split_constraints_iter;
201 }
202
Shane Farmercb6c3f92017-11-27 13:19:36 -0800203 if (options_.apk_artifacts && options_.output_dir) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700204 MultiApkGenerator generator{apk.get(), context_};
Shane Farmer666de342017-11-29 16:07:51 -0800205 MultiApkGeneratorOptions generator_options = {
Shane Farmercb6c3f92017-11-27 13:19:36 -0800206 options_.output_dir.value(), options_.apk_artifacts.value(),
207 options_.table_flattener_options, options_.kept_artifacts};
Shane Farmerefe45392017-08-21 14:39:28 -0700208 if (!generator.FromBaseApk(generator_options)) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700209 return 1;
Shane Farmer57669432017-06-19 12:52:04 -0700210 }
211 }
212
213 if (options_.output_path) {
214 std::unique_ptr<IArchiveWriter> writer =
215 CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path.value());
216 if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) {
217 return 1;
218 }
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700219 }
220
221 return 0;
222 }
223
224 private:
225 bool WriteSplitApk(ResourceTable* table, xml::XmlResource* manifest, IArchiveWriter* writer) {
226 BigBuffer manifest_buffer(4096);
227 XmlFlattener xml_flattener(&manifest_buffer, {});
228 if (!xml_flattener.Consume(context_, manifest)) {
229 return false;
230 }
231
232 io::BigBufferInputStream manifest_buffer_in(&manifest_buffer);
233 if (!io::CopyInputStreamToArchive(context_, &manifest_buffer_in, "AndroidManifest.xml",
234 ArchiveEntry::kCompress, writer)) {
235 return false;
236 }
237
238 std::map<std::pair<ConfigDescription, StringPiece>, FileReference*> config_sorted_files;
239 for (auto& pkg : table->packages) {
240 for (auto& type : pkg->types) {
241 // Sort by config and name, so that we get better locality in the zip file.
242 config_sorted_files.clear();
243
244 for (auto& entry : type->entries) {
245 for (auto& config_value : entry->values) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700246 auto* file_ref = ValueCast<FileReference>(config_value->value.get());
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700247 if (file_ref == nullptr) {
248 continue;
249 }
250
251 if (file_ref->file == nullptr) {
252 ResourceNameRef name(pkg->name, type->type, entry->name);
Adam Lesinski742888f2017-04-28 15:34:52 -0700253 context_->GetDiagnostics()->Warn(DiagMessage(file_ref->GetSource())
Shane Farmer57669432017-06-19 12:52:04 -0700254 << "file for resource " << name << " with config '"
255 << config_value->config << "' not found");
Adam Lesinski742888f2017-04-28 15:34:52 -0700256 continue;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700257 }
258
259 const StringPiece entry_name = entry->name;
260 config_sorted_files[std::make_pair(config_value->config, entry_name)] = file_ref;
261 }
262 }
263
264 for (auto& entry : config_sorted_files) {
265 FileReference* file_ref = entry.second;
Pierre Lecesned55bef72017-11-10 22:31:01 +0000266 if (!io::CopyFileToArchivePreserveCompression(context_, file_ref->file, *file_ref->path,
267 writer)) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700268 return false;
269 }
270 }
271 }
272 }
273
274 BigBuffer table_buffer(4096);
275 TableFlattener table_flattener(options_.table_flattener_options, &table_buffer);
276 if (!table_flattener.Consume(context_, table)) {
277 return false;
278 }
279
280 io::BigBufferInputStream table_buffer_in(&table_buffer);
Shane Farmer0a5b2012017-06-22 12:24:12 -0700281 return io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc",
282 ArchiveEntry::kAlign, writer);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700283 }
284
285 OptimizeOptions options_;
286 OptimizeContext* context_;
287};
288
Luke Nicholsonb0643302017-12-01 15:29:03 -0800289bool ExtractWhitelistFromConfig(const std::string& path, OptimizeContext* context,
290 OptimizeOptions* options) {
291 std::string contents;
292 if (!ReadFileToString(path, &contents, true)) {
293 context->GetDiagnostics()->Error(DiagMessage()
294 << "failed to parse whitelist from config file: " << path);
295 return false;
296 }
297 for (const StringPiece& resource_name : util::Tokenize(contents, ',')) {
298 options->table_flattener_options.whitelisted_resources.insert(resource_name.to_string());
299 }
300 return true;
301}
302
Adam Lesinski8780eb62017-10-31 17:44:39 -0700303bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk,
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700304 OptimizeOptions* out_options) {
Adam Lesinski8780eb62017-10-31 17:44:39 -0700305 const xml::XmlResource* manifest = apk->GetManifest();
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700306 if (manifest == nullptr) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700307 return false;
308 }
309
Adam Lesinski8780eb62017-10-31 17:44:39 -0700310 Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*manifest, context->GetDiagnostics());
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700311 if (!app_info) {
312 context->GetDiagnostics()->Error(DiagMessage()
313 << "failed to extract data from AndroidManifest.xml");
314 return false;
315 }
316
317 out_options->app_info = std::move(app_info.value());
318 context->SetMinSdkVersion(out_options->app_info.min_sdk_version.value_or_default(0));
319 return true;
320}
321
322int Optimize(const std::vector<StringPiece>& args) {
323 OptimizeContext context;
324 OptimizeOptions options;
Shane Farmer57669432017-06-19 12:52:04 -0700325 Maybe<std::string> config_path;
Luke Nicholsonb0643302017-12-01 15:29:03 -0800326 Maybe<std::string> whitelist_path;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700327 Maybe<std::string> target_densities;
328 std::vector<std::string> configs;
329 std::vector<std::string> split_args;
Shane Farmer666de342017-11-29 16:07:51 -0800330 std::unordered_set<std::string> kept_artifacts;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700331 bool verbose = false;
Shane Farmer9ecc0752017-08-24 15:55:36 -0700332 bool print_only = false;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700333 Flags flags =
334 Flags()
Shane Farmer57669432017-06-19 12:52:04 -0700335 .OptionalFlag("-o", "Path to the output APK.", &options.output_path)
336 .OptionalFlag("-d", "Path to the output directory (for splits).", &options.output_dir)
337 .OptionalFlag("-x", "Path to XML configuration file.", &config_path)
Shane Farmer9ecc0752017-08-24 15:55:36 -0700338 .OptionalSwitch("-p", "Print the multi APK artifacts and exit.", &print_only)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700339 .OptionalFlag(
340 "--target-densities",
341 "Comma separated list of the screen densities that the APK will be optimized for.\n"
342 "All the resources that would be unused on devices of the given densities will be \n"
343 "removed from the APK.",
344 &target_densities)
Luke Nicholsonb0643302017-12-01 15:29:03 -0800345 .OptionalFlag("--whitelist-config-path",
346 "Path to the whitelist.cfg file containing whitelisted resources \n"
347 "whose names should not be altered in final resource tables.",
348 &whitelist_path)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700349 .OptionalFlagList("-c",
350 "Comma separated list of configurations to include. The default\n"
351 "is all configurations.",
352 &configs)
353 .OptionalFlagList("--split",
354 "Split resources matching a set of configs out to a "
Adam Lesinskidb091572017-04-13 12:48:56 -0700355 "Split APK.\nSyntax: path/to/output.apk;<config>[,<config>[...]].\n"
356 "On Windows, use a semicolon ';' separator instead.",
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700357 &split_args)
Shane Farmer666de342017-11-29 16:07:51 -0800358 .OptionalFlagList("--keep-artifacts",
359 "Comma separated list of artifacts to keep. If none are specified,\n"
360 "all artifacts will be kept.",
361 &kept_artifacts)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700362 .OptionalSwitch("--enable-sparse-encoding",
363 "Enables encoding sparse entries using a binary search tree.\n"
364 "This decreases APK size at the cost of resource retrieval performance.",
365 &options.table_flattener_options.use_sparse_entries)
Luke Nicholsonb0643302017-12-01 15:29:03 -0800366 .OptionalSwitch("--enable-resource-obfuscation",
367 "Enables obfuscation of key string pool to single value",
368 &options.table_flattener_options.collapse_key_stringpool)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700369 .OptionalSwitch("-v", "Enables verbose logging", &verbose);
370
371 if (!flags.Parse("aapt2 optimize", args, &std::cerr)) {
372 return 1;
373 }
374
375 if (flags.GetArgs().size() != 1u) {
376 std::cerr << "must have one APK as argument.\n\n";
377 flags.Usage("aapt2 optimize", &std::cerr);
378 return 1;
379 }
380
Shane Farmer0a5b2012017-06-22 12:24:12 -0700381 const std::string& apk_path = flags.GetArgs()[0];
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700382
383 context.SetVerbose(verbose);
Shane Farmer9ecc0752017-08-24 15:55:36 -0700384 IDiagnostics* diag = context.GetDiagnostics();
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700385
Shane Farmer57669432017-06-19 12:52:04 -0700386 if (config_path) {
Shane Farmer57669432017-06-19 12:52:04 -0700387 std::string& path = config_path.value();
388 Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path);
389 if (for_path) {
Shane Farmercb6c3f92017-11-27 13:19:36 -0800390 options.apk_artifacts = for_path.value().WithDiagnostics(diag).Parse(apk_path);
391 if (!options.apk_artifacts) {
392 diag->Error(DiagMessage() << "Failed to parse the output artifact list");
393 return 1;
394 }
395
Shane Farmer57669432017-06-19 12:52:04 -0700396 } else {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700397 diag->Error(DiagMessage() << "Could not parse config file " << path);
Shane Farmer57669432017-06-19 12:52:04 -0700398 return 1;
399 }
Shane Farmer9ecc0752017-08-24 15:55:36 -0700400
401 if (print_only) {
Shane Farmercb6c3f92017-11-27 13:19:36 -0800402 for (const OutputArtifact& artifact : options.apk_artifacts.value()) {
403 std::cout << artifact.name << std::endl;
Shane Farmer9ecc0752017-08-24 15:55:36 -0700404 }
405 return 0;
406 }
407
Shane Farmer666de342017-11-29 16:07:51 -0800408 if (!kept_artifacts.empty()) {
Shane Farmercb6c3f92017-11-27 13:19:36 -0800409 for (const std::string& artifact_str : kept_artifacts) {
410 for (const StringPiece& artifact : util::Tokenize(artifact_str, ',')) {
Shane Farmer666de342017-11-29 16:07:51 -0800411 options.kept_artifacts.insert(artifact.to_string());
412 }
413 }
414 }
415
Shane Farmer9ecc0752017-08-24 15:55:36 -0700416 // Since we know that we are going to process the APK (not just print targets), make sure we
417 // have somewhere to write them to.
418 if (!options.output_dir) {
419 diag->Error(DiagMessage() << "Output directory is required when using a configuration file");
420 return 1;
421 }
422 } else if (print_only) {
423 diag->Error(DiagMessage() << "Asked to print artifacts without providing a configurations");
424 return 1;
Shane Farmer57669432017-06-19 12:52:04 -0700425 }
426
Shane Farmer2c122412017-12-15 16:55:54 -0800427 std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
428 if (!apk) {
429 return 1;
430 }
431
432 if (target_densities) {
433 // Parse the target screen densities.
434 for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
435 Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
436 if (!target_density) {
437 return 1;
438 }
439 options.table_splitter_options.preferred_densities.push_back(target_density.value());
440 }
441 }
442
443 std::unique_ptr<IConfigFilter> filter;
444 if (!configs.empty()) {
445 filter = ParseConfigFilterParameters(configs, diag);
446 if (filter == nullptr) {
447 return 1;
448 }
449 options.table_splitter_options.config_filter = filter.get();
450 }
451
452 // Parse the split parameters.
453 for (const std::string& split_arg : split_args) {
454 options.split_paths.emplace_back();
455 options.split_constraints.emplace_back();
456 if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(),
457 &options.split_constraints.back())) {
458 return 1;
459 }
460 }
461
Luke Nicholsonb0643302017-12-01 15:29:03 -0800462 if (options.table_flattener_options.collapse_key_stringpool) {
463 if (whitelist_path) {
464 std::string& path = whitelist_path.value();
465 if (!ExtractWhitelistFromConfig(path, &context, &options)) {
466 return 1;
467 }
468 }
469 }
470
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700471 if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) {
472 return 1;
473 }
474
475 OptimizeCommand cmd(&context, options);
476 return cmd.Run(std::move(apk));
477}
478
479} // namespace aapt