blob: 2bf91a530526eeee3107192cefd812ac8bd1fc35 [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;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070076};
77
78class OptimizeContext : public IAaptContext {
79 public:
Adam Lesinskib522f042017-04-21 16:57:59 -070080 OptimizeContext() = default;
81
82 PackageType GetPackageType() override {
83 // Not important here. Using anything other than kApp adds EXTRA validation, which we want to
84 // avoid.
85 return PackageType::kApp;
86 }
87
Adam Lesinskid0f492d2017-04-03 18:12:45 -070088 IDiagnostics* GetDiagnostics() override {
89 return &diagnostics_;
90 }
91
92 NameMangler* GetNameMangler() override {
93 UNIMPLEMENTED(FATAL);
94 return nullptr;
95 }
96
97 const std::string& GetCompilationPackage() override {
98 static std::string empty;
99 return empty;
100 }
101
102 uint8_t GetPackageId() override {
103 return 0;
104 }
105
106 SymbolTable* GetExternalSymbols() override {
107 UNIMPLEMENTED(FATAL);
108 return nullptr;
109 }
110
111 bool IsVerbose() override {
112 return verbose_;
113 }
114
115 void SetVerbose(bool val) {
116 verbose_ = val;
117 }
118
119 void SetMinSdkVersion(int sdk_version) {
120 sdk_version_ = sdk_version;
121 }
122
123 int GetMinSdkVersion() override {
124 return sdk_version_;
125 }
126
127 private:
Adam Lesinskib522f042017-04-21 16:57:59 -0700128 DISALLOW_COPY_AND_ASSIGN(OptimizeContext);
129
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700130 StdErrDiagnostics diagnostics_;
131 bool verbose_ = false;
132 int sdk_version_ = 0;
133};
134
135class OptimizeCommand {
136 public:
137 OptimizeCommand(OptimizeContext* context, const OptimizeOptions& options)
138 : options_(options), context_(context) {
139 }
140
141 int Run(std::unique_ptr<LoadedApk> apk) {
142 if (context_->IsVerbose()) {
143 context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK...");
144 }
145
146 VersionCollapser collapser;
147 if (!collapser.Consume(context_, apk->GetResourceTable())) {
148 return 1;
149 }
150
151 ResourceDeduper deduper;
152 if (!deduper.Consume(context_, apk->GetResourceTable())) {
153 context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources");
154 return 1;
155 }
156
157 // Adjust the SplitConstraints so that their SDK version is stripped if it is less than or
158 // equal to the minSdk.
159 options_.split_constraints =
160 AdjustSplitConstraintsForMinSdk(context_->GetMinSdkVersion(), options_.split_constraints);
161
162 // Stripping the APK using the TableSplitter. The resource table is modified in place in the
163 // LoadedApk.
164 TableSplitter splitter(options_.split_constraints, options_.table_splitter_options);
165 if (!splitter.VerifySplitConstraints(context_)) {
166 return 1;
167 }
168 splitter.SplitTable(apk->GetResourceTable());
169
170 auto path_iter = options_.split_paths.begin();
171 auto split_constraints_iter = options_.split_constraints.begin();
172 for (std::unique_ptr<ResourceTable>& split_table : splitter.splits()) {
173 if (context_->IsVerbose()) {
174 context_->GetDiagnostics()->Note(
175 DiagMessage(*path_iter) << "generating split with configurations '"
176 << util::Joiner(split_constraints_iter->configs, ", ") << "'");
177 }
178
179 // Generate an AndroidManifest.xml for each split.
180 std::unique_ptr<xml::XmlResource> split_manifest =
181 GenerateSplitManifest(options_.app_info, *split_constraints_iter);
182 std::unique_ptr<IArchiveWriter> split_writer =
183 CreateZipFileArchiveWriter(context_->GetDiagnostics(), *path_iter);
184 if (!split_writer) {
185 return 1;
186 }
187
188 if (!WriteSplitApk(split_table.get(), split_manifest.get(), split_writer.get())) {
189 return 1;
190 }
191
192 ++path_iter;
193 ++split_constraints_iter;
194 }
195
Shane Farmer57669432017-06-19 12:52:04 -0700196 if (options_.configuration && options_.output_dir) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700197 MultiApkGenerator generator{apk.get(), context_};
Shane Farmerefe45392017-08-21 14:39:28 -0700198 MultiApkGeneratorOptions generator_options = {options_.output_dir.value(),
199 options_.configuration.value(),
200 options_.table_flattener_options};
201 if (!generator.FromBaseApk(generator_options)) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700202 return 1;
Shane Farmer57669432017-06-19 12:52:04 -0700203 }
204 }
205
206 if (options_.output_path) {
207 std::unique_ptr<IArchiveWriter> writer =
208 CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path.value());
209 if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) {
210 return 1;
211 }
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700212 }
213
214 return 0;
215 }
216
217 private:
218 bool WriteSplitApk(ResourceTable* table, xml::XmlResource* manifest, IArchiveWriter* writer) {
219 BigBuffer manifest_buffer(4096);
220 XmlFlattener xml_flattener(&manifest_buffer, {});
221 if (!xml_flattener.Consume(context_, manifest)) {
222 return false;
223 }
224
225 io::BigBufferInputStream manifest_buffer_in(&manifest_buffer);
226 if (!io::CopyInputStreamToArchive(context_, &manifest_buffer_in, "AndroidManifest.xml",
227 ArchiveEntry::kCompress, writer)) {
228 return false;
229 }
230
231 std::map<std::pair<ConfigDescription, StringPiece>, FileReference*> config_sorted_files;
232 for (auto& pkg : table->packages) {
233 for (auto& type : pkg->types) {
234 // Sort by config and name, so that we get better locality in the zip file.
235 config_sorted_files.clear();
236
237 for (auto& entry : type->entries) {
238 for (auto& config_value : entry->values) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700239 auto* file_ref = ValueCast<FileReference>(config_value->value.get());
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700240 if (file_ref == nullptr) {
241 continue;
242 }
243
244 if (file_ref->file == nullptr) {
245 ResourceNameRef name(pkg->name, type->type, entry->name);
Adam Lesinski742888f2017-04-28 15:34:52 -0700246 context_->GetDiagnostics()->Warn(DiagMessage(file_ref->GetSource())
Shane Farmer57669432017-06-19 12:52:04 -0700247 << "file for resource " << name << " with config '"
248 << config_value->config << "' not found");
Adam Lesinski742888f2017-04-28 15:34:52 -0700249 continue;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700250 }
251
252 const StringPiece entry_name = entry->name;
253 config_sorted_files[std::make_pair(config_value->config, entry_name)] = file_ref;
254 }
255 }
256
257 for (auto& entry : config_sorted_files) {
258 FileReference* file_ref = entry.second;
Pierre Lecesned55bef72017-11-10 22:31:01 +0000259 if (!io::CopyFileToArchivePreserveCompression(context_, file_ref->file, *file_ref->path,
260 writer)) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700261 return false;
262 }
263 }
264 }
265 }
266
267 BigBuffer table_buffer(4096);
268 TableFlattener table_flattener(options_.table_flattener_options, &table_buffer);
269 if (!table_flattener.Consume(context_, table)) {
270 return false;
271 }
272
273 io::BigBufferInputStream table_buffer_in(&table_buffer);
Shane Farmer0a5b2012017-06-22 12:24:12 -0700274 return io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc",
275 ArchiveEntry::kAlign, writer);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700276 }
277
278 OptimizeOptions options_;
279 OptimizeContext* context_;
280};
281
Adam Lesinski8780eb62017-10-31 17:44:39 -0700282bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk,
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700283 OptimizeOptions* out_options) {
Adam Lesinski8780eb62017-10-31 17:44:39 -0700284 const xml::XmlResource* manifest = apk->GetManifest();
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700285 if (manifest == nullptr) {
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700286 return false;
287 }
288
Adam Lesinski8780eb62017-10-31 17:44:39 -0700289 Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*manifest, context->GetDiagnostics());
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700290 if (!app_info) {
291 context->GetDiagnostics()->Error(DiagMessage()
292 << "failed to extract data from AndroidManifest.xml");
293 return false;
294 }
295
296 out_options->app_info = std::move(app_info.value());
297 context->SetMinSdkVersion(out_options->app_info.min_sdk_version.value_or_default(0));
298 return true;
299}
300
301int Optimize(const std::vector<StringPiece>& args) {
302 OptimizeContext context;
303 OptimizeOptions options;
Shane Farmer57669432017-06-19 12:52:04 -0700304 Maybe<std::string> config_path;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700305 Maybe<std::string> target_densities;
Shane Farmer0a5b2012017-06-22 12:24:12 -0700306 Maybe<std::string> target_abis;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700307 std::vector<std::string> configs;
308 std::vector<std::string> split_args;
309 bool verbose = false;
Shane Farmer9ecc0752017-08-24 15:55:36 -0700310 bool print_only = false;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700311 Flags flags =
312 Flags()
Shane Farmer57669432017-06-19 12:52:04 -0700313 .OptionalFlag("-o", "Path to the output APK.", &options.output_path)
314 .OptionalFlag("-d", "Path to the output directory (for splits).", &options.output_dir)
315 .OptionalFlag("-x", "Path to XML configuration file.", &config_path)
Shane Farmer9ecc0752017-08-24 15:55:36 -0700316 .OptionalSwitch("-p", "Print the multi APK artifacts and exit.", &print_only)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700317 .OptionalFlag(
318 "--target-densities",
319 "Comma separated list of the screen densities that the APK will be optimized for.\n"
320 "All the resources that would be unused on devices of the given densities will be \n"
321 "removed from the APK.",
322 &target_densities)
Shane Farmer0a5b2012017-06-22 12:24:12 -0700323 .OptionalFlag(
324 "--target-abis",
325 "Comma separated list of the CPU ABIs that the APK will be optimized for.\n"
326 "All the native libraries that would be unused on devices of the given ABIs will \n"
327 "be removed from the APK.",
328 &target_abis)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700329 .OptionalFlagList("-c",
330 "Comma separated list of configurations to include. The default\n"
331 "is all configurations.",
332 &configs)
333 .OptionalFlagList("--split",
334 "Split resources matching a set of configs out to a "
Adam Lesinskidb091572017-04-13 12:48:56 -0700335 "Split APK.\nSyntax: path/to/output.apk;<config>[,<config>[...]].\n"
336 "On Windows, use a semicolon ';' separator instead.",
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700337 &split_args)
338 .OptionalSwitch("--enable-sparse-encoding",
339 "Enables encoding sparse entries using a binary search tree.\n"
340 "This decreases APK size at the cost of resource retrieval performance.",
341 &options.table_flattener_options.use_sparse_entries)
342 .OptionalSwitch("-v", "Enables verbose logging", &verbose);
343
344 if (!flags.Parse("aapt2 optimize", args, &std::cerr)) {
345 return 1;
346 }
347
348 if (flags.GetArgs().size() != 1u) {
349 std::cerr << "must have one APK as argument.\n\n";
350 flags.Usage("aapt2 optimize", &std::cerr);
351 return 1;
352 }
353
Shane Farmer0a5b2012017-06-22 12:24:12 -0700354 const std::string& apk_path = flags.GetArgs()[0];
Adam Lesinski8780eb62017-10-31 17:44:39 -0700355 std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700356 if (!apk) {
357 return 1;
358 }
359
360 context.SetVerbose(verbose);
Shane Farmer9ecc0752017-08-24 15:55:36 -0700361 IDiagnostics* diag = context.GetDiagnostics();
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700362
363 if (target_densities) {
364 // Parse the target screen densities.
365 for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700366 Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700367 if (!target_density) {
368 return 1;
369 }
370 options.table_splitter_options.preferred_densities.push_back(target_density.value());
371 }
372 }
373
374 std::unique_ptr<IConfigFilter> filter;
375 if (!configs.empty()) {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700376 filter = ParseConfigFilterParameters(configs, diag);
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700377 if (filter == nullptr) {
378 return 1;
379 }
380 options.table_splitter_options.config_filter = filter.get();
381 }
382
383 // Parse the split parameters.
384 for (const std::string& split_arg : split_args) {
Shane Farmer0a5b2012017-06-22 12:24:12 -0700385 options.split_paths.emplace_back();
386 options.split_constraints.emplace_back();
Shane Farmer9ecc0752017-08-24 15:55:36 -0700387 if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(),
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700388 &options.split_constraints.back())) {
389 return 1;
390 }
391 }
392
Shane Farmer57669432017-06-19 12:52:04 -0700393 if (config_path) {
Shane Farmer57669432017-06-19 12:52:04 -0700394 std::string& path = config_path.value();
395 Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path);
396 if (for_path) {
Shane Farmer9ecc0752017-08-24 15:55:36 -0700397 options.configuration = for_path.value().WithDiagnostics(diag).Parse();
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) {
404 std::vector<std::string> names;
405 const PostProcessingConfiguration& config = options.configuration.value();
406 if (!config.AllArtifactNames(file::GetFilename(apk_path), &names, diag)) {
407 diag->Error(DiagMessage() << "Failed to generate output artifact list");
408 return 1;
409 }
410
411 for (const auto& name : names) {
412 std::cout << name << std::endl;
413 }
414 return 0;
415 }
416
417 // Since we know that we are going to process the APK (not just print targets), make sure we
418 // have somewhere to write them to.
419 if (!options.output_dir) {
420 diag->Error(DiagMessage() << "Output directory is required when using a configuration file");
421 return 1;
422 }
423 } else if (print_only) {
424 diag->Error(DiagMessage() << "Asked to print artifacts without providing a configurations");
425 return 1;
Shane Farmer57669432017-06-19 12:52:04 -0700426 }
427
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700428 if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) {
429 return 1;
430 }
431
432 OptimizeCommand cmd(&context, options);
433 return cmd.Run(std::move(apk));
434}
435
436} // namespace aapt