Main.cpp revision 40e8eefbedcafc51948945647d746daaee092f16
1/*
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}
315