Skip to content

Commit

Permalink
Prepare for release 0.15.0.
Browse files Browse the repository at this point in the history
  • Loading branch information
Iurii Makhno committed May 18, 2020
1 parent 9831cec commit 8c3b870
Show file tree
Hide file tree
Showing 44 changed files with 2,011 additions and 422 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ https://developer.android.com/studio/command-line/bundletool

## Releases

Latest release: [0.14.0](https://github.com/google/bundletool/releases)
Latest release: [0.15.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.14.0
release_version = 0.15.0
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ public Builder setExecutorService(ListeningExecutorService executorService) {
* Provides the lowest variant number to use.
*
* <p>By default, variants are numbered from 0 to {@code variantNum - 1}. By setting a value
* here, the variants will be numbered from {@code lowestVariantNumber} and up.
* here, the variants will be numbered from {@code firstVariantNumber} and up.
*/
public abstract Builder setFirstVariantNumber(int firstVariantNumber);

Expand Down Expand Up @@ -730,55 +730,6 @@ public static CommandHelp help() {
.setDescription(
"If set, a stamp will be generated and embedded in the generated APKs.")
.build())
.addFlag(
FlagDescription.builder()
.setFlagName(STAMP_KEYSTORE_FLAG.getName())
.setExampleValue("path/to/keystore")
.setOptional(true)
.setDescription(
"Path to the stamp keystore that should be used to sign the APK contents hash."
+ " If not set, the '%s' keystore will be tried if present. Otherwise, the"
+ " default debug keystore will be used if it exists. If a default debug"
+ " keystore is not found, the stamp will fail to get generated. If"
+ " set, the flag '%s' must also be set.",
KEYSTORE_FLAG, STAMP_KEY_ALIAS_FLAG)
.build())
.addFlag(
FlagDescription.builder()
.setFlagName(STAMP_KEYSTORE_PASSWORD_FLAG.getName())
.setExampleValue("[pass|file]:value")
.setOptional(true)
.setDescription(
"Password of the stamp keystore to use to sign the APK contents hash. If"
+ " provided, must be prefixed with either 'pass:' (if the password is"
+ " passed in clear text, e.g. 'pass:qwerty') or 'file:' (if the password"
+ " is the first line of a file, e.g. 'file:/tmp/myPassword.txt'). If this"
+ " flag is not set, the password will be requested on the prompt.")
.build())
.addFlag(
FlagDescription.builder()
.setFlagName(STAMP_KEY_ALIAS_FLAG.getName())
.setExampleValue("stamp-key-alias")
.setOptional(true)
.setDescription(
"Alias of the stamp key to use in the keystore to sign the APK contents hash."
+ " If not set, the '%s' key alias will be tried if present.",
KEY_ALIAS_FLAG)
.build())
.addFlag(
FlagDescription.builder()
.setFlagName(STAMP_KEY_PASSWORD_FLAG.getName())
.setExampleValue("stamp-key-password")
.setOptional(true)
.setDescription(
"Password of the stamp key in the keystore to use to sign the APK contents"
+ " hash. if provided, must be prefixed with either 'pass:' (if the"
+ " password is passed in clear text, e.g. 'pass:qwerty') or 'file:' (if"
+ " the password is the first line of a file, e.g."
+ " 'file:/tmp/myPassword.txt'). If this flag is not set, the keystore"
+ " password will be tried. If that fails, the password will be requested"
+ " on the prompt.")
.build())
.addFlag(
FlagDescription.builder()
.setFlagName(STAMP_SOURCE_FLAG.getName())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import com.android.tools.build.bundletool.io.ApkSetBuilderFactory.ApkSetBuilder;
import com.android.tools.build.bundletool.io.SplitApkSerializer;
import com.android.tools.build.bundletool.io.StandaloneApkSerializer;
import com.android.tools.build.bundletool.mergers.BundleModuleMerger;
import com.android.tools.build.bundletool.model.Aapt2Command;
import com.android.tools.build.bundletool.model.ApkListener;
import com.android.tools.build.bundletool.model.ApkModifier;
Expand Down Expand Up @@ -160,13 +161,18 @@ private void executeWithZip(
GeneratedAssetSlices.Builder generatedAssetSlices = GeneratedAssetSlices.builder();

boolean enableUniversalAsFallbackForSplits = false;
boolean enableInstallTimePermanentModules = false;
ApksToGenerate apksToGenerate =
new ApksToGenerate(
appBundle, command.getApkBuildMode(), enableUniversalAsFallbackForSplits, deviceSpec);

// Split APKs
if (apksToGenerate.generateSplitApks()) {
generatedApksBuilder.setSplitApks(generateSplitApks(appBundle, stampSource));
AppBundle mergedAppBundle =
BundleModuleMerger.mergePermanentInstallTimeModules(
appBundle, enableInstallTimePermanentModules);
bundleValidator.validate(mergedAppBundle);
generatedApksBuilder.setSplitApks(generateSplitApks(mergedAppBundle, stampSource));
}

// Instant APKs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,16 @@ public void execute() {
}
ImmutableList<ZipFile> moduleZipFiles = moduleZipFilesBuilder.build();

ImmutableList<BundleModule> modules = new BundleModulesValidator().validate(moduleZipFiles);
// Read the Bundle Config file if provided by the developer.
BundleConfig bundleConfig =
getBundleConfig().orElse(BundleConfig.getDefaultInstance()).toBuilder()
.setBundletool(
Bundletool.newBuilder()
.setVersion(BundleToolVersion.getCurrentVersion().toString()))
.build();

ImmutableList<BundleModule> modules =
new BundleModulesValidator().validate(moduleZipFiles, bundleConfig);
checkState(
moduleZipFiles.size() == modules.size(),
"Incorrect number of modules parsed (%s != %s).",
Expand All @@ -229,14 +238,6 @@ public void execute() {
modulesWithTargeting.add(moduleWithTargeting.build());
}

// Read the Bundle Config file if provided by the developer.
BundleConfig bundleConfig =
getBundleConfig().orElse(BundleConfig.getDefaultInstance()).toBuilder()
.setBundletool(
Bundletool.newBuilder()
.setVersion(BundleToolVersion.getCurrentVersion().toString()))
.build();

AppBundle appBundle =
AppBundle.buildFromModules(
modulesWithTargeting.build(), bundleConfig, getBundleMetadata());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,31 @@
package com.android.tools.build.bundletool.commands;

import static com.android.tools.build.bundletool.commands.CommandUtils.ANDROID_SERIAL_VARIABLE;
import static com.android.tools.build.bundletool.model.utils.ResultUtils.readTableOfContents;
import static com.android.tools.build.bundletool.model.utils.SdkToolsLocator.ANDROID_HOME_VARIABLE;
import static com.android.tools.build.bundletool.model.utils.SdkToolsLocator.SYSTEM_PATH_VARIABLE;
import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndExecutable;
import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndReadable;
import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileHasExtension;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableListMultimap.toImmutableListMultimap;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.Streams.stream;
import static java.util.function.Function.identity;

import com.android.bundle.Commands.BuildApksResult;
import com.android.bundle.Devices.DeviceSpec;
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.AdbServer;
import com.android.tools.build.bundletool.device.AdbShellCommandTask;
import com.android.tools.build.bundletool.device.BadgingPackageNameParser;
import com.android.tools.build.bundletool.device.BadgingInfoParser;
import com.android.tools.build.bundletool.device.BadgingInfoParser.BadgingInfo;
import com.android.tools.build.bundletool.device.Device;
import com.android.tools.build.bundletool.device.DeviceAnalyzer;
import com.android.tools.build.bundletool.device.IncompatibleDeviceException;
import com.android.tools.build.bundletool.device.MultiPackagesInstaller;
import com.android.tools.build.bundletool.device.MultiPackagesInstaller.InstallableApk;
import com.android.tools.build.bundletool.device.PackagesParser;
import com.android.tools.build.bundletool.device.PackagesParser.InstalledPackageInfo;
import com.android.tools.build.bundletool.flags.Flag;
import com.android.tools.build.bundletool.flags.ParsedFlags;
import com.android.tools.build.bundletool.io.TempDirectory;
Expand All @@ -54,7 +55,7 @@
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.ByteStreams;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException;
Expand Down Expand Up @@ -206,16 +207,16 @@ public void execute() throws TimeoutException, IOException {
Supplier<Aapt2Command> aapt2CommandSupplier =
Suppliers.memoize(() -> getOrExtractAapt2Command(aapt2Dir));

ImmutableSet<String> existingPackages =
getUpdateOnly() ? listPackagesInstalledOnDevice(device) : ImmutableSet.of();
ImmutableMap<String, InstalledPackageInfo> existingPackages =
getPackagesInstalledOnDevice(device);

ImmutableListMultimap<String, InstallableApk> apkToInstallByPackage =
getActualApksPaths(tempDirectory).stream()
.flatMap(
apksArchivePath ->
stream(
apksWithPackageName(apksArchivePath, deviceSpec, aapt2CommandSupplier)))
.filter(apk -> !getUpdateOnly() || isInstalled(apk, existingPackages))
.filter(apk -> shouldInstall(apk, existingPackages))
.flatMap(apks -> extractApkListFromApks(deviceSpec, apks, tempDirectory).stream())
.collect(toImmutableListMultimap(InstallableApk::getPackageName, identity()));

Expand All @@ -227,33 +228,70 @@ public void execute() throws TimeoutException, IOException {
}
}

private static boolean isInstalled(InstallableApk apk, ImmutableSet<String> existingPackages) {
boolean exist = existingPackages.contains(apk.getPackageName());
if (!exist) {
/**
* The package should be installed if:
*
* <ul>
* <li>If it is not already present on the device and --update-only is not set, or
* <li>The installable version has a equal or higher version code than the one already
* installed.
* </ul>
*/
private boolean shouldInstall(
InstallableApk apk, ImmutableMap<String, InstalledPackageInfo> existingPackages) {
if (getUpdateOnly() && !existingPackages.containsKey(apk.getPackageName())) {
logger.info(
String.format(
"Package '%s' not present on device, skipping due to --%s.",
apk.getPackageName(), UPDATE_ONLY_FLAG.getName()));
return false;
}

if (!existingPackages.containsKey(apk.getPackageName())) {
return true;
}

InstalledPackageInfo existingPackage = existingPackages.get(apk.getPackageName());

if (existingPackage.getVersionCode() <= apk.getVersionCode()) {
return true;
}
return exist;

// If the user is attempting to install a mixture of lower and higher version .apks, that
// likely indicates something is wrong.
logger.warning(
String.format(
"A higher version of package '%s' (%d vs %d) is already present on device,"
+ " skipping.",
apk.getPackageName(), apk.getVersionCode(), existingPackage.getVersionCode()));

return false;
}

private static ImmutableSet<String> listPackagesInstalledOnDevice(Device device) {
private static ImmutableMap<String, InstalledPackageInfo> getPackagesInstalledOnDevice(
Device device) {
// List standard packages (excluding apex)
ImmutableList<String> listPackagesOutput =
new AdbShellCommandTask(device, "pm list packages").execute();
return new PackagesParser().parse(listPackagesOutput);
new AdbShellCommandTask(device, "pm list packages --show-versioncode").execute();
// List .apex packages.
ImmutableList<String> listApexPackagesOutput =
new AdbShellCommandTask(device, "pm list packages --apex-only --show-versioncode")
.execute();

return new PackagesParser()
.parse(
ImmutableList.<String>builder()
.addAll(listPackagesOutput)
.addAll(listApexPackagesOutput)
.build())
.stream()
.collect(
toImmutableMap(
InstalledPackageInfo::getPackageName,
installedPackageInfo -> installedPackageInfo));
}

private static Optional<InstallableApk> apksWithPackageName(
Path apkArchivePath, DeviceSpec deviceSpec, Supplier<Aapt2Command> aapt2CommandSupplier) {
BuildApksResult toc = readTableOfContents(apkArchivePath);
if (toc.getPackageName().isEmpty()) {
return getApksWithPackageNameFromAapt2(apkArchivePath, deviceSpec, aapt2CommandSupplier);
}
return Optional.of(InstallableApk.create(apkArchivePath, toc.getPackageName()));
}

private static Optional<InstallableApk> getApksWithPackageNameFromAapt2(
Path apksArchivePath, DeviceSpec deviceSpec, Supplier<Aapt2Command> aapt2CommandSupplier) {
try (TempDirectory tempDirectory = new TempDirectory()) {
// Any of the extracted .apk/.apex files will work.
Expand All @@ -266,9 +304,11 @@ private static Optional<InstallableApk> getApksWithPackageNameFromAapt2(
.execute()
.get(0);

String packageName =
BadgingPackageNameParser.parse(aapt2CommandSupplier.get().dumpBadging(extractedFile));
return Optional.of(InstallableApk.create(apksArchivePath, packageName));
BadgingInfo badgingInfo =
BadgingInfoParser.parse(aapt2CommandSupplier.get().dumpBadging(extractedFile));
return Optional.of(
InstallableApk.create(
apksArchivePath, badgingInfo.getPackageName(), badgingInfo.getVersionCode()));
} catch (IncompatibleDeviceException e) {
logger.warning(
String.format(
Expand Down Expand Up @@ -310,7 +350,10 @@ private static ImmutableList<InstallableApk> extractApkListFromApks(
.setOutputDirectory(output);

return extractApksCommand.build().execute().stream()
.map(path -> InstallableApk.create(path, apksArchive.getPackageName()))
.map(
path ->
InstallableApk.create(
path, apksArchive.getPackageName(), apksArchive.getVersionCode()))
.collect(toImmutableList());
} catch (IncompatibleDeviceException e) {
logger.warning(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,20 @@
package com.android.tools.build.bundletool.device;

import com.android.tools.build.bundletool.model.exceptions.ParseException;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/** Parses the Package Name from the output of "aapt2 dump badging". */
public final class BadgingPackageNameParser {
/** Parses the Package Name and version code from the output of "aapt2 dump badging". */
public final class BadgingInfoParser {

private static final Pattern PACKAGE_NAME_PATTERN = Pattern.compile(".*? name='(.*?)'.*");
private static final Pattern PACKAGE_NAME_PATTERN =
Pattern.compile(".*? name='(?<name>.*?)' versionCode='(?<version>\\d+?)' .*");

private BadgingPackageNameParser() {}
private BadgingInfoParser() {}

public static String parse(ImmutableList<String> badgingOutput) {
public static BadgingInfo parse(ImmutableList<String> badgingOutput) {
String packageLine =
badgingOutput.stream()
.filter(line -> line.trim().startsWith("package:"))
Expand All @@ -41,8 +43,21 @@ public static String parse(ImmutableList<String> badgingOutput) {
String.join("\n", badgingOutput))));
Matcher matcher = PACKAGE_NAME_PATTERN.matcher(packageLine);
if (!matcher.matches()) {
throw new ParseException(String.format("'name=' not found in package line: %s", packageLine));
throw new ParseException(
String.format("'name=' and 'versionCode=' not found in package line: %s", packageLine));
}
return matcher.group(1);
return BadgingInfo.create(matcher.group("name"), Long.parseLong(matcher.group("version")));
}

/** Represents the badging info of an .apk/.apex file. */
@AutoValue
public abstract static class BadgingInfo {
static BadgingInfo create(String packageName, long versionCode) {
return new AutoValue_BadgingInfoParser_BadgingInfo(packageName, versionCode);
}

public abstract String getPackageName();

public abstract long getVersionCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ public void install(ImmutableListMultimap<String, InstallableApk> apksByPackageN
abandonSession = noCommit;
} finally {
finalizeParentSession(parentSessionId, abandonSession);
logger.info(String.format("Install %s", abandonSession ? "abandoned" : "committed"));
logger.info(
String.format(
"Install %s [%d]", abandonSession ? "abandoned" : "committed", parentSessionId));
}
}

Expand Down Expand Up @@ -173,14 +175,16 @@ private static ImmutableList<String> executeAndValidateSuccess(Device device, St
@AutoValue
public abstract static class InstallableApk {

public static InstallableApk create(Path path, String packageName) {
return new AutoValue_MultiPackagesInstaller_InstallableApk(path, packageName);
public static InstallableApk create(Path path, String packageName, long versionCode) {
return new AutoValue_MultiPackagesInstaller_InstallableApk(path, packageName, versionCode);
}

public abstract Path getPath();

public abstract String getPackageName();

public abstract long getVersionCode();

InstallableApk() {}
}
}
Loading

0 comments on commit 8c3b870

Please sign in to comment.