blob: d6251c3c151166b9df14fc60319367e05a2602e4 [file] [log] [blame]
Adam Lesinski40e8eef2014-09-16 14:43:29 -07001/*
2 * Copyright (C) 2014 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 <algorithm>
18#include <cstdio>
19
20#include "aapt/AaptUtil.h"
21
22#include "Grouper.h"
23#include "Rule.h"
24#include "RuleGenerator.h"
25#include "SplitDescription.h"
26
27#include <androidfw/AssetManager.h>
28#include <androidfw/ResourceTypes.h>
29#include <utils/KeyedVector.h>
30#include <utils/Vector.h>
31
32using namespace android;
33
34namespace split {
35
36static void usage() {
37 fprintf(stderr,
38 "split-select --help\n"
39 "split-select --target <config> --split <path/to/apk> [--split <path/to/apk> [...]]\n"
40 "split-select --generate --split <path/to/apk> [--split <path/to/apk> [...]]\n"
41 "\n"
42 " --help Displays more information about this program.\n"
43 " --target <config> Performs the Split APK selection on the given configuration.\n"
44 " --generate Generates the logic for selecting the Split APK, in JSON format.\n"
45 " --split <path/to/apk> Includes a Split APK in the selection process.\n"
46 "\n"
47 " Where <config> is an extended AAPT resource qualifier of the form\n"
48 " 'resource-qualifiers:extended-qualifiers', where 'resource-qualifiers' is an AAPT resource\n"
49 " qualifier (ex: en-rUS-sw600dp-xhdpi), and 'extended-qualifiers' is an ordered list of one\n"
50 " qualifier (or none) from each category:\n"
51 " Architecture: armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, mips\n");
52}
53
54static void help() {
55 usage();
56 fprintf(stderr, "\n"
57 " Generates the logic for selecting a Split APK given some target Android device configuration.\n"
58 " Using the flag --generate will emit a JSON encoded tree of rules that must be satisfied in order\n"
59 " to install the given Split APK. Using the flag --target along with the device configuration\n"
60 " will emit the set of Split APKs to install, following the same logic that would have been emitted\n"
61 " via JSON.\n");
62}
63
64class SplitSelector {
65public:
66 SplitSelector() = default;
67 SplitSelector(const Vector<SplitDescription>& splits);
68
69 Vector<SplitDescription> getBestSplits(const SplitDescription& target) const;
70
71 template <typename RuleGenerator>
72 KeyedVector<SplitDescription, sp<Rule> > getRules() const;
73
74private:
75 Vector<SortedVector<SplitDescription> > mGroups;
76};
77
78SplitSelector::SplitSelector(const Vector<SplitDescription>& splits)
79 : mGroups(groupByMutualExclusivity(splits)) {
80}
81
82static void selectBestFromGroup(const SortedVector<SplitDescription>& splits,
83 const SplitDescription& target, Vector<SplitDescription>& splitsOut) {
84 SplitDescription bestSplit;
85 bool isSet = false;
86 const size_t splitCount = splits.size();
87 for (size_t j = 0; j < splitCount; j++) {
88 const SplitDescription& thisSplit = splits[j];
89 if (!thisSplit.match(target)) {
90 continue;
91 }
92
93 if (!isSet || thisSplit.isBetterThan(bestSplit, target)) {
94 isSet = true;
95 bestSplit = thisSplit;
96 }
97 }
98
99 if (isSet) {
100 splitsOut.add(bestSplit);
101 }
102}
103
104Vector<SplitDescription> SplitSelector::getBestSplits(const SplitDescription& target) const {
105 Vector<SplitDescription> bestSplits;
106 const size_t groupCount = mGroups.size();
107 for (size_t i = 0; i < groupCount; i++) {
108 selectBestFromGroup(mGroups[i], target, bestSplits);
109 }
110 return bestSplits;
111}
112
113template <typename RuleGenerator>
114KeyedVector<SplitDescription, sp<Rule> > SplitSelector::getRules() const {
115 KeyedVector<SplitDescription, sp<Rule> > rules;
116
117 const size_t groupCount = mGroups.size();
118 for (size_t i = 0; i < groupCount; i++) {
119 const SortedVector<SplitDescription>& splits = mGroups[i];
120 const size_t splitCount = splits.size();
121 for (size_t j = 0; j < splitCount; j++) {
122 sp<Rule> rule = Rule::simplify(RuleGenerator::generate(splits, j));
123 if (rule != NULL) {
124 rules.add(splits[j], rule);
125 }
126 }
127 }
128 return rules;
129}
130
131Vector<SplitDescription> select(const SplitDescription& target, const Vector<SplitDescription>& splits) {
132 const SplitSelector selector(splits);
133 return selector.getBestSplits(target);
134}
135
136void generate(const KeyedVector<String8, Vector<SplitDescription> >& splits) {
137 Vector<SplitDescription> allSplits;
138 const size_t apkSplitCount = splits.size();
139 for (size_t i = 0; i < apkSplitCount; i++) {
140 allSplits.appendVector(splits[i]);
141 }
142 const SplitSelector selector(allSplits);
143 KeyedVector<SplitDescription, sp<Rule> > rules(selector.getRules<RuleGenerator>());
144
145 fprintf(stdout, "[\n");
146 for (size_t i = 0; i < apkSplitCount; i++) {
147 sp<Rule> masterRule = new Rule();
148 masterRule->op = Rule::OR_SUBRULES;
149 const Vector<SplitDescription>& splitDescriptions = splits[i];
150 const size_t splitDescriptionCount = splitDescriptions.size();
151 for (size_t j = 0; j < splitDescriptionCount; j++) {
152 masterRule->subrules.add(rules.valueFor(splitDescriptions[j]));
153 }
154 masterRule = Rule::simplify(masterRule);
155 fprintf(stdout, " {\n \"path\": \"%s\",\n \"rules\": %s\n }%s\n",
156 splits.keyAt(i).string(),
157 masterRule->toJson(2).string(),
158 i < apkSplitCount - 1 ? "," : "");
159 }
160 fprintf(stdout, "]\n");
161}
162
163static void removeRuntimeQualifiers(ConfigDescription* outConfig) {
164 outConfig->imsi = 0;
165 outConfig->orientation = ResTable_config::ORIENTATION_ANY;
166 outConfig->screenWidth = ResTable_config::SCREENWIDTH_ANY;
167 outConfig->screenHeight = ResTable_config::SCREENHEIGHT_ANY;
168 outConfig->uiMode &= ResTable_config::UI_MODE_NIGHT_ANY;
169}
170
171static Vector<SplitDescription> extractSplitDescriptionsFromApk(const String8& path) {
172 AssetManager assetManager;
173 Vector<SplitDescription> splits;
174 int32_t cookie = 0;
175 if (!assetManager.addAssetPath(path, &cookie)) {
176 return splits;
177 }
178
179 const ResTable& res = assetManager.getResources(false);
180 if (res.getError() == NO_ERROR) {
181 Vector<ResTable_config> configs;
182 res.getConfigurations(&configs);
183 const size_t configCount = configs.size();
184 for (size_t i = 0; i < configCount; i++) {
185 splits.add();
186 splits.editTop().config = configs[i];
187 }
188 }
189
190 AssetDir* dir = assetManager.openNonAssetDir(cookie, "lib");
191 if (dir != NULL) {
192 const size_t fileCount = dir->getFileCount();
193 for (size_t i = 0; i < fileCount; i++) {
194 splits.add();
195 Vector<String8> parts = AaptUtil::splitAndLowerCase(dir->getFileName(i), '-');
196 if (parseAbi(parts, 0, &splits.editTop()) < 0) {
197 fprintf(stderr, "Malformed library %s\n", dir->getFileName(i).string());
198 splits.pop();
199 }
200 }
201 delete dir;
202 }
203 return splits;
204}
205
206static int main(int argc, char** argv) {
207 // Skip over the first argument.
208 argc--;
209 argv++;
210
211 bool generateFlag = false;
212 String8 targetConfigStr;
213 Vector<String8> splitApkPaths;
214 while (argc > 0) {
215 const String8 arg(*argv);
216 if (arg == "--target") {
217 argc--;
218 argv++;
219 if (argc < 1) {
220 fprintf(stderr, "Missing parameter for --split.\n");
221 usage();
222 return 1;
223 }
224 targetConfigStr.setTo(*argv);
225 } else if (arg == "--split") {
226 argc--;
227 argv++;
228 if (argc < 1) {
229 fprintf(stderr, "Missing parameter for --split.\n");
230 usage();
231 return 1;
232 }
233 splitApkPaths.add(String8(*argv));
234 } else if (arg == "--generate") {
235 generateFlag = true;
236 } else if (arg == "--help") {
237 help();
238 return 0;
239 } else {
240 fprintf(stderr, "Unknown argument '%s'\n", arg.string());
241 usage();
242 return 1;
243 }
244 argc--;
245 argv++;
246 }
247
248 if (!generateFlag && targetConfigStr == "") {
249 usage();
250 return 1;
251 }
252
253 if (splitApkPaths.size() == 0) {
254 usage();
255 return 1;
256 }
257
258 SplitDescription targetSplit;
259 if (!generateFlag) {
260 if (!SplitDescription::parse(targetConfigStr, &targetSplit)) {
261 fprintf(stderr, "Invalid --target config: '%s'\n",
262 targetConfigStr.string());
263 usage();
264 return 1;
265 }
266
267 // We don't want to match on things that will change at run-time
268 // (orientation, w/h, etc.).
269 removeRuntimeQualifiers(&targetSplit.config);
270 }
271
272 KeyedVector<String8, Vector<SplitDescription> > apkPathSplitMap;
273 KeyedVector<SplitDescription, String8> splitApkPathMap;
274 Vector<SplitDescription> splitConfigs;
275 const size_t splitCount = splitApkPaths.size();
276 for (size_t i = 0; i < splitCount; i++) {
277 Vector<SplitDescription> splits = extractSplitDescriptionsFromApk(splitApkPaths[i]);
278 if (splits.isEmpty()) {
279 fprintf(stderr, "Invalid --split path: '%s'. No splits found.\n",
280 splitApkPaths[i].string());
281 usage();
282 return 1;
283 }
284 apkPathSplitMap.replaceValueFor(splitApkPaths[i], splits);
285 const size_t apkSplitDescriptionCount = splits.size();
286 for (size_t j = 0; j < apkSplitDescriptionCount; j++) {
287 splitApkPathMap.replaceValueFor(splits[j], splitApkPaths[i]);
288 }
289 splitConfigs.appendVector(splits);
290 }
291
292 if (!generateFlag) {
293 Vector<SplitDescription> matchingConfigs = select(targetSplit, splitConfigs);
294 const size_t matchingConfigCount = matchingConfigs.size();
295 SortedVector<String8> matchingSplitPaths;
296 for (size_t i = 0; i < matchingConfigCount; i++) {
297 matchingSplitPaths.add(splitApkPathMap.valueFor(matchingConfigs[i]));
298 }
299
300 const size_t matchingSplitApkPathCount = matchingSplitPaths.size();
301 for (size_t i = 0; i < matchingSplitApkPathCount; i++) {
302 fprintf(stderr, "%s\n", matchingSplitPaths[i].string());
303 }
304 } else {
305 generate(apkPathSplitMap);
306 }
307 return 0;
308}
309
310} // namespace split
311
312int main(int argc, char** argv) {
313 return split::main(argc, argv);
314}