blob: f40a1df47db9222563d1281aab34d6e6eb498d3e [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
Shane Farmer57669432017-06-19 12:52:04 -070020#include "android-base/stringprintf.h"
Adam Lesinskid3ffa8442017-09-28 13:34:35 -070021
Shane Farmer0a5b2012017-06-22 12:24:12 -070022#include "androidfw/ResourceTypes.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070023#include "androidfw/StringPiece.h"
24
25#include "Diagnostics.h"
26#include "Flags.h"
27#include "LoadedApk.h"
28#include "ResourceUtils.h"
29#include "SdkConstants.h"
30#include "ValueVisitor.h"
31#include "cmd/Util.h"
Shane Farmer57669432017-06-19 12:52:04 -070032#include "configuration/ConfigurationParser.h"
33#include "filter/AbiFilter.h"
Adam Lesinski46708052017-09-29 14:49:15 -070034#include "format/binary/TableFlattener.h"
35#include "format/binary/XmlFlattener.h"
Adam Lesinski00451162017-10-03 07:44:08 -070036#include "io/BigBufferStream.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070037#include "io/Util.h"
Shane Farmer0a5b2012017-06-22 12:24:12 -070038#include "optimize/MultiApkGenerator.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070039#include "optimize/ResourceDeduper.h"
40#include "optimize/VersionCollapser.h"
41#include "split/TableSplitter.h"
Shane Farmer57669432017-06-19 12:52:04 -070042#include "util/Files.h"
Shane Farmer0a5b2012017-06-22 12:24:12 -070043#include "util/Util.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070044
Shane Farmer57669432017-06-19 12:52:04 -070045using ::aapt::configuration::Abi;
46using ::aapt::configuration::Artifact;
Shane Farmer280be342017-06-21 15:20:15 -070047using ::aapt::configuration::PostProcessingConfiguration;
Shane Farmer0a5b2012017-06-22 12:24:12 -070048using ::android::ResTable_config;
Shane Farmer57669432017-06-19 12:52:04 -070049using ::android::StringPiece;
Shane Farmer0a5b2012017-06-22 12:24:12 -070050using ::android::base::StringAppendF;
Shane Farmer57669432017-06-19 12:52:04 -070051using ::android::base::StringPrintf;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070052
53namespace aapt {
54
55struct OptimizeOptions {
56 // Path to the output APK.
Shane Farmer57669432017-06-19 12:52:04 -070057 Maybe<std::string> output_path;
58 // Path to the output APK directory for splits.
59 Maybe<std::string> output_dir;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070060
61 // Details of the app extracted from the AndroidManifest.xml
62 AppInfo app_info;
63
64 // Split APK options.
65 TableSplitterOptions table_splitter_options;
66
67 // List of output split paths. These are in the same order as `split_constraints`.
68 std::vector<std::string> split_paths;
69
70 // List of SplitConstraints governing what resources go into each split. Ordered by `split_paths`.
71 std::vector<SplitConstraints> split_constraints;
72
73 TableFlattenerOptions table_flattener_options;
Shane Farmer57669432017-06-19 12:52:04 -070074
Shane Farmer280be342017-06-21 15:20:15 -070075 Maybe<PostProcessingConfiguration> configuration;
Shane Farmer666de342017-11-29 16:07:51 -080076
77 // Set of artifacts to keep when generating multi-APK splits. If the list is empty, all artifacts
78 // are kept and will be written as output.
79 std::unordered_set<std::string> kept_artifacts;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070080};
81
82class OptimizeContext : public IAaptContext {
83 public:
Adam Lesinskib522f042017-04-21 16:57:59 -070084 OptimizeContext() = default;
85
86 PackageType GetPackageType() override {
87 // Not important here. Using anything other than kApp adds EXTRA validation, which we want to
88 // avoid.
89 return PackageType::kApp;
90 }
91
Adam Lesinskid0f492d2017-04-03 18:12:45 -070092 IDiagnostics* GetDiagnostics() override {
93 return &diagnostics_;
94 }
95
96 NameMangler* GetNameMangler() override {
97 UNIMPLEMENTED(FATAL);
98 return nullptr;
99 }
100
101 const std::string& GetCompilationPackage() override {
102 static std::string empty;
103 return empty;
104 }
105
106 uint8_t GetPackageId() override {
107 return 0;
108 }
109
110 SymbolTable* GetExternalSymbols() override {
111 UNIMPLEMENTED(FATAL);
112 return nullptr;
113 }
114
115 bool IsVerbose() override {
116 return verbose_;
117 }
118
119 void SetVerbose(bool val) {
120 verbose_ = val;
121 }
122
123 void SetMinSdkVersion(int sdk_version) {
124 sdk_version_ = sdk_version;
125 }
126
127 int GetMinSdkVersion() override {
128 return sdk_version_;
129 }
130
131 private:
Adam Lesinskib522f042017-04-21 16:57:59 -0700132 DISALLOW_COPY_AND_ASSIGN(OptimizeContext);
133
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700134 StdErrDiagnostics diagnostics_;
135 bool verbose_ = false;
136 int sdk_version_ = 0;
137};
138
139class OptimizeCommand {
140 public:
141 OptimizeCommand(OptimizeContext* context, const OptimizeOptions& options)
142 : options_(options), context_(context) {
143 }
144
145 int Run(std::unique_ptr<LoadedApk> apk) {
146 if (context_->IsVerbose()) {
147 context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK...");
148 }
149
150 VersionCollapser collapser;
151 if (!collapser.Consume(context_, apk->GetResourceTable())) {
152 return 1;
153 }
154
155 ResourceDeduper deduper;
156 if (!deduper.Consume(context_, apk->GetResourceTable())) {
157 context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources");
158 return 1;
159 }
160
161 // Adjust the SplitConstraints so that their SDK version is stripped if it is less than or
162 // equal to the minSdk.
163 options_.split_constraints =
164 AdjustSplitConstraintsForMinSdk(context_->GetMinSdkVersion(), options_.split_constraints);
165
166 // Stripping the APK using the TableSplitter. The resource table is modified in place in the
167 // LoadedApk.
168 TableSplitter splitter(options_.split_constraints, options_.table_splitter_options);
169 if (!splitter.VerifySplitConstraints(context_)) {
170 return 1;
171 }
172 splitter.SplitTable(apk->GetResourceTable());
173
174 auto path_iter = options_.split_paths.begin();
175 auto split_constraints_iter = options_.split_constraints.begin();
176 for (std::unique_ptr<ResourceTable>& split_table : splitter.splits()) {
177 if (context_->IsVerbose()) {
178 context_->GetDiagnostics()->Note(
179 DiagMessage(*path_iter) << "generating split with configurations '"
180 << util::Joiner(split_constraints_iter->configs, ", ") << "'");
181 }
182
183 // Generate an AndroidManifest.xml for each split.
184 std::unique_ptr<xml::XmlResource> split_manifest =
185 GenerateSplitManifest(options_.app_info, *split_constraints_iter);
186 std::unique_ptr<IArchiveWriter> split_writer =
187 CreateZipFileArchiveWriter(context_->GetDiagnostics(), *path_iter);
188 if (!split_writer) {
189 return 1;
190 }
191
192 if (!WriteSplitApk(split_table.get(), split_manifest.get(), split_writer.get())) {
193 return 1;
194 }
195
196 ++path_iter;
197 ++split_constraints_iter;
198 }
199
Shane Farmer57669432017-06-19 12:52:04 -0700200 if (options_.configuration && options_.output_dir) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700201 MultiApkGenerator generator{apk.get(), context_};
Shane Farmer666de342017-11-29 16:07:51 -0800202 MultiApkGeneratorOptions generator_options = {
203 options_.output_dir.value(),
204 options_.configuration.value(),
205 options_.table_flattener_options,
206 options_.kept_artifacts,
207 };
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
Adam Lesinski8780eb62017-10-31 17:44:39 -0700289bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk,
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700290 OptimizeOptions* out_options) {
Adam Lesinski8780eb62017-10-31 17:44:39 -0700291 const xml::XmlResource* manifest = apk->GetManifest();
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700292 if (manifest == nullptr) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700293 return false;
294 }
295
Adam Lesinski8780eb62017-10-31 17:44:39 -0700296 Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*manifest, context->GetDiagnostics());
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700297 if (!app_info) {
298 context->GetDiagnostics()->Error(DiagMessage()
299 << "failed to extract data from AndroidManifest.xml");
300 return false;
301 }
302
303 out_options->app_info = std::move(app_info.value());
304 context->SetMinSdkVersion(out_options->app_info.min_sdk_version.value_or_default(0));
305 return true;
306}
307
308int Optimize(const std::vector<StringPiece>& args) {
309 OptimizeContext context;
310 OptimizeOptions options;
Shane Farmer57669432017-06-19 12:52:04 -0700311 Maybe<std::string> config_path;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700312 Maybe<std::string> target_densities;
Shane Farmer0a5b2012017-06-22 12:24:12 -0700313 Maybe<std::string> target_abis;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700314 std::vector<std::string> configs;
315 std::vector<std::string> split_args;
Shane Farmer666de342017-11-29 16:07:51 -0800316 std::unordered_set<std::string> kept_artifacts;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700317 bool verbose = false;
Shane Farmer9ecc0752017-08-24 15:55:36 -0700318 bool print_only = false;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700319 Flags flags =
320 Flags()
Shane Farmer57669432017-06-19 12:52:04 -0700321 .OptionalFlag("-o", "Path to the output APK.", &options.output_path)
322 .OptionalFlag("-d", "Path to the output directory (for splits).", &options.output_dir)
323 .OptionalFlag("-x", "Path to XML configuration file.", &config_path)
Shane Farmer9ecc0752017-08-24 15:55:36 -0700324 .OptionalSwitch("-p", "Print the multi APK artifacts and exit.", &print_only)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700325 .OptionalFlag(
326 "--target-densities",
327 "Comma separated list of the screen densities that the APK will be optimized for.\n"
328 "All the resources that would be unused on devices of the given densities will be \n"
329 "removed from the APK.",
330 &target_densities)
Shane Farmer0a5b2012017-06-22 12:24:12 -0700331 .OptionalFlag(
332 "--target-abis",
333 "Comma separated list of the CPU ABIs that the APK will be optimized for.\n"
334 "All the native libraries that would be unused on devices of the given ABIs will \n"
335 "be removed from the APK.",
336 &target_abis)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700337 .OptionalFlagList("-c",
338 "Comma separated list of configurations to include. The default\n"
339 "is all configurations.",
340 &configs)
341 .OptionalFlagList("--split",
342 "Split resources matching a set of configs out to a "
Adam Lesinskidb091572017-04-13 12:48:56 -0700343 "Split APK.\nSyntax: path/to/output.apk;<config>[,<config>[...]].\n"
344 "On Windows, use a semicolon ';' separator instead.",
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700345 &split_args)
Shane Farmer666de342017-11-29 16:07:51 -0800346 .OptionalFlagList("--keep-artifacts",
347 "Comma separated list of artifacts to keep. If none are specified,\n"
348 "all artifacts will be kept.",
349 &kept_artifacts)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700350 .OptionalSwitch("--enable-sparse-encoding",
351 "Enables encoding sparse entries using a binary search tree.\n"
352 "This decreases APK size at the cost of resource retrieval performance.",
353 &options.table_flattener_options.use_sparse_entries)
354 .OptionalSwitch("-v", "Enables verbose logging", &verbose);
355
356 if (!flags.Parse("aapt2 optimize", args, &std::cerr)) {
357 return 1;
358 }
359
360 if (flags.GetArgs().size() != 1u) {
361 std::cerr << "must have one APK as argument.\n\n";
362 flags.Usage("aapt2 optimize", &std::cerr);
363 return 1;
364 }
365
Shane Farmer0a5b2012017-06-22 12:24:12 -0700366 const std::string& apk_path = flags.GetArgs()[0];
Adam Lesinski8780eb62017-10-31 17:44:39 -0700367 std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700368 if (!apk) {
369 return 1;
370 }
371
372 context.SetVerbose(verbose);
Shane Farmer9ecc0752017-08-24 15:55:36 -0700373 IDiagnostics* diag = context.GetDiagnostics();
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700374
375 if (target_densities) {
376 // Parse the target screen densities.
377 for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700378 Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700379 if (!target_density) {
380 return 1;
381 }
382 options.table_splitter_options.preferred_densities.push_back(target_density.value());
383 }
384 }
385
386 std::unique_ptr<IConfigFilter> filter;
387 if (!configs.empty()) {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700388 filter = ParseConfigFilterParameters(configs, diag);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700389 if (filter == nullptr) {
390 return 1;
391 }
392 options.table_splitter_options.config_filter = filter.get();
393 }
394
395 // Parse the split parameters.
396 for (const std::string& split_arg : split_args) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700397 options.split_paths.emplace_back();
398 options.split_constraints.emplace_back();
Shane Farmer9ecc0752017-08-24 15:55:36 -0700399 if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(),
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700400 &options.split_constraints.back())) {
401 return 1;
402 }
403 }
404
Shane Farmer57669432017-06-19 12:52:04 -0700405 if (config_path) {
Shane Farmer57669432017-06-19 12:52:04 -0700406 std::string& path = config_path.value();
407 Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path);
408 if (for_path) {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700409 options.configuration = for_path.value().WithDiagnostics(diag).Parse();
Shane Farmer57669432017-06-19 12:52:04 -0700410 } else {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700411 diag->Error(DiagMessage() << "Could not parse config file " << path);
Shane Farmer57669432017-06-19 12:52:04 -0700412 return 1;
413 }
Shane Farmer9ecc0752017-08-24 15:55:36 -0700414
415 if (print_only) {
416 std::vector<std::string> names;
417 const PostProcessingConfiguration& config = options.configuration.value();
418 if (!config.AllArtifactNames(file::GetFilename(apk_path), &names, diag)) {
419 diag->Error(DiagMessage() << "Failed to generate output artifact list");
420 return 1;
421 }
422
423 for (const auto& name : names) {
424 std::cout << name << std::endl;
425 }
426 return 0;
427 }
428
Shane Farmer666de342017-11-29 16:07:51 -0800429 if (!kept_artifacts.empty()) {
430 for (const auto& artifact_str : kept_artifacts) {
431 for (const auto& artifact : util::Tokenize(artifact_str, ',')) {
432 options.kept_artifacts.insert(artifact.to_string());
433 }
434 }
435 }
436
Shane Farmer9ecc0752017-08-24 15:55:36 -0700437 // Since we know that we are going to process the APK (not just print targets), make sure we
438 // have somewhere to write them to.
439 if (!options.output_dir) {
440 diag->Error(DiagMessage() << "Output directory is required when using a configuration file");
441 return 1;
442 }
443 } else if (print_only) {
444 diag->Error(DiagMessage() << "Asked to print artifacts without providing a configurations");
445 return 1;
Shane Farmer57669432017-06-19 12:52:04 -0700446 }
447
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700448 if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) {
449 return 1;
450 }
451
452 OptimizeCommand cmd(&context, options);
453 return cmd.Run(std::move(apk));
454}
455
456} // namespace aapt