blob: 9c76119f9504a206b9c36475d78b2c5242fdd2df [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
132 private:
Adam Lesinskib522f042017-04-21 16:57:59 -0700133 DISALLOW_COPY_AND_ASSIGN(OptimizeContext);
134
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700135 StdErrDiagnostics diagnostics_;
136 bool verbose_ = false;
137 int sdk_version_ = 0;
138};
139
140class OptimizeCommand {
141 public:
142 OptimizeCommand(OptimizeContext* context, const OptimizeOptions& options)
143 : options_(options), context_(context) {
144 }
145
146 int Run(std::unique_ptr<LoadedApk> apk) {
147 if (context_->IsVerbose()) {
148 context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK...");
149 }
150
151 VersionCollapser collapser;
152 if (!collapser.Consume(context_, apk->GetResourceTable())) {
153 return 1;
154 }
155
156 ResourceDeduper deduper;
157 if (!deduper.Consume(context_, apk->GetResourceTable())) {
158 context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources");
159 return 1;
160 }
161
162 // Adjust the SplitConstraints so that their SDK version is stripped if it is less than or
163 // equal to the minSdk.
164 options_.split_constraints =
165 AdjustSplitConstraintsForMinSdk(context_->GetMinSdkVersion(), options_.split_constraints);
166
167 // Stripping the APK using the TableSplitter. The resource table is modified in place in the
168 // LoadedApk.
169 TableSplitter splitter(options_.split_constraints, options_.table_splitter_options);
170 if (!splitter.VerifySplitConstraints(context_)) {
171 return 1;
172 }
173 splitter.SplitTable(apk->GetResourceTable());
174
175 auto path_iter = options_.split_paths.begin();
176 auto split_constraints_iter = options_.split_constraints.begin();
177 for (std::unique_ptr<ResourceTable>& split_table : splitter.splits()) {
178 if (context_->IsVerbose()) {
179 context_->GetDiagnostics()->Note(
180 DiagMessage(*path_iter) << "generating split with configurations '"
181 << util::Joiner(split_constraints_iter->configs, ", ") << "'");
182 }
183
184 // Generate an AndroidManifest.xml for each split.
185 std::unique_ptr<xml::XmlResource> split_manifest =
186 GenerateSplitManifest(options_.app_info, *split_constraints_iter);
187 std::unique_ptr<IArchiveWriter> split_writer =
188 CreateZipFileArchiveWriter(context_->GetDiagnostics(), *path_iter);
189 if (!split_writer) {
190 return 1;
191 }
192
193 if (!WriteSplitApk(split_table.get(), split_manifest.get(), split_writer.get())) {
194 return 1;
195 }
196
197 ++path_iter;
198 ++split_constraints_iter;
199 }
200
Shane Farmercb6c3f92017-11-27 13:19:36 -0800201 if (options_.apk_artifacts && options_.output_dir) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700202 MultiApkGenerator generator{apk.get(), context_};
Shane Farmer666de342017-11-29 16:07:51 -0800203 MultiApkGeneratorOptions generator_options = {
Shane Farmercb6c3f92017-11-27 13:19:36 -0800204 options_.output_dir.value(), options_.apk_artifacts.value(),
205 options_.table_flattener_options, options_.kept_artifacts};
Shane Farmerefe45392017-08-21 14:39:28 -0700206 if (!generator.FromBaseApk(generator_options)) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700207 return 1;
Shane Farmer57669432017-06-19 12:52:04 -0700208 }
209 }
210
211 if (options_.output_path) {
212 std::unique_ptr<IArchiveWriter> writer =
213 CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path.value());
214 if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) {
215 return 1;
216 }
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700217 }
218
219 return 0;
220 }
221
222 private:
223 bool WriteSplitApk(ResourceTable* table, xml::XmlResource* manifest, IArchiveWriter* writer) {
224 BigBuffer manifest_buffer(4096);
225 XmlFlattener xml_flattener(&manifest_buffer, {});
226 if (!xml_flattener.Consume(context_, manifest)) {
227 return false;
228 }
229
230 io::BigBufferInputStream manifest_buffer_in(&manifest_buffer);
231 if (!io::CopyInputStreamToArchive(context_, &manifest_buffer_in, "AndroidManifest.xml",
232 ArchiveEntry::kCompress, writer)) {
233 return false;
234 }
235
236 std::map<std::pair<ConfigDescription, StringPiece>, FileReference*> config_sorted_files;
237 for (auto& pkg : table->packages) {
238 for (auto& type : pkg->types) {
239 // Sort by config and name, so that we get better locality in the zip file.
240 config_sorted_files.clear();
241
242 for (auto& entry : type->entries) {
243 for (auto& config_value : entry->values) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700244 auto* file_ref = ValueCast<FileReference>(config_value->value.get());
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700245 if (file_ref == nullptr) {
246 continue;
247 }
248
249 if (file_ref->file == nullptr) {
250 ResourceNameRef name(pkg->name, type->type, entry->name);
Adam Lesinski742888f2017-04-28 15:34:52 -0700251 context_->GetDiagnostics()->Warn(DiagMessage(file_ref->GetSource())
Shane Farmer57669432017-06-19 12:52:04 -0700252 << "file for resource " << name << " with config '"
253 << config_value->config << "' not found");
Adam Lesinski742888f2017-04-28 15:34:52 -0700254 continue;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700255 }
256
257 const StringPiece entry_name = entry->name;
258 config_sorted_files[std::make_pair(config_value->config, entry_name)] = file_ref;
259 }
260 }
261
262 for (auto& entry : config_sorted_files) {
263 FileReference* file_ref = entry.second;
Pierre Lecesned55bef72017-11-10 22:31:01 +0000264 if (!io::CopyFileToArchivePreserveCompression(context_, file_ref->file, *file_ref->path,
265 writer)) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700266 return false;
267 }
268 }
269 }
270 }
271
272 BigBuffer table_buffer(4096);
273 TableFlattener table_flattener(options_.table_flattener_options, &table_buffer);
274 if (!table_flattener.Consume(context_, table)) {
275 return false;
276 }
277
278 io::BigBufferInputStream table_buffer_in(&table_buffer);
Shane Farmer0a5b2012017-06-22 12:24:12 -0700279 return io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc",
280 ArchiveEntry::kAlign, writer);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700281 }
282
283 OptimizeOptions options_;
284 OptimizeContext* context_;
285};
286
Luke Nicholsonb0643302017-12-01 15:29:03 -0800287bool ExtractWhitelistFromConfig(const std::string& path, OptimizeContext* context,
288 OptimizeOptions* options) {
289 std::string contents;
290 if (!ReadFileToString(path, &contents, true)) {
291 context->GetDiagnostics()->Error(DiagMessage()
292 << "failed to parse whitelist from config file: " << path);
293 return false;
294 }
295 for (const StringPiece& resource_name : util::Tokenize(contents, ',')) {
296 options->table_flattener_options.whitelisted_resources.insert(resource_name.to_string());
297 }
298 return true;
299}
300
Adam Lesinski8780eb62017-10-31 17:44:39 -0700301bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk,
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700302 OptimizeOptions* out_options) {
Adam Lesinski8780eb62017-10-31 17:44:39 -0700303 const xml::XmlResource* manifest = apk->GetManifest();
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700304 if (manifest == nullptr) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700305 return false;
306 }
307
Adam Lesinski8780eb62017-10-31 17:44:39 -0700308 Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*manifest, context->GetDiagnostics());
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700309 if (!app_info) {
310 context->GetDiagnostics()->Error(DiagMessage()
311 << "failed to extract data from AndroidManifest.xml");
312 return false;
313 }
314
315 out_options->app_info = std::move(app_info.value());
316 context->SetMinSdkVersion(out_options->app_info.min_sdk_version.value_or_default(0));
317 return true;
318}
319
320int Optimize(const std::vector<StringPiece>& args) {
321 OptimizeContext context;
322 OptimizeOptions options;
Shane Farmer57669432017-06-19 12:52:04 -0700323 Maybe<std::string> config_path;
Luke Nicholsonb0643302017-12-01 15:29:03 -0800324 Maybe<std::string> whitelist_path;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700325 Maybe<std::string> target_densities;
326 std::vector<std::string> configs;
327 std::vector<std::string> split_args;
Shane Farmer666de342017-11-29 16:07:51 -0800328 std::unordered_set<std::string> kept_artifacts;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700329 bool verbose = false;
Shane Farmer9ecc0752017-08-24 15:55:36 -0700330 bool print_only = false;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700331 Flags flags =
332 Flags()
Shane Farmer57669432017-06-19 12:52:04 -0700333 .OptionalFlag("-o", "Path to the output APK.", &options.output_path)
334 .OptionalFlag("-d", "Path to the output directory (for splits).", &options.output_dir)
335 .OptionalFlag("-x", "Path to XML configuration file.", &config_path)
Shane Farmer9ecc0752017-08-24 15:55:36 -0700336 .OptionalSwitch("-p", "Print the multi APK artifacts and exit.", &print_only)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700337 .OptionalFlag(
338 "--target-densities",
339 "Comma separated list of the screen densities that the APK will be optimized for.\n"
340 "All the resources that would be unused on devices of the given densities will be \n"
341 "removed from the APK.",
342 &target_densities)
Luke Nicholsonb0643302017-12-01 15:29:03 -0800343 .OptionalFlag("--whitelist-config-path",
344 "Path to the whitelist.cfg file containing whitelisted resources \n"
345 "whose names should not be altered in final resource tables.",
346 &whitelist_path)
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)
Shane Farmer666de342017-11-29 16:07:51 -0800356 .OptionalFlagList("--keep-artifacts",
357 "Comma separated list of artifacts to keep. If none are specified,\n"
358 "all artifacts will be kept.",
359 &kept_artifacts)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700360 .OptionalSwitch("--enable-sparse-encoding",
361 "Enables encoding sparse entries using a binary search tree.\n"
362 "This decreases APK size at the cost of resource retrieval performance.",
363 &options.table_flattener_options.use_sparse_entries)
Luke Nicholsonb0643302017-12-01 15:29:03 -0800364 .OptionalSwitch("--enable-resource-obfuscation",
365 "Enables obfuscation of key string pool to single value",
366 &options.table_flattener_options.collapse_key_stringpool)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700367 .OptionalSwitch("-v", "Enables verbose logging", &verbose);
368
369 if (!flags.Parse("aapt2 optimize", args, &std::cerr)) {
370 return 1;
371 }
372
373 if (flags.GetArgs().size() != 1u) {
374 std::cerr << "must have one APK as argument.\n\n";
375 flags.Usage("aapt2 optimize", &std::cerr);
376 return 1;
377 }
378
Shane Farmer0a5b2012017-06-22 12:24:12 -0700379 const std::string& apk_path = flags.GetArgs()[0];
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700380
381 context.SetVerbose(verbose);
Shane Farmer9ecc0752017-08-24 15:55:36 -0700382 IDiagnostics* diag = context.GetDiagnostics();
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700383
Shane Farmer57669432017-06-19 12:52:04 -0700384 if (config_path) {
Shane Farmer57669432017-06-19 12:52:04 -0700385 std::string& path = config_path.value();
386 Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path);
387 if (for_path) {
Shane Farmercb6c3f92017-11-27 13:19:36 -0800388 options.apk_artifacts = for_path.value().WithDiagnostics(diag).Parse(apk_path);
389 if (!options.apk_artifacts) {
390 diag->Error(DiagMessage() << "Failed to parse the output artifact list");
391 return 1;
392 }
393
Shane Farmer57669432017-06-19 12:52:04 -0700394 } else {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700395 diag->Error(DiagMessage() << "Could not parse config file " << path);
Shane Farmer57669432017-06-19 12:52:04 -0700396 return 1;
397 }
Shane Farmer9ecc0752017-08-24 15:55:36 -0700398
399 if (print_only) {
Shane Farmercb6c3f92017-11-27 13:19:36 -0800400 for (const OutputArtifact& artifact : options.apk_artifacts.value()) {
401 std::cout << artifact.name << std::endl;
Shane Farmer9ecc0752017-08-24 15:55:36 -0700402 }
403 return 0;
404 }
405
Shane Farmer666de342017-11-29 16:07:51 -0800406 if (!kept_artifacts.empty()) {
Shane Farmercb6c3f92017-11-27 13:19:36 -0800407 for (const std::string& artifact_str : kept_artifacts) {
408 for (const StringPiece& artifact : util::Tokenize(artifact_str, ',')) {
Shane Farmer666de342017-11-29 16:07:51 -0800409 options.kept_artifacts.insert(artifact.to_string());
410 }
411 }
412 }
413
Shane Farmer9ecc0752017-08-24 15:55:36 -0700414 // Since we know that we are going to process the APK (not just print targets), make sure we
415 // have somewhere to write them to.
416 if (!options.output_dir) {
417 diag->Error(DiagMessage() << "Output directory is required when using a configuration file");
418 return 1;
419 }
420 } else if (print_only) {
421 diag->Error(DiagMessage() << "Asked to print artifacts without providing a configurations");
422 return 1;
Shane Farmer57669432017-06-19 12:52:04 -0700423 }
424
Shane Farmer2c122412017-12-15 16:55:54 -0800425 std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
426 if (!apk) {
427 return 1;
428 }
429
430 if (target_densities) {
431 // Parse the target screen densities.
432 for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
433 Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
434 if (!target_density) {
435 return 1;
436 }
437 options.table_splitter_options.preferred_densities.push_back(target_density.value());
438 }
439 }
440
441 std::unique_ptr<IConfigFilter> filter;
442 if (!configs.empty()) {
443 filter = ParseConfigFilterParameters(configs, diag);
444 if (filter == nullptr) {
445 return 1;
446 }
447 options.table_splitter_options.config_filter = filter.get();
448 }
449
450 // Parse the split parameters.
451 for (const std::string& split_arg : split_args) {
452 options.split_paths.emplace_back();
453 options.split_constraints.emplace_back();
454 if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(),
455 &options.split_constraints.back())) {
456 return 1;
457 }
458 }
459
Luke Nicholsonb0643302017-12-01 15:29:03 -0800460 if (options.table_flattener_options.collapse_key_stringpool) {
461 if (whitelist_path) {
462 std::string& path = whitelist_path.value();
463 if (!ExtractWhitelistFromConfig(path, &context, &options)) {
464 return 1;
465 }
466 }
467 }
468
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700469 if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) {
470 return 1;
471 }
472
473 OptimizeCommand cmd(&context, options);
474 return cmd.Run(std::move(apk));
475}
476
477} // namespace aapt