summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkuantung <kuantung@google.com>2017-07-31 10:16:44 -0700
committerTom Dobek <tdobek@google.com>2017-08-02 15:31:47 +0100
commit5e25b96772b38f70dda4fab22f671933c9238737 (patch)
tree8287e9f1597057808fe61164a159c18fb68c3ed6
downloadappbundle-5e25b96772b38f70dda4fab22f671933c9238737.tar.gz
Initial, fledgling implementation of bundle2installable tool.
Test: e2e.sh Change-Id: I30c032ffca0908bf5d81869d563308ed0ff30aab
-rw-r--r--bundle2installable/.idea/scopes/scope_settings.xml5
-rw-r--r--bundle2installable/Android.mk49
-rw-r--r--bundle2installable/bundle2installable.iml20
-rwxr-xr-xbundle2installable/e2e.sh32
-rw-r--r--bundle2installable/etc/bundle2installable89
-rw-r--r--bundle2installable/etc/bundle2installable.bat88
-rw-r--r--bundle2installable/example/bundle/bundle.zipbin0 -> 8752 bytes
-rw-r--r--bundle2installable/example/bundle/module1/assets/some_file.txt1
-rw-r--r--bundle2installable/example/bundle/module1/format.textpb4
-rw-r--r--bundle2installable/example/bundle/module1/res/drawable/hdpi/res1.jpg1
-rw-r--r--bundle2installable/example/bundle/module1/res/drawable/mdpi/res1.jpg1
-rw-r--r--bundle2installable/example/bundle/module1/res/drawable/mdpi/res2.jpg1
-rw-r--r--bundle2installable/example/bundle/module2/format.textpb4
-rw-r--r--bundle2installable/example/bundle/module2/native/arm64-v8/alibrary.so1
-rw-r--r--bundle2installable/example/bundle/module2/native/x86/alibrary.so1
-rw-r--r--bundle2installable/example/bundle/module2/res/drawable/hdpi/a.jpg1
-rw-r--r--bundle2installable/example/bundle/module2/res/drawable/mdpi/a.jpg1
-rw-r--r--bundle2installable/example/bundle/module2/res/drawable/xhdpi/a.jpg1
-rw-r--r--bundle2installable/example/bundle/module3/assets/gl1/textures.etc11
-rw-r--r--bundle2installable/example/bundle/module3/assets/gl2/textures.etc11
-rw-r--r--bundle2installable/example/bundle/module3/assets/gl3/textures.etc11
-rw-r--r--bundle2installable/example/bundle/module3/format.textpb4
-rw-r--r--bundle2installable/example/bundle/module3/native/arm64-v8/lib1.so1
-rw-r--r--bundle2installable/example/bundle/module3/native/x86/lib1.so1
-rw-r--r--bundle2installable/example/bundle/module3/res/drawable/hdpi/a.jpg1
-rw-r--r--bundle2installable/example/bundle/module3/res/drawable/mdpi/a.jpg1
-rw-r--r--bundle2installable/example/module/assets/gl1/textures.etc11
-rw-r--r--bundle2installable/example/module/assets/gl2/textures.etc11
-rw-r--r--bundle2installable/example/module/assets/gl3/textures.etc11
-rw-r--r--bundle2installable/example/module/assets/textures.etc11
-rw-r--r--bundle2installable/example/module/format.textpb4
-rw-r--r--bundle2installable/example/module/module.zipbin0 -> 3164 bytes
-rw-r--r--bundle2installable/example/module/native/arm64-v8/library.so1
-rw-r--r--bundle2installable/example/module/native/x86/library.so1
-rw-r--r--bundle2installable/example/module/res/drawable-hdpi/picture.png1
-rw-r--r--bundle2installable/example/module/res/drawable-mdpi/picture.png1
-rw-r--r--bundle2installable/example/module/res/drawable/picture.png1
-rw-r--r--bundle2installable/manifest.txt1
-rw-r--r--bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/AssetsProcessor.java103
-rw-r--r--bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/Bundle2InstallableMain.java56
-rw-r--r--bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/DeliverableLinker.java51
-rw-r--r--bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/ModuleSplitter.java71
-rw-r--r--bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/ModuleZipMaker.java87
-rw-r--r--bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/NativeProcessor.java102
-rw-r--r--bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/PreSplitGenerator.java50
-rw-r--r--bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/ResourcesProcessor.java79
-rw-r--r--bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/VariantProcessor.java44
-rw-r--r--bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/Pair.java82
-rw-r--r--bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/PathUtils.java72
-rw-r--r--bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/ProtoUtils.java43
-rw-r--r--bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/ZipUtils.java42
-rw-r--r--bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/ZipWalker.java63
-rw-r--r--bundle2installable/src/proto/Format.proto214
53 files changed, 1484 insertions, 0 deletions
diff --git a/bundle2installable/.idea/scopes/scope_settings.xml b/bundle2installable/.idea/scopes/scope_settings.xml
new file mode 100644
index 0000000..922003b
--- /dev/null
+++ b/bundle2installable/.idea/scopes/scope_settings.xml
@@ -0,0 +1,5 @@
+<component name="DependencyValidationManager">
+ <state>
+ <option name="SKIP_IMPORT_STATEMENTS" value="false" />
+ </state>
+</component> \ No newline at end of file
diff --git a/bundle2installable/Android.mk b/bundle2installable/Android.mk
new file mode 100644
index 0000000..b9e9f72
--- /dev/null
+++ b/bundle2installable/Android.mk
@@ -0,0 +1,49 @@
+LOCAL_PATH:= $(call my-dir)
+
+## bundletool script
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := bundle2installable
+LOCAL_MODULE_TAG := optional
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_IS_HOST_MODULE := true
+
+include $(BUILD_SYSTEM)/base_rules.mk
+
+$(LOCAL_BUILT_MODULE): $(HOST_OUT_JAVA_LIBRARIES)/bundle2installable$(COMMON_JAVA_PACKAGE_SUFFIX)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/etc/bundle2installable | $(ACP)
+ @echo "Copy: $(PRIVATE_MODULE) ($@)"
+ $(copy-file-to-new-target)
+ $(hide) chmod 755 $@
+
+## proto library
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := bundle2installable-protos
+LOCAL_MODULE_TAG := optional
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := full
+LOCAL_SRC_FILES := $(call all-proto-files-under, src)
+LOCAL_STATIC_JAVA_LIBRARIES := host-libprotobuf-java-full
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+## tool jar
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := bundle2installable
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := bundle2installable-protos jsr305lib
+
+LOCAL_JAR_MANIFEST := manifest.txt
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+
diff --git a/bundle2installable/bundle2installable.iml b/bundle2installable/bundle2installable.iml
new file mode 100644
index 0000000..7df2ab0
--- /dev/null
+++ b/bundle2installable/bundle2installable.iml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src/java" isTestSource="false" />
+ </content>
+ <content url="file://$MODULE_DIR$/../../../out/target/common/obj/APPS/bundle2installable_intermediates/src">
+ <sourceFolder url="file://$MODULE_DIR$/../../../out/target/common/obj/APPS/bundle2installable_intermediates/src" isTestSource="false" />
+ </content>
+
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" module-name="jsr305" />
+ <orderEntry type="library" module-name="protobuf" />
+
+ </component>
+</module>
+
diff --git a/bundle2installable/e2e.sh b/bundle2installable/e2e.sh
new file mode 100755
index 0000000..36cf8cc
--- /dev/null
+++ b/bundle2installable/e2e.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+TOOL_CMD=../../../out/host/linux-x86/bin/bundle2installable
+
+# set up directories
+mkdir -p output/
+mkdir -p output-splits/
+mkdir -p output-deliverables/
+
+# clean up
+rm -fr output/*
+rm -fr output-splits/*
+rm -fr output-deliverables/*
+
+echo "--------------------------------------------------------------------"
+echo "Generating module zips"
+echo "--------------------------------------------------------------------"
+$TOOL_CMD generate example/bundle/bundle.zip output
+echo "--------------------------------------------------------------------"
+
+for MODULEZIP in `ls output`;
+do
+ echo "Splitting module: $MODULEZIP"
+ echo "--------------------------------------------------------------------"
+ $TOOL_CMD split-module output/$MODULEZIP output-splits/
+ echo "--------------------------------------------------------------------"
+done
+
+echo "Linking deliverable: x86,res-default,gl1"
+echo "--------------------------------------------------------------------"
+$TOOL_CMD link output-deliverables/deliverable.zip output-splits --modules=module1,module2,module3 --splits=x86,res-default,gl1
+
diff --git a/bundle2installable/etc/bundle2installable b/bundle2installable/etc/bundle2installable
new file mode 100644
index 0000000..ac7cac3
--- /dev/null
+++ b/bundle2installable/etc/bundle2installable
@@ -0,0 +1,89 @@
+#!/bin/bash
+#
+# Copyright (C) 2017 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.
+
+# Set up prog to be the path of this script, including following symlinks,
+# and set up progdir to be the fully-qualified pathname of its directory.
+prog="$0"
+while [ -h "${prog}" ]; do
+ newProg=`/bin/ls -ld "${prog}"`
+ newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
+ if expr "x${newProg}" : 'x/' >/dev/null; then
+ prog="${newProg}"
+ else
+ progdir=`dirname "${prog}"`
+ prog="${progdir}/${newProg}"
+ fi
+done
+oldwd=`pwd`
+progdir=`dirname "${prog}"`
+cd "${progdir}"
+progdir=`pwd`
+prog="${progdir}"/`basename "${prog}"`
+cd "${oldwd}"
+
+jarfile=bundletool.jar
+libdir="$progdir"
+
+if [ ! -r "$libdir/$jarfile" ]; then
+ # set bundletool.jar location for the SDK case
+ libdir="$libdir/lib"
+fi
+
+
+if [ ! -r "$libdir/$jarfile" ]; then
+ # set bundletool.jar location for the Android tree case
+ libdir=`dirname "$progdir"`/framework
+fi
+
+if [ ! -r "$libdir/$jarfile" ]; then
+ echo `basename "$prog"`": can't find $jarfile"
+ exit 1
+fi
+
+# By default, give bundletool a max heap size of 1 gig. This can be overridden
+# by using a "-J" option (see below).
+defaultMx="-Xmx1024M"
+
+# The following will extract any initial parameters of the form
+# "-J<stuff>" from the command line and pass them to the Java
+# invocation (instead of to bundletool). This makes it possible for you to add
+# a command-line parameter such as "-JXmx256M" in your scripts, for
+# example. "java" (with no args) and "java -X" give a summary of
+# available options.
+
+javaOpts=""
+
+while expr "x$1" : 'x-J' >/dev/null; do
+ opt=`expr "x$1" : 'x-J\(.*\)'`
+ javaOpts="${javaOpts} -${opt}"
+ if expr "x${opt}" : "xXmx[0-9]" >/dev/null; then
+ defaultMx="no"
+ fi
+ shift
+done
+
+if [ "${defaultMx}" != "no" ]; then
+ javaOpts="${javaOpts} ${defaultMx}"
+fi
+
+if [ "$OSTYPE" = "cygwin" ]; then
+ # For Cygwin, convert the jarfile path into native Windows style.
+ jarpath=`cygpath -w "$libdir/$jarfile"`
+else
+ jarpath="$libdir/$jarfile"
+fi
+
+exec java $javaOpts -jar "$jarpath" "$@"
diff --git a/bundle2installable/etc/bundle2installable.bat b/bundle2installable/etc/bundle2installable.bat
new file mode 100644
index 0000000..09a9d36
--- /dev/null
+++ b/bundle2installable/etc/bundle2installable.bat
@@ -0,0 +1,88 @@
+@echo off↵
+REM Copyright (C) 2017 The Android Open Source Project↵
+REM↵
+REM Licensed under the Apache License, Version 2.0 (the "License");↵
+REM you may not use this file except in compliance with the License.↵
+REM You may obtain a copy of the License at↵
+REM↵
+REM http://www.apache.org/licenses/LICENSE-2.0↵
+REM↵
+REM Unless required by applicable law or agreed to in writing, software↵
+REM distributed under the License is distributed on an "AS IS" BASIS,↵
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.↵
+REM See the License for the specific language governing permissions and↵
+REM limitations under the License.↵
+↵
+REM don't modify the caller's environment↵
+setlocal↵
+↵
+REM Locate bundletool.jar in the directory where bundletool.bat was found and start it.↵
+↵
+REM Set up prog to be the path of this script, including following symlinks,↵
+REM and set up progdir to be the fully-qualified pathname of its directory.↵
+set prog=%~f0↵
+↵
+rem Check we have a valid Java.exe in the path.↵
+set java_exe=↵
+if exist "%~dp0..\tools\lib\find_java.bat" call "%~dp0..\tools\lib\find_java.bat"↵
+if exist "%~dp0..\..\tools\lib\find_java.bat" call "%~dp0..\..\tools\lib\find_java.bat"↵
+if not defined java_exe goto :EOF↵
+↵
+set jarfile=bundletool.jar↵
+set "frameworkdir=%~dp0"↵
+rem frameworkdir must not end with a dir sep.↵
+set "frameworkdir=%frameworkdir:~0,-1%"↵
+↵
+if exist "%frameworkdir%\%jarfile%" goto JarFileOk↵
+ set "frameworkdir=%~dp0lib"↵
+↵
+if exist "%frameworkdir%\%jarfile%" goto JarFileOk↵
+ set "frameworkdir=%~dp0..\framework"↵
+↵
+:JarFileOk↵
+↵
+set "jarpath=%frameworkdir%\%jarfile%"↵
+↵
+set javaOpts=↵
+set args=↵
+↵
+REM By default, give bundletool a max heap size of 1 gig and a stack size of 1meg.↵
+rem This can be overridden by using "-JXmx..." and "-JXss..." options below.↵
+set defaultXmx=-Xmx1024M↵
+set defaultXss=-Xss1m↵
+↵
+REM Capture all arguments that are not -J options.↵
+REM Note that when reading the input arguments with %1, the cmd.exe↵
+REM automagically converts --name=value arguments into 2 arguments "--name"↵
+REM followed by "value". Dx has been changed to know how to deal with that.↵
+set params=↵
+↵
+:firstArg↵
+if [%1]==[] goto endArgs↵
+set a=%~1↵
+↵
+ if [%defaultXmx%]==[] goto notXmx↵
+ if %a:~0,5% NEQ -JXmx goto notXmx↵
+ set defaultXmx=↵
+ :notXmx↵
+↵
+ if [%defaultXss%]==[] goto notXss↵
+ if %a:~0,5% NEQ -JXss goto notXss↵
+ set defaultXss=↵
+ :notXss↵
+↵
+ if %a:~0,2% NEQ -J goto notJ↵
+ set javaOpts=%javaOpts% -%a:~2%↵
+ shift /1↵
+ goto firstArg↵
+↵
+ :notJ↵
+ set params=%params% %1↵
+ shift /1↵
+ goto firstArg↵
+↵
+:endArgs↵
+↵
+set javaOpts=%javaOpts% %defaultXmx% %defaultXss%↵
+call "%java_exe%" %javaOpts% -Djava.ext.dirs="%frameworkdir%" -jar "%jarpath%" %params%↵
+↵
diff --git a/bundle2installable/example/bundle/bundle.zip b/bundle2installable/example/bundle/bundle.zip
new file mode 100644
index 0000000..21cb2f2
--- /dev/null
+++ b/bundle2installable/example/bundle/bundle.zip
Binary files differ
diff --git a/bundle2installable/example/bundle/module1/assets/some_file.txt b/bundle2installable/example/bundle/module1/assets/some_file.txt
new file mode 100644
index 0000000..a984be3
--- /dev/null
+++ b/bundle2installable/example/bundle/module1/assets/some_file.txt
@@ -0,0 +1 @@
+Some asset.
diff --git a/bundle2installable/example/bundle/module1/format.textpb b/bundle2installable/example/bundle/module1/format.textpb
new file mode 100644
index 0000000..7b0ecc5
--- /dev/null
+++ b/bundle2installable/example/bundle/module1/format.textpb
@@ -0,0 +1,4 @@
+packages {
+ package_id: 128
+ package_name: "mainPackage"
+}
diff --git a/bundle2installable/example/bundle/module1/res/drawable/hdpi/res1.jpg b/bundle2installable/example/bundle/module1/res/drawable/hdpi/res1.jpg
new file mode 100644
index 0000000..769a602
--- /dev/null
+++ b/bundle2installable/example/bundle/module1/res/drawable/hdpi/res1.jpg
@@ -0,0 +1 @@
+hdpi jpeg
diff --git a/bundle2installable/example/bundle/module1/res/drawable/mdpi/res1.jpg b/bundle2installable/example/bundle/module1/res/drawable/mdpi/res1.jpg
new file mode 100644
index 0000000..ffa7a30
--- /dev/null
+++ b/bundle2installable/example/bundle/module1/res/drawable/mdpi/res1.jpg
@@ -0,0 +1 @@
+Mpdi jpeg
diff --git a/bundle2installable/example/bundle/module1/res/drawable/mdpi/res2.jpg b/bundle2installable/example/bundle/module1/res/drawable/mdpi/res2.jpg
new file mode 100644
index 0000000..9f4d332
--- /dev/null
+++ b/bundle2installable/example/bundle/module1/res/drawable/mdpi/res2.jpg
@@ -0,0 +1 @@
+mdpi res2
diff --git a/bundle2installable/example/bundle/module2/format.textpb b/bundle2installable/example/bundle/module2/format.textpb
new file mode 100644
index 0000000..7b0ecc5
--- /dev/null
+++ b/bundle2installable/example/bundle/module2/format.textpb
@@ -0,0 +1,4 @@
+packages {
+ package_id: 128
+ package_name: "mainPackage"
+}
diff --git a/bundle2installable/example/bundle/module2/native/arm64-v8/alibrary.so b/bundle2installable/example/bundle/module2/native/arm64-v8/alibrary.so
new file mode 100644
index 0000000..3391b8b
--- /dev/null
+++ b/bundle2installable/example/bundle/module2/native/arm64-v8/alibrary.so
@@ -0,0 +1 @@
+arm64-v8 library
diff --git a/bundle2installable/example/bundle/module2/native/x86/alibrary.so b/bundle2installable/example/bundle/module2/native/x86/alibrary.so
new file mode 100644
index 0000000..d758c3d
--- /dev/null
+++ b/bundle2installable/example/bundle/module2/native/x86/alibrary.so
@@ -0,0 +1 @@
+x86 library
diff --git a/bundle2installable/example/bundle/module2/res/drawable/hdpi/a.jpg b/bundle2installable/example/bundle/module2/res/drawable/hdpi/a.jpg
new file mode 100644
index 0000000..1d83daf
--- /dev/null
+++ b/bundle2installable/example/bundle/module2/res/drawable/hdpi/a.jpg
@@ -0,0 +1 @@
+hdpi a.jpg
diff --git a/bundle2installable/example/bundle/module2/res/drawable/mdpi/a.jpg b/bundle2installable/example/bundle/module2/res/drawable/mdpi/a.jpg
new file mode 100644
index 0000000..94213bf
--- /dev/null
+++ b/bundle2installable/example/bundle/module2/res/drawable/mdpi/a.jpg
@@ -0,0 +1 @@
+mdpi a.jpg
diff --git a/bundle2installable/example/bundle/module2/res/drawable/xhdpi/a.jpg b/bundle2installable/example/bundle/module2/res/drawable/xhdpi/a.jpg
new file mode 100644
index 0000000..40fb343
--- /dev/null
+++ b/bundle2installable/example/bundle/module2/res/drawable/xhdpi/a.jpg
@@ -0,0 +1 @@
+xhdpi a.jpg
diff --git a/bundle2installable/example/bundle/module3/assets/gl1/textures.etc1 b/bundle2installable/example/bundle/module3/assets/gl1/textures.etc1
new file mode 100644
index 0000000..2849ff7
--- /dev/null
+++ b/bundle2installable/example/bundle/module3/assets/gl1/textures.etc1
@@ -0,0 +1 @@
+textures for gl1
diff --git a/bundle2installable/example/bundle/module3/assets/gl2/textures.etc1 b/bundle2installable/example/bundle/module3/assets/gl2/textures.etc1
new file mode 100644
index 0000000..2c853ce
--- /dev/null
+++ b/bundle2installable/example/bundle/module3/assets/gl2/textures.etc1
@@ -0,0 +1 @@
+textures for gl2
diff --git a/bundle2installable/example/bundle/module3/assets/gl3/textures.etc1 b/bundle2installable/example/bundle/module3/assets/gl3/textures.etc1
new file mode 100644
index 0000000..cf30c2c
--- /dev/null
+++ b/bundle2installable/example/bundle/module3/assets/gl3/textures.etc1
@@ -0,0 +1 @@
+textures for gl3
diff --git a/bundle2installable/example/bundle/module3/format.textpb b/bundle2installable/example/bundle/module3/format.textpb
new file mode 100644
index 0000000..7b0ecc5
--- /dev/null
+++ b/bundle2installable/example/bundle/module3/format.textpb
@@ -0,0 +1,4 @@
+packages {
+ package_id: 128
+ package_name: "mainPackage"
+}
diff --git a/bundle2installable/example/bundle/module3/native/arm64-v8/lib1.so b/bundle2installable/example/bundle/module3/native/arm64-v8/lib1.so
new file mode 100644
index 0000000..826009f
--- /dev/null
+++ b/bundle2installable/example/bundle/module3/native/arm64-v8/lib1.so
@@ -0,0 +1 @@
+module3 arm64-v8 lib1.so
diff --git a/bundle2installable/example/bundle/module3/native/x86/lib1.so b/bundle2installable/example/bundle/module3/native/x86/lib1.so
new file mode 100644
index 0000000..90e549c
--- /dev/null
+++ b/bundle2installable/example/bundle/module3/native/x86/lib1.so
@@ -0,0 +1 @@
+module3 lib1.so x86
diff --git a/bundle2installable/example/bundle/module3/res/drawable/hdpi/a.jpg b/bundle2installable/example/bundle/module3/res/drawable/hdpi/a.jpg
new file mode 100644
index 0000000..2a2f661
--- /dev/null
+++ b/bundle2installable/example/bundle/module3/res/drawable/hdpi/a.jpg
@@ -0,0 +1 @@
+module3 hdpi a.jpg
diff --git a/bundle2installable/example/bundle/module3/res/drawable/mdpi/a.jpg b/bundle2installable/example/bundle/module3/res/drawable/mdpi/a.jpg
new file mode 100644
index 0000000..2b04ec0
--- /dev/null
+++ b/bundle2installable/example/bundle/module3/res/drawable/mdpi/a.jpg
@@ -0,0 +1 @@
+module3 mdpi a.jpg
diff --git a/bundle2installable/example/module/assets/gl1/textures.etc1 b/bundle2installable/example/module/assets/gl1/textures.etc1
new file mode 100644
index 0000000..4fa3fae
--- /dev/null
+++ b/bundle2installable/example/module/assets/gl1/textures.etc1
@@ -0,0 +1 @@
+gl1-textures
diff --git a/bundle2installable/example/module/assets/gl2/textures.etc1 b/bundle2installable/example/module/assets/gl2/textures.etc1
new file mode 100644
index 0000000..7bd0987
--- /dev/null
+++ b/bundle2installable/example/module/assets/gl2/textures.etc1
@@ -0,0 +1 @@
+gl2-textures
diff --git a/bundle2installable/example/module/assets/gl3/textures.etc1 b/bundle2installable/example/module/assets/gl3/textures.etc1
new file mode 100644
index 0000000..d88e8a7
--- /dev/null
+++ b/bundle2installable/example/module/assets/gl3/textures.etc1
@@ -0,0 +1 @@
+gl3-textures
diff --git a/bundle2installable/example/module/assets/textures.etc1 b/bundle2installable/example/module/assets/textures.etc1
new file mode 100644
index 0000000..3b6f771
--- /dev/null
+++ b/bundle2installable/example/module/assets/textures.etc1
@@ -0,0 +1 @@
+no-variant-textures
diff --git a/bundle2installable/example/module/format.textpb b/bundle2installable/example/module/format.textpb
new file mode 100644
index 0000000..7b0ecc5
--- /dev/null
+++ b/bundle2installable/example/module/format.textpb
@@ -0,0 +1,4 @@
+packages {
+ package_id: 128
+ package_name: "mainPackage"
+}
diff --git a/bundle2installable/example/module/module.zip b/bundle2installable/example/module/module.zip
new file mode 100644
index 0000000..7b00859
--- /dev/null
+++ b/bundle2installable/example/module/module.zip
Binary files differ
diff --git a/bundle2installable/example/module/native/arm64-v8/library.so b/bundle2installable/example/module/native/arm64-v8/library.so
new file mode 100644
index 0000000..3391b8b
--- /dev/null
+++ b/bundle2installable/example/module/native/arm64-v8/library.so
@@ -0,0 +1 @@
+arm64-v8 library
diff --git a/bundle2installable/example/module/native/x86/library.so b/bundle2installable/example/module/native/x86/library.so
new file mode 100644
index 0000000..ffc9498
--- /dev/null
+++ b/bundle2installable/example/module/native/x86/library.so
@@ -0,0 +1 @@
+x86-library
diff --git a/bundle2installable/example/module/res/drawable-hdpi/picture.png b/bundle2installable/example/module/res/drawable-hdpi/picture.png
new file mode 100644
index 0000000..520659f
--- /dev/null
+++ b/bundle2installable/example/module/res/drawable-hdpi/picture.png
@@ -0,0 +1 @@
+hdpi
diff --git a/bundle2installable/example/module/res/drawable-mdpi/picture.png b/bundle2installable/example/module/res/drawable-mdpi/picture.png
new file mode 100644
index 0000000..d3e3b70
--- /dev/null
+++ b/bundle2installable/example/module/res/drawable-mdpi/picture.png
@@ -0,0 +1 @@
+mdpi
diff --git a/bundle2installable/example/module/res/drawable/picture.png b/bundle2installable/example/module/res/drawable/picture.png
new file mode 100644
index 0000000..55cb19a
--- /dev/null
+++ b/bundle2installable/example/module/res/drawable/picture.png
@@ -0,0 +1 @@
+fallback
diff --git a/bundle2installable/manifest.txt b/bundle2installable/manifest.txt
new file mode 100644
index 0000000..b00d463
--- /dev/null
+++ b/bundle2installable/manifest.txt
@@ -0,0 +1 @@
+Main-Class: com.android.bundle.tool.Bundle2InstallableMain
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/AssetsProcessor.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/AssetsProcessor.java
new file mode 100644
index 0000000..1cbd5cb
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/AssetsProcessor.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2017 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
+ */
+
+package com.android.tools.appbundle.bundle2installable;
+
+import static com.android.tools.appbundle.bundle2installable.util.PathUtils.getFilePathWithoutTopLevel;
+import static com.android.tools.appbundle.bundle2installable.util.PathUtils.getFilePathWithoutVariant;
+
+import com.android.tools.appbundle.bundle2installable.util.Pair;
+import com.android.tools.appbundle.bundle2installable.util.PathUtils;
+import com.android.tools.appbundle.bundle2installable.util.ZipWalker;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Assets directory variant processor.
+ *
+ * Currently, scans the directory in the assets directory of the module zip. Every directory name
+ * is considered a variant name, and the final split will contain files from the variant directory
+ * with that directory elided: all goes directly in the assets directory. Each variant will include
+ * files that are directly in the assets root directory.
+ */
+public class AssetsProcessor implements ZipWalker.ZipEntryListener, VariantProcessor {
+
+ private static final String ASSETS_DIR = "assets";
+
+ private Map<String, List<ZipEntry>> variantAssetsMap;
+ private List<ZipEntry> nonVariantAssets;
+
+ public AssetsProcessor() {
+ this.variantAssetsMap = new HashMap<>();
+ this.nonVariantAssets = new ArrayList<>();
+ }
+
+ public void notify(ZipFile bundle, ZipEntry entry) throws IOException {
+ // TODO(b/64285122): fix the asset variants detection when there is only non-variant data.
+ Path p = Paths.get(entry.getName());
+ if (PathUtils.isInsideTopDirectory(p, ASSETS_DIR)) {
+ String variant = PathUtils.resolveVariant(p);
+ if (variant != null) { // inside the variant directory
+ if (!variantAssetsMap.containsKey(variant)) {
+ variantAssetsMap.put(variant, new ArrayList<>());
+ }
+ variantAssetsMap.get(variant).add(entry);
+ } else { // inside the assets directory
+ System.out.println("Entry name: " + entry.getName());
+ if (!entry.isDirectory()) {
+ System.out.println("..is not a directory");
+ nonVariantAssets.add(entry);
+ }
+ }
+ }
+ }
+
+ public List<Pair<ZipEntry, String>> generateForVariant(String variant) {
+ List<Pair<ZipEntry, String>> res = new ArrayList<>();
+ Set<String> coveredFiles = new HashSet<>();
+ if (!variantAssetsMap.containsKey(variant)) {
+ System.out.println(
+ String.format("Warning! The '%s' variant has no dedicated assets.",
+ variant));
+ } else {
+ for (ZipEntry entry : variantAssetsMap.get(variant)) {
+ String pathWithoutVariant = getFilePathWithoutVariant(entry.getName());
+ coveredFiles.add(pathWithoutVariant);
+ res.add(Pair.of(entry, Paths.get(ASSETS_DIR, pathWithoutVariant).toString()));
+ }
+ }
+ for (ZipEntry entry : nonVariantAssets) {
+ String pathWithoutVariant = getFilePathWithoutTopLevel(entry.getName());
+ if (!coveredFiles.contains(pathWithoutVariant)) {
+ res.add(Pair.of(entry, Paths.get(ASSETS_DIR, pathWithoutVariant).toString()));
+ }
+ }
+ return res;
+ }
+
+ public List<String> getAllVariants() {
+ return new ArrayList<>(variantAssetsMap.keySet());
+ }
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/Bundle2InstallableMain.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/Bundle2InstallableMain.java
new file mode 100644
index 0000000..71b1111
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/Bundle2InstallableMain.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 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
+ */
+
+package com.android.tools.appbundle.bundle2installable;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Main entry point of the bundle2installable tool
+ */
+public class Bundle2InstallableMain {
+
+ public static void main(String[] args) throws IOException {
+ if (args.length < 1) {
+ throw new IllegalStateException(
+ "Incorrect number of args. Use bundle2installable [command] ...");
+ }
+ String command = args[0];
+ switch (command) {
+ case "split-module":
+ ModuleSplitter.doSplitModule(args);
+ break;
+ case "generate":
+ ModuleZipMaker.generateModuleZips(args[1], args[2]);
+ break;
+ case "link":
+ DeliverableLinker.makeDeliverable(args[1], args[2], makeList(args[3]),
+ makeList(args[4]));
+ break;
+ }
+ }
+
+ private static List<String> makeList(String flag) {
+ String[] equal = flag.split("=");
+ String equalVal = equal[0];
+ if (equal.length > 1) { // assignment
+ equalVal = equal[1];
+ }
+ return Arrays.asList(equalVal.split(","));
+ }
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/DeliverableLinker.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/DeliverableLinker.java
new file mode 100644
index 0000000..b42910b
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/DeliverableLinker.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 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
+ */
+
+package com.android.tools.appbundle.bundle2installable;
+
+import com.android.tools.appbundle.bundle2installable.util.PathUtils;
+import com.android.tools.appbundle.bundle2installable.util.ZipUtils;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * A linker that can create an installable from the module splits.
+ */
+public class DeliverableLinker {
+
+ public static void makeDeliverable(String deliverablePath, String splitDirectory,
+ List<String> modules, List<String> splits) throws IOException {
+ ZipOutputStream outputStream = new ZipOutputStream(new FileOutputStream(deliverablePath));
+ for (String module : modules) {
+ for (String split : splits) {
+ Path p = Paths.get(splitDirectory, PathUtils.makeSplitName(module, split));
+ if (Files.isRegularFile(p)) {
+ System.out.println(String.format("Linking %s", p.toString()));
+ ZipUtils.writeEntry(PathUtils.stripDirectories(p.toString()), outputStream,
+ new FileInputStream(p.toString()));
+ }
+ }
+ }
+ outputStream.close();
+ }
+
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/ModuleSplitter.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/ModuleSplitter.java
new file mode 100644
index 0000000..b15bb35
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/ModuleSplitter.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 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
+ */
+
+package com.android.tools.appbundle.bundle2installable;
+
+import com.android.tools.appbundle.bundle2installable.util.PathUtils;
+import com.android.tools.appbundle.bundle2installable.util.ZipWalker;
+import java.io.IOException;
+
+/**
+ * Implementation of the split-module command.
+ */
+public class ModuleSplitter {
+
+ public static void doSplitModule(String[] args) throws IOException {
+ if (args.length < 3) {
+ throw new IllegalStateException(
+ "Incorrect number of args. Use bundle2installable split-module [module.zip] [output-dir]");
+ }
+ String moduleFilename = args[1];
+ String outputFilename = args[2];
+
+ ZipWalker visitor = new ZipWalker(moduleFilename);
+ ResourcesProcessor resourcesProcessor = new ResourcesProcessor();
+ AssetsProcessor assetsProcessor = new AssetsProcessor();
+ NativeProcessor nativeProcessor = new NativeProcessor();
+ visitor.addListener(resourcesProcessor);
+ visitor.addListener(assetsProcessor);
+ visitor.addListener(nativeProcessor);
+ visitor.walk();
+
+ System.out.println("Detected asset variants: " + assetsProcessor.getAllVariants().toString());
+ System.out.println("Detected native variants: " + nativeProcessor.getAllVariants().toString());
+ System.out
+ .println("Detected resource variants: " + resourcesProcessor.getAllVariants().toString());
+
+ for (String variant : assetsProcessor.getAllVariants()) {
+ String splitName = outputFilename + "/" + PathUtils
+ .stripExtension(PathUtils.stripDirectories(moduleFilename)) + "-" + variant + ".zip";
+ PreSplitGenerator outGen = new PreSplitGenerator(splitName);
+ outGen.writePreSplit(visitor.getZipFile(), assetsProcessor.generateForVariant(variant));
+ }
+ for (String variant : nativeProcessor.getAllVariants()) {
+ String splitName = outputFilename + "/" + PathUtils
+ .stripExtension(PathUtils.stripDirectories(moduleFilename)) + "-" + variant + ".zip";
+ PreSplitGenerator outGen = new PreSplitGenerator(splitName);
+ outGen.writePreSplit(visitor.getZipFile(), nativeProcessor.generateForVariant(variant));
+ }
+ for (String variant : resourcesProcessor.getAllVariants()) {
+ String splitName = outputFilename + "/" + PathUtils
+ .stripExtension(PathUtils.stripDirectories(moduleFilename)) + "-res-" + variant + ".zip";
+ PreSplitGenerator outGen = new PreSplitGenerator(splitName);
+ outGen.writePreSplit(visitor.getZipFile(), resourcesProcessor.generateForVariant(variant));
+ }
+ }
+
+
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/ModuleZipMaker.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/ModuleZipMaker.java
new file mode 100644
index 0000000..0b5643a
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/ModuleZipMaker.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 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
+ */
+
+package com.android.tools.appbundle.bundle2installable;
+
+import com.android.tools.appbundle.bundle2installable.util.PathUtils;
+import com.android.tools.appbundle.bundle2installable.util.ZipUtils;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Class that takes a bundle and outputs the zipped modules from it.
+ *
+ * As modules are being processed separately, this will put them in the right
+ * input format.
+ *
+ * By default it outputs all modules contained in the bundle.
+ */
+public class ModuleZipMaker {
+
+ public static void generateModuleZips(String bundleFile, String outputDirectory)
+ throws IOException {
+ ZipFile bundleZip = new ZipFile(bundleFile);
+ Enumeration<? extends ZipEntry> e = bundleZip.entries();
+ Map<String, List<ZipEntry>> moduleEntries = new HashMap<>();
+ ZipEntry entry;
+ while (e.hasMoreElements()) {
+ entry = e.nextElement();
+ System.out.println(String.format("Entry: %s", entry.getName()));
+ Path p = Paths.get(entry.getName());
+ if (p.getNameCount() > 1) {
+ String moduleName = p.getName(0).toString();
+ if (!moduleEntries.containsKey(moduleName)) {
+ moduleEntries.put(moduleName, new ArrayList<ZipEntry>());
+ }
+ if (!entry.isDirectory()) {
+ moduleEntries.get(moduleName).add(entry);
+ }
+ }
+ }
+ for (String moduleDirectory : moduleEntries.keySet()) {
+ System.out.println(String.format("Module: %s", moduleDirectory));
+ for (ZipEntry moduleEntry : moduleEntries.get(moduleDirectory)) {
+ System.out.println(String.format("Entry : %s", moduleEntry.getName()));
+ }
+ deflateModule(Paths.get(outputDirectory, moduleDirectory + ".zip").toString(),
+ bundleZip, moduleEntries.get(moduleDirectory));
+ }
+ }
+
+ private static void deflateModule(String moduleZipOutput, ZipFile bundleFile,
+ List<ZipEntry> entries) throws IOException {
+ ZipOutputStream outputStream = new ZipOutputStream(new FileOutputStream(moduleZipOutput));
+ for (ZipEntry entry : entries) {
+ ZipUtils.writeEntry(PathUtils.getFilePathWithoutTopLevel(entry.getName()), outputStream,
+ bundleFile.getInputStream(entry));
+ }
+ outputStream.close();
+ }
+
+ private static boolean isModuleDirectory(ZipEntry entry) {
+ throw new UnsupportedOperationException("Not implemented yet");
+ }
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/NativeProcessor.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/NativeProcessor.java
new file mode 100644
index 0000000..e742621
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/NativeProcessor.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 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
+ */
+
+package com.android.tools.appbundle.bundle2installable;
+
+import com.android.tools.appbundle.bundle2installable.util.Pair;
+import com.android.tools.appbundle.bundle2installable.util.PathUtils;
+import com.android.tools.appbundle.bundle2installable.util.ZipWalker;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Processes native library directory and discovers cpu variants.
+ *
+ * Currently, expects ABI to be expressed as a subdirectory name inside "native" directory.
+ * The libraries are outputted to the "lib" directory as per APK standard.
+ */
+public class NativeProcessor implements ZipWalker.ZipEntryListener, VariantProcessor {
+
+ private static final String NATIVE_DIR = "native";
+ private static final String DESTINATION_DIR = "lib";
+
+ private Map<String, List<ZipEntry>> variantLibsMap;
+ private List<ZipEntry> nonVariantLibs;
+
+ public NativeProcessor() {
+ this.variantLibsMap = new HashMap<>();
+ this.nonVariantLibs = new ArrayList<>();
+ }
+
+ public void notify(ZipFile bundle, ZipEntry entry) throws IOException {
+ Path p = Paths.get(entry.getName());
+ if (PathUtils.isInsideTopDirectory(p, NATIVE_DIR)) {
+ String variant = PathUtils.resolveVariant(p);
+ if (variant != null) {
+ if (!variantLibsMap.containsKey(variant)) {
+ variantLibsMap.put(variant, new ArrayList<>());
+ }
+ variantLibsMap.get(variant).add(entry);
+ } else { // inside the libs directory
+ if (!entry.isDirectory()) {
+ // aapt2 doesn't strip any files in libs/ top-level. So we should
+ // include them in each variant as well.
+ nonVariantLibs.add(entry);
+ }
+ }
+ }
+ }
+
+ public List<String> getAllVariants() {
+ return new ArrayList<>(variantLibsMap.keySet());
+ }
+
+ /**
+ * Returns pair of zip entries and the destination path in the split for the variant
+ */
+ public List<Pair<ZipEntry, String>> generateForVariant(String variant) {
+ List<Pair<ZipEntry, String>> res = new ArrayList<>();
+ Set<String> coveredFiles = new HashSet<>();
+ if (!variantLibsMap.containsKey(variant)) {
+ throw new IllegalStateException(
+ String.format("Variant '%s' doesn't exist for the native libraries.", variant));
+ } else {
+ for (ZipEntry entry : variantLibsMap.get(variant)) {
+ String pathWithoutVariant = PathUtils.getFilePathWithoutVariant(entry.getName());
+ coveredFiles.add(pathWithoutVariant);
+ // our destination path needs to include architecture/variant though
+ Path destinationPath = Paths.get(DESTINATION_DIR, variant, pathWithoutVariant);
+ res.add(Pair.of(entry, destinationPath.toString()));
+ }
+ }
+ for (ZipEntry entry : nonVariantLibs) {
+ String pathWithoutVariant = PathUtils.getFilePathWithoutTopLevel(entry.getName());
+ if (!coveredFiles.contains(pathWithoutVariant)) {
+ res.add(Pair.of(entry, Paths.get(DESTINATION_DIR, pathWithoutVariant).toString()));
+ }
+ }
+ return res;
+ }
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/PreSplitGenerator.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/PreSplitGenerator.java
new file mode 100644
index 0000000..b646ef9
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/PreSplitGenerator.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2017 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
+ */
+
+package com.android.tools.appbundle.bundle2installable;
+
+import com.android.tools.appbundle.bundle2installable.util.Pair;
+import com.android.tools.appbundle.bundle2installable.util.ZipUtils;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Generates a zip file with assets, resources and libs in a correct place.
+ *
+ * This is not yet APK split as it has to be processed by the aapt2.
+ */
+public class PreSplitGenerator {
+
+ private ZipOutputStream outputStream;
+
+ PreSplitGenerator(String outputFile) throws FileNotFoundException {
+ this.outputStream = new ZipOutputStream(new FileOutputStream(outputFile));
+ }
+
+ public void writePreSplit(ZipFile moduleZip, List<Pair<ZipEntry, String>> entriesToWrite)
+ throws IOException, FileNotFoundException {
+ for (Pair<ZipEntry, String> entry : entriesToWrite) {
+ ZipUtils.writeEntry(entry.getSecond(), outputStream,
+ moduleZip.getInputStream(entry.getFirst()));
+ }
+ outputStream.close();
+ }
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/ResourcesProcessor.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/ResourcesProcessor.java
new file mode 100644
index 0000000..ffd7264
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/ResourcesProcessor.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 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
+ */
+
+package com.android.tools.appbundle.bundle2installable;
+
+import com.android.tools.appbundle.bundle2installable.util.Pair;
+import com.android.tools.appbundle.bundle2installable.util.ProtoUtils;
+import com.android.tools.appbundle.bundle2installable.util.ZipWalker;
+import com.android.tools.aapt2.FormatProto;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Detects variants in the res directory of the module zip.
+ *
+ * Currently, this class detects only one variant that will contain all resources.
+ */
+public class ResourcesProcessor implements ZipWalker.ZipEntryListener, VariantProcessor {
+
+ public static final String FORMAT_PROTO_NAME = "format.textpb";
+ public static final String RES_DIRECTORY = "res/";
+
+ /**
+ * For now we just copy all resources
+ */
+ private List<ZipEntry> allVariants;
+
+ public ResourcesProcessor() {
+ this.allVariants = new ArrayList<ZipEntry>();
+ }
+
+ public void notify(ZipFile bundle, ZipEntry entry) throws IOException {
+ if (entry.getName().equals(FORMAT_PROTO_NAME)) {
+ FormatProto.ResourceTable resourceProto = ProtoUtils.extractResourceProto(bundle, entry);
+ // ... processing
+ }
+ if (entry.getName().startsWith(RES_DIRECTORY)) {
+ if (!entry.isDirectory()) {
+ allVariants.add(entry);
+ }
+ }
+ }
+
+ /**
+ * For now, just generate everything
+ */
+ public List<Pair<ZipEntry, String>> generateForVariant(String variant) {
+ List<Pair<ZipEntry, String>> res = new ArrayList<>();
+ for (ZipEntry entry : allVariants) {
+ res.add(Pair.of(entry, entry.getName()));
+ }
+ return res;
+ }
+
+ /**
+ * Recognizes only one default variant
+ */
+ public List<String> getAllVariants() {
+ return Arrays.asList("default");
+ }
+
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/VariantProcessor.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/VariantProcessor.java
new file mode 100644
index 0000000..c7bde25
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/VariantProcessor.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 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
+ */
+
+package com.android.tools.appbundle.bundle2installable;
+
+import com.android.tools.appbundle.bundle2installable.util.Pair;
+import java.util.List;
+import java.util.zip.ZipEntry;
+
+/**
+ * Interface that describes ability to generate a set of files in the resulting split APK for a
+ * given variant. Currently, the notion of a variant is very simplified: one dimension, and a
+ * string.
+ */
+public interface VariantProcessor {
+
+ /**
+ * Returns the list of files from the module zip to be included in the split for a variant.
+ *
+ * @param variant a simple string representing a variant, currently we support only one
+ * dimension.
+ * @return A list of pairs: {@code ZipEntry} from the module zip to be included in the split,
+ * {@code string} destination path in the split.
+ */
+ List<Pair<ZipEntry, String>> generateForVariant(String variant);
+
+ /**
+ * Returns list of all variants detected by this processor.
+ */
+ List<String> getAllVariants();
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/Pair.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/Pair.java
new file mode 100644
index 0000000..26b7e72
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/Pair.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 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
+ */
+
+package com.android.tools.appbundle.bundle2installable.util;
+
+/**
+ * Pair of two elements.
+ */
+public final class Pair<A, B> {
+
+ private final A mFirst;
+ private final B mSecond;
+
+ private Pair(A first, B second) {
+ mFirst = first;
+ mSecond = second;
+ }
+
+ public static <A, B> Pair<A, B> of(A first, B second) {
+ return new Pair<A, B>(first, second);
+ }
+
+ public A getFirst() {
+ return mFirst;
+ }
+
+ public B getSecond() {
+ return mSecond;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((mFirst == null) ? 0 : mFirst.hashCode());
+ result = prime * result + ((mSecond == null) ? 0 : mSecond.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ @SuppressWarnings("rawtypes")
+ Pair other = (Pair) obj;
+ if (mFirst == null) {
+ if (other.mFirst != null) {
+ return false;
+ }
+ } else if (!mFirst.equals(other.mFirst)) {
+ return false;
+ }
+ if (mSecond == null) {
+ if (other.mSecond != null) {
+ return false;
+ }
+ } else if (!mSecond.equals(other.mSecond)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/PathUtils.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/PathUtils.java
new file mode 100644
index 0000000..0169525
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/PathUtils.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 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
+ */
+
+package com.android.tools.appbundle.bundle2installable.util;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import javax.annotation.Nullable;
+
+/**
+ * Various utilities for path processing.
+ */
+public class PathUtils {
+
+ private static final int TOP_LEVEL = 0;
+ private static final int VARIANT_LEVEL = 1;
+ private static final int LEVEL_IN_VARIANT = 2;
+ private static final int LEVEL_IN_DEFAULT = 1;
+
+ public static String stripExtension(String filename) {
+ return filename.substring(0, filename.lastIndexOf('.'));
+ }
+
+ public static String makeSplitName(String module, String split) {
+ return module + "-" + split + ".zip";
+ }
+
+ public static String stripDirectories(String fullPath) {
+ Path p = Paths.get(fullPath);
+ return p.getName(p.getNameCount() - 1).toString();
+ }
+
+ public static boolean isInsideTopDirectory(Path p, String directoryName) {
+ return p.getNameCount() > 1 && p.getName(TOP_LEVEL).toString().equals(directoryName);
+ }
+
+ @Nullable
+ public static String resolveVariant(Path p) {
+ if (p.getNameCount() > 2) { // inside the variant directory
+ return p.getName(VARIANT_LEVEL).toString();
+ }
+ return null;
+ }
+
+ public static String getFilePathWithoutVariant(String entryPath) {
+ return removeTopLevelPath(entryPath, LEVEL_IN_VARIANT);
+ }
+
+ public static String getFilePathWithoutTopLevel(String entryPath) {
+ return removeTopLevelPath(entryPath, LEVEL_IN_DEFAULT);
+ }
+
+ public static String removeTopLevelPath(String entryPath, int numLevels) {
+ Path p = Paths.get(entryPath);
+ System.out.println(entryPath);
+ return p.subpath(numLevels, p.getNameCount()).toString();
+ }
+
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/ProtoUtils.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/ProtoUtils.java
new file mode 100644
index 0000000..6d0c787
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/ProtoUtils.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 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
+ */
+
+package com.android.tools.appbundle.bundle2installable.util;
+
+import com.android.tools.aapt2.FormatProto;
+import com.google.protobuf.TextFormat;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * Protobuf utility functions used throughout the tool.
+ */
+public class ProtoUtils {
+
+ /**
+ * Reads format.proto from the aapt2 output. For now using the textproto format.
+ */
+ public static FormatProto.ResourceTable extractResourceProto(ZipFile bundle, ZipEntry entry)
+ throws IOException {
+ System.out.println(String.format("Extracting %s", entry.getName()));
+
+ FormatProto.ResourceTable.Builder tableBuilder = FormatProto.ResourceTable.newBuilder();
+ TextFormat.merge(new InputStreamReader(bundle.getInputStream(entry)), tableBuilder);
+ return tableBuilder.build();
+ }
+
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/ZipUtils.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/ZipUtils.java
new file mode 100644
index 0000000..ae4b5ac
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/ZipUtils.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 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
+ */
+
+package com.android.tools.appbundle.bundle2installable.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * Various zip utilities to avoid code duplication.
+ */
+public class ZipUtils {
+
+ private static final int BUFFER = 2048;
+
+ public static void writeEntry(String entryName, ZipOutputStream outputStream,
+ InputStream inputStream) throws IOException {
+ outputStream.putNextEntry(new ZipEntry(entryName));
+ byte data[] = new byte[BUFFER];
+ int count;
+ while ((count = inputStream.read(data, 0, BUFFER)) != -1) {
+ outputStream.write(data, 0, count);
+ }
+ inputStream.close();
+ outputStream.closeEntry();
+ }
+}
diff --git a/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/ZipWalker.java b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/ZipWalker.java
new file mode 100644
index 0000000..71c3060
--- /dev/null
+++ b/bundle2installable/src/java/com/android/tools/appbundle/bundle2installable/util/ZipWalker.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2017 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
+ */
+
+package com.android.tools.appbundle.bundle2installable.util;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+public class ZipWalker {
+
+ private String zipFilename;
+ private List<ZipEntryListener> listeners;
+ private ZipFile zipFile;
+
+ public ZipWalker(String zipFilename) {
+ this.zipFilename = zipFilename;
+ this.listeners = new ArrayList<ZipEntryListener>();
+ this.zipFile = null;
+ }
+
+ public void walk() throws IOException {
+ zipFile = new ZipFile(zipFilename);
+ Enumeration<? extends ZipEntry> e = zipFile.entries();
+ ZipEntry entry;
+ while (e.hasMoreElements()) {
+ entry = e.nextElement();
+ System.out.println(String.format("Entry : %s", entry.getName()));
+ for (ZipEntryListener listener : listeners) {
+ listener.notify(zipFile, entry);
+ }
+ }
+ }
+
+ public void addListener(ZipEntryListener listener) {
+ listeners.add(listener);
+ }
+
+ public ZipFile getZipFile() {
+ return zipFile;
+ }
+
+ public interface ZipEntryListener {
+
+ void notify(ZipFile bundle, ZipEntry entry) throws IOException;
+ }
+}
diff --git a/bundle2installable/src/proto/Format.proto b/bundle2installable/src/proto/Format.proto
new file mode 100644
index 0000000..8b0f81d
--- /dev/null
+++ b/bundle2installable/src/proto/Format.proto
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+syntax = "proto2";
+
+// option optimize_for = LITE_RUNTIME;
+
+package aapt.pb;
+
+option java_package = "com.android.tools.aapt2";
+option java_outer_classname = "FormatProto";
+
+message ConfigDescription {
+ optional bytes data = 1;
+ optional string product = 2;
+}
+
+message StringPool {
+ optional bytes data = 1;
+}
+
+message CompiledFile {
+ message Symbol {
+ optional string resource_name = 1;
+ optional uint32 line_no = 2;
+ }
+
+ optional string resource_name = 1;
+ optional ConfigDescription config = 2;
+ optional string source_path = 3;
+ repeated Symbol exported_symbols = 4;
+}
+
+message ResourceTable {
+ optional StringPool string_pool = 1;
+ optional StringPool source_pool = 2;
+ optional StringPool symbol_pool = 3;
+ repeated Package packages = 4;
+}
+
+message Package {
+ optional uint32 package_id = 1;
+ optional string package_name = 2;
+ repeated Type types = 3;
+}
+
+message Type {
+ optional uint32 id = 1;
+ optional string name = 2;
+ repeated Entry entries = 3;
+}
+
+message SymbolStatus {
+ enum Visibility {
+ Unknown = 0;
+ Private = 1;
+ Public = 2;
+ }
+ optional Visibility visibility = 1;
+ optional Source source = 2;
+ optional string comment = 3;
+ optional bool allow_new = 4;
+}
+
+message Entry {
+ optional uint32 id = 1;
+ optional string name = 2;
+ optional SymbolStatus symbol_status = 3;
+ repeated ConfigValue config_values = 4;
+}
+
+message ConfigValue {
+ optional ConfigDescription config = 1;
+ optional Value value = 2;
+}
+
+message Source {
+ optional uint32 path_idx = 1;
+ optional uint32 line_no = 2;
+ optional uint32 col_no = 3;
+}
+
+message Reference {
+ enum Type {
+ Ref = 0;
+ Attr = 1;
+ }
+ optional Type type = 1;
+ optional uint32 id = 2;
+ optional uint32 symbol_idx = 3;
+ optional bool private = 4;
+}
+
+message Id {
+}
+
+message XString {
+ optional uint32 idx = 1;
+}
+
+message RawString {
+ optional uint32 idx = 1;
+}
+
+message FileReference {
+ optional uint32 path_idx = 1;
+}
+
+message Primitive {
+ optional uint32 type = 1;
+ optional uint32 data = 2;
+}
+
+message Attribute {
+ message Symbol {
+ optional Source source = 1;
+ optional string comment = 2;
+ optional Reference name = 3;
+ optional uint32 value = 4;
+ }
+ optional uint32 format_flags = 1;
+ optional int32 min_int = 2;
+ optional int32 max_int = 3;
+ repeated Symbol symbols = 4;
+}
+
+message Style {
+ message Entry {
+ optional Source source = 1;
+ optional string comment = 2;
+ optional Reference key = 3;
+ optional Item item = 4;
+ }
+
+ optional Reference parent = 1;
+ optional Source parent_source = 2;
+ repeated Entry entries = 3;
+}
+
+message Styleable {
+ message Entry {
+ optional Source source = 1;
+ optional string comment = 2;
+ optional Reference attr = 3;
+ }
+ repeated Entry entries = 1;
+}
+
+message Array {
+ message Entry {
+ optional Source source = 1;
+ optional string comment = 2;
+ optional Item item = 3;
+ }
+ repeated Entry entries = 1;
+}
+
+message Plural {
+ enum Arity {
+ Zero = 0;
+ One = 1;
+ Two = 2;
+ Few = 3;
+ Many = 4;
+ Other = 5;
+ }
+
+ message Entry {
+ optional Source source = 1;
+ optional string comment = 2;
+ optional Arity arity = 3;
+ optional Item item = 4;
+ }
+ repeated Entry entries = 1;
+}
+
+message Item {
+ optional Reference ref = 1;
+ optional XString str = 2;
+ optional RawString raw_str = 3;
+ optional FileReference file = 4;
+ optional Id id = 5;
+ optional Primitive prim = 6;
+}
+
+message CompoundValue {
+ optional Attribute attr = 1;
+ optional Style style = 2;
+ optional Styleable styleable = 3;
+ optional Array array = 4;
+ optional Plural plural = 5;
+}
+
+message Value {
+ optional Source source = 1;
+ optional string comment = 2;
+ optional bool weak = 3;
+
+ optional Item item = 4;
+ optional CompoundValue compound_value = 5;
+}