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