Skip to content

Commit

Permalink
Prepare for release 0.5.0.
Browse files Browse the repository at this point in the history
  • Loading branch information
plecesne committed Jul 11, 2018
1 parent 9e2c217 commit f855ea6
Show file tree
Hide file tree
Showing 59 changed files with 2,383 additions and 1,211 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Bundletool is a tool to manipulate Android App Bundles.

The **Android App Bundle** is a new format for publishing Android apps in app
distribution stores such as the Play Store.
distribution stores such as Google Play.

Bundletool has a few different responsibilities:

Expand All @@ -24,5 +24,4 @@ Read more about the App Bundle format and Bundletool's usage at

## Releases

Latest release: [0.4.2](https://github.com/google/bundletool/releases)

Latest release: [0.5.0](https://github.com/google/bundletool/releases)
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
release_version = 0.4.2
release_version = 0.5.0
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import com.android.tools.build.bundletool.commands.CommandHelp.FlagDescription;
import com.android.tools.build.bundletool.device.AdbServer;
import com.android.tools.build.bundletool.device.DeviceAnalyzer;
import com.android.tools.build.bundletool.device.DeviceSpecParser;
import com.android.tools.build.bundletool.exceptions.CommandExecutionException;
import com.android.tools.build.bundletool.exceptions.ValidationException;
import com.android.tools.build.bundletool.io.ApkSerializerManager;
Expand All @@ -48,6 +49,7 @@
import com.android.tools.build.bundletool.model.AppBundle;
import com.android.tools.build.bundletool.model.BundleMetadata;
import com.android.tools.build.bundletool.model.BundleModule;
import com.android.tools.build.bundletool.model.GeneratedApks;
import com.android.tools.build.bundletool.model.ModuleSplit;
import com.android.tools.build.bundletool.model.OptimizationDimension;
import com.android.tools.build.bundletool.model.SigningConfiguration;
Expand All @@ -74,6 +76,8 @@
import com.google.common.util.concurrent.MoreExecutors;
import java.io.IOException;
import java.io.PrintStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
Expand All @@ -92,6 +96,7 @@ public abstract class BuildApksCommand {

private static final Flag<Path> BUNDLE_LOCATION_FLAG = Flag.path("bundle");
private static final Flag<Path> OUTPUT_FILE_FLAG = Flag.path("output");
private static final Flag<Boolean> OVERWRITE_OUTPUT_FLAG = Flag.booleanFlag("overwrite");
private static final Flag<ImmutableSet<OptimizationDimension>> OPTIMIZE_FOR_FLAG =
Flag.enumSet("optimize-for", OptimizationDimension.class);
private static final Flag<Path> AAPT2_PATH_FLAG = Flag.path("aapt2");
Expand All @@ -103,6 +108,8 @@ public abstract class BuildApksCommand {
private static final Flag<String> DEVICE_ID_FLAG = Flag.string("device-id");
private static final String ANDROID_HOME_VARIABLE = "ANDROID_HOME";

private static final Flag<Path> DEVICE_SPEC_FLAG = Flag.path("device-spec");

// Signing-related flags: should match flags from apksig library.
private static final Flag<Path> KEYSTORE_FLAG = Flag.path("ks");
private static final Flag<String> KEY_ALIAS_FLAG = Flag.string("ks-key-alias");
Expand All @@ -118,8 +125,12 @@ public abstract class BuildApksCommand {

public abstract Path getOutputFile();

public abstract boolean getOverwriteOutput();

public abstract ImmutableSet<OptimizationDimension> getOptimizationDimensions();

public abstract Optional<Path> getDeviceSpecPath();

public abstract boolean getGenerateOnlyForConnectedDevice();

public abstract Optional<String> getDeviceId();
Expand All @@ -141,6 +152,7 @@ public abstract class BuildApksCommand {

public static Builder builder() {
return new AutoValue_BuildApksCommand.Builder()
.setOverwriteOutput(false)
.setGenerateOnlyUniversalApk(false)
.setGenerateOnlyForConnectedDevice(false)
.setOptimizationDimensions(ImmutableSet.of())
Expand All @@ -156,6 +168,14 @@ public abstract static class Builder {
/** Sets the path to where the APK Set must be generated. Must have the extension ".apks". */
public abstract Builder setOutputFile(Path outputFile);

/**
* Sets whether to overwrite the contents of the output file.
*
* <p>The default is {@code false}. If set to {@code false} and a file is present, exception is
* thrown.
*/
public abstract Builder setOverwriteOutput(boolean overwriteOutput);

/** List of config dimensions to split the APKs by. */
@Deprecated // Use setBundleConfig() instead.
public abstract Builder setOptimizationDimensions(
Expand All @@ -173,6 +193,9 @@ public abstract Builder setOptimizationDimensions(
*/
public abstract Builder setGenerateOnlyForConnectedDevice(boolean onlyForConnectedDevice);

/** Sets the path for the device spec for which the only the matching APKs will be generated. */
public abstract Builder setDeviceSpecPath(Path deviceSpec);

/**
* Sets the device serial number. Required if more than one device including emulators is
* connected.
Expand Down Expand Up @@ -219,6 +242,21 @@ public BuildApksCommand build() {
+ "at the same time.");
}

if (command.getDeviceSpecPath().isPresent() && command.getGenerateOnlyUniversalApk()) {
throw new ValidationException(
"Cannot generate universal APK and optimize for the device spec at the same time.");
}

if (command.getGenerateOnlyForConnectedDevice() && command.getDeviceSpecPath().isPresent()) {
throw new ValidationException(
"Cannot optimize for the device spec and connected device at the same time.");
}

if (command.getDeviceId().isPresent() && !command.getGenerateOnlyForConnectedDevice()) {
throw new ValidationException(
"Setting --device-id requires using the --connected-device flag.");
}

if (!APK_SET_ARCHIVE_EXTENSION.equals(MoreFiles.getFileExtension(command.getOutputFile()))) {
throw ValidationException.builder()
.withMessage(
Expand Down Expand Up @@ -246,6 +284,7 @@ static BuildApksCommand fromFlags(
.setOutputFile(OUTPUT_FILE_FLAG.getRequiredValue(flags));

// Optional arguments.
OVERWRITE_OUTPUT_FLAG.getValue(flags).ifPresent(buildApksCommand::setOverwriteOutput);
AAPT2_PATH_FLAG
.getValue(flags)
.ifPresent(
Expand Down Expand Up @@ -306,6 +345,9 @@ static BuildApksCommand fromFlags(
+ "variable.")));
buildApksCommand.setAdbPath(adbPath).setAdbServer(adbServer);
}

DEVICE_SPEC_FLAG.getValue(flags).ifPresent(buildApksCommand::setDeviceSpecPath);

flags.checkNoUnknownFlags();

return buildApksCommand.build();
Expand All @@ -321,9 +363,11 @@ private Path executeWithTempDir(Path tempDir) {
Aapt2Command aapt2Command = getAapt2Command().orElseGet(() -> extractAapt2FromJar(tempDir));

// Fail fast with ADB before generating any APKs.
Optional<DeviceSpec> targetedDevice = Optional.empty();
Optional<DeviceSpec> deviceSpec = Optional.empty();
if (getGenerateOnlyForConnectedDevice()) {
targetedDevice = Optional.of(getDeviceSpec());
deviceSpec = Optional.of(getDeviceSpec());
} else if (getDeviceSpecPath().isPresent()) {
deviceSpec = Optional.of(DeviceSpecParser.parseDeviceSpec(getDeviceSpecPath().get()));
}

try (ZipFile bundleZip = new ZipFile(getBundlePath().toFile())) {
Expand All @@ -349,14 +393,11 @@ private Path executeWithTempDir(Path tempDir) {
: new OptimizationsMerger()
.mergeWithDefaults(bundleConfig, getOptimizationDimensions());

ImmutableList<ModuleSplit> splitApks = ImmutableList.of();
ImmutableList<ModuleSplit> standaloneApks = ImmutableList.of();

boolean generateSplitApks = !getGenerateOnlyUniversalApk() && !targetsOnlyPreL(appBundle);
boolean generateStandaloneApks = getGenerateOnlyUniversalApk() || targetsPreL(appBundle);

if (targetedDevice.isPresent()) {
if (targetedDevice.get().getSdkVersion() >= Versions.ANDROID_L_API_VERSION) {
if (deviceSpec.isPresent()) {
if (deviceSpec.get().getSdkVersion() >= Versions.ANDROID_L_API_VERSION) {
generateStandaloneApks = false;
if (!generateSplitApks) {
throw new CommandExecutionException(
Expand All @@ -372,40 +413,41 @@ private Path executeWithTempDir(Path tempDir) {
}
}

GeneratedApks.Builder generatedApksBuilder = GeneratedApks.builder();
if (generateSplitApks) {
splitApks = generateSplitApks(allModules, apkOptimizations, bundleVersion);
generatedApksBuilder.setSplitApks(
generateSplitApks(allModules, apkOptimizations, bundleVersion));
}
if (generateStandaloneApks) {
// Note: Universal APK is a special type of standalone, with no optimization dimensions.
ImmutableList<BundleModule> modulesForFusing =
allModules.stream().filter(BundleModule::isIncludedInFusing).collect(toImmutableList());
standaloneApks =
generatedApksBuilder.setStandaloneApks(
generateStandaloneApks(
modulesForFusing,
appBundle.getBundleMetadata(),
getGenerateOnlyUniversalApk(),
tempDir,
apkOptimizations,
bundleVersion);
bundleVersion));
}

// Populate alternative targeting based on variant targeting of all APKs.
ImmutableList<ModuleSplit> allApks =
GeneratedApks generatedApks =
AlternativeVariantTargetingPopulator.populateAlternativeVariantTargeting(
splitApks, standaloneApks);
generatedApksBuilder.build());

// Create variants and serialize APKs.
ApkSerializerManager apkSerializerManager = new ApkSerializerManager(getExecutorService());
ImmutableList<Variant> allVariantsWithTargeting = ImmutableList.of();
if (targetedDevice.isPresent()) {
ImmutableList<Variant> allVariantsWithTargeting;
if (deviceSpec.isPresent()) {
allVariantsWithTargeting =
ImmutableList.of(
apkSerializerManager.serializeAndGenerateVariantForDevice(
targetedDevice.get(), allApks, apkSetBuilder, appBundle));
apkSerializerManager.serializeApksForDevice(
deviceSpec.get(), generatedApks, apkSetBuilder, appBundle));
} else {
allVariantsWithTargeting =
apkSerializerManager.serializeAndGenerateAllVariants(
allApks, apkSetBuilder, appBundle, getGenerateOnlyUniversalApk());
apkSerializerManager.serializeApks(
generatedApks, apkSetBuilder, appBundle, getGenerateOnlyUniversalApk());
}
// Finalize the output archive.
apkSetBuilder.setTableOfContentsFile(
Expand All @@ -415,12 +457,13 @@ private Path executeWithTempDir(Path tempDir) {
Bundletool.newBuilder()
.setVersion(BundleToolVersion.getCurrentVersion().toString()))
.build());
if (getOverwriteOutput()) {
Files.deleteIfExists(getOutputFile());
}
apkSetBuilder.writeTo(getOutputFile());
} catch (IOException e) {
throw ValidationException.builder()
.withCause(e)
.withMessage("Error reading zip file '%s'.", getBundlePath())
.build();
throw new UncheckedIOException(
String.format("An error occurred when processing the bundle '%s'.", getBundlePath()), e);
}


Expand Down Expand Up @@ -461,7 +504,9 @@ private static Aapt2Command extractAapt2FromJar(Path tempDir) {

private void validateInput() {
checkFileExistsAndReadable(getBundlePath());
checkFileDoesNotExist(getOutputFile());
if (!getOverwriteOutput()) {
checkFileDoesNotExist(getOutputFile());
}

if (getGenerateOnlyForConnectedDevice()) {
checkArgument(
Expand Down Expand Up @@ -580,6 +625,12 @@ public static CommandHelp help() {
.setExampleValue("output.apks")
.setDescription("Path to where the APK Set archive should be created.")
.build())
.addFlag(
FlagDescription.builder()
.setFlagName(OVERWRITE_OUTPUT_FLAG.getName())
.setOptional(true)
.setDescription("If set, any previous existing output will be overwritten.")
.build())
.addFlag(
FlagDescription.builder()
.setFlagName(AAPT2_PATH_FLAG.getName())
Expand Down Expand Up @@ -690,6 +741,15 @@ public static CommandHelp help() {
+ "connected. Used only if %s flag is set.",
CONNECTED_DEVICE_FLAG)
.build())
.addFlag(
FlagDescription.builder()
.setFlagName(DEVICE_SPEC_FLAG.getName())
.setExampleValue("device-spec.json")
.setDescription(
"Path to the device spec file generated by the '%s' command. If present, "
+ "it will generate an APK Set optimized for the specified device spec.",
GetDeviceSpecCommand.COMMAND_NAME)
.build())
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import com.android.tools.build.bundletool.commands.CommandHelp.CommandDescription;
import com.android.tools.build.bundletool.commands.CommandHelp.FlagDescription;
import com.android.tools.build.bundletool.device.ApkMatcher;
import com.android.tools.build.bundletool.exceptions.CommandExecutionException;
import com.android.tools.build.bundletool.device.DeviceSpecParser;
import com.android.tools.build.bundletool.exceptions.ValidationException;
import com.android.tools.build.bundletool.model.ZipPath;
import com.android.tools.build.bundletool.utils.files.BufferedIo;
Expand All @@ -40,12 +40,10 @@
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.common.io.ByteStreams;
import com.google.common.io.MoreFiles;
import com.google.protobuf.util.JsonFormat;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.util.Optional;
import java.util.Set;
Expand All @@ -63,8 +61,6 @@ public abstract class ExtractApksCommand {
private static final Flag<Path> OUTPUT_DIRECTORY = Flag.path("output-dir");
private static final Flag<ImmutableSet<String>> MODULES_FLAG = Flag.stringSet("modules");

private static final String JSON_EXTENSION = "json";

public abstract Path getApksArchivePath();

public abstract DeviceSpec getDeviceSpec();
Expand Down Expand Up @@ -104,7 +100,7 @@ public static ExtractApksCommand fromFlags(ParsedFlags flags) {
command.setApksArchivePath(apksArchivePath);

checkFileExistsAndReadable(deviceSpecPath);
command.setDeviceSpec(parseDeviceSpec(deviceSpecPath));
command.setDeviceSpec(DeviceSpecParser.parseDeviceSpec(deviceSpecPath));

checkDirectoryExists(outputDirectory);
command.setOutputDirectory(outputDirectory);
Expand All @@ -114,27 +110,6 @@ public static ExtractApksCommand fromFlags(ParsedFlags flags) {
return command.build();
}

private static DeviceSpec parseDeviceSpec(Path deviceSpecFile) {
DeviceSpec.Builder builder = DeviceSpec.newBuilder();
try {
if (!JSON_EXTENSION.equals(MoreFiles.getFileExtension(deviceSpecFile))) {
throw CommandExecutionException.builder()
.withMessage(
"Expected .json extension of the device spec file but found '%s'.", deviceSpecFile)
.build();
}
try (Reader deviceSpecReader = BufferedIo.reader(deviceSpecFile)) {
JsonFormat.parser().merge(deviceSpecReader, builder);
}
} catch (IOException e) {
throw CommandExecutionException.builder()
.withCause(e)
.withMessage("I/O error while reading the device spec file '%s'.", deviceSpecFile)
.build();
}
return builder.build();
}

public ImmutableList<Path> execute() {
validateInput();

Expand Down Expand Up @@ -184,17 +159,14 @@ private ImmutableList<Path> extractMatchedApks(ImmutableList<ZipPath> matchedApk
ByteStreams.copy(inputStream, outputApk);
builder.add(extractedApkPath);
} catch (IOException e) {
throw CommandExecutionException.builder()
.withCause(e)
.withMessage("I/O error while extracting APK '%s' from the APK Set.", matchedApk)
.build();
throw new UncheckedIOException(
String.format("Error while extracting APK '%s' from the APK Set.", matchedApk), e);
}
}
} catch (IOException e) {
throw CommandExecutionException.builder()
.withCause(e)
.withMessage("I/O error while processing the APK Set archive '%s'.", getApksArchivePath())
.build();
throw new UncheckedIOException(
String.format("Error while processing the APK Set archive '%s'.", getApksArchivePath()),
e);
}
return builder.build();
}
Expand All @@ -205,11 +177,10 @@ private BuildApksResult readTableOfContents() {
BufferedIo.inputStream(apksArchive, new ZipEntry(TABLE_OF_CONTENTS_FILE))) {
return BuildApksResult.parseFrom(tocStream);
} catch (IOException e) {
throw CommandExecutionException.builder()
.withCause(e)
.withMessage(
"I/O error while reading the table of contents file from '%s'.", getApksArchivePath())
.build();
throw new UncheckedIOException(
String.format(
"Error while reading the table of contents file from '%s'.", getApksArchivePath()),
e);
}
}

Expand Down
Loading

0 comments on commit f855ea6

Please sign in to comment.