blob: 887803e147da2142ca4533ba41643b2205f5cee1 [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"
Shane Farmer0a5b2012017-06-22 12:24:12 -070021#include "androidfw/ResourceTypes.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070022#include "androidfw/StringPiece.h"
23
24#include "Diagnostics.h"
25#include "Flags.h"
26#include "LoadedApk.h"
27#include "ResourceUtils.h"
28#include "SdkConstants.h"
29#include "ValueVisitor.h"
30#include "cmd/Util.h"
Shane Farmer57669432017-06-19 12:52:04 -070031#include "configuration/ConfigurationParser.h"
32#include "filter/AbiFilter.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070033#include "flatten/TableFlattener.h"
34#include "flatten/XmlFlattener.h"
35#include "io/BigBufferInputStream.h"
36#include "io/Util.h"
Shane Farmer0a5b2012017-06-22 12:24:12 -070037#include "optimize/MultiApkGenerator.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070038#include "optimize/ResourceDeduper.h"
39#include "optimize/VersionCollapser.h"
40#include "split/TableSplitter.h"
Shane Farmer57669432017-06-19 12:52:04 -070041#include "util/Files.h"
Shane Farmer0a5b2012017-06-22 12:24:12 -070042#include "util/Util.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070043
Shane Farmer57669432017-06-19 12:52:04 -070044using ::aapt::configuration::Abi;
45using ::aapt::configuration::Artifact;
Shane Farmer280be342017-06-21 15:20:15 -070046using ::aapt::configuration::PostProcessingConfiguration;
Shane Farmer0a5b2012017-06-22 12:24:12 -070047using ::android::ResTable_config;
Shane Farmer57669432017-06-19 12:52:04 -070048using ::android::StringPiece;
Shane Farmer0a5b2012017-06-22 12:24:12 -070049using ::android::base::StringAppendF;
Shane Farmer57669432017-06-19 12:52:04 -070050using ::android::base::StringPrintf;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070051
52namespace aapt {
53
54struct OptimizeOptions {
55 // Path to the output APK.
Shane Farmer57669432017-06-19 12:52:04 -070056 Maybe<std::string> output_path;
57 // Path to the output APK directory for splits.
58 Maybe<std::string> output_dir;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070059
60 // Details of the app extracted from the AndroidManifest.xml
61 AppInfo app_info;
62
63 // Split APK options.
64 TableSplitterOptions table_splitter_options;
65
66 // List of output split paths. These are in the same order as `split_constraints`.
67 std::vector<std::string> split_paths;
68
69 // List of SplitConstraints governing what resources go into each split. Ordered by `split_paths`.
70 std::vector<SplitConstraints> split_constraints;
71
72 TableFlattenerOptions table_flattener_options;
Shane Farmer57669432017-06-19 12:52:04 -070073
Shane Farmer280be342017-06-21 15:20:15 -070074 Maybe<PostProcessingConfiguration> configuration;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070075};
76
77class OptimizeContext : public IAaptContext {
78 public:
Adam Lesinskib522f042017-04-21 16:57:59 -070079 OptimizeContext() = default;
80
81 PackageType GetPackageType() override {
82 // Not important here. Using anything other than kApp adds EXTRA validation, which we want to
83 // avoid.
84 return PackageType::kApp;
85 }
86
Adam Lesinskid0f492d2017-04-03 18:12:45 -070087 IDiagnostics* GetDiagnostics() override {
88 return &diagnostics_;
89 }
90
91 NameMangler* GetNameMangler() override {
92 UNIMPLEMENTED(FATAL);
93 return nullptr;
94 }
95
96 const std::string& GetCompilationPackage() override {
97 static std::string empty;
98 return empty;
99 }
100
101 uint8_t GetPackageId() override {
102 return 0;
103 }
104
105 SymbolTable* GetExternalSymbols() override {
106 UNIMPLEMENTED(FATAL);
107 return nullptr;
108 }
109
110 bool IsVerbose() override {
111 return verbose_;
112 }
113
114 void SetVerbose(bool val) {
115 verbose_ = val;
116 }
117
118 void SetMinSdkVersion(int sdk_version) {
119 sdk_version_ = sdk_version;
120 }
121
122 int GetMinSdkVersion() override {
123 return sdk_version_;
124 }
125
126 private:
Adam Lesinskib522f042017-04-21 16:57:59 -0700127 DISALLOW_COPY_AND_ASSIGN(OptimizeContext);
128
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700129 StdErrDiagnostics diagnostics_;
130 bool verbose_ = false;
131 int sdk_version_ = 0;
132};
133
134class OptimizeCommand {
135 public:
136 OptimizeCommand(OptimizeContext* context, const OptimizeOptions& options)
137 : options_(options), context_(context) {
138 }
139
140 int Run(std::unique_ptr<LoadedApk> apk) {
141 if (context_->IsVerbose()) {
142 context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK...");
143 }
144
145 VersionCollapser collapser;
146 if (!collapser.Consume(context_, apk->GetResourceTable())) {
147 return 1;
148 }
149
150 ResourceDeduper deduper;
151 if (!deduper.Consume(context_, apk->GetResourceTable())) {
152 context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources");
153 return 1;
154 }
155
156 // Adjust the SplitConstraints so that their SDK version is stripped if it is less than or
157 // equal to the minSdk.
158 options_.split_constraints =
159 AdjustSplitConstraintsForMinSdk(context_->GetMinSdkVersion(), options_.split_constraints);
160
161 // Stripping the APK using the TableSplitter. The resource table is modified in place in the
162 // LoadedApk.
163 TableSplitter splitter(options_.split_constraints, options_.table_splitter_options);
164 if (!splitter.VerifySplitConstraints(context_)) {
165 return 1;
166 }
167 splitter.SplitTable(apk->GetResourceTable());
168
169 auto path_iter = options_.split_paths.begin();
170 auto split_constraints_iter = options_.split_constraints.begin();
171 for (std::unique_ptr<ResourceTable>& split_table : splitter.splits()) {
172 if (context_->IsVerbose()) {
173 context_->GetDiagnostics()->Note(
174 DiagMessage(*path_iter) << "generating split with configurations '"
175 << util::Joiner(split_constraints_iter->configs, ", ") << "'");
176 }
177
178 // Generate an AndroidManifest.xml for each split.
179 std::unique_ptr<xml::XmlResource> split_manifest =
180 GenerateSplitManifest(options_.app_info, *split_constraints_iter);
181 std::unique_ptr<IArchiveWriter> split_writer =
182 CreateZipFileArchiveWriter(context_->GetDiagnostics(), *path_iter);
183 if (!split_writer) {
184 return 1;
185 }
186
187 if (!WriteSplitApk(split_table.get(), split_manifest.get(), split_writer.get())) {
188 return 1;
189 }
190
191 ++path_iter;
192 ++split_constraints_iter;
193 }
194
Shane Farmer57669432017-06-19 12:52:04 -0700195 if (options_.configuration && options_.output_dir) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700196 MultiApkGenerator generator{apk.get(), context_};
197 if (!generator.FromBaseApk(options_.output_dir.value(), options_.configuration.value(),
198 options_.table_flattener_options)) {
199 return 1;
Shane Farmer57669432017-06-19 12:52:04 -0700200 }
201 }
202
203 if (options_.output_path) {
204 std::unique_ptr<IArchiveWriter> writer =
205 CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path.value());
206 if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) {
207 return 1;
208 }
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700209 }
210
211 return 0;
212 }
213
214 private:
215 bool WriteSplitApk(ResourceTable* table, xml::XmlResource* manifest, IArchiveWriter* writer) {
216 BigBuffer manifest_buffer(4096);
217 XmlFlattener xml_flattener(&manifest_buffer, {});
218 if (!xml_flattener.Consume(context_, manifest)) {
219 return false;
220 }
221
222 io::BigBufferInputStream manifest_buffer_in(&manifest_buffer);
223 if (!io::CopyInputStreamToArchive(context_, &manifest_buffer_in, "AndroidManifest.xml",
224 ArchiveEntry::kCompress, writer)) {
225 return false;
226 }
227
228 std::map<std::pair<ConfigDescription, StringPiece>, FileReference*> config_sorted_files;
229 for (auto& pkg : table->packages) {
230 for (auto& type : pkg->types) {
231 // Sort by config and name, so that we get better locality in the zip file.
232 config_sorted_files.clear();
233
234 for (auto& entry : type->entries) {
235 for (auto& config_value : entry->values) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700236 auto* file_ref = ValueCast<FileReference>(config_value->value.get());
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700237 if (file_ref == nullptr) {
238 continue;
239 }
240
241 if (file_ref->file == nullptr) {
242 ResourceNameRef name(pkg->name, type->type, entry->name);
Adam Lesinski742888f2017-04-28 15:34:52 -0700243 context_->GetDiagnostics()->Warn(DiagMessage(file_ref->GetSource())
Shane Farmer57669432017-06-19 12:52:04 -0700244 << "file for resource " << name << " with config '"
245 << config_value->config << "' not found");
Adam Lesinski742888f2017-04-28 15:34:52 -0700246 continue;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700247 }
248
249 const StringPiece entry_name = entry->name;
250 config_sorted_files[std::make_pair(config_value->config, entry_name)] = file_ref;
251 }
252 }
253
254 for (auto& entry : config_sorted_files) {
255 FileReference* file_ref = entry.second;
256 uint32_t compression_flags =
257 file_ref->file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
258 if (!io::CopyFileToArchive(context_, file_ref->file, *file_ref->path, compression_flags,
259 writer)) {
260 return false;
261 }
262 }
263 }
264 }
265
266 BigBuffer table_buffer(4096);
267 TableFlattener table_flattener(options_.table_flattener_options, &table_buffer);
268 if (!table_flattener.Consume(context_, table)) {
269 return false;
270 }
271
272 io::BigBufferInputStream table_buffer_in(&table_buffer);
Shane Farmer0a5b2012017-06-22 12:24:12 -0700273 return io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc",
274 ArchiveEntry::kAlign, writer);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700275 }
276
277 OptimizeOptions options_;
278 OptimizeContext* context_;
279};
280
281bool ExtractAppDataFromManifest(OptimizeContext* context, LoadedApk* apk,
282 OptimizeOptions* out_options) {
283 io::IFile* manifest_file = apk->GetFileCollection()->FindFile("AndroidManifest.xml");
284 if (manifest_file == nullptr) {
285 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
286 << "missing AndroidManifest.xml");
287 return false;
288 }
289
290 std::unique_ptr<io::IData> data = manifest_file->OpenAsData();
291 if (data == nullptr) {
292 context->GetDiagnostics()->Error(DiagMessage(manifest_file->GetSource())
293 << "failed to open file");
294 return false;
295 }
296
297 std::unique_ptr<xml::XmlResource> manifest = xml::Inflate(
298 data->data(), data->size(), context->GetDiagnostics(), manifest_file->GetSource());
299 if (manifest == nullptr) {
300 context->GetDiagnostics()->Error(DiagMessage() << "failed to read binary AndroidManifest.xml");
301 return false;
302 }
303
304 Maybe<AppInfo> app_info =
305 ExtractAppInfoFromBinaryManifest(manifest.get(), context->GetDiagnostics());
306 if (!app_info) {
307 context->GetDiagnostics()->Error(DiagMessage()
308 << "failed to extract data from AndroidManifest.xml");
309 return false;
310 }
311
312 out_options->app_info = std::move(app_info.value());
313 context->SetMinSdkVersion(out_options->app_info.min_sdk_version.value_or_default(0));
314 return true;
315}
316
317int Optimize(const std::vector<StringPiece>& args) {
318 OptimizeContext context;
319 OptimizeOptions options;
Shane Farmer57669432017-06-19 12:52:04 -0700320 Maybe<std::string> config_path;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700321 Maybe<std::string> target_densities;
Shane Farmer0a5b2012017-06-22 12:24:12 -0700322 Maybe<std::string> target_abis;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700323 std::vector<std::string> configs;
324 std::vector<std::string> split_args;
325 bool verbose = false;
Shane Farmer9ecc0752017-08-24 15:55:36 -0700326 bool print_only = false;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700327 Flags flags =
328 Flags()
Shane Farmer57669432017-06-19 12:52:04 -0700329 .OptionalFlag("-o", "Path to the output APK.", &options.output_path)
330 .OptionalFlag("-d", "Path to the output directory (for splits).", &options.output_dir)
331 .OptionalFlag("-x", "Path to XML configuration file.", &config_path)
Shane Farmer9ecc0752017-08-24 15:55:36 -0700332 .OptionalSwitch("-p", "Print the multi APK artifacts and exit.", &print_only)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700333 .OptionalFlag(
334 "--target-densities",
335 "Comma separated list of the screen densities that the APK will be optimized for.\n"
336 "All the resources that would be unused on devices of the given densities will be \n"
337 "removed from the APK.",
338 &target_densities)
Shane Farmer0a5b2012017-06-22 12:24:12 -0700339 .OptionalFlag(
340 "--target-abis",
341 "Comma separated list of the CPU ABIs that the APK will be optimized for.\n"
342 "All the native libraries that would be unused on devices of the given ABIs will \n"
343 "be removed from the APK.",
344 &target_abis)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700345 .OptionalFlagList("-c",
346 "Comma separated list of configurations to include. The default\n"
347 "is all configurations.",
348 &configs)
349 .OptionalFlagList("--split",
350 "Split resources matching a set of configs out to a "
Adam Lesinskidb091572017-04-13 12:48:56 -0700351 "Split APK.\nSyntax: path/to/output.apk;<config>[,<config>[...]].\n"
352 "On Windows, use a semicolon ';' separator instead.",
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700353 &split_args)
354 .OptionalSwitch("--enable-sparse-encoding",
355 "Enables encoding sparse entries using a binary search tree.\n"
356 "This decreases APK size at the cost of resource retrieval performance.",
357 &options.table_flattener_options.use_sparse_entries)
358 .OptionalSwitch("-v", "Enables verbose logging", &verbose);
359
360 if (!flags.Parse("aapt2 optimize", args, &std::cerr)) {
361 return 1;
362 }
363
364 if (flags.GetArgs().size() != 1u) {
365 std::cerr << "must have one APK as argument.\n\n";
366 flags.Usage("aapt2 optimize", &std::cerr);
367 return 1;
368 }
369
Shane Farmer0a5b2012017-06-22 12:24:12 -0700370 const std::string& apk_path = flags.GetArgs()[0];
371 std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(&context, apk_path);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700372 if (!apk) {
373 return 1;
374 }
375
376 context.SetVerbose(verbose);
Shane Farmer9ecc0752017-08-24 15:55:36 -0700377 IDiagnostics* diag = context.GetDiagnostics();
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700378
379 if (target_densities) {
380 // Parse the target screen densities.
381 for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700382 Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700383 if (!target_density) {
384 return 1;
385 }
386 options.table_splitter_options.preferred_densities.push_back(target_density.value());
387 }
388 }
389
390 std::unique_ptr<IConfigFilter> filter;
391 if (!configs.empty()) {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700392 filter = ParseConfigFilterParameters(configs, diag);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700393 if (filter == nullptr) {
394 return 1;
395 }
396 options.table_splitter_options.config_filter = filter.get();
397 }
398
399 // Parse the split parameters.
400 for (const std::string& split_arg : split_args) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700401 options.split_paths.emplace_back();
402 options.split_constraints.emplace_back();
Shane Farmer9ecc0752017-08-24 15:55:36 -0700403 if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(),
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700404 &options.split_constraints.back())) {
405 return 1;
406 }
407 }
408
Shane Farmer57669432017-06-19 12:52:04 -0700409 if (config_path) {
Shane Farmer57669432017-06-19 12:52:04 -0700410 std::string& path = config_path.value();
411 Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path);
412 if (for_path) {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700413 options.configuration = for_path.value().WithDiagnostics(diag).Parse();
Shane Farmer57669432017-06-19 12:52:04 -0700414 } else {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700415 diag->Error(DiagMessage() << "Could not parse config file " << path);
Shane Farmer57669432017-06-19 12:52:04 -0700416 return 1;
417 }
Shane Farmer9ecc0752017-08-24 15:55:36 -0700418
419 if (print_only) {
420 std::vector<std::string> names;
421 const PostProcessingConfiguration& config = options.configuration.value();
422 if (!config.AllArtifactNames(file::GetFilename(apk_path), &names, diag)) {
423 diag->Error(DiagMessage() << "Failed to generate output artifact list");
424 return 1;
425 }
426
427 for (const auto& name : names) {
428 std::cout << name << std::endl;
429 }
430 return 0;
431 }
432
433 // Since we know that we are going to process the APK (not just print targets), make sure we
434 // have somewhere to write them to.
435 if (!options.output_dir) {
436 diag->Error(DiagMessage() << "Output directory is required when using a configuration file");
437 return 1;
438 }
439 } else if (print_only) {
440 diag->Error(DiagMessage() << "Asked to print artifacts without providing a configurations");
441 return 1;
Shane Farmer57669432017-06-19 12:52:04 -0700442 }
443
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700444 if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) {
445 return 1;
446 }
447
448 OptimizeCommand cmd(&context, options);
449 return cmd.Run(std::move(apk));
450}
451
452} // namespace aapt