Main.cpp revision c3dc0b57b8d0b3875f868788e110aa67fb032b4a
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();
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() {
79}
80
81SplitSelector::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}
318