diff options
author | Xin Li <delphij@google.com> | 2018-08-06 16:52:56 -0700 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2018-08-06 16:52:56 -0700 |
commit | 9f85eef4a9394b99031dd4c2aaafc4b74f3b303e (patch) | |
tree | c4b6047eda351d2e6bcbd190dc1e80bf06755fb6 | |
parent | 4d5d2806a9eef94b589d282c773f26f7e94459a0 (diff) | |
parent | c24ae13cbeb590b56547b0e539f1be195160c93c (diff) | |
download | appbundle-sdk-release.tar.gz |
Merge Android Pie into mastersdk-release
Bug: 112104996
Change-Id: Ie05d3082adfc859729dfd395e80c597fe4e5e496
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 |