summaryrefslogtreecommitdiffstats
path: root/tools/split-select
diff options
context:
space:
mode:
Diffstat (limited to 'tools/split-select')
-rw-r--r--tools/split-select/Abi.cpp99
-rw-r--r--tools/split-select/Abi.h50
-rw-r--r--tools/split-select/Android.mk115
-rw-r--r--tools/split-select/Grouper.cpp77
-rw-r--r--tools/split-select/Grouper.h32
-rw-r--r--tools/split-select/Grouper_test.cpp185
-rw-r--r--tools/split-select/Main.cpp317
-rw-r--r--tools/split-select/Rule.cpp206
-rw-r--r--tools/split-select/Rule.h79
-rw-r--r--tools/split-select/RuleGenerator.cpp163
-rw-r--r--tools/split-select/RuleGenerator.h39
-rw-r--r--tools/split-select/RuleGenerator_test.cpp110
-rw-r--r--tools/split-select/Rule_test.cpp100
-rw-r--r--tools/split-select/SplitDescription.cpp175
-rw-r--r--tools/split-select/SplitDescription.h64
-rw-r--r--tools/split-select/TestRules.cpp90
-rw-r--r--tools/split-select/TestRules.h66
17 files changed, 1967 insertions, 0 deletions
diff --git a/tools/split-select/Abi.cpp b/tools/split-select/Abi.cpp
new file mode 100644
index 0000000..180dd8f
--- /dev/null
+++ b/tools/split-select/Abi.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Abi.h"
+
+using namespace android;
+
+namespace split {
+namespace abi {
+
+static Vector<Variant> buildVariants(Variant v1, Variant v2) {
+ Vector<Variant> v;
+ v.add(v1);
+ v.add(v2);
+ return v;
+}
+
+static Vector<Variant> buildVariants(Variant v1, Variant v2, Variant v3) {
+ Vector<Variant> v;
+ v.add(v1);
+ v.add(v2);
+ v.add(v3);
+ return v;
+}
+
+static const Vector<Variant> sNoneVariants;
+static const Vector<Variant> sArmVariants = buildVariants(Variant_armeabi, Variant_armeabi_v7a, Variant_arm64_v8a);
+static const Vector<Variant> sIntelVariants = buildVariants(Variant_x86, Variant_x86_64);
+static const Vector<Variant> sMipsVariants = buildVariants(Variant_mips, Variant_mips64);
+
+Family getFamily(Variant variant) {
+ switch (variant) {
+ case Variant_none:
+ return Family_none;
+ case Variant_armeabi:
+ case Variant_armeabi_v7a:
+ case Variant_arm64_v8a:
+ return Family_arm;
+ case Variant_x86:
+ case Variant_x86_64:
+ return Family_intel;
+ case Variant_mips:
+ case Variant_mips64:
+ return Family_mips;
+ }
+ return Family_none;
+}
+
+const Vector<Variant>& getVariants(Family family) {
+ switch (family) {
+ case Family_none:
+ return sNoneVariants;
+ case Family_arm:
+ return sArmVariants;
+ case Family_intel:
+ return sIntelVariants;
+ case Family_mips:
+ return sMipsVariants;
+ }
+ return sNoneVariants;
+}
+
+const char* toString(Variant variant) {
+ switch (variant) {
+ case Variant_none:
+ return "";
+ case Variant_armeabi:
+ return "armeabi";
+ case Variant_armeabi_v7a:
+ return "armeabi-v7a";
+ case Variant_arm64_v8a:
+ return "arm64-v8a";
+ case Variant_x86:
+ return "x86";
+ case Variant_x86_64:
+ return "x86_64";
+ case Variant_mips:
+ return "mips";
+ case Variant_mips64:
+ return "mips64";
+ }
+ return "";
+}
+
+} // namespace abi
+} // namespace split
diff --git a/tools/split-select/Abi.h b/tools/split-select/Abi.h
new file mode 100644
index 0000000..85b4d62
--- /dev/null
+++ b/tools/split-select/Abi.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef H_ANDROID_SPLIT_ABI
+#define H_ANDROID_SPLIT_ABI
+
+#include <utils/Vector.h>
+
+namespace split {
+namespace abi {
+
+enum Variant {
+ Variant_none = 0,
+ Variant_armeabi,
+ Variant_armeabi_v7a,
+ Variant_arm64_v8a,
+ Variant_x86,
+ Variant_x86_64,
+ Variant_mips,
+ Variant_mips64,
+};
+
+enum Family {
+ Family_none,
+ Family_arm,
+ Family_intel,
+ Family_mips,
+};
+
+Family getFamily(Variant variant);
+const android::Vector<Variant>& getVariants(Family family);
+const char* toString(Variant variant);
+
+} // namespace abi
+} // namespace split
+
+#endif // H_ANDROID_SPLIT_ABI
diff --git a/tools/split-select/Android.mk b/tools/split-select/Android.mk
new file mode 100644
index 0000000..968d22b
--- /dev/null
+++ b/tools/split-select/Android.mk
@@ -0,0 +1,115 @@
+#
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# This tool is prebuilt if we're doing an app-only build.
+ifeq ($(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)),)
+
+# ==========================================================
+# Setup some common variables for the different build
+# targets here.
+# ==========================================================
+LOCAL_PATH:= $(call my-dir)
+
+main := Main.cpp
+sources := \
+ Abi.cpp \
+ Grouper.cpp \
+ Rule.cpp \
+ RuleGenerator.cpp \
+ SplitDescription.cpp
+
+testSources := \
+ Grouper_test.cpp \
+ Rule_test.cpp \
+ RuleGenerator_test.cpp \
+ TestRules.cpp
+
+cIncludes := \
+ external/zlib \
+ frameworks/base/tools
+
+hostLdLibs :=
+hostStaticLibs := \
+ libaapt \
+ libandroidfw \
+ libpng \
+ liblog \
+ libutils \
+ libcutils \
+ libexpat \
+ libziparchive-host
+
+cFlags := -Wall -Werror
+
+ifeq ($(HOST_OS),linux)
+ hostLdLibs += -lrt -ldl -lpthread
+endif
+
+# Statically link libz for MinGW (Win SDK under Linux),
+# and dynamically link for all others.
+ifneq ($(strip $(USE_MINGW)),)
+ hostStaticLibs += libz
+else
+ hostLdLibs += -lz
+endif
+
+
+# ==========================================================
+# Build the host static library: libsplit-select
+# ==========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libsplit-select
+
+LOCAL_SRC_FILES := $(sources)
+
+LOCAL_C_INCLUDES += $(cIncludes)
+LOCAL_CFLAGS += $(cFlags) -D_DARWIN_UNLIMITED_STREAMS
+
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+
+# ==========================================================
+# Build the host tests: libsplit-select_tests
+# ==========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := libsplit-select_tests
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(testSources)
+
+LOCAL_C_INCLUDES += $(cIncludes)
+LOCAL_STATIC_LIBRARIES += libsplit-select $(hostStaticLibs)
+LOCAL_LDLIBS += $(hostLdLibs)
+LOCAL_CFLAGS += $(cFlags)
+
+include $(BUILD_HOST_NATIVE_TEST)
+
+# ==========================================================
+# Build the host executable: split-select
+# ==========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := split-select
+
+LOCAL_SRC_FILES := $(main)
+
+LOCAL_C_INCLUDES += $(cIncludes)
+LOCAL_STATIC_LIBRARIES += libsplit-select $(hostStaticLibs)
+LOCAL_LDLIBS += $(hostLdLibs)
+LOCAL_CFLAGS += $(cFlags)
+
+include $(BUILD_HOST_EXECUTABLE)
+
+endif # No TARGET_BUILD_APPS or TARGET_BUILD_PDK
diff --git a/tools/split-select/Grouper.cpp b/tools/split-select/Grouper.cpp
new file mode 100644
index 0000000..22685cd
--- /dev/null
+++ b/tools/split-select/Grouper.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Grouper.h"
+
+#include "aapt/AaptUtil.h"
+#include "SplitDescription.h"
+
+#include <utils/KeyedVector.h>
+#include <utils/Vector.h>
+
+using namespace android;
+using AaptUtil::appendValue;
+
+namespace split {
+
+Vector<SortedVector<SplitDescription> >
+groupByMutualExclusivity(const Vector<SplitDescription>& splits) {
+ Vector<SortedVector<SplitDescription> > groups;
+
+ // Find mutually exclusive splits and group them.
+ KeyedVector<SplitDescription, SortedVector<SplitDescription> > densityGroups;
+ KeyedVector<SplitDescription, SortedVector<SplitDescription> > abiGroups;
+ KeyedVector<SplitDescription, SortedVector<SplitDescription> > localeGroups;
+ const size_t splitCount = splits.size();
+ for (size_t i = 0; i < splitCount; i++) {
+ const SplitDescription& split = splits[i];
+ if (split.config.density != 0) {
+ SplitDescription key(split);
+ key.config.density = 0;
+ key.config.sdkVersion = 0; // Ignore density so we can support anydpi.
+ appendValue(densityGroups, key, split);
+ } else if (split.abi != abi::Variant_none) {
+ SplitDescription key(split);
+ key.abi = abi::Variant_none;
+ appendValue(abiGroups, key, split);
+ } else if (split.config.locale != 0) {
+ SplitDescription key(split);
+ key.config.clearLocale();
+ appendValue(localeGroups, key, split);
+ } else {
+ groups.add();
+ groups.editTop().add(split);
+ }
+ }
+
+ const size_t densityCount = densityGroups.size();
+ for (size_t i = 0; i < densityCount; i++) {
+ groups.add(densityGroups[i]);
+ }
+
+ const size_t abiCount = abiGroups.size();
+ for (size_t i = 0; i < abiCount; i++) {
+ groups.add(abiGroups[i]);
+ }
+
+ const size_t localeCount = localeGroups.size();
+ for (size_t i = 0; i < localeCount; i++) {
+ groups.add(localeGroups[i]);
+ }
+ return groups;
+}
+
+} // namespace split
diff --git a/tools/split-select/Grouper.h b/tools/split-select/Grouper.h
new file mode 100644
index 0000000..5cb0b5b
--- /dev/null
+++ b/tools/split-select/Grouper.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef H_ANDROID_SPLIT_GROUPER
+#define H_ANDROID_SPLIT_GROUPER
+
+#include "SplitDescription.h"
+
+#include <utils/SortedVector.h>
+#include <utils/Vector.h>
+
+namespace split {
+
+android::Vector<android::SortedVector<SplitDescription> >
+groupByMutualExclusivity(const android::Vector<SplitDescription>& splits);
+
+} // namespace split
+
+#endif // H_ANDROID_SPLIT_GROUPER
diff --git a/tools/split-select/Grouper_test.cpp b/tools/split-select/Grouper_test.cpp
new file mode 100644
index 0000000..a5f9c5a
--- /dev/null
+++ b/tools/split-select/Grouper_test.cpp
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Grouper.h"
+
+#include "SplitDescription.h"
+
+#include <gtest/gtest.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+using namespace android;
+
+namespace split {
+
+class GrouperTest : public ::testing::Test {
+protected:
+ virtual void SetUp() {
+ Vector<SplitDescription> splits;
+ addSplit(splits, "en-rUS-sw600dp-hdpi");
+ addSplit(splits, "fr-rFR-sw600dp-hdpi");
+ addSplit(splits, "fr-rFR-sw600dp-xhdpi");
+ addSplit(splits, ":armeabi");
+ addSplit(splits, "en-rUS-sw300dp-xhdpi");
+ addSplit(splits, "large");
+ addSplit(splits, "pl-rPL");
+ addSplit(splits, "xlarge");
+ addSplit(splits, "en-rUS-sw600dp-xhdpi");
+ addSplit(splits, "en-rUS-sw300dp-hdpi");
+ addSplit(splits, "xxhdpi");
+ addSplit(splits, "hdpi");
+ addSplit(splits, "de-rDE");
+ addSplit(splits, "xhdpi");
+ addSplit(splits, ":x86");
+ addSplit(splits, "anydpi");
+ addSplit(splits, "v7");
+ addSplit(splits, "v8");
+ addSplit(splits, "sw600dp");
+ addSplit(splits, "sw300dp");
+ mGroups = groupByMutualExclusivity(splits);
+ }
+
+ void addSplit(Vector<SplitDescription>& splits, const char* str);
+ void expectHasGroupWithSplits(const char* a);
+ void expectHasGroupWithSplits(const char* a, const char* b);
+ void expectHasGroupWithSplits(const char* a, const char* b, const char* c);
+ void expectHasGroupWithSplits(const char* a, const char* b, const char* c, const char* d);
+ void expectHasGroupWithSplits(const Vector<const char*>& expectedStrs);
+
+ Vector<SortedVector<SplitDescription> > mGroups;
+};
+
+TEST_F(GrouperTest, shouldHaveCorrectNumberOfGroups) {
+ EXPECT_EQ(12u, mGroups.size());
+}
+
+TEST_F(GrouperTest, shouldGroupDensities) {
+ expectHasGroupWithSplits("en-rUS-sw300dp-hdpi", "en-rUS-sw300dp-xhdpi");
+ expectHasGroupWithSplits("en-rUS-sw600dp-hdpi", "en-rUS-sw600dp-xhdpi");
+ expectHasGroupWithSplits("fr-rFR-sw600dp-hdpi", "fr-rFR-sw600dp-xhdpi");
+ expectHasGroupWithSplits("hdpi", "xhdpi", "xxhdpi", "anydpi");
+}
+
+TEST_F(GrouperTest, shouldGroupAbi) {
+ expectHasGroupWithSplits(":armeabi", ":x86");
+}
+
+TEST_F(GrouperTest, shouldGroupLocale) {
+ expectHasGroupWithSplits("pl-rPL", "de-rDE");
+}
+
+TEST_F(GrouperTest, shouldGroupEachSplitIntoItsOwnGroup) {
+ expectHasGroupWithSplits("large");
+ expectHasGroupWithSplits("xlarge");
+ expectHasGroupWithSplits("v7");
+ expectHasGroupWithSplits("v8");
+ expectHasGroupWithSplits("sw600dp");
+ expectHasGroupWithSplits("sw300dp");
+}
+
+//
+// Helper methods
+//
+
+void GrouperTest::expectHasGroupWithSplits(const char* a) {
+ Vector<const char*> expected;
+ expected.add(a);
+ expectHasGroupWithSplits(expected);
+}
+
+void GrouperTest::expectHasGroupWithSplits(const char* a, const char* b) {
+ Vector<const char*> expected;
+ expected.add(a);
+ expected.add(b);
+ expectHasGroupWithSplits(expected);
+}
+
+void GrouperTest::expectHasGroupWithSplits(const char* a, const char* b, const char* c) {
+ Vector<const char*> expected;
+ expected.add(a);
+ expected.add(b);
+ expected.add(c);
+ expectHasGroupWithSplits(expected);
+}
+
+void GrouperTest::expectHasGroupWithSplits(const char* a, const char* b, const char* c, const char* d) {
+ Vector<const char*> expected;
+ expected.add(a);
+ expected.add(b);
+ expected.add(c);
+ expected.add(d);
+ expectHasGroupWithSplits(expected);
+}
+
+void GrouperTest::expectHasGroupWithSplits(const Vector<const char*>& expectedStrs) {
+ Vector<SplitDescription> splits;
+ const size_t expectedStrCount = expectedStrs.size();
+ for (size_t i = 0; i < expectedStrCount; i++) {
+ splits.add();
+ if (!SplitDescription::parse(String8(expectedStrs[i]), &splits.editTop())) {
+ ADD_FAILURE() << "Failed to parse SplitDescription " << expectedStrs[i];
+ return;
+ }
+ }
+ const size_t splitCount = splits.size();
+
+ const size_t groupCount = mGroups.size();
+ for (size_t i = 0; i < groupCount; i++) {
+ const SortedVector<SplitDescription>& group = mGroups[i];
+ if (group.size() != splitCount) {
+ continue;
+ }
+
+ size_t found = 0;
+ for (size_t j = 0; j < splitCount; j++) {
+ if (group.indexOf(splits[j]) >= 0) {
+ found++;
+ }
+ }
+
+ if (found == splitCount) {
+ return;
+ }
+ }
+
+ String8 errorMessage("Failed to find expected group [");
+ for (size_t i = 0; i < splitCount; i++) {
+ if (i != 0) {
+ errorMessage.append(", ");
+ }
+ errorMessage.append(splits[i].toString());
+ }
+ errorMessage.append("].\nActual:\n");
+
+ for (size_t i = 0; i < groupCount; i++) {
+ errorMessage.appendFormat("Group %d:\n", int(i + 1));
+ const SortedVector<SplitDescription>& group = mGroups[i];
+ for (size_t j = 0; j < group.size(); j++) {
+ errorMessage.append(" ");
+ errorMessage.append(group[j].toString());
+ errorMessage.append("\n");
+ }
+ }
+ ADD_FAILURE() << errorMessage.string();
+}
+
+void GrouperTest::addSplit(Vector<SplitDescription>& splits, const char* str) {
+ splits.add();
+ EXPECT_TRUE(SplitDescription::parse(String8(str), &splits.editTop()));
+}
+
+} // namespace split
diff --git a/tools/split-select/Main.cpp b/tools/split-select/Main.cpp
new file mode 100644
index 0000000..434494e
--- /dev/null
+++ b/tools/split-select/Main.cpp
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <algorithm>
+#include <cstdio>
+
+#include "aapt/AaptUtil.h"
+
+#include "Grouper.h"
+#include "Rule.h"
+#include "RuleGenerator.h"
+#include "SplitDescription.h"
+
+#include <androidfw/AssetManager.h>
+#include <androidfw/ResourceTypes.h>
+#include <utils/KeyedVector.h>
+#include <utils/Vector.h>
+
+using namespace android;
+
+namespace split {
+
+static void usage() {
+ fprintf(stderr,
+ "split-select --help\n"
+ "split-select --target <config> --split <path/to/apk> [--split <path/to/apk> [...]]\n"
+ "split-select --generate --split <path/to/apk> [--split <path/to/apk> [...]]\n"
+ "\n"
+ " --help Displays more information about this program.\n"
+ " --target <config> Performs the Split APK selection on the given configuration.\n"
+ " --generate Generates the logic for selecting the Split APK, in JSON format.\n"
+ " --split <path/to/apk> Includes a Split APK in the selection process.\n"
+ "\n"
+ " Where <config> is an extended AAPT resource qualifier of the form\n"
+ " 'resource-qualifiers:extended-qualifiers', where 'resource-qualifiers' is an AAPT resource\n"
+ " qualifier (ex: en-rUS-sw600dp-xhdpi), and 'extended-qualifiers' is an ordered list of one\n"
+ " qualifier (or none) from each category:\n"
+ " Architecture: armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, mips\n");
+}
+
+static void help() {
+ usage();
+ fprintf(stderr, "\n"
+ " Generates the logic for selecting a Split APK given some target Android device configuration.\n"
+ " Using the flag --generate will emit a JSON encoded tree of rules that must be satisfied in order\n"
+ " to install the given Split APK. Using the flag --target along with the device configuration\n"
+ " will emit the set of Split APKs to install, following the same logic that would have been emitted\n"
+ " via JSON.\n");
+}
+
+class SplitSelector {
+public:
+ SplitSelector();
+ SplitSelector(const Vector<SplitDescription>& splits);
+
+ Vector<SplitDescription> getBestSplits(const SplitDescription& target) const;
+
+ template <typename RuleGenerator>
+ KeyedVector<SplitDescription, sp<Rule> > getRules() const;
+
+private:
+ Vector<SortedVector<SplitDescription> > mGroups;
+};
+
+SplitSelector::SplitSelector() {
+}
+
+SplitSelector::SplitSelector(const Vector<SplitDescription>& splits)
+ : mGroups(groupByMutualExclusivity(splits)) {
+}
+
+static void selectBestFromGroup(const SortedVector<SplitDescription>& splits,
+ const SplitDescription& target, Vector<SplitDescription>& splitsOut) {
+ SplitDescription bestSplit;
+ bool isSet = false;
+ const size_t splitCount = splits.size();
+ for (size_t j = 0; j < splitCount; j++) {
+ const SplitDescription& thisSplit = splits[j];
+ if (!thisSplit.match(target)) {
+ continue;
+ }
+
+ if (!isSet || thisSplit.isBetterThan(bestSplit, target)) {
+ isSet = true;
+ bestSplit = thisSplit;
+ }
+ }
+
+ if (isSet) {
+ splitsOut.add(bestSplit);
+ }
+}
+
+Vector<SplitDescription> SplitSelector::getBestSplits(const SplitDescription& target) const {
+ Vector<SplitDescription> bestSplits;
+ const size_t groupCount = mGroups.size();
+ for (size_t i = 0; i < groupCount; i++) {
+ selectBestFromGroup(mGroups[i], target, bestSplits);
+ }
+ return bestSplits;
+}
+
+template <typename RuleGenerator>
+KeyedVector<SplitDescription, sp<Rule> > SplitSelector::getRules() const {
+ KeyedVector<SplitDescription, sp<Rule> > rules;
+
+ const size_t groupCount = mGroups.size();
+ for (size_t i = 0; i < groupCount; i++) {
+ const SortedVector<SplitDescription>& splits = mGroups[i];
+ const size_t splitCount = splits.size();
+ for (size_t j = 0; j < splitCount; j++) {
+ sp<Rule> rule = Rule::simplify(RuleGenerator::generate(splits, j));
+ if (rule != NULL) {
+ rules.add(splits[j], rule);
+ }
+ }
+ }
+ return rules;
+}
+
+Vector<SplitDescription> select(const SplitDescription& target, const Vector<SplitDescription>& splits) {
+ const SplitSelector selector(splits);
+ return selector.getBestSplits(target);
+}
+
+void generate(const KeyedVector<String8, Vector<SplitDescription> >& splits) {
+ Vector<SplitDescription> allSplits;
+ const size_t apkSplitCount = splits.size();
+ for (size_t i = 0; i < apkSplitCount; i++) {
+ allSplits.appendVector(splits[i]);
+ }
+ const SplitSelector selector(allSplits);
+ KeyedVector<SplitDescription, sp<Rule> > rules(selector.getRules<RuleGenerator>());
+
+ fprintf(stdout, "[\n");
+ for (size_t i = 0; i < apkSplitCount; i++) {
+ sp<Rule> masterRule = new Rule();
+ masterRule->op = Rule::OR_SUBRULES;
+ const Vector<SplitDescription>& splitDescriptions = splits[i];
+ const size_t splitDescriptionCount = splitDescriptions.size();
+ for (size_t j = 0; j < splitDescriptionCount; j++) {
+ masterRule->subrules.add(rules.valueFor(splitDescriptions[j]));
+ }
+ masterRule = Rule::simplify(masterRule);
+ fprintf(stdout, " {\n \"path\": \"%s\",\n \"rules\": %s\n }%s\n",
+ splits.keyAt(i).string(),
+ masterRule->toJson(2).string(),
+ i < apkSplitCount - 1 ? "," : "");
+ }
+ fprintf(stdout, "]\n");
+}
+
+static void removeRuntimeQualifiers(ConfigDescription* outConfig) {
+ outConfig->imsi = 0;
+ outConfig->orientation = ResTable_config::ORIENTATION_ANY;
+ outConfig->screenWidth = ResTable_config::SCREENWIDTH_ANY;
+ outConfig->screenHeight = ResTable_config::SCREENHEIGHT_ANY;
+ outConfig->uiMode &= ResTable_config::UI_MODE_NIGHT_ANY;
+}
+
+static Vector<SplitDescription> extractSplitDescriptionsFromApk(const String8& path) {
+ AssetManager assetManager;
+ Vector<SplitDescription> splits;
+ int32_t cookie = 0;
+ if (!assetManager.addAssetPath(path, &cookie)) {
+ return splits;
+ }
+
+ const ResTable& res = assetManager.getResources(false);
+ if (res.getError() == NO_ERROR) {
+ Vector<ResTable_config> configs;
+ res.getConfigurations(&configs);
+ const size_t configCount = configs.size();
+ for (size_t i = 0; i < configCount; i++) {
+ splits.add();
+ splits.editTop().config = configs[i];
+ }
+ }
+
+ AssetDir* dir = assetManager.openNonAssetDir(cookie, "lib");
+ if (dir != NULL) {
+ const size_t fileCount = dir->getFileCount();
+ for (size_t i = 0; i < fileCount; i++) {
+ splits.add();
+ Vector<String8> parts = AaptUtil::splitAndLowerCase(dir->getFileName(i), '-');
+ if (parseAbi(parts, 0, &splits.editTop()) < 0) {
+ fprintf(stderr, "Malformed library %s\n", dir->getFileName(i).string());
+ splits.pop();
+ }
+ }
+ delete dir;
+ }
+ return splits;
+}
+
+static int main(int argc, char** argv) {
+ // Skip over the first argument.
+ argc--;
+ argv++;
+
+ bool generateFlag = false;
+ String8 targetConfigStr;
+ Vector<String8> splitApkPaths;
+ while (argc > 0) {
+ const String8 arg(*argv);
+ if (arg == "--target") {
+ argc--;
+ argv++;
+ if (argc < 1) {
+ fprintf(stderr, "Missing parameter for --split.\n");
+ usage();
+ return 1;
+ }
+ targetConfigStr.setTo(*argv);
+ } else if (arg == "--split") {
+ argc--;
+ argv++;
+ if (argc < 1) {
+ fprintf(stderr, "Missing parameter for --split.\n");
+ usage();
+ return 1;
+ }
+ splitApkPaths.add(String8(*argv));
+ } else if (arg == "--generate") {
+ generateFlag = true;
+ } else if (arg == "--help") {
+ help();
+ return 0;
+ } else {
+ fprintf(stderr, "Unknown argument '%s'\n", arg.string());
+ usage();
+ return 1;
+ }
+ argc--;
+ argv++;
+ }
+
+ if (!generateFlag && targetConfigStr == "") {
+ usage();
+ return 1;
+ }
+
+ if (splitApkPaths.size() == 0) {
+ usage();
+ return 1;
+ }
+
+ SplitDescription targetSplit;
+ if (!generateFlag) {
+ if (!SplitDescription::parse(targetConfigStr, &targetSplit)) {
+ fprintf(stderr, "Invalid --target config: '%s'\n",
+ targetConfigStr.string());
+ usage();
+ return 1;
+ }
+
+ // We don't want to match on things that will change at run-time
+ // (orientation, w/h, etc.).
+ removeRuntimeQualifiers(&targetSplit.config);
+ }
+
+ KeyedVector<String8, Vector<SplitDescription> > apkPathSplitMap;
+ KeyedVector<SplitDescription, String8> splitApkPathMap;
+ Vector<SplitDescription> splitConfigs;
+ const size_t splitCount = splitApkPaths.size();
+ for (size_t i = 0; i < splitCount; i++) {
+ Vector<SplitDescription> splits = extractSplitDescriptionsFromApk(splitApkPaths[i]);
+ if (splits.isEmpty()) {
+ fprintf(stderr, "Invalid --split path: '%s'. No splits found.\n",
+ splitApkPaths[i].string());
+ usage();
+ return 1;
+ }
+ apkPathSplitMap.replaceValueFor(splitApkPaths[i], splits);
+ const size_t apkSplitDescriptionCount = splits.size();
+ for (size_t j = 0; j < apkSplitDescriptionCount; j++) {
+ splitApkPathMap.replaceValueFor(splits[j], splitApkPaths[i]);
+ }
+ splitConfigs.appendVector(splits);
+ }
+
+ if (!generateFlag) {
+ Vector<SplitDescription> matchingConfigs = select(targetSplit, splitConfigs);
+ const size_t matchingConfigCount = matchingConfigs.size();
+ SortedVector<String8> matchingSplitPaths;
+ for (size_t i = 0; i < matchingConfigCount; i++) {
+ matchingSplitPaths.add(splitApkPathMap.valueFor(matchingConfigs[i]));
+ }
+
+ const size_t matchingSplitApkPathCount = matchingSplitPaths.size();
+ for (size_t i = 0; i < matchingSplitApkPathCount; i++) {
+ fprintf(stderr, "%s\n", matchingSplitPaths[i].string());
+ }
+ } else {
+ generate(apkPathSplitMap);
+ }
+ return 0;
+}
+
+} // namespace split
+
+int main(int argc, char** argv) {
+ return split::main(argc, argv);
+}
diff --git a/tools/split-select/Rule.cpp b/tools/split-select/Rule.cpp
new file mode 100644
index 0000000..48d21ff
--- /dev/null
+++ b/tools/split-select/Rule.cpp
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Rule.h"
+
+#include <utils/String8.h>
+
+using namespace android;
+
+namespace split {
+
+inline static void indentStr(String8& str, int indent) {
+ while (indent > 0) {
+ str.append(" ");
+ indent--;
+ }
+}
+
+Rule::Rule(const Rule& rhs)
+ : RefBase()
+ , op(rhs.op)
+ , key(rhs.key)
+ , negate(rhs.negate)
+ , stringArgs(rhs.stringArgs)
+ , longArgs(rhs.longArgs)
+ , subrules(rhs.subrules) {
+}
+
+String8 Rule::toJson(int indent) const {
+ String8 str;
+ indentStr(str, indent);
+ str.append("{\n");
+ indent++;
+ indentStr(str, indent);
+ str.append("\"op\": \"");
+ switch (op) {
+ case ALWAYS_TRUE:
+ str.append("ALWAYS_TRUE");
+ break;
+ case GREATER_THAN:
+ str.append("GREATER_THAN");
+ break;
+ case LESS_THAN:
+ str.append("LESS_THAN");
+ break;
+ case EQUALS:
+ str.append("EQUALS");
+ break;
+ case AND_SUBRULES:
+ str.append("AND_SUBRULES");
+ break;
+ case OR_SUBRULES:
+ str.append("OR_SUBRULES");
+ break;
+ case CONTAINS_ANY:
+ str.append("CONTAINS_ANY");
+ break;
+ default:
+ str.appendFormat("%d", op);
+ break;
+ }
+ str.append("\"");
+
+ if (negate) {
+ str.append(",\n");
+ indentStr(str, indent);
+ str.append("\"negate\": true");
+ }
+
+ bool includeKey = true;
+ switch (op) {
+ case AND_SUBRULES:
+ case OR_SUBRULES:
+ includeKey = false;
+ break;
+ default:
+ break;
+ }
+
+ if (includeKey) {
+ str.append(",\n");
+ indentStr(str, indent);
+ str.append("\"property\": \"");
+ switch (key) {
+ case NONE:
+ str.append("NONE");
+ break;
+ case SDK_VERSION:
+ str.append("SDK_VERSION");
+ break;
+ case SCREEN_DENSITY:
+ str.append("SCREEN_DENSITY");
+ break;
+ case NATIVE_PLATFORM:
+ str.append("NATIVE_PLATFORM");
+ break;
+ case LANGUAGE:
+ str.append("LANGUAGE");
+ break;
+ default:
+ str.appendFormat("%d", key);
+ break;
+ }
+ str.append("\"");
+ }
+
+ if (op == AND_SUBRULES || op == OR_SUBRULES) {
+ str.append(",\n");
+ indentStr(str, indent);
+ str.append("\"subrules\": [\n");
+ const size_t subruleCount = subrules.size();
+ for (size_t i = 0; i < subruleCount; i++) {
+ str.append(subrules[i]->toJson(indent + 1));
+ if (i != subruleCount - 1) {
+ str.append(",");
+ }
+ str.append("\n");
+ }
+ indentStr(str, indent);
+ str.append("]");
+ } else {
+ switch (key) {
+ case SDK_VERSION:
+ case SCREEN_DENSITY: {
+ str.append(",\n");
+ indentStr(str, indent);
+ str.append("\"args\": [");
+ const size_t argCount = longArgs.size();
+ for (size_t i = 0; i < argCount; i++) {
+ if (i != 0) {
+ str.append(", ");
+ }
+ str.appendFormat("%d", longArgs[i]);
+ }
+ str.append("]");
+ break;
+ }
+ case LANGUAGE:
+ case NATIVE_PLATFORM: {
+ str.append(",\n");
+ indentStr(str, indent);
+ str.append("\"args\": [");
+ const size_t argCount = stringArgs.size();
+ for (size_t i = 0; i < argCount; i++) {
+ if (i != 0) {
+ str.append(", ");
+ }
+ str.append(stringArgs[i]);
+ }
+ str.append("]");
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ str.append("\n");
+ indent--;
+ indentStr(str, indent);
+ str.append("}");
+ return str;
+}
+
+sp<Rule> Rule::simplify(sp<Rule> rule) {
+ if (rule->op != AND_SUBRULES && rule->op != OR_SUBRULES) {
+ return rule;
+ }
+
+ Vector<sp<Rule> > newSubrules;
+ newSubrules.setCapacity(rule->subrules.size());
+ const size_t subruleCount = rule->subrules.size();
+ for (size_t i = 0; i < subruleCount; i++) {
+ sp<Rule> simplifiedRule = simplify(rule->subrules.editItemAt(i));
+ if (simplifiedRule != NULL) {
+ if (simplifiedRule->op == rule->op) {
+ newSubrules.appendVector(simplifiedRule->subrules);
+ } else {
+ newSubrules.add(simplifiedRule);
+ }
+ }
+ }
+
+ const size_t newSubruleCount = newSubrules.size();
+ if (newSubruleCount == 0) {
+ return NULL;
+ } else if (subruleCount == 1) {
+ return newSubrules.editTop();
+ }
+ rule->subrules = newSubrules;
+ return rule;
+}
+
+} // namespace split
diff --git a/tools/split-select/Rule.h b/tools/split-select/Rule.h
new file mode 100644
index 0000000..08a2075
--- /dev/null
+++ b/tools/split-select/Rule.h
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef H_ANDROID_SPLIT_RULE
+#define H_ANDROID_SPLIT_RULE
+
+#include "SplitDescription.h"
+
+#include <utils/RefBase.h>
+#include <utils/StrongPointer.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+namespace split {
+
+struct Rule : public virtual android::RefBase {
+ inline Rule();
+ Rule(const Rule& rhs);
+
+ enum Operator {
+ LESS_THAN = 1,
+ GREATER_THAN,
+ EQUALS,
+ CONTAINS_ANY,
+ CONTAINS_ALL,
+ IS_TRUE,
+ IS_FALSE,
+ AND_SUBRULES,
+ OR_SUBRULES,
+ ALWAYS_TRUE,
+ };
+
+ Operator op;
+
+ enum Key {
+ NONE = 0,
+ SDK_VERSION,
+ SCREEN_DENSITY,
+ LANGUAGE,
+ NATIVE_PLATFORM,
+ TOUCH_SCREEN,
+ SCREEN_SIZE,
+ SCREEN_LAYOUT,
+ };
+
+ Key key;
+ bool negate;
+
+ android::Vector<android::String8> stringArgs;
+ android::Vector<int> longArgs;
+ android::Vector<double> doubleArgs;
+ android::Vector<android::sp<Rule> > subrules;
+
+ android::String8 toJson(int indent=0) const;
+
+ static android::sp<Rule> simplify(android::sp<Rule> rule);
+};
+
+Rule::Rule()
+: op(ALWAYS_TRUE)
+, key(NONE)
+, negate(false) {}
+
+} // namespace split
+
+#endif // H_ANDROID_SPLIT_RULE
diff --git a/tools/split-select/RuleGenerator.cpp b/tools/split-select/RuleGenerator.cpp
new file mode 100644
index 0000000..83c9795
--- /dev/null
+++ b/tools/split-select/RuleGenerator.cpp
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RuleGenerator.h"
+#include "aapt/SdkConstants.h"
+
+#include <algorithm>
+#include <cmath>
+#include <vector>
+#include <androidfw/ResourceTypes.h>
+
+using namespace android;
+
+namespace split {
+
+// Calculate the point at which the density selection changes between l and h.
+static inline int findMid(int l, int h) {
+ double root = sqrt((h*h) + (8*l*h));
+ return (double(-h) + root) / 2.0;
+}
+
+sp<Rule> RuleGenerator::generateDensity(const Vector<int>& allDensities, size_t index) {
+ if (allDensities[index] != ResTable_config::DENSITY_ANY) {
+ sp<Rule> densityRule = new Rule();
+ densityRule->op = Rule::AND_SUBRULES;
+
+ const bool hasAnyDensity = std::find(allDensities.begin(),
+ allDensities.end(), (int) ResTable_config::DENSITY_ANY) != allDensities.end();
+
+ if (hasAnyDensity) {
+ sp<Rule> version = new Rule();
+ version->op = Rule::LESS_THAN;
+ version->key = Rule::SDK_VERSION;
+ version->longArgs.add((long) SDK_LOLLIPOP);
+ densityRule->subrules.add(version);
+ }
+
+ if (index > 0) {
+ sp<Rule> gt = new Rule();
+ gt->op = Rule::GREATER_THAN;
+ gt->key = Rule::SCREEN_DENSITY;
+ gt->longArgs.add(findMid(allDensities[index - 1], allDensities[index]) - 1);
+ densityRule->subrules.add(gt);
+ }
+
+ if (index + 1 < allDensities.size() && allDensities[index + 1] != ResTable_config::DENSITY_ANY) {
+ sp<Rule> lt = new Rule();
+ lt->op = Rule::LESS_THAN;
+ lt->key = Rule::SCREEN_DENSITY;
+ lt->longArgs.add(findMid(allDensities[index], allDensities[index + 1]));
+ densityRule->subrules.add(lt);
+ }
+ return densityRule;
+ } else {
+ // SDK_VERSION is handled elsewhere, so we always pick DENSITY_ANY if it's
+ // available.
+ sp<Rule> always = new Rule();
+ always->op = Rule::ALWAYS_TRUE;
+ return always;
+ }
+}
+
+sp<Rule> RuleGenerator::generateAbi(const Vector<abi::Variant>& splitAbis, size_t index) {
+ const abi::Variant thisAbi = splitAbis[index];
+ const Vector<abi::Variant>& familyVariants = abi::getVariants(abi::getFamily(thisAbi));
+
+ Vector<abi::Variant>::const_iterator start =
+ std::find(familyVariants.begin(), familyVariants.end(), thisAbi);
+
+ Vector<abi::Variant>::const_iterator end = familyVariants.end();
+ if (index + 1 < splitAbis.size()) {
+ end = std::find(start, familyVariants.end(), splitAbis[index + 1]);
+ }
+
+ sp<Rule> abiRule = new Rule();
+ abiRule->op = Rule::CONTAINS_ANY;
+ abiRule->key = Rule::NATIVE_PLATFORM;
+ while (start != end) {
+ abiRule->stringArgs.add(String8(abi::toString(*start)));
+ ++start;
+ }
+ return abiRule;
+}
+
+sp<Rule> RuleGenerator::generate(const SortedVector<SplitDescription>& group, size_t index) {
+ sp<Rule> rootRule = new Rule();
+ rootRule->op = Rule::AND_SUBRULES;
+
+ if (group[index].config.locale != 0) {
+ sp<Rule> locale = new Rule();
+ locale->op = Rule::EQUALS;
+ locale->key = Rule::LANGUAGE;
+ char str[RESTABLE_MAX_LOCALE_LEN];
+ group[index].config.getBcp47Locale(str);
+ locale->stringArgs.add(String8(str));
+ rootRule->subrules.add(locale);
+ }
+
+ if (group[index].config.sdkVersion != 0) {
+ sp<Rule> sdk = new Rule();
+ sdk->op = Rule::GREATER_THAN;
+ sdk->key = Rule::SDK_VERSION;
+ sdk->longArgs.add(group[index].config.sdkVersion - 1);
+ rootRule->subrules.add(sdk);
+ }
+
+ if (group[index].config.density != 0) {
+ size_t densityIndex = 0;
+ Vector<int> allDensities;
+ allDensities.add(group[index].config.density);
+
+ const size_t groupSize = group.size();
+ for (size_t i = 0; i < groupSize; i++) {
+ if (group[i].config.density != group[index].config.density) {
+ // This group differs by density.
+ allDensities.clear();
+ for (size_t j = 0; j < groupSize; j++) {
+ allDensities.add(group[j].config.density);
+ }
+ densityIndex = index;
+ break;
+ }
+ }
+ rootRule->subrules.add(generateDensity(allDensities, densityIndex));
+ }
+
+ if (group[index].abi != abi::Variant_none) {
+ size_t abiIndex = 0;
+ Vector<abi::Variant> allVariants;
+ allVariants.add(group[index].abi);
+
+ const size_t groupSize = group.size();
+ for (size_t i = 0; i < groupSize; i++) {
+ if (group[i].abi != group[index].abi) {
+ // This group differs by ABI.
+ allVariants.clear();
+ for (size_t j = 0; j < groupSize; j++) {
+ allVariants.add(group[j].abi);
+ }
+ abiIndex = index;
+ break;
+ }
+ }
+ rootRule->subrules.add(generateAbi(allVariants, abiIndex));
+ }
+
+ return rootRule;
+}
+
+} // namespace split
diff --git a/tools/split-select/RuleGenerator.h b/tools/split-select/RuleGenerator.h
new file mode 100644
index 0000000..619acd9
--- /dev/null
+++ b/tools/split-select/RuleGenerator.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef H_ANDROID_SPLIT_RULE_GENERATOR
+#define H_ANDROID_SPLIT_RULE_GENERATOR
+
+#include "Abi.h"
+#include "Rule.h"
+#include "SplitDescription.h"
+
+#include <utils/SortedVector.h>
+#include <utils/Vector.h>
+
+namespace split {
+
+struct RuleGenerator {
+ // Generate rules for a Split given the group of mutually exclusive splits it belongs to
+ static android::sp<Rule> generate(const android::SortedVector<SplitDescription>& group, size_t index);
+
+ static android::sp<Rule> generateAbi(const android::Vector<abi::Variant>& allVariants, size_t index);
+ static android::sp<Rule> generateDensity(const android::Vector<int>& allDensities, size_t index);
+};
+
+} // namespace split
+
+#endif // H_ANDROID_SPLIT_RULE_GENERATOR
diff --git a/tools/split-select/RuleGenerator_test.cpp b/tools/split-select/RuleGenerator_test.cpp
new file mode 100644
index 0000000..470cadc
--- /dev/null
+++ b/tools/split-select/RuleGenerator_test.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RuleGenerator.h"
+
+#include "aapt/SdkConstants.h"
+#include "TestRules.h"
+
+#include <gtest/gtest.h>
+#include <utils/Vector.h>
+
+using namespace android;
+using namespace split::test;
+
+namespace split {
+
+TEST(RuleGeneratorTest, testAbiRules) {
+ Vector<abi::Variant> abis;
+ const ssize_t armeabiIndex = abis.add(abi::Variant_armeabi);
+ const ssize_t armeabi_v7aIndex = abis.add(abi::Variant_armeabi_v7a);
+ const ssize_t x86Index = abis.add(abi::Variant_x86);
+
+ EXPECT_RULES_EQ(RuleGenerator::generateAbi(abis, armeabiIndex),
+ ContainsAnyRule(Rule::NATIVE_PLATFORM, "armeabi")
+ );
+
+ EXPECT_RULES_EQ(RuleGenerator::generateAbi(abis, armeabi_v7aIndex),
+ ContainsAnyRule(Rule::NATIVE_PLATFORM, "armeabi-v7a", "arm64-v8a")
+ );
+
+ EXPECT_RULES_EQ(RuleGenerator::generateAbi(abis, x86Index),
+ ContainsAnyRule(Rule::NATIVE_PLATFORM, "x86", "x86_64")
+ );
+}
+
+TEST(RuleGeneratorTest, densityConstantsAreSane) {
+ EXPECT_LT(263, (int) ConfigDescription::DENSITY_XHIGH);
+ EXPECT_GT(262, (int) ConfigDescription::DENSITY_HIGH);
+ EXPECT_LT(363, (int) ConfigDescription::DENSITY_XXHIGH);
+ EXPECT_GT(362, (int) ConfigDescription::DENSITY_XHIGH);
+}
+
+TEST(RuleGeneratorTest, testDensityRules) {
+ Vector<int> densities;
+ const ssize_t highIndex = densities.add(ConfigDescription::DENSITY_HIGH);
+ const ssize_t xhighIndex = densities.add(ConfigDescription::DENSITY_XHIGH);
+ const ssize_t xxhighIndex = densities.add(ConfigDescription::DENSITY_XXHIGH);
+
+ EXPECT_RULES_EQ(RuleGenerator::generateDensity(densities, highIndex),
+ AndRule()
+ .add(LtRule(Rule::SCREEN_DENSITY, 263))
+ );
+
+ EXPECT_RULES_EQ(RuleGenerator::generateDensity(densities, xhighIndex),
+ AndRule()
+ .add(GtRule(Rule::SCREEN_DENSITY, 262))
+ .add(LtRule(Rule::SCREEN_DENSITY, 363))
+ );
+
+ EXPECT_RULES_EQ(RuleGenerator::generateDensity(densities, xxhighIndex),
+ AndRule()
+ .add(GtRule(Rule::SCREEN_DENSITY, 362))
+ );
+}
+
+TEST(RuleGeneratorTest, testDensityRulesWithAnyDpi) {
+ Vector<int> densities;
+ const ssize_t highIndex = densities.add(ConfigDescription::DENSITY_HIGH);
+ const ssize_t xhighIndex = densities.add(ConfigDescription::DENSITY_XHIGH);
+ const ssize_t xxhighIndex = densities.add(ConfigDescription::DENSITY_XXHIGH);
+ const ssize_t anyIndex = densities.add(ConfigDescription::DENSITY_ANY);
+
+ EXPECT_RULES_EQ(RuleGenerator::generateDensity(densities, highIndex),
+ AndRule()
+ .add(LtRule(Rule::SDK_VERSION, SDK_LOLLIPOP))
+ .add(LtRule(Rule::SCREEN_DENSITY, 263))
+ );
+
+ EXPECT_RULES_EQ(RuleGenerator::generateDensity(densities, xhighIndex),
+ AndRule()
+ .add(LtRule(Rule::SDK_VERSION, SDK_LOLLIPOP))
+ .add(GtRule(Rule::SCREEN_DENSITY, 262))
+ .add(LtRule(Rule::SCREEN_DENSITY, 363))
+ );
+
+ EXPECT_RULES_EQ(RuleGenerator::generateDensity(densities, xxhighIndex),
+ AndRule()
+ .add(LtRule(Rule::SDK_VERSION, SDK_LOLLIPOP))
+ .add(GtRule(Rule::SCREEN_DENSITY, 362))
+ );
+
+ // We expect AlwaysTrue because anydpi always has attached v21 to the configuration
+ // and the rest of the rule generation code generates the sdk version checks.
+ EXPECT_RULES_EQ(RuleGenerator::generateDensity(densities, anyIndex), AlwaysTrue());
+}
+
+} // namespace split
diff --git a/tools/split-select/Rule_test.cpp b/tools/split-select/Rule_test.cpp
new file mode 100644
index 0000000..c6cff0d
--- /dev/null
+++ b/tools/split-select/Rule_test.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "Rule.h"
+
+#include "SplitDescription.h"
+#include "TestRules.h"
+
+#include <algorithm>
+#include <gtest/gtest.h>
+#include <string>
+#include <utils/String8.h>
+
+using namespace android;
+using namespace split::test;
+
+namespace split {
+
+TEST(RuleTest, generatesValidJson) {
+ Rule rule(AndRule()
+ .add(EqRule(Rule::SDK_VERSION, 7))
+ .add(OrRule()
+ .add(GtRule(Rule::SCREEN_DENSITY, 10))
+ .add(LtRule(Rule::SCREEN_DENSITY, 5))
+ )
+ );
+
+ // Expected
+ std::string expected(
+ "{"
+ " \"op\": \"AND_SUBRULES\","
+ " \"subrules\": ["
+ " {"
+ " \"op\": \"EQUALS\","
+ " \"property\": \"SDK_VERSION\","
+ " \"args\": [7]"
+ " },"
+ " {"
+ " \"op\": \"OR_SUBRULES\","
+ " \"subrules\": ["
+ " {"
+ " \"op\": \"GREATER_THAN\","
+ " \"property\": \"SCREEN_DENSITY\","
+ " \"args\": [10]"
+ " },"
+ " {"
+ " \"op\": \"LESS_THAN\","
+ " \"property\": \"SCREEN_DENSITY\","
+ " \"args\": [5]"
+ " }"
+ " ]"
+ " }"
+ " ]"
+ "}");
+ expected.erase(std::remove_if(expected.begin(), expected.end(), ::isspace), expected.end());
+
+ // Result
+ std::string result(rule.toJson().string());
+ result.erase(std::remove_if(result.begin(), result.end(), ::isspace), result.end());
+
+ ASSERT_EQ(expected, result);
+}
+
+TEST(RuleTest, simplifiesSingleSubruleRules) {
+ sp<Rule> rule = new Rule(AndRule()
+ .add(EqRule(Rule::SDK_VERSION, 7))
+ );
+
+ EXPECT_RULES_EQ(Rule::simplify(rule), EqRule(Rule::SDK_VERSION, 7));
+}
+
+TEST(RuleTest, simplifiesNestedSameOpSubrules) {
+ sp<Rule> rule = new Rule(AndRule()
+ .add(AndRule()
+ .add(EqRule(Rule::SDK_VERSION, 7))
+ )
+ .add(EqRule(Rule::SDK_VERSION, 8))
+ );
+
+ EXPECT_RULES_EQ(Rule::simplify(rule),
+ AndRule()
+ .add(EqRule(Rule::SDK_VERSION, 7))
+ .add(EqRule(Rule::SDK_VERSION, 8))
+ );
+}
+
+} // namespace split
diff --git a/tools/split-select/SplitDescription.cpp b/tools/split-select/SplitDescription.cpp
new file mode 100644
index 0000000..99bc23d
--- /dev/null
+++ b/tools/split-select/SplitDescription.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "SplitDescription.h"
+
+#include "aapt/AaptConfig.h"
+#include "aapt/AaptUtil.h"
+
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+using namespace android;
+
+namespace split {
+
+SplitDescription::SplitDescription()
+: abi(abi::Variant_none) {
+}
+
+int SplitDescription::compare(const SplitDescription& rhs) const {
+ int cmp;
+ cmp = (int)abi - (int)rhs.abi;
+ if (cmp != 0) return cmp;
+ return config.compareLogical(rhs.config);
+}
+
+bool SplitDescription::isBetterThan(const SplitDescription& o, const SplitDescription& target) const {
+ if (abi != abi::Variant_none || o.abi != abi::Variant_none) {
+ abi::Family family = abi::getFamily(abi);
+ abi::Family oFamily = abi::getFamily(o.abi);
+ if (family != oFamily) {
+ return family != abi::Family_none;
+ }
+
+ if (int(target.abi) - int(abi) < int(target.abi) - int(o.abi)) {
+ return true;
+ }
+ }
+ return config.isBetterThan(o.config, &target.config);
+}
+
+bool SplitDescription::match(const SplitDescription& o) const {
+ if (abi != abi::Variant_none) {
+ abi::Family family = abi::getFamily(abi);
+ abi::Family oFamily = abi::getFamily(o.abi);
+ if (family != oFamily) {
+ return false;
+ }
+
+ if (int(abi) > int(o.abi)) {
+ return false;
+ }
+ }
+ return config.match(o.config);
+}
+
+String8 SplitDescription::toString() const {
+ String8 extension;
+ if (abi != abi::Variant_none) {
+ if (extension.isEmpty()) {
+ extension.append(":");
+ } else {
+ extension.append("-");
+ }
+ extension.append(abi::toString(abi));
+ }
+ String8 str(config.toString());
+ str.append(extension);
+ return str;
+}
+
+ssize_t parseAbi(const Vector<String8>& parts, const ssize_t index,
+ SplitDescription* outSplit) {
+ const ssize_t N = parts.size();
+ abi::Variant abi = abi::Variant_none;
+ ssize_t endIndex = index;
+ if (parts[endIndex] == "arm64") {
+ endIndex++;
+ if (endIndex < N) {
+ if (parts[endIndex] == "v8a") {
+ endIndex++;
+ abi = abi::Variant_arm64_v8a;
+ }
+ }
+ } else if (parts[endIndex] == "armeabi") {
+ endIndex++;
+ abi = abi::Variant_armeabi;
+ if (endIndex < N) {
+ if (parts[endIndex] == "v7a") {
+ endIndex++;
+ abi = abi::Variant_armeabi_v7a;
+ }
+ }
+ } else if (parts[endIndex] == "x86") {
+ endIndex++;
+ abi = abi::Variant_x86;
+ } else if (parts[endIndex] == "x86_64") {
+ endIndex++;
+ abi = abi::Variant_x86_64;
+ } else if (parts[endIndex] == "mips") {
+ endIndex++;
+ abi = abi::Variant_mips;
+ } else if (parts[endIndex] == "mips64") {
+ endIndex++;
+ abi = abi::Variant_mips64;
+ }
+
+ if (abi == abi::Variant_none && endIndex != index) {
+ return -1;
+ }
+
+ if (outSplit != NULL) {
+ outSplit->abi = abi;
+ }
+ return endIndex;
+}
+
+bool SplitDescription::parse(const String8& str, SplitDescription* outSplit) {
+ ssize_t index = str.find(":");
+
+ String8 configStr;
+ String8 extensionStr;
+ if (index >= 0) {
+ configStr.setTo(str.string(), index);
+ extensionStr.setTo(str.string() + index + 1);
+ } else {
+ configStr.setTo(str);
+ }
+
+ SplitDescription split;
+ if (!AaptConfig::parse(configStr, &split.config)) {
+ return false;
+ }
+
+ Vector<String8> parts = AaptUtil::splitAndLowerCase(extensionStr, '-');
+ const ssize_t N = parts.size();
+ index = 0;
+
+ if (extensionStr.length() == 0) {
+ goto success;
+ }
+
+ index = parseAbi(parts, index, &split);
+ if (index < 0) {
+ return false;
+ } else {
+ if (index == N) {
+ goto success;
+ }
+ }
+
+ // Unrecognized
+ return false;
+
+success:
+ if (outSplit != NULL) {
+ *outSplit = split;
+ }
+ return true;
+}
+
+} // namespace split
diff --git a/tools/split-select/SplitDescription.h b/tools/split-select/SplitDescription.h
new file mode 100644
index 0000000..b13c9ee
--- /dev/null
+++ b/tools/split-select/SplitDescription.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef H_ANDROID_SPLIT_SPLIT_DESCRIPTION
+#define H_ANDROID_SPLIT_SPLIT_DESCRIPTION
+
+#include "aapt/ConfigDescription.h"
+#include "Abi.h"
+
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+namespace split {
+
+struct SplitDescription {
+ SplitDescription();
+
+ ConfigDescription config;
+ abi::Variant abi;
+
+ int compare(const SplitDescription& rhs) const;
+ inline bool operator<(const SplitDescription& rhs) const;
+ inline bool operator==(const SplitDescription& rhs) const;
+ inline bool operator!=(const SplitDescription& rhs) const;
+
+ bool match(const SplitDescription& o) const;
+ bool isBetterThan(const SplitDescription& o, const SplitDescription& target) const;
+
+ android::String8 toString() const;
+
+ static bool parse(const android::String8& str, SplitDescription* outSplit);
+};
+
+ssize_t parseAbi(const android::Vector<android::String8>& parts, const ssize_t index,
+ SplitDescription* outSplit);
+
+bool SplitDescription::operator<(const SplitDescription& rhs) const {
+ return compare(rhs) < 0;
+}
+
+bool SplitDescription::operator==(const SplitDescription& rhs) const {
+ return compare(rhs) == 0;
+}
+
+bool SplitDescription::operator!=(const SplitDescription& rhs) const {
+ return compare(rhs) != 0;
+}
+
+} // namespace split
+
+#endif // H_ANDROID_SPLIT_SPLIT_DESCRIPTION
diff --git a/tools/split-select/TestRules.cpp b/tools/split-select/TestRules.cpp
new file mode 100644
index 0000000..86ccd6a
--- /dev/null
+++ b/tools/split-select/TestRules.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TestRules.h"
+
+#include <utils/String8.h>
+
+using android::String8;
+using android::sp;
+
+namespace split {
+namespace test {
+
+const Rule EqRule(Rule::Key key, long value) {
+ Rule rule;
+ rule.op = Rule::EQUALS;
+ rule.key = key;
+ rule.longArgs.add(value);
+ return rule;
+}
+
+const Rule GtRule(Rule::Key key, long value) {
+ Rule rule;
+ rule.op = Rule::GREATER_THAN;
+ rule.key = key;
+ rule.longArgs.add(value);
+ return rule;
+}
+
+const Rule LtRule(Rule::Key key, long value) {
+ Rule rule;
+ rule.op = Rule::LESS_THAN;
+ rule.key = key;
+ rule.longArgs.add(value);
+ return rule;
+}
+
+const Rule ContainsAnyRule(Rule::Key key, const char* str1) {
+ Rule rule;
+ rule.op = Rule::CONTAINS_ANY;
+ rule.key = key;
+ rule.stringArgs.add(String8(str1));
+ return rule;
+}
+
+const Rule ContainsAnyRule(Rule::Key key, const char* str1, const char* str2) {
+ Rule rule;
+ rule.op = Rule::CONTAINS_ANY;
+ rule.key = key;
+ rule.stringArgs.add(String8(str1));
+ rule.stringArgs.add(String8(str2));
+ return rule;
+}
+
+const Rule AlwaysTrue() {
+ Rule rule;
+ rule.op = Rule::ALWAYS_TRUE;
+ return rule;
+}
+
+::testing::AssertionResult RulePredFormat(
+ const char*, const char*,
+ const sp<Rule>& actual, const Rule& expected) {
+ const String8 expectedStr(expected.toJson());
+ const String8 actualStr(actual != NULL ? actual->toJson() : String8());
+
+ if (expectedStr != actualStr) {
+ return ::testing::AssertionFailure()
+ << "Expected: " << expectedStr.string() << "\n"
+ << " Actual: " << actualStr.string();
+ }
+ return ::testing::AssertionSuccess();
+}
+
+
+} // namespace test
+} // namespace split
diff --git a/tools/split-select/TestRules.h b/tools/split-select/TestRules.h
new file mode 100644
index 0000000..50b7ad1
--- /dev/null
+++ b/tools/split-select/TestRules.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef H_AAPT_SPLIT_TEST_RULES
+#define H_AAPT_SPLIT_TEST_RULES
+
+#include "Rule.h"
+
+#include <gtest/gtest.h>
+
+namespace split {
+namespace test {
+
+struct AndRule : public Rule {
+ AndRule() {
+ op = Rule::AND_SUBRULES;
+ }
+
+ AndRule& add(const Rule& rhs) {
+ subrules.add(new Rule(rhs));
+ return *this;
+ }
+};
+
+struct OrRule : public Rule {
+ OrRule() {
+ op = Rule::OR_SUBRULES;
+ }
+
+ OrRule& add(const Rule& rhs) {
+ subrules.add(new Rule(rhs));
+ return *this;
+ }
+};
+
+const Rule EqRule(Rule::Key key, long value);
+const Rule LtRule(Rule::Key key, long value);
+const Rule GtRule(Rule::Key key, long value);
+const Rule ContainsAnyRule(Rule::Key key, const char* str1);
+const Rule ContainsAnyRule(Rule::Key key, const char* str1, const char* str2);
+const Rule AlwaysTrue();
+
+::testing::AssertionResult RulePredFormat(
+ const char* actualExpr, const char* expectedExpr,
+ const android::sp<Rule>& actual, const Rule& expected);
+
+#define EXPECT_RULES_EQ(actual, expected) \
+ EXPECT_PRED_FORMAT2(::split::test::RulePredFormat, actual, expected)
+
+} // namespace test
+} // namespace split
+
+#endif // H_AAPT_SPLIT_TEST_RULES