diff options
author | kuantung <kuantung@google.com> | 2017-07-31 10:16:44 -0700 |
---|---|---|
committer | Tom Dobek <tdobek@google.com> | 2017-08-02 15:31:47 +0100 |
commit | 5e25b96772b38f70dda4fab22f671933c9238737 (patch) | |
tree | 8287e9f1597057808fe61164a159c18fb68c3ed6 | |
download | appbundle-5e25b96772b38f70dda4fab22f671933c9238737.tar.gz |
Initial, fledgling implementation of bundle2installable tool.
Test: e2e.sh
Change-Id: I30c032ffca0908bf5d81869d563308ed0ff30aab
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 Binary files differnew file mode 100644 index 0000000..21cb2f2 --- /dev/null +++ b/bundle2installable/example/bundle/bundle.zip 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 Binary files differnew file mode 100644 index 0000000..7b00859 --- /dev/null +++ b/bundle2installable/example/module/module.zip 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; +} |