blob: 434494ead3a879670b79f853239d0d22b868fed5 [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:
Adam Lesinskic3dc0b52014-11-03 12:05:15 -080066 SplitSelector();
Adam Lesinski40e8eef2014-09-16 14:43:29 -070067 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
Adam Lesinskic3dc0b52014-11-03 12:05:15 -080078SplitSelector::SplitSelector() {
79}
80
Adam Lesinski40e8eef2014-09-16 14:43:29 -070081SplitSelector::SplitSelector(const Vector<SplitDescription>& splits)
82 : mGroups(groupByMutualExclusivity(splits)) {
83}
84
85static void selectBestFromGroup(const SortedVector<SplitDescription>& splits,
86 const SplitDescription& target, Vector<SplitDescription>& splitsOut) {
87 SplitDescription bestSplit;
88 bool isSet = false;
89 const size_t splitCount = splits.size();
90 for (size_t j = 0; j < splitCount; j++) {
91 const SplitDescription& thisSplit = splits[j];
92 if (!thisSplit.match(target)) {
93 continue;
94 }
95
96 if (!isSet || thisSplit.isBetterThan(bestSplit, target)) {
97 isSet = true;
98 bestSplit = thisSplit;
99 }
100 }
101
102 if (isSet) {
103 splitsOut.add(bestSplit);
104 }
105}
106
107Vector<SplitDescription> SplitSelector::getBestSplits(const SplitDescription& target) const {
108 Vector<SplitDescription> bestSplits;
109 const size_t groupCount = mGroups.size();
110 for (size_t i = 0; i < groupCount; i++) {
111 selectBestFromGroup(mGroups[i], target, bestSplits);
112 }
113 return bestSplits;
114}
115
116template <typename RuleGenerator>
117KeyedVector<SplitDescription, sp<Rule> > SplitSelector::getRules() const {
118 KeyedVector<SplitDescription, sp<Rule> > rules;
119
120 const size_t groupCount = mGroups.size();
121 for (size_t i = 0; i < groupCount; i++) {
122 const SortedVector<SplitDescription>& splits = mGroups[i];
123 const size_t splitCount = splits.size();
124 for (size_t j = 0; j < splitCount; j++) {
125 sp<Rule> rule = Rule::simplify(RuleGenerator::generate(splits, j));
126 if (rule != NULL) {
127 rules.add(splits[j], rule);
128 }
129 }
130 }
131 return rules;
132}
133
134Vector<SplitDescription> select(const SplitDescription& target, const Vector<SplitDescription>& splits) {
135 const SplitSelector selector(splits);
136 return selector.getBestSplits(target);
137}
138
139void generate(const KeyedVector<String8, Vector<SplitDescription> >& splits) {
140 Vector<SplitDescription> allSplits;
141 const size_t apkSplitCount = splits.size();
142 for (size_t i = 0; i < apkSplitCount; i++) {
143 allSplits.appendVector(splits[i]);
144 }
145 const SplitSelector selector(allSplits);
146 KeyedVector<SplitDescription, sp<Rule> > rules(selector.getRules<RuleGenerator>());
147
148 fprintf(stdout, "[\n");
149 for (size_t i = 0; i < apkSplitCount; i++) {
150 sp<Rule> masterRule = new Rule();
151 masterRule->op = Rule::OR_SUBRULES;
152 const Vector<SplitDescription>& splitDescriptions = splits[i];
153 const size_t splitDescriptionCount = splitDescriptions.size();
154 for (size_t j = 0; j < splitDescriptionCount; j++) {
155 masterRule->subrules.add(rules.valueFor(splitDescriptions[j]));
156 }
157 masterRule = Rule::simplify(masterRule);
158 fprintf(stdout, " {\n \"path\": \"%s\",\n \"rules\": %s\n }%s\n",
159 splits.keyAt(i).string(),
160 masterRule->toJson(2).string(),
161 i < apkSplitCount - 1 ? "," : "");
162 }
163 fprintf(stdout, "]\n");
164}
165
166static void removeRuntimeQualifiers(ConfigDescription* outConfig) {
167 outConfig->imsi = 0;
168 outConfig->orientation = ResTable_config::ORIENTATION_ANY;
169 outConfig->screenWidth = ResTable_config::SCREENWIDTH_ANY;
170 outConfig->screenHeight = ResTable_config::SCREENHEIGHT_ANY;
171 outConfig->uiMode &= ResTable_config::UI_MODE_NIGHT_ANY;
172}
173
174static Vector<SplitDescription> extractSplitDescriptionsFromApk(const String8& path) {
175 AssetManager assetManager;
176 Vector<SplitDescription> splits;
177 int32_t cookie = 0;
178 if (!assetManager.addAssetPath(path, &cookie)) {
179 return splits;
180 }
181
182 const ResTable& res = assetManager.getResources(false);
183 if (res.getError() == NO_ERROR) {
184 Vector<ResTable_config> configs;
185 res.getConfigurations(&configs);
186 const size_t configCount = configs.size();
187 for (size_t i = 0; i < configCount; i++) {
188 splits.add();
189 splits.editTop().config = configs[i];
190 }
191 }
192
193 AssetDir* dir = assetManager.openNonAssetDir(cookie, "lib");
194 if (dir != NULL) {
195 const size_t fileCount = dir->getFileCount();
196 for (size_t i = 0; i < fileCount; i++) {
197 splits.add();
198 Vector<String8> parts = AaptUtil::splitAndLowerCase(dir->getFileName(i), '-');
199 if (parseAbi(parts, 0, &splits.editTop()) < 0) {
200 fprintf(stderr, "Malformed library %s\n", dir->getFileName(i).string());
201 splits.pop();
202 }
203 }
204 delete dir;
205 }
206 return splits;
207}
208
209static int main(int argc, char** argv) {
210 // Skip over the first argument.
211 argc--;
212 argv++;
213
214 bool generateFlag = false;
215 String8 targetConfigStr;
216 Vector<String8> splitApkPaths;
217 while (argc > 0) {
218 const String8 arg(*argv);
219 if (arg == "--target") {
220 argc--;
221 argv++;
222 if (argc < 1) {
223 fprintf(stderr, "Missing parameter for --split.\n");
224 usage();
225 return 1;
226 }
227 targetConfigStr.setTo(*argv);
228 } else if (arg == "--split") {
229 argc--;
230 argv++;
231 if (argc < 1) {
232 fprintf(stderr, "Missing parameter for --split.\n");
233 usage();
234 return 1;
235 }
236 splitApkPaths.add(String8(*argv));
237 } else if (arg == "--generate") {
238 generateFlag = true;
239 } else if (arg == "--help") {
240 help();
241 return 0;
242 } else {
243 fprintf(stderr, "Unknown argument '%s'\n", arg.string());
244 usage();
245 return 1;
246 }
247 argc--;
248 argv++;
249 }
250
251 if (!generateFlag && targetConfigStr == "") {
252 usage();
253 return 1;
254 }
255
256 if (splitApkPaths.size() == 0) {
257 usage();
258 return 1;
259 }
260
261 SplitDescription targetSplit;
262 if (!generateFlag) {
263 if (!SplitDescription::parse(targetConfigStr, &targetSplit)) {
264 fprintf(stderr, "Invalid --target config: '%s'\n",
265 targetConfigStr.string());
266 usage();
267 return 1;
268 }
269
270 // We don't want to match on things that will change at run-time
271 // (orientation, w/h, etc.).
272 removeRuntimeQualifiers(&targetSplit.config);
273 }
274
275 KeyedVector<String8, Vector<SplitDescription> > apkPathSplitMap;
276 KeyedVector<SplitDescription, String8> splitApkPathMap;
277 Vector<SplitDescription> splitConfigs;
278 const size_t splitCount = splitApkPaths.size();
279 for (size_t i = 0; i < splitCount; i++) {
280 Vector<SplitDescription> splits = extractSplitDescriptionsFromApk(splitApkPaths[i]);
281 if (splits.isEmpty()) {
282 fprintf(stderr, "Invalid --split path: '%s'. No splits found.\n",
283 splitApkPaths[i].string());
284 usage();
285 return 1;
286 }
287 apkPathSplitMap.replaceValueFor(splitApkPaths[i], splits);
288 const size_t apkSplitDescriptionCount = splits.size();
289 for (size_t j = 0; j < apkSplitDescriptionCount; j++) {
290 splitApkPathMap.replaceValueFor(splits[j], splitApkPaths[i]);
291 }
292 splitConfigs.appendVector(splits);
293 }
294
295 if (!generateFlag) {
296 Vector<SplitDescription> matchingConfigs = select(targetSplit, splitConfigs);
297 const size_t matchingConfigCount = matchingConfigs.size();
298 SortedVector<String8> matchingSplitPaths;
299 for (size_t i = 0; i < matchingConfigCount; i++) {
300 matchingSplitPaths.add(splitApkPathMap.valueFor(matchingConfigs[i]));
301 }
302
303 const size_t matchingSplitApkPathCount = matchingSplitPaths.size();
304 for (size_t i = 0; i < matchingSplitApkPathCount; i++) {
305 fprintf(stderr, "%s\n", matchingSplitPaths[i].string());
306 }
307 } else {
308 generate(apkPathSplitMap);
309 }
310 return 0;
311}
312
313} // namespace split
314
315int main(int argc, char** argv) {
316 return split::main(argc, argv);
317}