summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2018-08-06 16:52:56 -0700
committerXin Li <delphij@google.com>2018-08-06 16:52:56 -0700
commit9f85eef4a9394b99031dd4c2aaafc4b74f3b303e (patch)
treec4b6047eda351d2e6bcbd190dc1e80bf06755fb6
parent4d5d2806a9eef94b589d282c773f26f7e94459a0 (diff)
parentc24ae13cbeb590b56547b0e539f1be195160c93c (diff)
downloadappbundle-9f85eef4a9394b99031dd4c2aaafc4b74f3b303e.tar.gz
Merge Android Pie into mastersdk-release
Bug: 112104996 Change-Id: Ie05d3082adfc859729dfd395e80c597fe4e5e496
-rw-r--r--bundletool/Android.mk37
-rw-r--r--bundletool/etc/bundletool89
-rw-r--r--bundletool/etc/bundletool.bat88
-rw-r--r--bundletool/java/com/android/tools/appbundle/bundletool/AppBundle.java71
-rw-r--r--bundletool/java/com/android/tools/appbundle/bundletool/BuildModuleCommand.java197
-rw-r--r--bundletool/java/com/android/tools/appbundle/bundletool/BundleModule.java70
-rw-r--r--bundletool/java/com/android/tools/appbundle/bundletool/BundleToolMain.java104
-rw-r--r--bundletool/java/com/android/tools/appbundle/bundletool/Command.java35
-rw-r--r--bundletool/java/com/android/tools/appbundle/bundletool/SplitModuleCommand.java80
-rw-r--r--bundletool/java/com/android/tools/appbundle/bundletool/utils/FlagParser.java147
-rw-r--r--bundletool/manifest.txt1
11 files changed, 919 insertions, 0 deletions
diff --git a/bundletool/Android.mk b/bundletool/Android.mk
new file mode 100644
index 0000000..b6c20f8
--- /dev/null
+++ b/bundletool/Android.mk
@@ -0,0 +1,37 @@
+LOCAL_PATH:= $(call my-dir)
+
+## bundletool script
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := bundletool
+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)/bundletool$(COMMON_JAVA_PACKAGE_SUFFIX)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/etc/bundletool | $(ACP)
+ @echo "Copy: $(PRIVATE_MODULE) ($@)"
+ $(copy-file-to-new-target)
+ $(hide) chmod 755 $@
+
+## tool jar
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := bundletool
+
+LOCAL_SRC_FILES := $(call all-java-files-under, java)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := guavalib jsr305lib dagger2-auto-value-host error_prone_annotations-2.0.18
+
+LOCAL_ANNOTATION_PROCESSORS := dagger2-auto-value-host
+LOCAL_ANNOTATION_PROCESSOR_CLASSES := com.google.auto.value.processor.AutoValueProcessor
+
+LOCAL_JAR_MANIFEST := manifest.txt
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/bundletool/etc/bundletool b/bundletool/etc/bundletool
new file mode 100644
index 0000000..ac7cac3
--- /dev/null
+++ b/bundletool/etc/bundletool
@@ -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/bundletool/etc/bundletool.bat b/bundletool/etc/bundletool.bat
new file mode 100644
index 0000000..09a9d36
--- /dev/null
+++ b/bundletool/etc/bundletool.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/bundletool/java/com/android/tools/appbundle/bundletool/AppBundle.java b/bundletool/java/com/android/tools/appbundle/bundletool/AppBundle.java
new file mode 100644
index 0000000..cbdf4e1
--- /dev/null
+++ b/bundletool/java/com/android/tools/appbundle/bundletool/AppBundle.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.bundletool;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Maps;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/** Represents an app bundle. */
+public class AppBundle {
+
+ private ZipFile bundleFile;
+ private Map<String, BundleModule> modules;
+
+ public AppBundle(ZipFile bundleFile) {
+ this.bundleFile = bundleFile;
+ this.modules = new HashMap<>();
+ open();
+ }
+
+ private void open() {
+ Map<String, BundleModule.Builder> moduleBuilders = new HashMap<>();
+ Enumeration<? extends ZipEntry> entries = bundleFile.entries();
+ while (entries.hasMoreElements()) {
+ ZipEntry entry = entries.nextElement();
+ Path path = Paths.get(entry.getName());
+ if (path.getNameCount() > 1) {
+ String moduleName = path.getName(0).toString();
+ BundleModule.Builder moduleBuilder =
+ moduleBuilders.computeIfAbsent(
+ moduleName, name -> new BundleModule.Builder(name, this));
+ moduleBuilder.addZipEntry(entry);
+ }
+ }
+ modules.putAll(Maps.transformValues(moduleBuilders, BundleModule.Builder::build));
+ }
+
+ public Map<String, BundleModule> getModules() {
+ return ImmutableMap.copyOf(modules);
+ }
+
+ public BundleModule getModule(String moduleName) {
+ return modules.get(moduleName);
+ }
+
+ public InputStream getEntryInputStream(ZipEntry entry) throws IOException {
+ return bundleFile.getInputStream(entry);
+ }
+}
diff --git a/bundletool/java/com/android/tools/appbundle/bundletool/BuildModuleCommand.java b/bundletool/java/com/android/tools/appbundle/bundletool/BuildModuleCommand.java
new file mode 100644
index 0000000..8e30d9a
--- /dev/null
+++ b/bundletool/java/com/android/tools/appbundle/bundletool/BuildModuleCommand.java
@@ -0,0 +1,197 @@
+/*
+ * 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.bundletool;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.android.tools.appbundle.bundletool.utils.FlagParser;
+import com.google.auto.value.AutoValue;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+
+/** Command responsible for building an App Bundle module. */
+@AutoValue
+public abstract class BuildModuleCommand {
+
+ public static final String COMMAND_NAME = "build-module";
+
+ private static final String OUTPUT_FLAG = "output";
+ private static final String MANIFEST_FLAG = "manifest";
+ private static final String MANIFEST_DIR_FLAG = "manifest-dir";
+ private static final String DEX_FLAG = "dex";
+ private static final String DEX_DIR_FLAG = "dex-dir";
+ private static final String RESOURCES_DIR_FLAG = "resources-dir";
+ private static final String ASSETS_DIR_FLAG = "assets-dir";
+ private static final String NATIVE_DIR_FLAG = "native-dir";
+
+ abstract Path getOutputPath();
+
+ abstract Optional<Path> getManifestPath();
+
+ abstract Optional<Path> getManifestDirPath();
+
+ abstract Optional<Path> getDexPath();
+
+ abstract Optional<Path> getDexDirPath();
+
+ abstract Optional<Path> getResourcesDirPath();
+
+ abstract Optional<Path> getAssetsDirPath();
+
+ abstract Optional<Path> getNativeDirPath();
+
+ public static Builder builder() {
+ return new AutoValue_BuildModuleCommand.Builder();
+ }
+
+ /** Builder for the {@link BuildModuleCommand} */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ abstract Builder setOutputPath(Path outputPath);
+
+ abstract Builder setManifestPath(Path manifestPath);
+
+ abstract Builder setManifestDirPath(Path manifestDirPath);
+
+ abstract Builder setDexPath(Path dexPath);
+
+ abstract Builder setDexDirPath(Path dexDirPath);
+
+ abstract Builder setResourcesDirPath(Path resourcesDirPath);
+
+ abstract Builder setAssetsDirPath(Path assetsDirPath);
+
+ abstract Builder setNativeDirPath(Path nativeDirPath);
+
+ abstract BuildModuleCommand build();
+ }
+
+ static BuildModuleCommand fromFlags(FlagParser flagParser) {
+ Builder builder =
+ builder().setOutputPath(Paths.get(flagParser.getRequiredFlagValue(OUTPUT_FLAG)));
+ flagParser.getFlagValueAsPath(MANIFEST_FLAG).ifPresent(builder::setManifestPath);
+ flagParser.getFlagValueAsPath(MANIFEST_DIR_FLAG).ifPresent(builder::setManifestDirPath);
+ flagParser.getFlagValueAsPath(DEX_FLAG).ifPresent(builder::setDexPath);
+ flagParser.getFlagValueAsPath(DEX_DIR_FLAG).ifPresent(builder::setDexDirPath);
+ flagParser.getFlagValueAsPath(RESOURCES_DIR_FLAG).ifPresent(builder::setResourcesDirPath);
+ flagParser.getFlagValueAsPath(ASSETS_DIR_FLAG).ifPresent(builder::setAssetsDirPath);
+ flagParser.getFlagValueAsPath(NATIVE_DIR_FLAG).ifPresent(builder::setNativeDirPath);
+
+ return builder.build();
+ }
+
+ public void execute() {
+ validateInput();
+
+
+ }
+
+ private void validateInput() {
+ checkArgument(
+ getManifestPath().isPresent() || getManifestDirPath().isPresent(),
+ "One of --%s or --%s is required.",
+ MANIFEST_FLAG,
+ MANIFEST_DIR_FLAG);
+ checkArgument(
+ !getManifestPath().isPresent() || !getManifestDirPath().isPresent(),
+ "Cannot set both --%s and --%s flags.",
+ MANIFEST_FLAG,
+ MANIFEST_DIR_FLAG);
+ checkArgument(
+ !getDexPath().isPresent() || !getDexDirPath().isPresent(),
+ "Cannot set both --%s and --%s flags.",
+ DEX_FLAG,
+ DEX_DIR_FLAG);
+
+ checkArgument(!Files.exists(getOutputPath()), "File %s already exists.", getOutputPath());
+ checkFileExistsAndReadable(getManifestPath());
+ checkDirectoryExists(getManifestDirPath());
+ checkFileExistsAndReadable(getDexPath());
+ checkDirectoryExists(getDexDirPath());
+ checkDirectoryExists(getResourcesDirPath());
+ checkDirectoryExists(getAssetsDirPath());
+ checkDirectoryExists(getNativeDirPath());
+ }
+
+ private static void checkFileExistsAndReadable(Optional<Path> pathOptional) {
+ if (pathOptional.isPresent()) {
+ Path path = pathOptional.get();
+ checkArgument(Files.exists(path), "File '%s' was not found.", path);
+ checkArgument(Files.isReadable(path), "File '%s' is not readable.", path);
+ }
+ }
+
+ private static void checkDirectoryExists(Optional<Path> pathOptional) {
+ if (pathOptional.isPresent()) {
+ Path path = pathOptional.get();
+ checkArgument(Files.exists(path), "Directory '%s' was not found.", path);
+ checkArgument(Files.isDirectory(path), "'%s' is not a directory.");
+ }
+ }
+
+ public static void help() {
+ System.out.println(
+ String.format(
+ "bundletool %s --output=<path/to/module.zip> "
+ + "[--%s=<path/to/AndroidManifest.flat>|--%s=<path/to/manifest-dir/>] "
+ + "[--%s=<path/to/classes.dex>|--%s=<path/to/dex-dir/>] "
+ + "[--%s=<path/to/res/>] "
+ + "[--%s=<path/to/assets/>] "
+ + "[--%s=<path/to/lib/>] ",
+ COMMAND_NAME,
+ MANIFEST_FLAG,
+ MANIFEST_DIR_FLAG,
+ DEX_FLAG,
+ DEX_DIR_FLAG,
+ RESOURCES_DIR_FLAG,
+ ASSETS_DIR_FLAG,
+ NATIVE_DIR_FLAG));
+ System.out.println();
+ System.out.println(
+ "Builds a module as a zip from an app's project. Note that the resources and the "
+ + "AndroidManifest.xml must already have been compiled with aapt2.");
+ System.out.println();
+ System.out.println("--output: Path to the zip file to build.");
+ System.out.printf(
+ "--%s: Path to the AndroidManifest.flat compiled by aapt2. Use --%s if there "
+ + "are more than one.\n",
+ MANIFEST_FLAG, MANIFEST_DIR_FLAG);
+ System.out.printf(
+ "--%s: Path to the directory containing multiple Android manifests compiled by aapt2. "
+ + "A file named 'manifest-targeting.xml' must be present in the directory "
+ + "describing the targeting of each manifest present.\n",
+ MANIFEST_DIR_FLAG);
+ System.out.printf(
+ "--%s: Path to the dex file. Use --%s if there are more than one.\n",
+ DEX_FLAG, DEX_DIR_FLAG);
+ System.out.printf(
+ "--%s: Path to the directory containing multiple dex files. Unless all dex files must "
+ + "be included in the generated APKs (for MultiDex), a file named "
+ + "'dex-targeting.xml' must be present in the directory describing the targeting "
+ + "of the different dex files.\n",
+ DEX_DIR_FLAG);
+ System.out.printf(
+ "--%s: Path to the directory containing the resources file(s). A file named "
+ + "'resources.flat' must be present in that directory corresponding to the output "
+ + "of the aapt2 compilation of the resources.\n",
+ RESOURCES_DIR_FLAG);
+ System.out.printf("--%s: Path to the directory containing the assets.\n", ASSETS_DIR_FLAG);
+ System.out.printf(
+ "--%s: Path to the directory containing the native libraries.\n", NATIVE_DIR_FLAG);
+ }
+}
diff --git a/bundletool/java/com/android/tools/appbundle/bundletool/BundleModule.java b/bundletool/java/com/android/tools/appbundle/bundletool/BundleModule.java
new file mode 100644
index 0000000..c88d287
--- /dev/null
+++ b/bundletool/java/com/android/tools/appbundle/bundletool/BundleModule.java
@@ -0,0 +1,70 @@
+/*
+ * 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.bundletool;
+
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.ZipEntry;
+
+/** Represents a single module inside App Bundle. */
+public class BundleModule {
+
+ private AppBundle parent;
+ private String name;
+ private List<ZipEntry> entries;
+
+ private BundleModule(String name, AppBundle parent, List<ZipEntry> entries) {
+ this.parent = parent;
+ this.name = name;
+ this.entries = entries;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public AppBundle getParent() {
+ return parent;
+ }
+
+ public List<ZipEntry> getEntries() {
+ return ImmutableList.copyOf(entries);
+ }
+
+ /** Builder for BundleModule. */
+ public static class Builder {
+ private List<ZipEntry> entries;
+ private String name;
+ private AppBundle parent;
+
+ public Builder(String name, AppBundle parent) {
+ this.name = name;
+ this.parent = parent;
+ this.entries = new ArrayList<>();
+ }
+
+ public Builder addZipEntry(ZipEntry entry) {
+ this.entries.add(entry);
+ return this;
+ }
+
+ public BundleModule build() {
+ return new BundleModule(name, parent, entries);
+ }
+ }
+}
diff --git a/bundletool/java/com/android/tools/appbundle/bundletool/BundleToolMain.java b/bundletool/java/com/android/tools/appbundle/bundletool/BundleToolMain.java
new file mode 100644
index 0000000..330a991
--- /dev/null
+++ b/bundletool/java/com/android/tools/appbundle/bundletool/BundleToolMain.java
@@ -0,0 +1,104 @@
+/*
+ * 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.bundletool;
+
+import com.android.tools.appbundle.bundletool.utils.FlagParser;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Main entry point of the bundle tool.
+ *
+ * <p>Consider running with -Dsun.zip.disableMemoryMapping when dealing with large bundles.
+ */
+public class BundleToolMain {
+
+ public static final String LINK_CMD = "link";
+ public static final String HELP_CMD = "help";
+
+ /** Parses the flags and routes to the appropriate command handler */
+ public static void main(String[] args) throws IOException {
+
+ FlagParser flagParser = new FlagParser();
+ try {
+ flagParser.parse(args);
+ } catch (FlagParser.ParseException e) {
+ System.out.println(String.format("Error while parsing the flags: %s", e.getMessage()));
+ return;
+ }
+ List<String> commands = flagParser.getCommands();
+
+ if (commands.isEmpty()) {
+ System.out.println("Error: you have to specify a command");
+ help();
+ return;
+ }
+
+ try {
+ switch (commands.get(0)) {
+ case BuildModuleCommand.COMMAND_NAME:
+ BuildModuleCommand.fromFlags(flagParser).execute();
+ break;
+ case SplitModuleCommand.COMMAND_NAME:
+ new SplitModuleCommand(flagParser).execute();
+ break;
+ case LINK_CMD:
+ throw new UnsupportedOperationException("Not implemented.");
+ case HELP_CMD:
+ if (commands.size() > 1) {
+ help(commands.get(1));
+ } else {
+ help();
+ }
+ return;
+ default:
+ System.out.println("Error: unrecognized command.");
+ help();
+ }
+ } catch (Exception e) {
+ System.out.println("Error: " + e.getMessage());
+ }
+ }
+
+ /** Displays a general help. */
+ public static void help() {
+ System.out.println(
+ String.format(
+ "bundletool [%s|%s|%s|%s] ...",
+ BuildModuleCommand.COMMAND_NAME, SplitModuleCommand.COMMAND_NAME, LINK_CMD, HELP_CMD));
+ System.out.println("Type: bundletool help [command] to learn more about a given command.");
+ }
+
+ /** Displays help about a given command. */
+ public static void help(String commandName) {
+ switch (commandName) {
+ case BuildModuleCommand.COMMAND_NAME:
+ BuildModuleCommand.help();
+ break;
+ case SplitModuleCommand.COMMAND_NAME:
+ SplitModuleCommand.help();
+ break;
+ case LINK_CMD:
+ System.out.println("Help is not yet available.");
+ break;
+ default:
+ System.out.println("Unrecognized command.");
+ help();
+ break;
+ }
+ }
+}
diff --git a/bundletool/java/com/android/tools/appbundle/bundletool/Command.java b/bundletool/java/com/android/tools/appbundle/bundletool/Command.java
new file mode 100644
index 0000000..670c0dc
--- /dev/null
+++ b/bundletool/java/com/android/tools/appbundle/bundletool/Command.java
@@ -0,0 +1,35 @@
+/*
+ * 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.bundletool;
+
+/** Interface for command implementations. */
+public interface Command {
+
+ void execute() throws ExecutionException;
+
+ /** Error indicating something went wrong during executing the command. */
+ class ExecutionException extends RuntimeException {
+
+ public ExecutionException(String message) {
+ super(message);
+ }
+
+ public ExecutionException(String message, Throwable cause) {
+ super(message, cause);
+ }
+ }
+}
diff --git a/bundletool/java/com/android/tools/appbundle/bundletool/SplitModuleCommand.java b/bundletool/java/com/android/tools/appbundle/bundletool/SplitModuleCommand.java
new file mode 100644
index 0000000..f6909af
--- /dev/null
+++ b/bundletool/java/com/android/tools/appbundle/bundletool/SplitModuleCommand.java
@@ -0,0 +1,80 @@
+/*
+ * 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.bundletool;
+
+import com.android.tools.appbundle.bundletool.utils.FlagParser;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+/** Implementation of the command to generate module splits. */
+public class SplitModuleCommand implements Command {
+
+ private final String bundleLocation;
+ private final String outputDirectory;
+ private final String moduleName;
+
+ public static final String COMMAND_NAME = "split-module";
+
+ private static final String BUNDLE_LOCATION_FLAG = "bundle";
+ private static final String OUTPUT_DIRECTORY_FLAG = "output";
+ private static final String MODULE_FLAG = "module";
+
+ public SplitModuleCommand(FlagParser parsedFlags) {
+ bundleLocation = parsedFlags.getRequiredFlagValue(BUNDLE_LOCATION_FLAG);
+ outputDirectory = parsedFlags.getRequiredFlagValue(OUTPUT_DIRECTORY_FLAG);
+ moduleName = parsedFlags.getRequiredFlagValue(MODULE_FLAG);
+ }
+
+ @Override
+ public void execute() throws ExecutionException {
+ try {
+ AppBundle appBundle = new AppBundle(new ZipFile(bundleLocation));
+ BundleModule module = appBundle.getModule(moduleName);
+ if (module == null) {
+ throw new ExecutionException(
+ String.format("Cannot find the %s module in the bundle", moduleName));
+ }
+ splitModule(moduleName, module, outputDirectory);
+ } catch (ZipException e) {
+ throw new ExecutionException("Zip error while opening the bundle " + e.getMessage(), e);
+ } catch (FileNotFoundException e) {
+ throw new ExecutionException("Bundle file not found", e);
+ } catch (IOException e) {
+ throw new ExecutionException("I/O error while processing the bundle " + e.getMessage(), e);
+ }
+ }
+
+ private void splitModule(String moduleName, BundleModule module, String outputDirectory) {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ public static void help() {
+ System.out.printf(
+ "bundletool %s --%s=[bundle.zip] --%s=[module-name] --%s=[output-dir]\n",
+ BUNDLE_LOCATION_FLAG, MODULE_FLAG, OUTPUT_DIRECTORY_FLAG, COMMAND_NAME);
+ System.out.println("Generates module splits for the given module of the bundle.");
+ System.out.println("For now, one split is generated containing all module's resources.");
+ System.out.println();
+ System.out.printf("--%s: the zip file containing an App Bundle.\n", BUNDLE_LOCATION_FLAG);
+ System.out.printf("--%s: module for which generate the splits.\n", MODULE_FLAG);
+ System.out.printf(
+ "--%s: the directory where the module zip files should be written to.\n",
+ OUTPUT_DIRECTORY_FLAG);
+ }
+}
diff --git a/bundletool/java/com/android/tools/appbundle/bundletool/utils/FlagParser.java b/bundletool/java/com/android/tools/appbundle/bundletool/utils/FlagParser.java
new file mode 100644
index 0000000..5f42165
--- /dev/null
+++ b/bundletool/java/com/android/tools/appbundle/bundletool/utils/FlagParser.java
@@ -0,0 +1,147 @@
+/*
+ * 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.bundletool.utils;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Utility for flag parsing, specific to the Bundle Tool.
+ *
+ * <p>The flags follow the below convention:
+ *
+ * <p>[bundle-tool] [command1] [command2] .. [command-n] [--flag1] [--flag2=v2].. [--flagn] where:
+ *
+ * <ul>
+ * <li>commands: cannot start with "-".
+ * <li>flags: have to start with "--". If they have "=" anything after the first occurrence is
+ * considered a flag value. By default the flag value is an empty string.
+ * </ul>
+ */
+public class FlagParser {
+
+ private List<String> commands = new ArrayList<>();
+ private Map<String, String> flags = new HashMap<>();
+
+ /**
+ * Parses the given arguments populating the structures.
+ *
+ * <p>Calling this function removes any previous parsing results.
+ */
+ public FlagParser parse(String[] args) throws ParseException {
+ this.commands.clear();
+ // Need to wrap it into a proper list implementation to be able to remove elements.
+ List<String> argsToProcess = new ArrayList<>(Arrays.asList(args));
+ while (argsToProcess.size() > 0 && !argsToProcess.get(0).startsWith("-")) {
+ commands.add(argsToProcess.get(0));
+ argsToProcess.remove(0);
+ }
+ this.flags = parseFlags(argsToProcess);
+ return this;
+ }
+
+ private Map<String, String> parseFlags(List<String> args) throws ParseException {
+ Map<String, String> flagMap = new HashMap<>();
+ for (String arg : args) {
+ if (!arg.startsWith("--")) {
+ throw new ParseException(
+ String.format("Syntax error: flags should start with -- (%s)", arg));
+ }
+ String[] segments = arg.substring(2).split("=", 2);
+ String value = "";
+ if (segments.length == 2) {
+ value = segments[1];
+ }
+ if (flagMap.putIfAbsent(segments[0], value) != null) {
+ throw new ParseException(
+ String.format("Flag %s has been set more than once.", segments[0]));
+ }
+ }
+ return flagMap;
+ }
+
+ /** Returns true if a given flag has been set. */
+ public boolean isFlagSet(String flagName) {
+ return flags.containsKey(flagName);
+ }
+
+ /** Returns the flag value wrapped in the Optional class. */
+ public Optional<String> getFlagValue(String flagName) {
+ return Optional.ofNullable(flags.get(flagName));
+ }
+
+ /**
+ * Returns a flag value. If absent throws IllegalStateException.
+ *
+ * @param flagName name of the flag to fetch
+ * @return string, the value of the flag
+ * @throws IllegalStateException if the flag was not set.
+ */
+ public String getRequiredFlagValue(String flagName) {
+ return getFlagValue(flagName)
+ .orElseThrow(
+ () ->
+ new IllegalArgumentException(
+ String.format("Missing the required --%s flag.", flagName)));
+ }
+
+ /** Returns the string value of the flag or the default if has not been set. */
+ public String getFlagValueOrDefault(String flagName, String defaultValue) {
+ return flags.getOrDefault(flagName, defaultValue);
+ }
+
+ public Optional<Path> getFlagValueAsPath(String flagName) {
+ return Optional.ofNullable(flags.get(flagName)).map(Paths::get);
+ }
+
+ /**
+ * Returns the value of the flag as list of strings.
+ *
+ * <p>It converts the string flag value to the list assuming it's delimited by a comma. The list
+ * is empty if the flag has not been set.
+ */
+ public List<String> getFlagListValue(String flagName) {
+ if (!isFlagSet(flagName)) {
+ return Collections.emptyList();
+ }
+ return Arrays.asList(flags.get(flagName).split(","));
+ }
+
+ /**
+ * Returns the list of commands that were parsed.
+ *
+ * @return the immutable list of commands.
+ */
+ public List<String> getCommands() {
+ return Collections.unmodifiableList(commands);
+ }
+
+ /** Exception encapsulating any flag parsing errors. */
+ public static class ParseException extends RuntimeException {
+
+ public ParseException(String message) {
+ super(message);
+ }
+ }
+}
diff --git a/bundletool/manifest.txt b/bundletool/manifest.txt
new file mode 100644
index 0000000..b8a4c92
--- /dev/null
+++ b/bundletool/manifest.txt
@@ -0,0 +1 @@
+Main-Class: com.android.tools.appbundle.bundletool.BundleToolMain