blob: 0d69c892c103fbcd84429eead825980d7e7e271c [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 Lesinskid0f492d2017-04-03 18:12:45 -070021#include "androidfw/StringPiece.h"
22
23#include "Diagnostics.h"
24#include "Flags.h"
25#include "LoadedApk.h"
26#include "ResourceUtils.h"
27#include "SdkConstants.h"
28#include "ValueVisitor.h"
29#include "cmd/Util.h"
Shane Farmer57669432017-06-19 12:52:04 -070030#include "configuration/ConfigurationParser.h"
31#include "filter/AbiFilter.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070032#include "flatten/TableFlattener.h"
33#include "flatten/XmlFlattener.h"
34#include "io/BigBufferInputStream.h"
35#include "io/Util.h"
36#include "optimize/ResourceDeduper.h"
37#include "optimize/VersionCollapser.h"
38#include "split/TableSplitter.h"
Shane Farmer57669432017-06-19 12:52:04 -070039#include "util/Files.h"
Adam Lesinskid0f492d2017-04-03 18:12:45 -070040
Shane Farmer57669432017-06-19 12:52:04 -070041using ::aapt::configuration::Abi;
42using ::aapt::configuration::Artifact;
43using ::aapt::configuration::Configuration;
44using ::android::StringPiece;
45using ::android::base::StringPrintf;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070046
47namespace aapt {
48
49struct OptimizeOptions {
50 // Path to the output APK.
Shane Farmer57669432017-06-19 12:52:04 -070051 Maybe<std::string> output_path;
52 // Path to the output APK directory for splits.
53 Maybe<std::string> output_dir;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070054
55 // Details of the app extracted from the AndroidManifest.xml
56 AppInfo app_info;
57
58 // Split APK options.
59 TableSplitterOptions table_splitter_options;
60
61 // List of output split paths. These are in the same order as `split_constraints`.
62 std::vector<std::string> split_paths;
63
64 // List of SplitConstraints governing what resources go into each split. Ordered by `split_paths`.
65 std::vector<SplitConstraints> split_constraints;
66
67 TableFlattenerOptions table_flattener_options;
Shane Farmer57669432017-06-19 12:52:04 -070068
69 // TODO: Come up with a better name for the Configuration struct.
70 Maybe<Configuration> configuration;
Adam Lesinskid0f492d2017-04-03 18:12:45 -070071};
72
73class OptimizeContext : public IAaptContext {
74 public:
Adam Lesinskib522f042017-04-21 16:57:59 -070075 OptimizeContext() = default;
76
77 PackageType GetPackageType() override {
78 // Not important here. Using anything other than kApp adds EXTRA validation, which we want to
79 // avoid.
80 return PackageType::kApp;
81 }
82
Adam Lesinskid0f492d2017-04-03 18:12:45 -070083 IDiagnostics* GetDiagnostics() override {
84 return &diagnostics_;
85 }
86
87 NameMangler* GetNameMangler() override {
88 UNIMPLEMENTED(FATAL);
89 return nullptr;
90 }
91
92 const std::string& GetCompilationPackage() override {
93 static std::string empty;
94 return empty;
95 }
96
97 uint8_t GetPackageId() override {
98 return 0;
99 }
100
101 SymbolTable* GetExternalSymbols() override {
102 UNIMPLEMENTED(FATAL);
103 return nullptr;
104 }
105
106 bool IsVerbose() override {
107 return verbose_;
108 }
109
110 void SetVerbose(bool val) {
111 verbose_ = val;
112 }
113
114 void SetMinSdkVersion(int sdk_version) {
115 sdk_version_ = sdk_version;
116 }
117
118 int GetMinSdkVersion() override {
119 return sdk_version_;
120 }
121
122 private:
Adam Lesinskib522f042017-04-21 16:57:59 -0700123 DISALLOW_COPY_AND_ASSIGN(OptimizeContext);
124
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700125 StdErrDiagnostics diagnostics_;
126 bool verbose_ = false;
127 int sdk_version_ = 0;
128};
129
130class OptimizeCommand {
131 public:
132 OptimizeCommand(OptimizeContext* context, const OptimizeOptions& options)
133 : options_(options), context_(context) {
134 }
135
136 int Run(std::unique_ptr<LoadedApk> apk) {
137 if (context_->IsVerbose()) {
138 context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK...");
139 }
140
141 VersionCollapser collapser;
142 if (!collapser.Consume(context_, apk->GetResourceTable())) {
143 return 1;
144 }
145
146 ResourceDeduper deduper;
147 if (!deduper.Consume(context_, apk->GetResourceTable())) {
148 context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources");
149 return 1;
150 }
151
152 // Adjust the SplitConstraints so that their SDK version is stripped if it is less than or
153 // equal to the minSdk.
154 options_.split_constraints =
155 AdjustSplitConstraintsForMinSdk(context_->GetMinSdkVersion(), options_.split_constraints);
156
157 // Stripping the APK using the TableSplitter. The resource table is modified in place in the
158 // LoadedApk.
159 TableSplitter splitter(options_.split_constraints, options_.table_splitter_options);
160 if (!splitter.VerifySplitConstraints(context_)) {
161 return 1;
162 }
163 splitter.SplitTable(apk->GetResourceTable());
164
165 auto path_iter = options_.split_paths.begin();
166 auto split_constraints_iter = options_.split_constraints.begin();
167 for (std::unique_ptr<ResourceTable>& split_table : splitter.splits()) {
168 if (context_->IsVerbose()) {
169 context_->GetDiagnostics()->Note(
170 DiagMessage(*path_iter) << "generating split with configurations '"
171 << util::Joiner(split_constraints_iter->configs, ", ") << "'");
172 }
173
174 // Generate an AndroidManifest.xml for each split.
175 std::unique_ptr<xml::XmlResource> split_manifest =
176 GenerateSplitManifest(options_.app_info, *split_constraints_iter);
177 std::unique_ptr<IArchiveWriter> split_writer =
178 CreateZipFileArchiveWriter(context_->GetDiagnostics(), *path_iter);
179 if (!split_writer) {
180 return 1;
181 }
182
183 if (!WriteSplitApk(split_table.get(), split_manifest.get(), split_writer.get())) {
184 return 1;
185 }
186
187 ++path_iter;
188 ++split_constraints_iter;
189 }
190
Shane Farmer57669432017-06-19 12:52:04 -0700191 if (options_.configuration && options_.output_dir) {
192 Configuration& config = options_.configuration.value();
193
194 // For now, just write out the stripped APK since ABI splitting doesn't modify anything else.
195 for (const Artifact& artifact : config.artifacts) {
196 if (artifact.abi_group) {
197 const std::string& group = artifact.abi_group.value();
198
199 auto abi_group = config.abi_groups.find(group);
200 // TODO: Remove validation when configuration parser ensures referential integrity.
201 if (abi_group == config.abi_groups.end()) {
202 context_->GetDiagnostics()->Note(
203 DiagMessage() << "could not find referenced ABI group '" << group << "'");
204 return 1;
205 }
206 FilterChain filters;
207 filters.AddFilter(AbiFilter::FromAbiList(abi_group->second));
208
209 const std::string& path = apk->GetSource().path;
210 const StringPiece ext = file::GetExtension(path);
211 const std::string name = path.substr(0, path.rfind(ext.to_string()));
212
213 // Name is hard coded for now since only one split dimension is supported.
214 // TODO: Incorporate name generation into the configuration objects.
215 const std::string file_name =
216 StringPrintf("%s.%s%s", name.c_str(), group.c_str(), ext.data());
217 std::string out = options_.output_dir.value();
218 file::AppendPath(&out, file_name);
219
220 std::unique_ptr<IArchiveWriter> writer =
221 CreateZipFileArchiveWriter(context_->GetDiagnostics(), out);
222
223 if (!apk->WriteToArchive(context_, options_.table_flattener_options, &filters,
224 writer.get())) {
225 return 1;
226 }
227 }
228 }
229 }
230
231 if (options_.output_path) {
232 std::unique_ptr<IArchiveWriter> writer =
233 CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path.value());
234 if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) {
235 return 1;
236 }
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700237 }
238
239 return 0;
240 }
241
242 private:
243 bool WriteSplitApk(ResourceTable* table, xml::XmlResource* manifest, IArchiveWriter* writer) {
244 BigBuffer manifest_buffer(4096);
245 XmlFlattener xml_flattener(&manifest_buffer, {});
246 if (!xml_flattener.Consume(context_, manifest)) {
247 return false;
248 }
249
250 io::BigBufferInputStream manifest_buffer_in(&manifest_buffer);
251 if (!io::CopyInputStreamToArchive(context_, &manifest_buffer_in, "AndroidManifest.xml",
252 ArchiveEntry::kCompress, writer)) {
253 return false;
254 }
255
256 std::map<std::pair<ConfigDescription, StringPiece>, FileReference*> config_sorted_files;
257 for (auto& pkg : table->packages) {
258 for (auto& type : pkg->types) {
259 // Sort by config and name, so that we get better locality in the zip file.
260 config_sorted_files.clear();
261
262 for (auto& entry : type->entries) {
263 for (auto& config_value : entry->values) {
264 FileReference* file_ref = ValueCast<FileReference>(config_value->value.get());
265 if (file_ref == nullptr) {
266 continue;
267 }
268
269 if (file_ref->file == nullptr) {
270 ResourceNameRef name(pkg->name, type->type, entry->name);
Adam Lesinski742888f2017-04-28 15:34:52 -0700271 context_->GetDiagnostics()->Warn(DiagMessage(file_ref->GetSource())
Shane Farmer57669432017-06-19 12:52:04 -0700272 << "file for resource " << name << " with config '"
273 << config_value->config << "' not found");
Adam Lesinski742888f2017-04-28 15:34:52 -0700274 continue;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700275 }
276
277 const StringPiece entry_name = entry->name;
278 config_sorted_files[std::make_pair(config_value->config, entry_name)] = file_ref;
279 }
280 }
281
282 for (auto& entry : config_sorted_files) {
283 FileReference* file_ref = entry.second;
284 uint32_t compression_flags =
285 file_ref->file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
286 if (!io::CopyFileToArchive(context_, file_ref->file, *file_ref->path, compression_flags,
287 writer)) {
288 return false;
289 }
290 }
291 }
292 }
293
294 BigBuffer table_buffer(4096);
295 TableFlattener table_flattener(options_.table_flattener_options, &table_buffer);
296 if (!table_flattener.Consume(context_, table)) {
297 return false;
298 }
299
300 io::BigBufferInputStream table_buffer_in(&table_buffer);
301 if (!io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc",
302 ArchiveEntry::kAlign, writer)) {
303 return false;
304 }
305 return true;
306 }
307
308 OptimizeOptions options_;
309 OptimizeContext* context_;
310};
311
312bool ExtractAppDataFromManifest(OptimizeContext* context, LoadedApk* apk,
313 OptimizeOptions* out_options) {
314 io::IFile* manifest_file = apk->GetFileCollection()->FindFile("AndroidManifest.xml");
315 if (manifest_file == nullptr) {
316 context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
317 << "missing AndroidManifest.xml");
318 return false;
319 }
320
321 std::unique_ptr<io::IData> data = manifest_file->OpenAsData();
322 if (data == nullptr) {
323 context->GetDiagnostics()->Error(DiagMessage(manifest_file->GetSource())
324 << "failed to open file");
325 return false;
326 }
327
328 std::unique_ptr<xml::XmlResource> manifest = xml::Inflate(
329 data->data(), data->size(), context->GetDiagnostics(), manifest_file->GetSource());
330 if (manifest == nullptr) {
331 context->GetDiagnostics()->Error(DiagMessage() << "failed to read binary AndroidManifest.xml");
332 return false;
333 }
334
335 Maybe<AppInfo> app_info =
336 ExtractAppInfoFromBinaryManifest(manifest.get(), context->GetDiagnostics());
337 if (!app_info) {
338 context->GetDiagnostics()->Error(DiagMessage()
339 << "failed to extract data from AndroidManifest.xml");
340 return false;
341 }
342
343 out_options->app_info = std::move(app_info.value());
344 context->SetMinSdkVersion(out_options->app_info.min_sdk_version.value_or_default(0));
345 return true;
346}
347
348int Optimize(const std::vector<StringPiece>& args) {
349 OptimizeContext context;
350 OptimizeOptions options;
Shane Farmer57669432017-06-19 12:52:04 -0700351 Maybe<std::string> config_path;
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700352 Maybe<std::string> target_densities;
353 std::vector<std::string> configs;
354 std::vector<std::string> split_args;
355 bool verbose = false;
356 Flags flags =
357 Flags()
Shane Farmer57669432017-06-19 12:52:04 -0700358 .OptionalFlag("-o", "Path to the output APK.", &options.output_path)
359 .OptionalFlag("-d", "Path to the output directory (for splits).", &options.output_dir)
360 .OptionalFlag("-x", "Path to XML configuration file.", &config_path)
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700361 .OptionalFlag(
362 "--target-densities",
363 "Comma separated list of the screen densities that the APK will be optimized for.\n"
364 "All the resources that would be unused on devices of the given densities will be \n"
365 "removed from the APK.",
366 &target_densities)
367 .OptionalFlagList("-c",
368 "Comma separated list of configurations to include. The default\n"
369 "is all configurations.",
370 &configs)
371 .OptionalFlagList("--split",
372 "Split resources matching a set of configs out to a "
Adam Lesinskidb091572017-04-13 12:48:56 -0700373 "Split APK.\nSyntax: path/to/output.apk;<config>[,<config>[...]].\n"
374 "On Windows, use a semicolon ';' separator instead.",
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700375 &split_args)
376 .OptionalSwitch("--enable-sparse-encoding",
377 "Enables encoding sparse entries using a binary search tree.\n"
378 "This decreases APK size at the cost of resource retrieval performance.",
379 &options.table_flattener_options.use_sparse_entries)
380 .OptionalSwitch("-v", "Enables verbose logging", &verbose);
381
382 if (!flags.Parse("aapt2 optimize", args, &std::cerr)) {
383 return 1;
384 }
385
386 if (flags.GetArgs().size() != 1u) {
387 std::cerr << "must have one APK as argument.\n\n";
388 flags.Usage("aapt2 optimize", &std::cerr);
389 return 1;
390 }
391
392 std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[0]);
393 if (!apk) {
394 return 1;
395 }
396
397 context.SetVerbose(verbose);
398
399 if (target_densities) {
400 // Parse the target screen densities.
401 for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
402 Maybe<uint16_t> target_density =
403 ParseTargetDensityParameter(config_str, context.GetDiagnostics());
404 if (!target_density) {
405 return 1;
406 }
407 options.table_splitter_options.preferred_densities.push_back(target_density.value());
408 }
409 }
410
411 std::unique_ptr<IConfigFilter> filter;
412 if (!configs.empty()) {
413 filter = ParseConfigFilterParameters(configs, context.GetDiagnostics());
414 if (filter == nullptr) {
415 return 1;
416 }
417 options.table_splitter_options.config_filter = filter.get();
418 }
419
420 // Parse the split parameters.
421 for (const std::string& split_arg : split_args) {
422 options.split_paths.push_back({});
423 options.split_constraints.push_back({});
424 if (!ParseSplitParameter(split_arg, context.GetDiagnostics(), &options.split_paths.back(),
425 &options.split_constraints.back())) {
426 return 1;
427 }
428 }
429
Shane Farmer57669432017-06-19 12:52:04 -0700430 if (config_path) {
431 if (!options.output_dir) {
432 context.GetDiagnostics()->Error(
433 DiagMessage() << "Output directory is required when using a configuration file");
434 return 1;
435 }
436 std::string& path = config_path.value();
437 Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path);
438 if (for_path) {
439 options.configuration = for_path.value().WithDiagnostics(context.GetDiagnostics()).Parse();
440 } else {
441 context.GetDiagnostics()->Error(DiagMessage() << "Could not parse config file " << path);
442 return 1;
443 }
444 }
445
Adam Lesinskid0f492d2017-04-03 18:12:45 -0700446 if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) {
447 return 1;
448 }
449
450 OptimizeCommand cmd(&context, options);
451 return cmd.Run(std::move(apk));
452}
453
454} // namespace aapt