| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 1 | /* | 
 | 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 Farmer | 5766943 | 2017-06-19 12:52:04 -0700 | [diff] [blame] | 20 | #include "android-base/stringprintf.h" | 
| Adam Lesinski | d3ffa844 | 2017-09-28 13:34:35 -0700 | [diff] [blame] | 21 |  | 
| Shane Farmer | 0a5b201 | 2017-06-22 12:24:12 -0700 | [diff] [blame] | 22 | #include "androidfw/ResourceTypes.h" | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 23 | #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 Farmer | 5766943 | 2017-06-19 12:52:04 -0700 | [diff] [blame] | 32 | #include "configuration/ConfigurationParser.h" | 
 | 33 | #include "filter/AbiFilter.h" | 
| Adam Lesinski | 4670805 | 2017-09-29 14:49:15 -0700 | [diff] [blame] | 34 | #include "format/binary/TableFlattener.h" | 
 | 35 | #include "format/binary/XmlFlattener.h" | 
| Adam Lesinski | 0045116 | 2017-10-03 07:44:08 -0700 | [diff] [blame] | 36 | #include "io/BigBufferStream.h" | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 37 | #include "io/Util.h" | 
| Shane Farmer | 0a5b201 | 2017-06-22 12:24:12 -0700 | [diff] [blame] | 38 | #include "optimize/MultiApkGenerator.h" | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 39 | #include "optimize/ResourceDeduper.h" | 
 | 40 | #include "optimize/VersionCollapser.h" | 
 | 41 | #include "split/TableSplitter.h" | 
| Shane Farmer | 5766943 | 2017-06-19 12:52:04 -0700 | [diff] [blame] | 42 | #include "util/Files.h" | 
| Shane Farmer | 0a5b201 | 2017-06-22 12:24:12 -0700 | [diff] [blame] | 43 | #include "util/Util.h" | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 44 |  | 
| Shane Farmer | 5766943 | 2017-06-19 12:52:04 -0700 | [diff] [blame] | 45 | using ::aapt::configuration::Abi; | 
 | 46 | using ::aapt::configuration::Artifact; | 
| Shane Farmer | 280be34 | 2017-06-21 15:20:15 -0700 | [diff] [blame] | 47 | using ::aapt::configuration::PostProcessingConfiguration; | 
| Shane Farmer | 0a5b201 | 2017-06-22 12:24:12 -0700 | [diff] [blame] | 48 | using ::android::ResTable_config; | 
| Shane Farmer | 5766943 | 2017-06-19 12:52:04 -0700 | [diff] [blame] | 49 | using ::android::StringPiece; | 
| Shane Farmer | 0a5b201 | 2017-06-22 12:24:12 -0700 | [diff] [blame] | 50 | using ::android::base::StringAppendF; | 
| Shane Farmer | 5766943 | 2017-06-19 12:52:04 -0700 | [diff] [blame] | 51 | using ::android::base::StringPrintf; | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 52 |  | 
 | 53 | namespace aapt { | 
 | 54 |  | 
 | 55 | struct OptimizeOptions { | 
 | 56 |   // Path to the output APK. | 
| Shane Farmer | 5766943 | 2017-06-19 12:52:04 -0700 | [diff] [blame] | 57 |   Maybe<std::string> output_path; | 
 | 58 |   // Path to the output APK directory for splits. | 
 | 59 |   Maybe<std::string> output_dir; | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 60 |  | 
 | 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 Farmer | 5766943 | 2017-06-19 12:52:04 -0700 | [diff] [blame] | 74 |  | 
| Shane Farmer | 280be34 | 2017-06-21 15:20:15 -0700 | [diff] [blame] | 75 |   Maybe<PostProcessingConfiguration> configuration; | 
| Shane Farmer | 666de34 | 2017-11-29 16:07:51 -0800 | [diff] [blame^] | 76 |  | 
 | 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 Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 80 | }; | 
 | 81 |  | 
 | 82 | class OptimizeContext : public IAaptContext { | 
 | 83 |  public: | 
| Adam Lesinski | b522f04 | 2017-04-21 16:57:59 -0700 | [diff] [blame] | 84 |   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 Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 92 |   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 Lesinski | b522f04 | 2017-04-21 16:57:59 -0700 | [diff] [blame] | 132 |   DISALLOW_COPY_AND_ASSIGN(OptimizeContext); | 
 | 133 |  | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 134 |   StdErrDiagnostics diagnostics_; | 
 | 135 |   bool verbose_ = false; | 
 | 136 |   int sdk_version_ = 0; | 
 | 137 | }; | 
 | 138 |  | 
 | 139 | class 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 Farmer | 5766943 | 2017-06-19 12:52:04 -0700 | [diff] [blame] | 200 |     if (options_.configuration && options_.output_dir) { | 
| Shane Farmer | 0a5b201 | 2017-06-22 12:24:12 -0700 | [diff] [blame] | 201 |       MultiApkGenerator generator{apk.get(), context_}; | 
| Shane Farmer | 666de34 | 2017-11-29 16:07:51 -0800 | [diff] [blame^] | 202 |       MultiApkGeneratorOptions generator_options = { | 
 | 203 |           options_.output_dir.value(), | 
 | 204 |           options_.configuration.value(), | 
 | 205 |           options_.table_flattener_options, | 
 | 206 |           options_.kept_artifacts, | 
 | 207 |       }; | 
| Shane Farmer | efe4539 | 2017-08-21 14:39:28 -0700 | [diff] [blame] | 208 |       if (!generator.FromBaseApk(generator_options)) { | 
| Shane Farmer | 0a5b201 | 2017-06-22 12:24:12 -0700 | [diff] [blame] | 209 |         return 1; | 
| Shane Farmer | 5766943 | 2017-06-19 12:52:04 -0700 | [diff] [blame] | 210 |       } | 
 | 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 Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 219 |     } | 
 | 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 Farmer | 0a5b201 | 2017-06-22 12:24:12 -0700 | [diff] [blame] | 246 |             auto* file_ref = ValueCast<FileReference>(config_value->value.get()); | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 247 |             if (file_ref == nullptr) { | 
 | 248 |               continue; | 
 | 249 |             } | 
 | 250 |  | 
 | 251 |             if (file_ref->file == nullptr) { | 
 | 252 |               ResourceNameRef name(pkg->name, type->type, entry->name); | 
| Adam Lesinski | 742888f | 2017-04-28 15:34:52 -0700 | [diff] [blame] | 253 |               context_->GetDiagnostics()->Warn(DiagMessage(file_ref->GetSource()) | 
| Shane Farmer | 5766943 | 2017-06-19 12:52:04 -0700 | [diff] [blame] | 254 |                                                << "file for resource " << name << " with config '" | 
 | 255 |                                                << config_value->config << "' not found"); | 
| Adam Lesinski | 742888f | 2017-04-28 15:34:52 -0700 | [diff] [blame] | 256 |               continue; | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 257 |             } | 
 | 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 Lecesne | d55bef7 | 2017-11-10 22:31:01 +0000 | [diff] [blame] | 266 |           if (!io::CopyFileToArchivePreserveCompression(context_, file_ref->file, *file_ref->path, | 
 | 267 |                                                         writer)) { | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 268 |             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 Farmer | 0a5b201 | 2017-06-22 12:24:12 -0700 | [diff] [blame] | 281 |     return io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc", | 
 | 282 |                                         ArchiveEntry::kAlign, writer); | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 283 |   } | 
 | 284 |  | 
 | 285 |   OptimizeOptions options_; | 
 | 286 |   OptimizeContext* context_; | 
 | 287 | }; | 
 | 288 |  | 
| Adam Lesinski | 8780eb6 | 2017-10-31 17:44:39 -0700 | [diff] [blame] | 289 | bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk, | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 290 |                                 OptimizeOptions* out_options) { | 
| Adam Lesinski | 8780eb6 | 2017-10-31 17:44:39 -0700 | [diff] [blame] | 291 |   const xml::XmlResource* manifest = apk->GetManifest(); | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 292 |   if (manifest == nullptr) { | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 293 |     return false; | 
 | 294 |   } | 
 | 295 |  | 
| Adam Lesinski | 8780eb6 | 2017-10-31 17:44:39 -0700 | [diff] [blame] | 296 |   Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*manifest, context->GetDiagnostics()); | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 297 |   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 |  | 
 | 308 | int Optimize(const std::vector<StringPiece>& args) { | 
 | 309 |   OptimizeContext context; | 
 | 310 |   OptimizeOptions options; | 
| Shane Farmer | 5766943 | 2017-06-19 12:52:04 -0700 | [diff] [blame] | 311 |   Maybe<std::string> config_path; | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 312 |   Maybe<std::string> target_densities; | 
| Shane Farmer | 0a5b201 | 2017-06-22 12:24:12 -0700 | [diff] [blame] | 313 |   Maybe<std::string> target_abis; | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 314 |   std::vector<std::string> configs; | 
 | 315 |   std::vector<std::string> split_args; | 
| Shane Farmer | 666de34 | 2017-11-29 16:07:51 -0800 | [diff] [blame^] | 316 |   std::unordered_set<std::string> kept_artifacts; | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 317 |   bool verbose = false; | 
| Shane Farmer | 9ecc075 | 2017-08-24 15:55:36 -0700 | [diff] [blame] | 318 |   bool print_only = false; | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 319 |   Flags flags = | 
 | 320 |       Flags() | 
| Shane Farmer | 5766943 | 2017-06-19 12:52:04 -0700 | [diff] [blame] | 321 |           .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 Farmer | 9ecc075 | 2017-08-24 15:55:36 -0700 | [diff] [blame] | 324 |           .OptionalSwitch("-p", "Print the multi APK artifacts and exit.", &print_only) | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 325 |           .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 Farmer | 0a5b201 | 2017-06-22 12:24:12 -0700 | [diff] [blame] | 331 |           .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 Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 337 |           .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 Lesinski | db09157 | 2017-04-13 12:48:56 -0700 | [diff] [blame] | 343 |                             "Split APK.\nSyntax: path/to/output.apk;<config>[,<config>[...]].\n" | 
 | 344 |                             "On Windows, use a semicolon ';' separator instead.", | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 345 |                             &split_args) | 
| Shane Farmer | 666de34 | 2017-11-29 16:07:51 -0800 | [diff] [blame^] | 346 |           .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 Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 350 |           .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 Farmer | 0a5b201 | 2017-06-22 12:24:12 -0700 | [diff] [blame] | 366 |   const std::string& apk_path = flags.GetArgs()[0]; | 
| Adam Lesinski | 8780eb6 | 2017-10-31 17:44:39 -0700 | [diff] [blame] | 367 |   std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics()); | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 368 |   if (!apk) { | 
 | 369 |     return 1; | 
 | 370 |   } | 
 | 371 |  | 
 | 372 |   context.SetVerbose(verbose); | 
| Shane Farmer | 9ecc075 | 2017-08-24 15:55:36 -0700 | [diff] [blame] | 373 |   IDiagnostics* diag = context.GetDiagnostics(); | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 374 |  | 
 | 375 |   if (target_densities) { | 
 | 376 |     // Parse the target screen densities. | 
 | 377 |     for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) { | 
| Shane Farmer | 9ecc075 | 2017-08-24 15:55:36 -0700 | [diff] [blame] | 378 |       Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag); | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 379 |       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 Farmer | 9ecc075 | 2017-08-24 15:55:36 -0700 | [diff] [blame] | 388 |     filter = ParseConfigFilterParameters(configs, diag); | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 389 |     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 Farmer | 0a5b201 | 2017-06-22 12:24:12 -0700 | [diff] [blame] | 397 |     options.split_paths.emplace_back(); | 
 | 398 |     options.split_constraints.emplace_back(); | 
| Shane Farmer | 9ecc075 | 2017-08-24 15:55:36 -0700 | [diff] [blame] | 399 |     if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(), | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 400 |                              &options.split_constraints.back())) { | 
 | 401 |       return 1; | 
 | 402 |     } | 
 | 403 |   } | 
 | 404 |  | 
| Shane Farmer | 5766943 | 2017-06-19 12:52:04 -0700 | [diff] [blame] | 405 |   if (config_path) { | 
| Shane Farmer | 5766943 | 2017-06-19 12:52:04 -0700 | [diff] [blame] | 406 |     std::string& path = config_path.value(); | 
 | 407 |     Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path); | 
 | 408 |     if (for_path) { | 
| Shane Farmer | 9ecc075 | 2017-08-24 15:55:36 -0700 | [diff] [blame] | 409 |       options.configuration = for_path.value().WithDiagnostics(diag).Parse(); | 
| Shane Farmer | 5766943 | 2017-06-19 12:52:04 -0700 | [diff] [blame] | 410 |     } else { | 
| Shane Farmer | 9ecc075 | 2017-08-24 15:55:36 -0700 | [diff] [blame] | 411 |       diag->Error(DiagMessage() << "Could not parse config file " << path); | 
| Shane Farmer | 5766943 | 2017-06-19 12:52:04 -0700 | [diff] [blame] | 412 |       return 1; | 
 | 413 |     } | 
| Shane Farmer | 9ecc075 | 2017-08-24 15:55:36 -0700 | [diff] [blame] | 414 |  | 
 | 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 Farmer | 666de34 | 2017-11-29 16:07:51 -0800 | [diff] [blame^] | 429 |     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 Farmer | 9ecc075 | 2017-08-24 15:55:36 -0700 | [diff] [blame] | 437 |     // 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 Farmer | 5766943 | 2017-06-19 12:52:04 -0700 | [diff] [blame] | 446 |   } | 
 | 447 |  | 
| Adam Lesinski | d0f492d | 2017-04-03 18:12:45 -0700 | [diff] [blame] | 448 |   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 |