From 2e9ac4e628a690c0fce402548fe90c747c86533c Mon Sep 17 00:00:00 2001 From: Hitish Chappidi Date: Fri, 19 Jul 2019 14:03:06 +0100 Subject: [PATCH] Prepare for release 0.10.2. --- README.md | 2 +- gradle.properties | 2 +- .../bundletool/commands/BuildApksCommand.java | 33 +- .../bundletool/commands/BuildApksManager.java | 60 +++- .../bundletool/commands/DumpManager.java | 4 + .../commands/ExtractApksCommand.java | 16 +- .../commands/GetDeviceSpecCommand.java | 2 +- .../bundletool/commands/GetSizeCommand.java | 8 +- .../build/bundletool/device/ApkMatcher.java | 63 ++-- .../build/bundletool/device/DdmlibDevice.java | 6 +- .../device/VariantTotalSizeAggregator.java | 10 +- .../bundletool/io/ApkSerializerManager.java | 77 +++-- .../bundletool/model/AndroidManifest.java | 8 +- .../build/bundletool/model/BundleModule.java | 16 +- .../model/ManifestDeliveryElement.java | 79 +++-- .../model/OptimizationDimension.java | 1 + .../exceptions/BundleInvalidZipException.java | 27 ++ .../model/utils/ModuleDependenciesUtils.java | 126 +++++++ .../bundletool/model/utils/Versions.java | 1 + .../model/version/BundleToolVersion.java | 2 +- .../bundletool/model/version/Version.java | 4 + .../optimizations/ApkOptimizations.java | 9 + .../bundletool/splitters/BundleSharder.java | 7 +- .../splitters/DexCompressionSplitter.java | 10 +- .../DexCompressionVariantGenerator.java | 6 +- .../bundletool/splitters/ModuleSplitter.java | 5 + .../splitters/ShardedApksGenerator.java | 5 +- .../validation/ModuleDependencyValidator.java | 45 +-- .../validation/ResourceTableValidator.java | 32 ++ src/main/proto/app_dependencies.proto | 4 +- src/main/proto/commands.proto | 33 +- src/main/proto/config.proto | 1 + .../commands/BuildApksCommandTest.java | 56 +++ .../commands/BuildApksManagerTest.java | 325 +++++++++++++++++- .../commands/ExtractApksCommandTest.java | 108 +++++- .../commands/GetSizeCommandTest.java | 77 ++++- .../commands/InstallApksCommandTest.java | 33 +- .../bundletool/device/ApkMatcherTest.java | 97 +++++- .../bundletool/device/DdmlibDeviceTest.java | 5 +- .../VariantTotalSizeAggregatorTest.java | 29 +- .../bundletool/model/AndroidManifestTest.java | 16 + .../model/ManifestDeliveryElementTest.java | 175 ++++++++-- .../optimizations/ApkOptimizationsTest.java | 15 + .../splitters/BundleSharderTest.java | 40 ++- .../splitters/DexCompressionSplitterTest.java | 25 +- .../DexCompressionVariantGeneratorTest.java | 6 +- .../splitters/ModuleSplitterTest.java | 6 +- .../splitters/ShardedApksGeneratorTest.java | 62 +++- .../splitters/SplitApksGeneratorTest.java | 16 +- .../splitters/VariantGeneratorTest.java | 12 +- .../build/bundletool/testing/ApkSetUtils.java | 20 +- .../testing/ApksArchiveHelpers.java | 44 ++- .../testing/ManifestProtoUtils.java | 22 ++ .../ResourceTableValidatorTest.java | 116 +++++++ 54 files changed, 1643 insertions(+), 366 deletions(-) create mode 100755 src/main/java/com/android/tools/build/bundletool/model/exceptions/BundleInvalidZipException.java create mode 100755 src/main/java/com/android/tools/build/bundletool/model/utils/ModuleDependenciesUtils.java diff --git a/README.md b/README.md index ed8526e4..b220d693 100755 --- a/README.md +++ b/README.md @@ -26,4 +26,4 @@ https://developer.android.com/studio/command-line/bundletool ## Releases -Latest release: [0.10.1](https://github.com/google/bundletool/releases) +Latest release: [0.10.2](https://github.com/google/bundletool/releases) diff --git a/gradle.properties b/gradle.properties index dc131b51..bdc760ff 100755 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -release_version = 0.10.1 +release_version = 0.10.2 diff --git a/src/main/java/com/android/tools/build/bundletool/commands/BuildApksCommand.java b/src/main/java/com/android/tools/build/bundletool/commands/BuildApksCommand.java index bf51e743..0f482923 100755 --- a/src/main/java/com/android/tools/build/bundletool/commands/BuildApksCommand.java +++ b/src/main/java/com/android/tools/build/bundletool/commands/BuildApksCommand.java @@ -109,6 +109,7 @@ public final boolean isAnySystemMode() { private static final Flag CONNECTED_DEVICE_FLAG = Flag.booleanFlag("connected-device"); private static final Flag DEVICE_ID_FLAG = Flag.string("device-id"); private static final String ANDROID_SERIAL_VARIABLE = "ANDROID_SERIAL"; + private static final Flag> MODULES_FLAG = Flag.stringSet("modules"); private static final Flag DEVICE_SPEC_FLAG = Flag.path("device-spec"); @@ -131,6 +132,8 @@ public final boolean isAnySystemMode() { public abstract ImmutableSet getOptimizationDimensions(); + public abstract ImmutableSet getModules(); + public abstract Optional getDeviceSpec(); public abstract boolean getGenerateOnlyForConnectedDevice(); @@ -174,7 +177,8 @@ public static Builder builder() { .setApkBuildMode(DEFAULT) .setGenerateOnlyForConnectedDevice(false) .setCreateApkSetArchive(true) - .setOptimizationDimensions(ImmutableSet.of()); + .setOptimizationDimensions(ImmutableSet.of()) + .setModules(ImmutableSet.of()); } /** Builder for the {@link BuildApksCommand}. */ @@ -211,6 +215,8 @@ public abstract Builder setOptimizationDimensions( */ public abstract Builder setGenerateOnlyForConnectedDevice(boolean onlyForConnectedDevice); + public abstract Builder setModules(ImmutableSet modules); + /** Sets the {@link DeviceSpec} for which the only the matching APKs will be generated. */ public abstract Builder setDeviceSpec(DeviceSpec deviceSpec); @@ -384,6 +390,17 @@ public BuildApksCommand build() { } } + if (!command.getModules().isEmpty() + && !command.getApkBuildMode().isAnySystemMode() + && !command.getApkBuildMode().equals(UNIVERSAL)) { + throw ValidationException.builder() + .withMessage( + "Modules can be only set when running with '%s', '%s' or '%s' mode flag.", + UNIVERSAL.getLowerCaseName(), + SYSTEM.getLowerCaseName(), + SYSTEM_COMPRESSED.getLowerCaseName()) + .build(); + } return command; } } @@ -493,6 +510,8 @@ static BuildApksCommand fromFlags( .map(deviceSpecParser) .ifPresent(buildApksCommand::setDeviceSpec); + MODULES_FLAG.getValue(flags).ifPresent(buildApksCommand::setModules); + flags.checkNoUnknownFlags(); return buildApksCommand.build(); @@ -692,6 +711,18 @@ public static CommandHelp help() { MODE_FLAG.getName(), DEFAULT.getLowerCaseName()) .build()) + .addFlag( + FlagDescription.builder() + .setFlagName(MODULES_FLAG.getName()) + .setExampleValue("base,module1,module2") + .setOptional(true) + .setDescription( + "List of module names to include in the generated APK Set in modes %s, %s and " + + "%s.", + UNIVERSAL.getLowerCaseName(), + SYSTEM.getLowerCaseName(), + SYSTEM_COMPRESSED.getLowerCaseName()) + .build()) .build(); } diff --git a/src/main/java/com/android/tools/build/bundletool/commands/BuildApksManager.java b/src/main/java/com/android/tools/build/bundletool/commands/BuildApksManager.java index 870099fe..576cf4df 100755 --- a/src/main/java/com/android/tools/build/bundletool/commands/BuildApksManager.java +++ b/src/main/java/com/android/tools/build/bundletool/commands/BuildApksManager.java @@ -15,12 +15,14 @@ */ package com.android.tools.build.bundletool.commands; +import static com.android.tools.build.bundletool.model.utils.ModuleDependenciesUtils.getModulesIncludingDependencies; import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileDoesNotExist; 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.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; +import static com.google.common.collect.ImmutableSet.toImmutableSet; import com.android.bundle.Config.BundleConfig; import com.android.bundle.Config.Compression; @@ -41,6 +43,7 @@ import com.android.tools.build.bundletool.model.ApkModifier; import com.android.tools.build.bundletool.model.AppBundle; import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.BundleModuleName; import com.android.tools.build.bundletool.model.GeneratedApks; import com.android.tools.build.bundletool.model.GeneratedAssetSlices; import com.android.tools.build.bundletool.model.ModuleSplit; @@ -61,6 +64,7 @@ import com.android.tools.build.bundletool.splitters.SplitApksGenerator; import com.android.tools.build.bundletool.validation.AppBundleValidator; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; @@ -133,6 +137,12 @@ private void executeWithZip(ZipFile bundleZip, Optional deviceSpec) + "APKs."); } + ImmutableSet requestedModules = + command.getModules().isEmpty() + ? ImmutableSet.of() + : getModulesIncludingDependencies( + appBundle, getBundleModules(appBundle, command.getModules())); + BundleConfig bundleConfig = appBundle.getBundleConfig(); Version bundleVersion = BundleToolVersion.getVersionFromBundleConfig(bundleConfig); @@ -160,28 +170,22 @@ private void executeWithZip(ZipFile bundleZip, Optional deviceSpec) // Universal APK if (apksToGenerate.generateUniversalApk()) { // Note: Universal APK is a special type of standalone, with no optimization dimensions. - ImmutableList featureModules = appBundle.getFeatureModules().values().asList(); + ImmutableList modulesToFuse = + requestedModules.isEmpty() + ? modulesToFuse(appBundle.getFeatureModules().values().asList()) + : requestedModules.asList(); generatedApksBuilder.setStandaloneApks( new ShardedApksGenerator(tempDir, bundleVersion) .generateSplits( - modulesToFuse(featureModules), + modulesToFuse, appBundle.getBundleMetadata(), ApkOptimizations.getOptimizationsForUniversalApk())); } // System APKs if (apksToGenerate.generateSystemApks()) { - ImmutableList featureModules = appBundle.getFeatureModules().values().asList(); generatedApksBuilder.setSystemApks( - new ShardedApksGenerator( - tempDir, - bundleVersion, - /* generate64BitShards= */ !appBundle.has32BitRenderscriptCode()) - .generateSystemSplits( - featureModules, - appBundle.getBundleMetadata(), - getApkOptimizations(bundleConfig), - deviceSpec)); + generateSystemApks(appBundle, deviceSpec, requestedModules)); } // Asset Slices @@ -289,6 +293,28 @@ private ImmutableList generateSplitApks(AppBundle appBundle) throws .generateSplits(); } + private ImmutableList generateSystemApks( + AppBundle appBundle, + Optional deviceSpec, + ImmutableSet requestedModules) { + Version bundleVersion = Version.of(appBundle.getBundleConfig().getBundletool().getVersion()); + ImmutableList featureModules = appBundle.getFeatureModules().values().asList(); + ImmutableList modulesToFuse = + requestedModules.isEmpty() ? modulesToFuse(featureModules) : requestedModules.asList(); + return new ShardedApksGenerator( + tempDir, + bundleVersion, + /* generate64BitShards= */ !appBundle.has32BitRenderscriptCode()) + .generateSystemSplits( + /* modules= */ featureModules, + /* modulesToFuse= */ modulesToFuse.stream() + .map(BundleModule::getName) + .collect(toImmutableSet()), + appBundle.getBundleMetadata(), + getApkOptimizations(appBundle.getBundleConfig()), + deviceSpec); + } + private static void checkDeviceCompatibilityWithBundle( GeneratedApks generatedApks, DeviceSpec deviceSpec) { ApkMatcher apkMatcher = new ApkMatcher(deviceSpec); @@ -371,7 +397,7 @@ private ApkGenerationConfiguration getAssetSliceGenerationConfiguration() { .build(); } - private ImmutableList modulesToFuse(ImmutableList modules) { + private static ImmutableList modulesToFuse(ImmutableList modules) { return modules.stream().filter(BundleModule::isIncludedInFusing).collect(toImmutableList()); } @@ -411,6 +437,14 @@ private static boolean targetsPreL(AppBundle bundle) { return baseMinSdkVersion < Versions.ANDROID_L_API_VERSION; } + private static ImmutableList getBundleModules( + AppBundle appBundle, ImmutableSet moduleNames) { + return moduleNames.stream() + .map(BundleModuleName::create) + .map(appBundle::getModule) + .collect(toImmutableList()); + } + private static class ApksToGenerate { private final AppBundle appBundle; private final ApkBuildMode apkBuildMode; diff --git a/src/main/java/com/android/tools/build/bundletool/commands/DumpManager.java b/src/main/java/com/android/tools/build/bundletool/commands/DumpManager.java index 68585dd1..3932d583 100755 --- a/src/main/java/com/android/tools/build/bundletool/commands/DumpManager.java +++ b/src/main/java/com/android/tools/build/bundletool/commands/DumpManager.java @@ -27,6 +27,7 @@ import com.android.tools.build.bundletool.model.BundleModuleName; import com.android.tools.build.bundletool.model.ResourceTableEntry; import com.android.tools.build.bundletool.model.ZipPath; +import com.android.tools.build.bundletool.model.exceptions.BundleInvalidZipException; import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.android.tools.build.bundletool.model.utils.ResourcesUtils; import com.android.tools.build.bundletool.model.utils.ZipUtils; @@ -49,6 +50,7 @@ import java.util.Optional; import java.util.function.Predicate; import java.util.zip.ZipEntry; +import java.util.zip.ZipException; import java.util.zip.ZipFile; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathExpression; @@ -159,6 +161,8 @@ private static T extractAndParse( Path bundlePath, ZipPath filePath, ProtoParser protoParser) { try (ZipFile zipFile = new ZipFile(bundlePath.toFile())) { return extractAndParse(zipFile, filePath, protoParser); + } catch (ZipException e) { + throw new BundleInvalidZipException(e); } catch (IOException e) { throw new UncheckedIOException("Error occurred when trying to open the bundle.", e); } diff --git a/src/main/java/com/android/tools/build/bundletool/commands/ExtractApksCommand.java b/src/main/java/com/android/tools/build/bundletool/commands/ExtractApksCommand.java index a3b7c47b..86503a72 100755 --- a/src/main/java/com/android/tools/build/bundletool/commands/ExtractApksCommand.java +++ b/src/main/java/com/android/tools/build/bundletool/commands/ExtractApksCommand.java @@ -111,7 +111,21 @@ public Builder setDeviceSpec(Path deviceSpecPath) { public abstract Builder setInstant(boolean instant); - public abstract ExtractApksCommand build(); + abstract ExtractApksCommand autoBuild(); + + /** + * Builds the command + * + * @throws ValidationException if the device spec is invalid. See {@link + * DeviceSpecParser#validateDeviceSpec} + */ + public ExtractApksCommand build() { + ExtractApksCommand command = autoBuild(); + DeviceSpecParser.validateDeviceSpec( + command.getDeviceSpec(), + /* canSkipFields= */ true); // Allow partial device spec for APEX bundles + return command; + } } public static ExtractApksCommand fromFlags(ParsedFlags flags) { diff --git a/src/main/java/com/android/tools/build/bundletool/commands/GetDeviceSpecCommand.java b/src/main/java/com/android/tools/build/bundletool/commands/GetDeviceSpecCommand.java index fb9d46ad..ce2b7769 100755 --- a/src/main/java/com/android/tools/build/bundletool/commands/GetDeviceSpecCommand.java +++ b/src/main/java/com/android/tools/build/bundletool/commands/GetDeviceSpecCommand.java @@ -172,7 +172,7 @@ private void writeDeviceSpecToFile(DeviceSpec deviceSpec, Path outputFile) { } Path outputDirectory = getOutputPath().getParent(); - if (!Files.exists(outputDirectory)) { + if (outputDirectory != null && !Files.exists(outputDirectory)) { logger.info("Output directory '" + outputDirectory + "' does not exist, creating it."); Files.createDirectories(outputDirectory); } diff --git a/src/main/java/com/android/tools/build/bundletool/commands/GetSizeCommand.java b/src/main/java/com/android/tools/build/bundletool/commands/GetSizeCommand.java index c6f94fda..80ca2342 100755 --- a/src/main/java/com/android/tools/build/bundletool/commands/GetSizeCommand.java +++ b/src/main/java/com/android/tools/build/bundletool/commands/GetSizeCommand.java @@ -42,6 +42,7 @@ import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.android.tools.build.bundletool.model.utils.ResultUtils; import com.android.tools.build.bundletool.model.utils.files.FilePreconditions; +import com.android.tools.build.bundletool.model.version.Version; import com.google.auto.value.AutoValue; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; @@ -227,7 +228,12 @@ ConfigurationSizes getSizeTotalInternal() { for (Variant variant : variants) { ConfigurationSizes configurationSizes = - new VariantTotalSizeAggregator(compressedSizeByApkPaths, variant, this).getSize(); + new VariantTotalSizeAggregator( + compressedSizeByApkPaths, + Version.of(buildApksResult.getBundletool().getVersion()), + variant, + this) + .getSize(); minSizeConfigurationMap = combineMaps( minSizeConfigurationMap, configurationSizes.getMinSizeConfigurationMap(), Math::min); diff --git a/src/main/java/com/android/tools/build/bundletool/device/ApkMatcher.java b/src/main/java/com/android/tools/build/bundletool/device/ApkMatcher.java index 532309f8..aa7c58a0 100755 --- a/src/main/java/com/android/tools/build/bundletool/device/ApkMatcher.java +++ b/src/main/java/com/android/tools/build/bundletool/device/ApkMatcher.java @@ -16,12 +16,15 @@ package com.android.tools.build.bundletool.device; +import static com.android.tools.build.bundletool.model.utils.ModuleDependenciesUtils.addModuleDependencies; +import static com.android.tools.build.bundletool.model.utils.ModuleDependenciesUtils.buildAdjacencyMap; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.ImmutableSet.toImmutableSet; import com.android.bundle.Commands.ApkDescription; import com.android.bundle.Commands.ApkSet; import com.android.bundle.Commands.BuildApksResult; +import com.android.bundle.Commands.DeliveryType; import com.android.bundle.Commands.ModuleMetadata; import com.android.bundle.Commands.Variant; import com.android.bundle.Devices.DeviceSpec; @@ -30,12 +33,12 @@ import com.android.tools.build.bundletool.model.ZipPath; import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.model.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.version.Version; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Multimap; import com.google.common.collect.Sets; import java.util.HashSet; import java.util.Optional; @@ -94,14 +97,15 @@ public ImmutableList getMatchingApks(BuildApksResult buildApksResult) { Optional matchingVariant = variantMatcher.getMatchingVariant(buildApksResult); return matchingVariant.isPresent() - ? getMatchingApksFromVariant(matchingVariant.get()) + ? getMatchingApksFromVariant( + matchingVariant.get(), Version.of(buildApksResult.getBundletool().getVersion())) : ImmutableList.of(); } - public ImmutableList getMatchingApksFromVariant(Variant variant) { + public ImmutableList getMatchingApksFromVariant(Variant variant, Version bundleVersion) { ImmutableList.Builder matchedApksBuilder = ImmutableList.builder(); - Predicate moduleNameMatcher = getModuleNameMatcher(variant); + Predicate moduleNameMatcher = getModuleNameMatcher(variant, bundleVersion); for (ApkSet apkSet : variant.getApkSetList()) { String moduleName = apkSet.getModuleMetadata().getName(); @@ -122,7 +126,7 @@ public ImmutableList getMatchingApksFromVariant(Variant variant) { return matchedApksBuilder.build(); } - private Predicate getModuleNameMatcher(Variant variant) { + private Predicate getModuleNameMatcher(Variant variant, Version bundleVersion) { if (requestedModuleNames.isPresent()) { validateVariant(variant); @@ -137,7 +141,8 @@ private Predicate getModuleNameMatcher(Variant variant) { return dependencyModules::contains; } else { return Predicates.or( - buildModulesDeliveredInstallTime(variant)::contains, dependencyModules::contains); + buildModulesDeliveredInstallTime(variant, bundleVersion)::contains, + dependencyModules::contains); } } else { if (matchInstant) { @@ -145,7 +150,7 @@ private Predicate getModuleNameMatcher(Variant variant) { return Predicates.alwaysTrue(); } else { // For conventional matching, only install-time modules are matched. - return buildModulesDeliveredInstallTime(variant)::contains; + return buildModulesDeliveredInstallTime(variant, bundleVersion)::contains; } } } @@ -168,51 +173,25 @@ private void validateVariant(Variant variant) { } } - /** Builds a map of module dependencies. */ - private static ImmutableMultimap buildAdjacencyMap(Variant variant) { - ImmutableMultimap.Builder moduleDependenciesMap = ImmutableMultimap.builder(); - variant.getApkSetList().stream() - .map(ApkSet::getModuleMetadata) - .forEach( - moduleMetadata -> { - moduleDependenciesMap.putAll( - moduleMetadata.getName(), moduleMetadata.getDependenciesList()); - moduleDependenciesMap.put(moduleMetadata.getName(), "base"); - }); - return moduleDependenciesMap.build(); - } - /** Builds a list of modules that will be delivered on installation. */ - private ImmutableSet buildModulesDeliveredInstallTime(Variant variant) { + private ImmutableSet buildModulesDeliveredInstallTime( + Variant variant, Version bundleVersion) { // Module dependency resolution can be skipped because install-time modules can't depend on // on-demand modules. return variant.getApkSetList().stream() .map(ApkSet::getModuleMetadata) - .filter(this::willBeDeliveredInstallTime) + .filter(moduleMetadata -> willBeDeliveredInstallTime(moduleMetadata, bundleVersion)) .map(ModuleMetadata::getName) .collect(toImmutableSet()); } - private boolean willBeDeliveredInstallTime(ModuleMetadata moduleMetadata) { - return !moduleMetadata.getOnDemand() - && moduleMatcher.matchesModuleTargeting(moduleMetadata.getTargeting()); - } + private boolean willBeDeliveredInstallTime(ModuleMetadata moduleMetadata, Version bundleVersion) { + boolean installTime = + bundleVersion.isNewerThan(Version.of("0.10.1")) + ? moduleMetadata.getDeliveryType().equals(DeliveryType.INSTALL_TIME) + : !moduleMetadata.getOnDemandDeprecated(); - /** Adds module dependencies to {@code dependencyModules}. */ - private static void addModuleDependencies( - String moduleName, - Multimap moduleDependenciesMap, - Set dependencyModules) { - if (!moduleDependenciesMap.containsKey(moduleName)) { - return; - } - - for (String moduleDependency : moduleDependenciesMap.get(moduleName)) { - // We do not examine again the dependency that was previously handled and added. - if (dependencyModules.add(moduleDependency)) { - addModuleDependencies(moduleDependency, moduleDependenciesMap, dependencyModules); - } - } + return installTime && moduleMatcher.matchesModuleTargeting(moduleMetadata.getTargeting()); } /** Returns whether a given APK generated by the Bundle Tool should be installed on a device. */ diff --git a/src/main/java/com/android/tools/build/bundletool/device/DdmlibDevice.java b/src/main/java/com/android/tools/build/bundletool/device/DdmlibDevice.java index 1cc07ac9..14356d5d 100755 --- a/src/main/java/com/android/tools/build/bundletool/device/DdmlibDevice.java +++ b/src/main/java/com/android/tools/build/bundletool/device/DdmlibDevice.java @@ -99,6 +99,8 @@ public void executeShellCommand( @Override public void installApks(ImmutableList apks, InstallOptions installOptions) { ImmutableList apkFiles = apks.stream().map(Path::toFile).collect(toImmutableList()); + ImmutableList extraArgs = + installOptions.getAllowDowngrade() ? ImmutableList.of("-d") : ImmutableList.of(); try { if (getVersion() @@ -106,14 +108,14 @@ public void installApks(ImmutableList apks, InstallOptions installOptions) device.installPackages( apkFiles, installOptions.getAllowReinstall(), - installOptions.getAllowDowngrade() ? ImmutableList.of("-d") : ImmutableList.of(), + extraArgs, installOptions.getTimeout().toMillis(), TimeUnit.MILLISECONDS); } else { device.installPackage( Iterables.getOnlyElement(apkFiles).toString(), installOptions.getAllowReinstall(), - installOptions.getAllowDowngrade() ? "-d" : null); + extraArgs.toArray(new String[0])); } } catch (InstallException e) { throw InstallationException.builder() diff --git a/src/main/java/com/android/tools/build/bundletool/device/VariantTotalSizeAggregator.java b/src/main/java/com/android/tools/build/bundletool/device/VariantTotalSizeAggregator.java index 812fe77e..a68dfb40 100755 --- a/src/main/java/com/android/tools/build/bundletool/device/VariantTotalSizeAggregator.java +++ b/src/main/java/com/android/tools/build/bundletool/device/VariantTotalSizeAggregator.java @@ -45,6 +45,7 @@ import com.android.tools.build.bundletool.model.GetSizeRequest.Dimension; import com.android.tools.build.bundletool.model.SizeConfiguration; import com.android.tools.build.bundletool.model.ZipPath; +import com.android.tools.build.bundletool.model.version.Version; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -62,12 +63,17 @@ public class VariantTotalSizeAggregator { private static final Joiner COMMA_JOINER = Joiner.on(','); private final ImmutableMap sizeByApkPaths; + private final Version bundleVersion; private final Variant variant; private final GetSizeRequest getSizeRequest; public VariantTotalSizeAggregator( - ImmutableMap sizeByApkPaths, Variant variant, GetSizeRequest getSizeRequest) { + ImmutableMap sizeByApkPaths, + Version bundleVersion, + Variant variant, + GetSizeRequest getSizeRequest) { this.sizeByApkPaths = sizeByApkPaths; + this.bundleVersion = bundleVersion; this.variant = variant; this.getSizeRequest = getSizeRequest; } @@ -219,7 +225,7 @@ private ConfigurationSizes getSizesPerConfiguration( languageTargeting), getSizeRequest.getModules(), getSizeRequest.getInstant()) - .getMatchingApksFromVariant(variant)); + .getMatchingApksFromVariant(variant, bundleVersion)); minSizeByConfiguration.merge(configuration, compressedSize, Math::min); maxSizeByConfiguration.merge(configuration, compressedSize, Math::max); diff --git a/src/main/java/com/android/tools/build/bundletool/io/ApkSerializerManager.java b/src/main/java/com/android/tools/build/bundletool/io/ApkSerializerManager.java index 1fa04cd7..10cdb28d 100755 --- a/src/main/java/com/android/tools/build/bundletool/io/ApkSerializerManager.java +++ b/src/main/java/com/android/tools/build/bundletool/io/ApkSerializerManager.java @@ -15,6 +15,7 @@ */ package com.android.tools.build.bundletool.io; +import static com.android.tools.build.bundletool.io.ConcurrencyUtils.waitForAll; import static com.android.tools.build.bundletool.model.utils.CollectorUtils.groupingBySortedKeys; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; @@ -22,12 +23,16 @@ import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableMap.toImmutableMap; import static java.util.function.Function.identity; +import static java.util.stream.Collectors.collectingAndThen; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.mapping; import com.android.bundle.Commands.ApkDescription; import com.android.bundle.Commands.ApkSet; import com.android.bundle.Commands.AssetModuleMetadata; import com.android.bundle.Commands.AssetSliceSet; import com.android.bundle.Commands.BuildApksResult; +import com.android.bundle.Commands.DeliveryType; import com.android.bundle.Commands.InstantMetadata; import com.android.bundle.Commands.Variant; import com.android.bundle.Config.Bundletool; @@ -55,14 +60,12 @@ import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Multimap; -import com.google.common.collect.Multimaps; import com.google.common.util.concurrent.ListeningExecutorService; import java.util.Collection; import java.util.Map.Entry; import java.util.Optional; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Predicate; -import java.util.stream.Collectors; /** Creates parts of table of contents and writes out APKs. */ public class ApkSerializerManager { @@ -175,7 +178,7 @@ private ImmutableList serializeApks( finalSplitsByVariant.values().stream() .distinct() .collect( - Collectors.collectingAndThen( + collectingAndThen( toImmutableMap( identity(), split -> executorService.submit(() -> apkSerializer.serialize(split))), @@ -219,29 +222,33 @@ ImmutableList serializeAssetSlices( ? new ApkMatcher(deviceSpec.get())::matchesModuleSplitByTargeting : alwaysTrue(); - ImmutableList assetSlices = - generatedAssetSlices.getAssetSlices().stream() - .filter(deviceFilter) - .collect(toImmutableList()); - - ImmutableMap> slicesByModule = - Multimaps.index(assetSlices, ModuleSplit::getModuleName).asMap(); - - ImmutableList.Builder finalSlices = new ImmutableList.Builder<>(); - ApkSerializer apkSerializer = new ApkSerializer(apkListener, apkBuildMode); - for (BundleModuleName moduleName : slicesByModule.keySet()) { - finalSlices.add( - AssetSliceSet.newBuilder() - .setAssetModuleMetadata(getAssetModuleMetadata(appBundle.getModule(moduleName))) - .addAllApkDescription( - slicesByModule.get(moduleName).stream() - .flatMap(slice -> apkSerializer.serialize(slice).stream()) - .collect(toImmutableList())) - .build()); - } - return finalSlices.build(); + ImmutableListMultimap generatedSlicesByModule = + generatedAssetSlices.getAssetSlices().stream() + .filter(deviceFilter) + .collect( + groupingBy( + ModuleSplit::getModuleName, + mapping( + assetSlice -> + executorService.submit(() -> apkSerializer.serialize(assetSlice)), + toImmutableList()))) + .entrySet() + .stream() + .collect( + ImmutableListMultimap.flatteningToImmutableListMultimap( + Entry::getKey, + entry -> waitForAll(entry.getValue()).stream().flatMap(Collection::stream))); + return generatedSlicesByModule.asMap().entrySet().stream() + .map( + entry -> + AssetSliceSet.newBuilder() + .setAssetModuleMetadata( + getAssetModuleMetadata(appBundle.getModule(entry.getKey()))) + .addAllApkDescription(entry.getValue()) + .build()) + .collect(toImmutableList()); } private AssetModuleMetadata getAssetModuleMetadata(BundleModule module) { @@ -249,8 +256,10 @@ private AssetModuleMetadata getAssetModuleMetadata(BundleModule module) { AssetModuleMetadata.Builder metadataBuilder = AssetModuleMetadata.newBuilder().setName(module.getName().getName()); Optional persistentDelivery = manifest.getManifestDeliveryElement(); - persistentDelivery.ifPresent( - delivery -> metadataBuilder.setOnDemand(delivery.hasOnDemandElement())); + metadataBuilder.setDeliveryType( + persistentDelivery + .map(delivery -> getDeliveryType(delivery)) + .orElse(DeliveryType.INSTALL_TIME)); // The module is instant if either the dist:instant attribute is true or the // dist:instant-delivery element is present. boolean isInstantModule = module.isInstantModule(); @@ -263,8 +272,10 @@ private AssetModuleMetadata getAssetModuleMetadata(BundleModule module) { // If it's an instant-enabled module, the instant delivery is on-demand if the dist:instant // attribute was set to true or if the dist:instant-delivery element was used without an // install-time element. - instantMetadataBuilder.setOnDemand( - instantDelivery.map(delivery -> !delivery.hasInstallTimeElement()).orElse(true)); + instantMetadataBuilder.setDeliveryType( + instantDelivery + .map(delivery -> getDeliveryType(delivery)) + .orElse(DeliveryType.ON_DEMAND)); } metadataBuilder.setInstantMetadata(instantMetadataBuilder.build()); return metadataBuilder.build(); @@ -336,6 +347,16 @@ private static ModuleSplit clearVariantTargeting(ModuleSplit moduleSplit) { .build(); } + private static DeliveryType getDeliveryType(ManifestDeliveryElement deliveryElement) { + if (deliveryElement.hasOnDemandElement()) { + return DeliveryType.ON_DEMAND; + } + if (deliveryElement.hasFastFollowElement()) { + return DeliveryType.FAST_FOLLOW; + } + return DeliveryType.INSTALL_TIME; + } + private final class ApkSerializer { private final ApkListener apkListener; private final ApkBuildMode apkBuildMode; diff --git a/src/main/java/com/android/tools/build/bundletool/model/AndroidManifest.java b/src/main/java/com/android/tools/build/bundletool/model/AndroidManifest.java index e5315eca..7e57c802 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/AndroidManifest.java +++ b/src/main/java/com/android/tools/build/bundletool/model/AndroidManifest.java @@ -136,12 +136,16 @@ XmlProtoElement getManifestElement() { @Memoized public Optional getManifestDeliveryElement() { - return ManifestDeliveryElement.fromManifestElement(getManifestElement()); + return ManifestDeliveryElement.fromManifestElement( + getManifestElement(), + /* isFastFollowAllowed= */ getModuleType().equals(ModuleType.ASSET_MODULE)); } @Memoized public Optional getInstantManifestDeliveryElement() { - return ManifestDeliveryElement.instantFromManifestElement(getManifestElement()); + return ManifestDeliveryElement.instantFromManifestElement( + getManifestElement(), + /* isFastFollowAllowed= */ getModuleType().equals(ModuleType.ASSET_MODULE)); } /** diff --git a/src/main/java/com/android/tools/build/bundletool/model/BundleModule.java b/src/main/java/com/android/tools/build/bundletool/model/BundleModule.java index 40115d5c..19f35b4a 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/BundleModule.java +++ b/src/main/java/com/android/tools/build/bundletool/model/BundleModule.java @@ -27,6 +27,7 @@ import com.android.aapt.Resources.ResourceTable; import com.android.aapt.Resources.XmlNode; +import com.android.bundle.Commands.DeliveryType; import com.android.bundle.Commands.ModuleMetadata; import com.android.bundle.Config.BundleConfig; import com.android.bundle.Files.ApexImages; @@ -85,6 +86,7 @@ public abstract class BundleModule { public enum ModuleDeliveryType { ALWAYS_INITIAL_INSTALL, CONDITIONAL_INITIAL_INSTALL, + // Covers both on-demand and fast-follow modes. NO_INITIAL_INSTALL } @@ -260,13 +262,25 @@ public Optional getEntry(ZipPath path) { public ModuleMetadata getModuleMetadata() { return ModuleMetadata.newBuilder() .setName(getName().getName()) - .setOnDemand(getDeliveryType().equals(NO_INITIAL_INSTALL)) .setIsInstant(isInstantModule()) .addAllDependencies(getDependencies()) .setTargeting(getModuleTargeting()) + .setDeliveryType(moduleDeliveryTypeToDeliveryType(getDeliveryType())) .build(); } + private static DeliveryType moduleDeliveryTypeToDeliveryType( + ModuleDeliveryType moduleDeliveryType) { + switch (moduleDeliveryType) { + case ALWAYS_INITIAL_INSTALL: + case CONDITIONAL_INITIAL_INSTALL: + return DeliveryType.INSTALL_TIME; + case NO_INITIAL_INSTALL: + return DeliveryType.ON_DEMAND; + } + throw new IllegalArgumentException("Unknown module delivery type: " + moduleDeliveryType); + } + public static Builder builder() { return new AutoValue_BundleModule.Builder(); } diff --git a/src/main/java/com/android/tools/build/bundletool/model/ManifestDeliveryElement.java b/src/main/java/com/android/tools/build/bundletool/model/ManifestDeliveryElement.java index 1cd422b5..cfcc9c5c 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/ManifestDeliveryElement.java +++ b/src/main/java/com/android/tools/build/bundletool/model/ManifestDeliveryElement.java @@ -39,8 +39,11 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; /** Parses and provides business logic utilities for element. */ @Immutable @@ -49,13 +52,15 @@ public abstract class ManifestDeliveryElement { private static final String VERSION_ATTRIBUTE_NAME = "version"; - private static final ImmutableList ALLOWED_DELIVERY_MODES = - ImmutableList.of("install-time", "on-demand"); + private static final ImmutableList KNOWN_DELIVERY_MODES = + ImmutableList.of("install-time", "on-demand", "fast-follow"); private static final ImmutableList CONDITIONS_ALLOWED_ONLY_ONCE = ImmutableList.of(CONDITION_MIN_SDK_VERSION_NAME, CONDITION_USER_COUNTRIES_NAME); abstract XmlProtoElement getDeliveryElement(); + abstract boolean isFastFollowAllowed(); + /** * Returns if this element is well-formed. * @@ -63,7 +68,9 @@ public abstract class ManifestDeliveryElement { * elements. */ public boolean isWellFormed() { - return hasOnDemandElement() || hasInstallTimeElement(); + return hasOnDemandElement() + || hasInstallTimeElement() + || (isFastFollowAllowed() && hasFastFollowElement()); } public boolean hasModuleConditions() { @@ -77,6 +84,12 @@ public boolean hasOnDemandElement() { .isPresent(); } + public boolean hasFastFollowElement() { + return getDeliveryElement() + .getOptionalChildElement(DISTRIBUTION_NAMESPACE_URI, "fast-follow") + .isPresent(); + } + @Memoized public boolean hasInstallTimeElement() { return getDeliveryElement() @@ -165,28 +178,40 @@ private UserCountriesCondition parseUserCountriesCondition(XmlProtoElement condi return UserCountriesCondition.create(countryCodes.build(), exclude); } - private static void validateDeliveryElement(XmlProtoElement deliveryElement) { - validateDeliveryElementChildren(deliveryElement); + private static void validateDeliveryElement( + XmlProtoElement deliveryElement, boolean isFastFollowAllowed) { + validateDeliveryElementChildren(deliveryElement, isFastFollowAllowed); validateInstallTimeElement( deliveryElement.getOptionalChildElement(DISTRIBUTION_NAMESPACE_URI, "install-time")); validateOnDemandElement( deliveryElement.getOptionalChildElement(DISTRIBUTION_NAMESPACE_URI, "on-demand")); + if (isFastFollowAllowed) { + validateFastFollowElement( + deliveryElement.getOptionalChildElement(DISTRIBUTION_NAMESPACE_URI, "fast-follow")); + } } - private static void validateDeliveryElementChildren(XmlProtoElement deliveryElement) { + private static void validateDeliveryElementChildren( + XmlProtoElement deliveryElement, boolean isFastFollowAllowed) { + Set allowedDeliveryModes = new LinkedHashSet<>(KNOWN_DELIVERY_MODES); + if (!isFastFollowAllowed) { + allowedDeliveryModes.remove("fast-follow"); + } Optional offendingElement = deliveryElement .getChildrenElements( child -> !(child.getNamespaceUri().equals(DISTRIBUTION_NAMESPACE_URI) - && ALLOWED_DELIVERY_MODES.contains(child.getName()))) + && allowedDeliveryModes.contains(child.getName()))) .findAny(); if (offendingElement.isPresent()) { throw ValidationException.builder() .withMessage( - "Expected element to contain only or " - + " elements but found: %s", + "Expected element to contain only %s elements but found: %s", + allowedDeliveryModes.stream() + .map(name -> String.format("", name)) + .collect(Collectors.joining(", ")), printElement(offendingElement.get())) .build(); } @@ -225,6 +250,18 @@ private static void validateOnDemandElement(Optional onDemandEl } } + private static void validateFastFollowElement(Optional fastFollowElement) { + Optional offendingChild = + fastFollowElement.flatMap(element -> element.getChildrenElements().findAny()); + if (offendingChild.isPresent()) { + throw ValidationException.builder() + .withMessage( + "Expected element to have no child elements but found: %s.", + printElement(offendingChild.get())) + .build(); + } + } + private ImmutableList getModuleConditionElements() { Optional installTimeElement = getDeliveryElement().getOptionalChildElement(DISTRIBUTION_NAMESPACE_URI, "install-time"); @@ -274,8 +311,8 @@ private static String printElement(XmlProtoElement element) { * contains the element. */ public static Optional fromManifestElement( - XmlProtoElement manifestElement) { - return fromManifestElement(manifestElement, "delivery"); + XmlProtoElement manifestElement, boolean isFastFollowAllowed) { + return fromManifestElement(manifestElement, "delivery", isFastFollowAllowed); } /** @@ -283,29 +320,31 @@ public static Optional fromManifestElement( * the element. */ public static Optional instantFromManifestElement( - XmlProtoElement manifestElement) { - return fromManifestElement(manifestElement, "instant-delivery"); + XmlProtoElement manifestElement, boolean isFastFollowAllowed) { + return fromManifestElement(manifestElement, "instant-delivery", isFastFollowAllowed); } private static Optional fromManifestElement( - XmlProtoElement manifestElement, String deliveryTag) { + XmlProtoElement manifestElement, String deliveryTag, boolean isFastFollowAllowed) { return manifestElement .getOptionalChildElement(DISTRIBUTION_NAMESPACE_URI, "module") .flatMap(elem -> elem.getOptionalChildElement(DISTRIBUTION_NAMESPACE_URI, deliveryTag)) .map( (XmlProtoElement elem) -> { - validateDeliveryElement(elem); - return new AutoValue_ManifestDeliveryElement(elem); + validateDeliveryElement(elem, isFastFollowAllowed); + return new AutoValue_ManifestDeliveryElement(elem, isFastFollowAllowed); }); } @VisibleForTesting - static Optional fromManifestRootNode(XmlNode xmlNode) { - return fromManifestElement(new XmlProtoNode(xmlNode).getElement()); + static Optional fromManifestRootNode( + XmlNode xmlNode, boolean isFastFollowAllowed) { + return fromManifestElement(new XmlProtoNode(xmlNode).getElement(), isFastFollowAllowed); } @VisibleForTesting - static Optional instantFromManifestRootNode(XmlNode xmlNode) { - return instantFromManifestElement(new XmlProtoNode(xmlNode).getElement()); + static Optional instantFromManifestRootNode( + XmlNode xmlNode, boolean isFastFollowAllowed) { + return instantFromManifestElement(new XmlProtoNode(xmlNode).getElement(), isFastFollowAllowed); } } diff --git a/src/main/java/com/android/tools/build/bundletool/model/OptimizationDimension.java b/src/main/java/com/android/tools/build/bundletool/model/OptimizationDimension.java index 6e0dd61a..28f46928 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/OptimizationDimension.java +++ b/src/main/java/com/android/tools/build/bundletool/model/OptimizationDimension.java @@ -21,4 +21,5 @@ public enum OptimizationDimension { ABI, SCREEN_DENSITY, LANGUAGE, + TEXTURE_COMPRESSION_FORMAT, } diff --git a/src/main/java/com/android/tools/build/bundletool/model/exceptions/BundleInvalidZipException.java b/src/main/java/com/android/tools/build/bundletool/model/exceptions/BundleInvalidZipException.java new file mode 100755 index 00000000..d2c2ca8a --- /dev/null +++ b/src/main/java/com/android/tools/build/bundletool/model/exceptions/BundleInvalidZipException.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2019 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.build.bundletool.model.exceptions; + +import java.util.zip.ZipException; + +/** Thrown when bundletool validation detects that the bundle is not in the valid zip format */ +public class BundleInvalidZipException extends ValidationException { + + public BundleInvalidZipException(ZipException cause) { + super("Bundle is not a valid zip file", cause); + } +} diff --git a/src/main/java/com/android/tools/build/bundletool/model/utils/ModuleDependenciesUtils.java b/src/main/java/com/android/tools/build/bundletool/model/utils/ModuleDependenciesUtils.java new file mode 100755 index 00000000..5ab1bc0a --- /dev/null +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/ModuleDependenciesUtils.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2019 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.build.bundletool.model.utils; + +import static com.android.tools.build.bundletool.model.BundleModuleName.BASE_MODULE_NAME; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.collect.ImmutableSet.toImmutableSet; +import static java.util.stream.Collectors.toSet; + +import com.android.bundle.Commands.ApkSet; +import com.android.bundle.Commands.Variant; +import com.android.tools.build.bundletool.model.AndroidManifest; +import com.android.tools.build.bundletool.model.AppBundle; +import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.BundleModuleName; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multimaps; +import java.util.Set; + +/** Helpers related to dependencies of the modules. */ +public final class ModuleDependenciesUtils { + + /** Gets modules including their dependencies for the requested modules. */ + public static ImmutableSet getModulesIncludingDependencies( + AppBundle appBundle, ImmutableList modules) { + Multimap adjacencyMap = buildAdjacencyMap(modules); + Set dependencyModules = + modules.stream().map(module -> module.getName().getName()).collect(toSet()); + for (BundleModule requestedModule : modules) { + addModuleDependencies(requestedModule.getName().getName(), adjacencyMap, dependencyModules); + } + return dependencyModules.stream() + .map(module -> appBundle.getModule(BundleModuleName.create(module))) + .collect(toImmutableSet()); + } + + /** Builds a map of module dependencies. */ + public static ImmutableMultimap buildAdjacencyMap(Variant variant) { + ImmutableMultimap.Builder moduleDependenciesMap = ImmutableMultimap.builder(); + variant.getApkSetList().stream() + .map(ApkSet::getModuleMetadata) + .forEach( + moduleMetadata -> { + moduleDependenciesMap.putAll( + moduleMetadata.getName(), moduleMetadata.getDependenciesList()); + moduleDependenciesMap.put(moduleMetadata.getName(), "base"); + }); + return moduleDependenciesMap.build(); + } + + /** + * Builds a map of module dependencies. + * + *

If module "a" contains {@code } manifest entry, then the map contains + * entry ("a", "b"). + * + *

All modules implicitly depend on the "base" module. Hence the map contains also dependency + * ("base", "base"). + */ + public static Multimap buildAdjacencyMap(ImmutableList modules) { + Multimap moduleDependenciesMap = ArrayListMultimap.create(); + + for (BundleModule module : modules) { + String moduleName = module.getName().getName(); + AndroidManifest manifest = module.getAndroidManifest(); + + checkArgument( + !moduleDependenciesMap.containsKey(moduleName), + "Module named '%s' was passed in multiple times.", + moduleName); + + moduleDependenciesMap.putAll(moduleName, manifest.getUsesSplits()); + + // Check that module does not declare explicit dependency on the "base" module + // (whose split ID actually is empty instead of "base" anyway). + if (moduleDependenciesMap.containsEntry(moduleName, BASE_MODULE_NAME.getName())) { + throw ValidationException.builder() + .withMessage( + "Module '%s' declares dependency on the '%s' module, which is implicit.", + moduleName, BASE_MODULE_NAME) + .build(); + } + + // Add implicit dependency on the base. Also ensures that every module has a key in the map. + moduleDependenciesMap.put(moduleName, BASE_MODULE_NAME.getName()); + } + + return Multimaps.unmodifiableMultimap(moduleDependenciesMap); + } + + /** Adds module dependencies to {@code dependencyModules}. */ + public static void addModuleDependencies( + String moduleName, + Multimap moduleDependenciesMap, + Set dependencyModules) { + if (!moduleDependenciesMap.containsKey(moduleName)) { + return; + } + + for (String moduleDependency : moduleDependenciesMap.get(moduleName)) { + // We do not examine again the dependency that was previously handled and added. + if (dependencyModules.add(moduleDependency)) { + addModuleDependencies(moduleDependency, moduleDependenciesMap, dependencyModules); + } + } + } +} diff --git a/src/main/java/com/android/tools/build/bundletool/model/utils/Versions.java b/src/main/java/com/android/tools/build/bundletool/model/utils/Versions.java index f866c95f..b85e7886 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/utils/Versions.java +++ b/src/main/java/com/android/tools/build/bundletool/model/utils/Versions.java @@ -24,6 +24,7 @@ public final class Versions { public static final int ANDROID_M_API_VERSION = 23; public static final int ANDROID_O_API_VERSION = 26; public static final int ANDROID_P_API_VERSION = 28; + public static final int ANDROID_Q_API_VERSION = 29; // Not meant to be instantiated. private Versions() {} diff --git a/src/main/java/com/android/tools/build/bundletool/model/version/BundleToolVersion.java b/src/main/java/com/android/tools/build/bundletool/model/version/BundleToolVersion.java index 72d23ec9..8b8c3b26 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/version/BundleToolVersion.java +++ b/src/main/java/com/android/tools/build/bundletool/model/version/BundleToolVersion.java @@ -26,7 +26,7 @@ */ public final class BundleToolVersion { - private static final String CURRENT_VERSION = "0.10.1"; + private static final String CURRENT_VERSION = "0.10.2"; /** Returns the version of BundleTool being run. */ public static Version getCurrentVersion() { diff --git a/src/main/java/com/android/tools/build/bundletool/model/version/Version.java b/src/main/java/com/android/tools/build/bundletool/model/version/Version.java index 44e7e930..9ce418e4 100755 --- a/src/main/java/com/android/tools/build/bundletool/model/version/Version.java +++ b/src/main/java/com/android/tools/build/bundletool/model/version/Version.java @@ -99,6 +99,10 @@ public boolean isOlderThan(Version version) { return this.compareTo(version) < 0; } + public boolean isNewerThan(Version version) { + return this.compareTo(version) > 0; + } + @Override public final String toString() { return getFullVersion(); diff --git a/src/main/java/com/android/tools/build/bundletool/optimizations/ApkOptimizations.java b/src/main/java/com/android/tools/build/bundletool/optimizations/ApkOptimizations.java index 4310228e..b8d0ac4c 100755 --- a/src/main/java/com/android/tools/build/bundletool/optimizations/ApkOptimizations.java +++ b/src/main/java/com/android/tools/build/bundletool/optimizations/ApkOptimizations.java @@ -18,6 +18,7 @@ import static com.android.tools.build.bundletool.model.OptimizationDimension.ABI; import static com.android.tools.build.bundletool.model.OptimizationDimension.LANGUAGE; import static com.android.tools.build.bundletool.model.OptimizationDimension.SCREEN_DENSITY; +import static com.android.tools.build.bundletool.model.OptimizationDimension.TEXTURE_COMPRESSION_FORMAT; import static com.google.common.base.Preconditions.checkNotNull; import com.android.tools.build.bundletool.model.OptimizationDimension; @@ -54,6 +55,14 @@ public abstract class ApkOptimizations { .setSplitDimensions(ImmutableSet.of(ABI, SCREEN_DENSITY, LANGUAGE)) .setUncompressNativeLibraries(true) .build()) + .put( + Version.of("0.10.2"), + ApkOptimizations.builder() + .setSplitDimensions( + ImmutableSet.of( + ABI, SCREEN_DENSITY, TEXTURE_COMPRESSION_FORMAT, LANGUAGE)) + .setUncompressNativeLibraries(true) + .build()) .build(); public abstract ImmutableSet getSplitDimensions(); diff --git a/src/main/java/com/android/tools/build/bundletool/splitters/BundleSharder.java b/src/main/java/com/android/tools/build/bundletool/splitters/BundleSharder.java index 935bc7d0..60135847 100755 --- a/src/main/java/com/android/tools/build/bundletool/splitters/BundleSharder.java +++ b/src/main/java/com/android/tools/build/bundletool/splitters/BundleSharder.java @@ -32,6 +32,7 @@ import com.android.tools.build.bundletool.mergers.SameTargetingMerger; import com.android.tools.build.bundletool.model.BundleMetadata; import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.BundleModuleName; import com.android.tools.build.bundletool.model.ModuleSplit; import com.android.tools.build.bundletool.model.OptimizationDimension; import com.android.tools.build.bundletool.model.ShardedSystemSplits; @@ -109,6 +110,7 @@ public ImmutableList shardBundle( */ public ShardedSystemSplits shardForSystemApps( ImmutableList modules, + ImmutableSet modulesToFuse, ImmutableSet shardingDimensions, BundleMetadata bundleMetadata) { checkState( @@ -116,10 +118,7 @@ public ShardedSystemSplits shardForSystemApps( "Device spec should be set when sharding for system apps."); return merger.mergeSystemShard( Iterables.getOnlyElement(generateUnfusedShards(modules, shardingDimensions)), - modules.stream() - .filter(BundleModule::isIncludedInFusing) - .map(BundleModule::getName) - .collect(toImmutableSet()), + modulesToFuse, bundleMetadata, bundleSharderConfiguration.getDeviceSpec().get()); } diff --git a/src/main/java/com/android/tools/build/bundletool/splitters/DexCompressionSplitter.java b/src/main/java/com/android/tools/build/bundletool/splitters/DexCompressionSplitter.java index 6394bf66..57148d6c 100755 --- a/src/main/java/com/android/tools/build/bundletool/splitters/DexCompressionSplitter.java +++ b/src/main/java/com/android/tools/build/bundletool/splitters/DexCompressionSplitter.java @@ -17,7 +17,7 @@ package com.android.tools.build.bundletool.splitters; import static com.android.tools.build.bundletool.model.BundleModule.DEX_DIRECTORY; -import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_P_API_VERSION; +import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_Q_API_VERSION; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.ImmutableSet.toImmutableSet; @@ -47,8 +47,8 @@ public ImmutableCollection split(ModuleSplit moduleSplit) { return ImmutableList.of(moduleSplit); } - // Only APKs targeting devices below Android P should be compressed. - boolean shouldCompress = targetsPreP(moduleSplit); + // Only APKs targeting devices below Android Q should be compressed. + boolean shouldCompress = targetsPreQ(moduleSplit); return ImmutableList.of( createModuleSplit( moduleSplit, mergeAndSetCompression(dexEntries, moduleSplit, shouldCompress))); @@ -71,14 +71,14 @@ private static ImmutableList mergeAndSetCompression( .build(); } - private static boolean targetsPreP(ModuleSplit moduleSplit) { + private static boolean targetsPreQ(ModuleSplit moduleSplit) { int sdkVersion = Iterables.getOnlyElement( moduleSplit.getVariantTargeting().getSdkVersionTargeting().getValueList()) .getMin() .getValue(); - return sdkVersion < ANDROID_P_API_VERSION; + return sdkVersion < ANDROID_Q_API_VERSION; } private static ModuleSplit createModuleSplit( diff --git a/src/main/java/com/android/tools/build/bundletool/splitters/DexCompressionVariantGenerator.java b/src/main/java/com/android/tools/build/bundletool/splitters/DexCompressionVariantGenerator.java index 23acbf5b..d7d8048a 100755 --- a/src/main/java/com/android/tools/build/bundletool/splitters/DexCompressionVariantGenerator.java +++ b/src/main/java/com/android/tools/build/bundletool/splitters/DexCompressionVariantGenerator.java @@ -20,7 +20,7 @@ import static com.android.tools.build.bundletool.model.utils.TargetingProtoUtils.sdkVersionFrom; import static com.android.tools.build.bundletool.model.utils.TargetingProtoUtils.sdkVersionTargeting; import static com.android.tools.build.bundletool.model.utils.TargetingProtoUtils.variantTargeting; -import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_P_API_VERSION; +import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_Q_API_VERSION; import static com.google.common.collect.ImmutableSet.toImmutableSet; import com.android.bundle.Targeting.VariantTargeting; @@ -53,6 +53,8 @@ public Stream generate(BundleModule module) { return Stream.of(); } - return Stream.of(variantTargeting(sdkVersionTargeting(sdkVersionFrom(ANDROID_P_API_VERSION)))); + // Uncompressed dex are supported starting from Android P, but only starting from Android Q the + // performance impact is negligible compared to a compressed dex. + return Stream.of(variantTargeting(sdkVersionTargeting(sdkVersionFrom(ANDROID_Q_API_VERSION)))); } } diff --git a/src/main/java/com/android/tools/build/bundletool/splitters/ModuleSplitter.java b/src/main/java/com/android/tools/build/bundletool/splitters/ModuleSplitter.java index c90d1191..c1d860d3 100755 --- a/src/main/java/com/android/tools/build/bundletool/splitters/ModuleSplitter.java +++ b/src/main/java/com/android/tools/build/bundletool/splitters/ModuleSplitter.java @@ -290,6 +290,11 @@ private SplittingPipeline createAssetsSplittingPipeline() { .contains(OptimizationDimension.LANGUAGE)) { assetsSplitters.add(LanguageAssetsSplitter.create()); } + if (apkGenerationConfiguration + .getOptimizationDimensions() + .contains(OptimizationDimension.TEXTURE_COMPRESSION_FORMAT)) { + assetsSplitters.add(TextureCompressionFormatAssetsSplitter.create()); + } return new SplittingPipeline(assetsSplitters.build()); } diff --git a/src/main/java/com/android/tools/build/bundletool/splitters/ShardedApksGenerator.java b/src/main/java/com/android/tools/build/bundletool/splitters/ShardedApksGenerator.java index 32aa49e4..1542d0b3 100755 --- a/src/main/java/com/android/tools/build/bundletool/splitters/ShardedApksGenerator.java +++ b/src/main/java/com/android/tools/build/bundletool/splitters/ShardedApksGenerator.java @@ -26,12 +26,14 @@ import com.android.bundle.Targeting.VariantTargeting; import com.android.tools.build.bundletool.model.BundleMetadata; import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.BundleModuleName; import com.android.tools.build.bundletool.model.ModuleSplit; import com.android.tools.build.bundletool.model.ModuleSplit.SplitType; import com.android.tools.build.bundletool.model.ShardedSystemSplits; import com.android.tools.build.bundletool.model.version.Version; import com.android.tools.build.bundletool.optimizations.ApkOptimizations; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; import java.nio.file.Path; import java.util.Optional; @@ -71,6 +73,7 @@ public ImmutableList generateSplits( public ImmutableList generateSystemSplits( ImmutableList modules, + ImmutableSet modulesToFuse, BundleMetadata bundleMetadata, ApkOptimizations apkOptimizations, Optional deviceSpec) { @@ -83,7 +86,7 @@ public ImmutableList generateSystemSplits( BundleSharder bundleSharder = new BundleSharder(tempDir, bundleVersion, configuration); ShardedSystemSplits shardedApks = bundleSharder.shardForSystemApps( - modules, apkOptimizations.getSplitDimensions(), bundleMetadata); + modules, modulesToFuse, apkOptimizations.getSplitDimensions(), bundleMetadata); ModuleSplit fusedApk = setVariantTargetingAndSplitType(shardedApks.getSystemImageSplit(), SplitType.SYSTEM) diff --git a/src/main/java/com/android/tools/build/bundletool/validation/ModuleDependencyValidator.java b/src/main/java/com/android/tools/build/bundletool/validation/ModuleDependencyValidator.java index cb703251..bdf0cae3 100755 --- a/src/main/java/com/android/tools/build/bundletool/validation/ModuleDependencyValidator.java +++ b/src/main/java/com/android/tools/build/bundletool/validation/ModuleDependencyValidator.java @@ -17,19 +17,16 @@ package com.android.tools.build.bundletool.validation; import static com.android.tools.build.bundletool.model.BundleModuleName.BASE_MODULE_NAME; -import static com.google.common.base.Preconditions.checkArgument; +import static com.android.tools.build.bundletool.model.utils.ModuleDependenciesUtils.buildAdjacencyMap; -import com.android.tools.build.bundletool.model.AndroidManifest; import com.android.tools.build.bundletool.model.BundleModule; import com.android.tools.build.bundletool.model.BundleModule.ModuleDeliveryType; import com.android.tools.build.bundletool.model.BundleModule.ModuleType; import com.android.tools.build.bundletool.model.exceptions.ValidationException; -import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; -import com.google.common.collect.Multimaps; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashSet; @@ -67,46 +64,6 @@ public void validateAllModules(ImmutableList modules) { checkNoDependenciesBetweenModulesOfDifferentTypes(moduleDependenciesMap, modulesByName); } - /** - * Builds a map of module dependencies. - * - *

If module "a" contains {@code } manifest entry, then the map contains - * entry ("a", "b"). - * - *

All modules implicitly depend on the "base" module. Hence the map contains also dependency - * ("base", "base"). - */ - private static Multimap buildAdjacencyMap(ImmutableList modules) { - Multimap moduleDependenciesMap = ArrayListMultimap.create(); - - for (BundleModule module : modules) { - String moduleName = module.getName().getName(); - AndroidManifest manifest = module.getAndroidManifest(); - - checkArgument( - !moduleDependenciesMap.containsKey(moduleName), - "Module named '%s' was passed in multiple times.", - moduleName); - - moduleDependenciesMap.putAll(moduleName, manifest.getUsesSplits()); - - // Check that module does not declare explicit dependency on the "base" module - // (whose split ID actually is empty instead of "base" anyway). - if (moduleDependenciesMap.containsEntry(moduleName, BASE_MODULE_NAME.getName())) { - throw ValidationException.builder() - .withMessage( - "Module '%s' declares dependency on the '%s' module, which is implicit.", - moduleName, BASE_MODULE_NAME) - .build(); - } - - // Add implicit dependency on the base. Also ensures that every module has a key in the map. - moduleDependenciesMap.put(moduleName, BASE_MODULE_NAME.getName()); - } - - return Multimaps.unmodifiableMultimap(moduleDependenciesMap); - } - private static void checkHasBaseModule(ImmutableList modules) { if (!modules.stream().anyMatch(BundleModule::isBaseModule)) { throw ValidationException.builder() diff --git a/src/main/java/com/android/tools/build/bundletool/validation/ResourceTableValidator.java b/src/main/java/com/android/tools/build/bundletool/validation/ResourceTableValidator.java index 60510aad..17280958 100755 --- a/src/main/java/com/android/tools/build/bundletool/validation/ResourceTableValidator.java +++ b/src/main/java/com/android/tools/build/bundletool/validation/ResourceTableValidator.java @@ -18,17 +18,23 @@ import static com.google.common.collect.ImmutableSet.toImmutableSet; import static com.google.common.collect.Sets.difference; +import static com.google.common.collect.Sets.newHashSet; import com.android.aapt.Resources.ResourceTable; import com.android.tools.build.bundletool.model.BundleModule; import com.android.tools.build.bundletool.model.ModuleEntry; +import com.android.tools.build.bundletool.model.ResourceId; import com.android.tools.build.bundletool.model.ZipPath; import com.android.tools.build.bundletool.model.exceptions.ResouceTableException.ReferencesFileOutsideOfResException; import com.android.tools.build.bundletool.model.exceptions.ResouceTableException.ReferencesMissingFilesException; import com.android.tools.build.bundletool.model.exceptions.ResouceTableException.ResourceTableMissingException; import com.android.tools.build.bundletool.model.exceptions.ResouceTableException.UnreferencedResourcesException; +import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.android.tools.build.bundletool.model.utils.ResourcesUtils; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; +import java.util.HashSet; /** * Validates the resource table. @@ -82,4 +88,30 @@ public void validateModule(BundleModule module) { throw new ReferencesMissingFilesException(moduleName, nonExistingFiles); } } + + @Override + public void validateAllModules(ImmutableList modules) { + checkResourceIdsAreUnique(modules); + } + + @VisibleForTesting + void checkResourceIdsAreUnique(ImmutableList modules) { + HashSet usedResourceIds = newHashSet(); + for (BundleModule module : modules) { + ResourceTable resourceTable = + module.getResourceTable().orElse(ResourceTable.getDefaultInstance()); + ResourcesUtils.entries(resourceTable) + .forEach( + resourceTableEntry -> { + boolean foundDuplicate = !usedResourceIds.add(resourceTableEntry.getResourceId()); + if (foundDuplicate) { + throw ValidationException.builder() + .withMessage( + "Duplicate resource id (%s).", + resourceTableEntry.getResourceId().toString()) + .build(); + } + }); + } + } } diff --git a/src/main/proto/app_dependencies.proto b/src/main/proto/app_dependencies.proto index 28e98a7c..9185beef 100755 --- a/src/main/proto/app_dependencies.proto +++ b/src/main/proto/app_dependencies.proto @@ -39,8 +39,8 @@ message Library { } message MavenLibrary { - optional string groupId = 1; - optional string artifactId = 2; + optional string group_id = 1; + optional string artifact_id = 2; optional string packaging = 3; optional string classifier = 4; optional string version = 5; diff --git a/src/main/proto/commands.proto b/src/main/proto/commands.proto index cf894c4c..1f4176fe 100755 --- a/src/main/proto/commands.proto +++ b/src/main/proto/commands.proto @@ -2,11 +2,11 @@ syntax = "proto3"; package android.bundle; -option java_package = "com.android.bundle"; - import "config.proto"; import "targeting.proto"; +option java_package = "com.android.bundle"; + // Describes the output of the "build-apks" command. message BuildApksResult { // List of the created variants. @@ -50,9 +50,8 @@ message ModuleMetadata { // Module name. string name = 1; - // Indicates whether this module is marked "on demand". - // Set only for Split APKs. - bool on_demand = 2; + // Indicates the delivery type (e.g. on-demand) of the module. + DeliveryType delivery_type = 6; // Indicates whether this module is marked "instant". bool is_instant = 3; @@ -64,6 +63,9 @@ message ModuleMetadata { // The targeting that makes a conditional module installed. // Relevant only for Split APKs. ModuleTargeting targeting = 5; + + // Deprecated. Please use delivery_type. + bool on_demand_deprecated = 2 [deprecated = true]; } // Set of asset slices belonging to a single asset module. @@ -79,19 +81,32 @@ message AssetModuleMetadata { // Module name. string name = 1; - // Indicates whether this module is marked "on demand" for persistent install. - bool on_demand = 2; + // Indicates the delivery type for persistent install. + DeliveryType delivery_type = 4; // Metadata for instant installs. InstantMetadata instant_metadata = 3; + + // Deprecated. Use delivery_type. + bool on_demand_deprecated = 2 [deprecated = true]; } message InstantMetadata { // Indicates whether this module is marked "instant". bool is_instant = 1; - // Indicates whether this module is marked "on demand" for instant install. - bool on_demand = 2; + // Indicates the delivery type for instant install. + DeliveryType delivery_type = 3; + + // Deprecated. Use delivery_type. + bool on_demand_deprecated = 2 [deprecated = true]; +} + +enum DeliveryType { + UNKNOWN_DELIVERY_TYPE = 0; + INSTALL_TIME = 1; + ON_DEMAND = 2; + FAST_FOLLOW = 3; } message ApkDescription { diff --git a/src/main/proto/config.proto b/src/main/proto/config.proto index eb4f5646..368abe87 100755 --- a/src/main/proto/config.proto +++ b/src/main/proto/config.proto @@ -59,6 +59,7 @@ message SplitDimension { ABI = 1; SCREEN_DENSITY = 2; LANGUAGE = 3; + TEXTURE_COMPRESSION_FORMAT = 4; } Value value = 1; diff --git a/src/test/java/com/android/tools/build/bundletool/commands/BuildApksCommandTest.java b/src/test/java/com/android/tools/build/bundletool/commands/BuildApksCommandTest.java index 3c265379..d0d58be7 100755 --- a/src/test/java/com/android/tools/build/bundletool/commands/BuildApksCommandTest.java +++ b/src/test/java/com/android/tools/build/bundletool/commands/BuildApksCommandTest.java @@ -16,9 +16,11 @@ package com.android.tools.build.bundletool.commands; +import static com.android.tools.build.bundletool.commands.BuildApksCommand.ApkBuildMode.DEFAULT; import static com.android.tools.build.bundletool.commands.BuildApksCommand.ApkBuildMode.UNIVERSAL; import static com.android.tools.build.bundletool.model.OptimizationDimension.ABI; import static com.android.tools.build.bundletool.model.OptimizationDimension.SCREEN_DENSITY; +import static com.android.tools.build.bundletool.model.OptimizationDimension.TEXTURE_COMPRESSION_FORMAT; import static com.android.tools.build.bundletool.testing.Aapt2Helper.AAPT2_PATH; import static com.android.tools.build.bundletool.testing.FakeSystemEnvironmentProvider.ANDROID_HOME; import static com.android.tools.build.bundletool.testing.FakeSystemEnvironmentProvider.ANDROID_SERIAL; @@ -180,6 +182,40 @@ public void buildingViaFlagsAndBuilderHasSameResult_optionalOptimizeFor() throws assertThat(commandViaBuilder.build()).isEqualTo(commandViaFlags); } + @Test + public void buildingViaFlagsAndBuilderHasSameResult_optimizeForTextureCompressionFormat() + throws Exception { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + BuildApksCommand commandViaFlags = + BuildApksCommand.fromFlags( + new FlagParser() + .parse( + "--bundle=" + bundlePath, + "--output=" + outputFilePath, + "--aapt2=" + AAPT2_PATH, + // Optional values. + "--optimize-for=texture_compression_format"), + new PrintStream(output), + systemEnvironmentProvider, + fakeAdbServer); + + BuildApksCommand.Builder commandViaBuilder = + BuildApksCommand.builder() + .setBundlePath(bundlePath) + .setOutputFile(outputFilePath) + // Optional values. + .setOptimizationDimensions(ImmutableSet.of(TEXTURE_COMPRESSION_FORMAT)) + // Must copy instance of the internal executor service. + .setAapt2Command(commandViaFlags.getAapt2Command().get()) + .setExecutorServiceInternal(commandViaFlags.getExecutorService()) + .setExecutorServiceCreatedByBundleTool(true) + .setOutputPrintStream(commandViaFlags.getOutputPrintStream().get()); + DebugKeystoreUtils.getDebugSigningConfiguration(systemEnvironmentProvider) + .ifPresent(commandViaBuilder::setSigningConfiguration); + + assertThat(commandViaBuilder.build()).isEqualTo(commandViaFlags); + } + @Test public void buildingViaFlagsAndBuilderHasSameResult_optionalSigning() throws Exception { BuildApksCommand commandViaFlags = @@ -420,6 +456,26 @@ public void optimizationDimensionsWithUniversal_throws() throws Exception { .contains("Optimization dimension can be only set when running with 'default' mode flag."); } + @Test + public void modulesFlagWithDefault_throws() throws Exception { + ValidationException builderException = + assertThrows( + ValidationException.class, + () -> + BuildApksCommand.builder() + .setBundlePath(bundlePath) + .setOutputFile(outputFilePath) + .setAapt2Command(aapt2Command) + .setApkBuildMode(DEFAULT) + .setModules(ImmutableSet.of("base")) + .build()); + assertThat(builderException) + .hasMessageThat() + .contains( + "Modules can be only set when running with 'universal', 'system' or " + + "'system_compressed' mode flag."); + } + @Test public void nonPositiveMaxThreads_throws() throws Exception { FlagParseException zeroException = diff --git a/src/test/java/com/android/tools/build/bundletool/commands/BuildApksManagerTest.java b/src/test/java/com/android/tools/build/bundletool/commands/BuildApksManagerTest.java index 9ac52ea5..797de5e5 100755 --- a/src/test/java/com/android/tools/build/bundletool/commands/BuildApksManagerTest.java +++ b/src/test/java/com/android/tools/build/bundletool/commands/BuildApksManagerTest.java @@ -36,6 +36,7 @@ import static com.android.tools.build.bundletool.model.utils.ResultUtils.systemApkVariants; import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_M_API_VERSION; import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_P_API_VERSION; +import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_Q_API_VERSION; import static com.android.tools.build.bundletool.testing.Aapt2Helper.AAPT2_PATH; import static com.android.tools.build.bundletool.testing.ApkSetUtils.extractFromApkSetFile; import static com.android.tools.build.bundletool.testing.ApkSetUtils.extractTocFromApkSetFile; @@ -102,6 +103,7 @@ import static com.google.common.truth.Truth8.assertThat; import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat; import static java.nio.charset.StandardCharsets.UTF_8; +import static junit.framework.TestCase.fail; import static org.junit.jupiter.api.Assertions.assertThrows; import com.android.aapt.ConfigurationOuterClass.Configuration; @@ -111,12 +113,16 @@ import com.android.bundle.Commands.ApkSet; import com.android.bundle.Commands.AssetSliceSet; import com.android.bundle.Commands.BuildApksResult; +import com.android.bundle.Commands.DeliveryType; import com.android.bundle.Commands.InstantMetadata; import com.android.bundle.Commands.ModuleMetadata; import com.android.bundle.Commands.SplitApkMetadata; +import com.android.bundle.Commands.StandaloneApkMetadata; import com.android.bundle.Commands.SystemApkMetadata; import com.android.bundle.Commands.SystemApkMetadata.SystemApkType; import com.android.bundle.Commands.Variant; +import com.android.bundle.Config.BundleConfig; +import com.android.bundle.Config.Bundletool; import com.android.bundle.Config.SplitDimension.Value; import com.android.bundle.Files.ApexImages; import com.android.bundle.Targeting.Abi; @@ -128,6 +134,8 @@ import com.android.bundle.Targeting.ScreenDensity; import com.android.bundle.Targeting.ScreenDensity.DensityAlias; import com.android.bundle.Targeting.SdkVersion; +import com.android.bundle.Targeting.TextureCompressionFormat; +import com.android.bundle.Targeting.TextureCompressionFormatTargeting; import com.android.bundle.Targeting.VariantTargeting; import com.android.tools.build.bundletool.TestData; import com.android.tools.build.bundletool.commands.BuildApksCommand.ApkBuildMode; @@ -146,6 +154,7 @@ import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.android.tools.build.bundletool.model.utils.files.FilePreconditions; import com.android.tools.build.bundletool.model.version.BundleToolVersion; +import com.android.tools.build.bundletool.model.version.Version; import com.android.tools.build.bundletool.testing.Aapt2Helper; import com.android.tools.build.bundletool.testing.ApkSetUtils; import com.android.tools.build.bundletool.testing.AppBundleBuilder; @@ -183,6 +192,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.Executors; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -391,6 +401,125 @@ public void selectsRightModules() throws Exception { .containsExactly("assets/base.txt", "assets/fused.txt"); } + @Test + public void selectsRightModules_universalMode_withModulesFlag() throws Exception { + AppBundle appBundle = createAppBundleWithBaseAndFeatureModules("ar", "vr"); + Path apkSetFilePath = + execute( + BuildApksCommand.builder() + .setBundlePath(createAndStoreBundle(appBundle)) + .setOutputFile(outputFilePath) + .setAapt2Command(aapt2Command) + .setModules(ImmutableSet.of("base", "vr")) + .setApkBuildMode(UNIVERSAL) + .build()); + + ZipFile apkSetFile = openZipFile(apkSetFilePath.toFile()); + BuildApksResult result = extractTocFromApkSetFile(apkSetFile, outputDir); + ImmutableList standaloneVariants = standaloneApkVariants(result); + assertThat(standaloneVariants).hasSize(1); + assertThat(splitApkVariants(result)).isEmpty(); + assertThat(apkDescriptions(standaloneVariants)) + .containsExactly( + ApkDescription.newBuilder() + .setPath("universal.apk") + .setTargeting(ApkTargeting.getDefaultInstance()) + .setStandaloneApkMetadata( + StandaloneApkMetadata.newBuilder() + .addAllFusedModuleName(ImmutableList.of("base", "vr"))) + .build()); + } + + @Test + public void selectsRightModules_systemMode_withModulesFlag() throws Exception { + AppBundle appBundle = createAppBundleWithBaseAndFeatureModules("ar", "vr"); + Path apkSetFilePath = + execute( + BuildApksCommand.builder() + .setBundlePath(createAndStoreBundle(appBundle)) + .setOutputFile(outputFilePath) + .setAapt2Command(aapt2Command) + .setModules(ImmutableSet.of("base", "vr")) + .setDeviceSpec( + mergeSpecs( + sdkVersion(28), abis("x86"), density(DensityAlias.MDPI), locales("en-US"))) + .setApkBuildMode(SYSTEM) + .build()); + + ZipFile apkSetFile = openZipFile(apkSetFilePath.toFile()); + BuildApksResult result = extractTocFromApkSetFile(apkSetFile, outputDir); + ImmutableList systemVariants = systemApkVariants(result); + assertThat(apkDescriptions(systemVariants)) + .containsExactly( + ApkDescription.newBuilder() + .setPath("system/system.apk") + .setTargeting(ApkTargeting.getDefaultInstance()) + .setSystemApkMetadata( + SystemApkMetadata.newBuilder() + .setSystemApkType(SystemApkType.SYSTEM) + .addAllFusedModuleName(ImmutableList.of("base", "vr"))) + .build(), + ApkDescription.newBuilder() + .setPath("splits/ar-master.apk") + .setTargeting(ApkTargeting.getDefaultInstance()) + .setSplitApkMetadata( + SplitApkMetadata.newBuilder().setSplitId("ar").setIsMasterSplit(true)) + .build()); + } + + @Test + public void systemMode_withModulesFlag_includesDependenciesOfModules() throws Exception { + AppBundle appBundle = + new AppBundleBuilder() + .addModule("base", builder -> builder.setManifest(androidManifest("com.test.app"))) + .addModule( + "feature1", + builder -> + builder.setManifest( + androidManifest( + "com.test.app", + withOnDemandAttribute(false), + withFusingAttribute(true)))) + .addModule( + "feature2", + builder -> + builder.setManifest( + androidManifest( + "com.test.app", + withUsesSplit("feature1"), + withOnDemandAttribute(false), + withFusingAttribute(true)))) + .build(); + Path bundlePath = createAndStoreBundle(appBundle); + + Path apkSetFilePath = + execute( + BuildApksCommand.builder() + .setBundlePath(bundlePath) + .setOutputFile(outputFilePath) + .setAapt2Command(aapt2Command) + .setModules(ImmutableSet.of("base", "feature2")) + .setDeviceSpec( + mergeSpecs( + sdkVersion(28), abis("x86"), density(DensityAlias.MDPI), locales("en-US"))) + .setApkBuildMode(SYSTEM) + .build()); + ZipFile apkSetFile = openZipFile(apkSetFilePath.toFile()); + BuildApksResult result = extractTocFromApkSetFile(apkSetFile, outputDir); + ImmutableList systemVariants = systemApkVariants(result); + // feature1 is automatically included because feature2 module depends on it. + assertThat(apkDescriptions(systemVariants)) + .containsExactly( + ApkDescription.newBuilder() + .setPath("system/system.apk") + .setTargeting(ApkTargeting.getDefaultInstance()) + .setSystemApkMetadata( + SystemApkMetadata.newBuilder() + .setSystemApkType(SystemApkType.SYSTEM) + .addAllFusedModuleName(ImmutableList.of("base", "feature1", "feature2"))) + .build()); + } + @DataPoints("systemApkBuildModes") public static final ImmutableSet SYSTEM_APK_BUILD_MODE = ImmutableSet.of(SYSTEM, SYSTEM_COMPRESSED); @@ -1346,13 +1475,15 @@ public void buildApksCommand_splitApks_twoModulesOneOnDemand() throws Exception ApkSet baseSplits = splitApkSetByModuleName.get("base"); assertThat(baseSplits.getModuleMetadata().getName()).isEqualTo("base"); - assertThat(baseSplits.getModuleMetadata().getOnDemand()).isFalse(); + assertThat(baseSplits.getModuleMetadata().getDeliveryType()) + .isEqualTo(DeliveryType.INSTALL_TIME); assertThat(baseSplits.getApkDescriptionList()).hasSize(1); assertThat(apkSetFile).hasFile(baseSplits.getApkDescription(0).getPath()); ApkSet onDemandSplits = splitApkSetByModuleName.get("onDemand"); assertThat(onDemandSplits.getModuleMetadata().getName()).isEqualTo("onDemand"); - assertThat(onDemandSplits.getModuleMetadata().getOnDemand()).isTrue(); + assertThat(onDemandSplits.getModuleMetadata().getDeliveryType()) + .isEqualTo(DeliveryType.ON_DEMAND); assertThat(onDemandSplits.getApkDescriptionList()).hasSize(1); assertThat(apkSetFile).hasFile(onDemandSplits.getApkDescription(0).getPath()); } @@ -2386,10 +2517,16 @@ public void buildApksCommand_apkNotificationMessageKeyApexBundle_hasRightSuffix( "standalones/standalone-arm64_v8a.armeabi_v7a.apex"); } + @DataPoints("bundleVersion") + public static final ImmutableSet BUNDLE_VERSION = + ImmutableSet.of(Version.of("0.10.1"), Version.of("0.10.2")); + @Test - public void buildApksCommand_featureAndAssetModules_generatesAssetSlices() throws Exception { + @Theory + public void buildApksCommand_featureAndAssetModules_generatesAssetSlices( + @FromDataPoints("bundleVersion") Version bundleVersion) throws Exception { AppBundle appBundle = - new AppBundleBuilder() + createAppBundleBuilder(bundleVersion) .addModule( "base", builder -> @@ -2442,7 +2579,8 @@ public void buildApksCommand_featureAndAssetModules_generatesAssetSlices() throw ApkSet baseSplits = apks.get(0); assertThat(baseSplits.getModuleMetadata().getName()).isEqualTo("base"); - assertThat(baseSplits.getModuleMetadata().getOnDemand()).isFalse(); + assertThat(baseSplits.getModuleMetadata().getDeliveryType()) + .isEqualTo(DeliveryType.INSTALL_TIME); assertThat(baseSplits.getApkDescriptionList()).hasSize(1); assertThat(apkSetFile).hasFile(baseSplits.getApkDescription(0).getPath()); @@ -2474,8 +2612,10 @@ public void buildApksCommand_featureAndAssetModules_generatesAssetSlices() throw .getInstantMetadata() .getIsInstant()) .isFalse(); + InstantMetadata.Builder instantMetadata = InstantMetadata.newBuilder().setIsInstant(true); + instantMetadata.setDeliveryType(DeliveryType.ON_DEMAND); assertThat(sliceSetsByName.get("asset_module2").getAssetModuleMetadata().getInstantMetadata()) - .isEqualTo(InstantMetadata.newBuilder().setIsInstant(true).setOnDemand(true).build()); + .isEqualTo(instantMetadata.build()); } /** @@ -2755,6 +2895,122 @@ public void splitNames_assetLanguages() throws Exception { .containsExactly("base-master.apk", "base-es.apk", "base-other_lang.apk"); } + @Test + public void splits_assetTextureCompressionFormat() throws Exception { + Path bundlePath = createAppBundleWithTextureTargeting(/* tcfSplittingEnabled= */ true); + + BuildApksCommand command = + BuildApksCommand.builder() + .setBundlePath(bundlePath) + .setOutputFile(outputFilePath) + .setAapt2Command(aapt2Command) + .build(); + + Path apkSetFilePath = execute(command); + ZipFile apkSetFile = openZipFile(apkSetFilePath.toFile()); + BuildApksResult result = extractTocFromApkSetFile(apkSetFile, outputDir); + + ImmutableList splitApkVariants = splitApkVariants(result); + ImmutableList splitApks = apkDescriptions(splitApkVariants); + + assertThat(splitApkVariants(result)).hasSize(1); + Variant splitApkVariant = splitApkVariants(result).get(0); + + assertThat(splitApkVariant.getApkSetList()).hasSize(1); + ImmutableList tcfSplits = + splitApks.stream() + .filter(apkDesc -> apkDesc.getTargeting().hasTextureCompressionFormatTargeting()) + .collect(toImmutableList()); + assertThat(apkNamesInApkDescriptions(tcfSplits)) + .containsExactly("base-atc.apk", "base-etc1_rgb8.apk"); + + for (ApkDescription split : tcfSplits) { + TextureCompressionFormatTargeting textureFormatTargeting = + split.getTargeting().getTextureCompressionFormatTargeting(); + assertThat(textureFormatTargeting.getValueList()).hasSize(1); + TextureCompressionFormat format = textureFormatTargeting.getValueList().get(0); + Set files = filesInApk(split, apkSetFile); + switch (format.getAlias()) { + case ATC: + assertThat(files).contains("assets/textures#tcf_atc/texture.dat"); + break; + case ETC1_RGB8: + assertThat(files).contains("assets/textures#tcf_etc1/texture.dat"); + break; + default: + fail("Unexpected texture compression format"); + } + } + } + + @Test + public void splits_assetTextureCompressionFormatDisabled() throws Exception { + Path bundlePath = createAppBundleWithTextureTargeting(false); + + BuildApksCommand command = + BuildApksCommand.builder() + .setBundlePath(bundlePath) + .setOutputFile(outputFilePath) + .setAapt2Command(aapt2Command) + .build(); + + Path apkSetFilePath = execute(command); + ZipFile apkSetFile = openZipFile(apkSetFilePath.toFile()); + BuildApksResult result = extractTocFromApkSetFile(apkSetFile, outputDir); + + ImmutableList splitApkVariants = splitApkVariants(result); + ImmutableList splitApks = apkDescriptions(splitApkVariants); + + assertThat(splitApkVariants(result)).hasSize(1); + Variant splitApkVariant = splitApkVariants(result).get(0); + + assertThat(splitApkVariant.getApkSetList()).hasSize(1); + assertThat(apkNamesInVariant(splitApkVariant)).containsExactly("base-master.apk"); + + ImmutableList tcfSplits = + splitApks.stream() + .filter(apkDesc -> apkDesc.getTargeting().hasTextureCompressionFormatTargeting()) + .collect(toImmutableList()); + assertThat(tcfSplits).isEmpty(); + + ApkDescription masterSplit = + splitApks.stream() + .filter(apkDesc -> apkDesc.getSplitApkMetadata().getIsMasterSplit()) + .collect(onlyElement()); + + assertThat(filesInApk(masterSplit, apkSetFile)) + .containsAtLeast( + "assets/textures#tcf_atc/texture.dat", "assets/textures#tcf_etc1/texture.dat"); + } + + private Path createAppBundleWithTextureTargeting(boolean tcfSplittingEnabled) throws Exception { + AppBundle appBundle = + new AppBundleBuilder() + .addModule( + "base", + builder -> + builder + .addFile("assets/textures#tcf_atc/texture.dat") + .addFile("assets/textures#tcf_etc1/texture.dat") + .setManifest(androidManifest("com.test.app")) + .setAssetsConfig( + assets( + targetedAssetsDirectory( + "assets/textures#tcf_atc", + assetsDirectoryTargeting(textureCompressionTargeting(ATC))), + targetedAssetsDirectory( + "assets/textures#tcf_etc1", + assetsDirectoryTargeting( + textureCompressionTargeting(ETC1_RGB8)))))) + .setBundleConfig( + BundleConfigBuilder.create() + .addSplitDimension( + Value.TEXTURE_COMPRESSION_FORMAT, /* negate= */ !tcfSplittingEnabled) + .build()) + .build(); + return createAndStoreBundle(appBundle); + } + @Test public void apksSigned() throws Exception { Path bundlePath = createAndStoreBundle(); @@ -3362,11 +3618,7 @@ public void pinningOfManifestReachableResources_enabledSince_0_8_1() throws Exce BuildApksResult result = extractTocFromApkSetFile(apkSetFile, outputDir); ImmutableList splitApkVariants = splitApkVariants(result); - ImmutableList splitApks = - splitApkVariants.stream() - .flatMap(variant -> variant.getApkSetList().stream()) - .flatMap(apkSet -> apkSet.getApkDescriptionList().stream()) - .collect(toImmutableList()); + ImmutableList splitApks = apkDescriptions(splitApkVariants); // The lowest density (mdpi) of "drawable/manifest_reachable_image" is in the master. ImmutableList masterSplits = @@ -3429,11 +3681,7 @@ public void pinningOfManifestReachableResources_disabledBefore_0_8_1() throws Ex BuildApksResult result = extractTocFromApkSetFile(apkSetFile, outputDir); ImmutableList splitApkVariants = splitApkVariants(result); - ImmutableList splitApks = - splitApkVariants.stream() - .flatMap(variant -> variant.getApkSetList().stream()) - .flatMap(apkSet -> apkSet.getApkDescriptionList().stream()) - .collect(toImmutableList()); + ImmutableList splitApks = apkDescriptions(splitApkVariants); ImmutableList masterSplits = splitApks.stream() @@ -3550,6 +3798,16 @@ private static ImmutableList apkNamesInVariant(Variant variant) { .collect(toImmutableList()); } + private static ImmutableList apkNamesInApkDescriptions( + Collection apkDescs) { + return apkDescs.stream() + .map(ApkDescription::getPath) + .map(ZipPath::create) + .map(ZipPath::getFileName) + .map(ZipPath::toString) + .collect(toImmutableList()); + } + /** * Extracts names of files inside the APKs described by {@code apkDescs}. * @@ -3639,4 +3897,39 @@ private ZipFile openZipFile(File zipPath) throws IOException { openedZipFiles.register(zipFile); return zipFile; } + + private static AppBundleBuilder createAppBundleBuilder(Version bundleVersion) { + return new AppBundleBuilder() + .setBundleConfig( + BundleConfig.newBuilder() + .setBundletool(Bundletool.newBuilder().setVersion(bundleVersion.toString())) + .build()); + } + + private static AppBundle createAppBundleWithBaseAndFeatureModules(String... featureModuleNames) + throws IOException { + AppBundleBuilder appBundle = + new AppBundleBuilder() + .addModule( + "base", + module -> + module + .addFile("assets/base.txt") + .setManifest(androidManifest("com.app")) + .setResourceTable(resourceTableWithTestLabel("Test feature"))); + + for (String featureModuleName : featureModuleNames) { + appBundle.addModule( + featureModuleName, + module -> + module + .addFile("assets/" + featureModuleName) + .setManifest( + androidManifestForFeature( + "com.app", + withFusingAttribute(true), + withTitle("@string/test_label", TEST_LABEL_RESOURCE_ID)))); + } + return appBundle.build(); + } } diff --git a/src/test/java/com/android/tools/build/bundletool/commands/ExtractApksCommandTest.java b/src/test/java/com/android/tools/build/bundletool/commands/ExtractApksCommandTest.java index 2c3e320c..0545230b 100755 --- a/src/test/java/com/android/tools/build/bundletool/commands/ExtractApksCommandTest.java +++ b/src/test/java/com/android/tools/build/bundletool/commands/ExtractApksCommandTest.java @@ -54,6 +54,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.android.bundle.Commands.BuildApksResult; +import com.android.bundle.Commands.DeliveryType; +import com.android.bundle.Config.Bundletool; import com.android.bundle.Devices.DeviceSpec; import com.android.bundle.Targeting.Abi.AbiAlias; import com.android.bundle.Targeting.ApkTargeting; @@ -66,6 +68,7 @@ import com.android.tools.build.bundletool.model.ZipPath; import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.model.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.version.BundleToolVersion; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; @@ -194,6 +197,9 @@ public void nonExistentModule_throws() throws Exception { ZipPath apkLBase = ZipPath.create("apkL-base.apk"); BuildApksResult tableOfContentsProto = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariant( VariantTargeting.getDefaultInstance(), @@ -275,6 +281,25 @@ public void deviceSpecUnknownExtension_throws() throws Exception { assertThat(exception).hasMessageThat().contains("bad_filename.dat"); } + @Test + public void deviceSpecViaJavaApi_invalid_throws() throws Exception { + DeviceSpec invalidDeviceSpec = deviceWithSdk(-1); + BuildApksResult tableOfContentsProto = minimalApkSet(); + Path apksArchiveFile = + createApksArchiveFile(tableOfContentsProto, tmpDir.resolve("bundle.apks")); + + Throwable exception = + assertThrows( + ValidationException.class, + () -> + ExtractApksCommand.builder() + .setApksArchivePath(apksArchiveFile) + .setDeviceSpec(invalidDeviceSpec) + .build()); + + assertThat(exception).hasMessageThat().contains("Device spec SDK version"); + } + @Test public void builderAndFlagsConstruction_inJavaViaProtos_equivalent() throws Exception { DeviceSpec deviceSpec = deviceWithSdk(21); @@ -429,6 +454,9 @@ public void oneModule_Ldevice_matchesLmasterSplit( ZipPath apkOne = ZipPath.create("apk_one.apk"); BuildApksResult tableOfContentsProto = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariantForSingleSplitApk( variantSdkTargeting(sdkVersionFrom(21)), @@ -465,6 +493,9 @@ public void oneModule_Mdevice_matchesMSplit( ZipPath apkM = ZipPath.create("splits/apkM.apk"); BuildApksResult tableOfContentsProto = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariantForSingleSplitApk( variantSdkTargeting( @@ -517,6 +548,9 @@ public void oneModule_Kdevice_matchesPreLSplit( ZipPath apkM = ZipPath.create("splits/apkM.apk"); BuildApksResult tableOfContentsProto = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariantForSingleSplitApk( variantSdkTargeting( @@ -570,6 +604,9 @@ public void oneModule_Ldevice_matchesLSplit( ZipPath apkM = ZipPath.create("splits/apkM.apk"); BuildApksResult tableOfContentsProto = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariantForSingleSplitApk( variantSdkTargeting( @@ -617,6 +654,9 @@ public void oneModule_Ldevice_matchesLSplit( public void apexModule_noMatch() throws Exception { BuildApksResult buildApksResult = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( multiAbiTargetingApexVariant( multiAbiTargeting(X86_64), ZipPath.create("standalones/standalone-x86_64.apk"))) @@ -658,6 +698,9 @@ public void apexModule_getsBestPossibleApk( BuildApksResult buildApksResult = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant(multiAbiTargetingApexVariant(x64Targeting, x64Apk)) .addVariant(multiAbiTargetingApexVariant(x64X86Targeting, x64X86Apk)) .addVariant(multiAbiTargetingApexVariant(x64ArmTargeting, x64ArmApk)) @@ -690,6 +733,9 @@ public void oneModule_Kdevice_noMatchingSdkVariant_throws() throws Exception { ZipPath apkM = ZipPath.create("splits/apkM.apk"); BuildApksResult tableOfContentsProto = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariantForSingleSplitApk( variantSdkTargeting(sdkVersionFrom(21), ImmutableSet.of(sdkVersionFrom(23))), @@ -724,6 +770,9 @@ public void oneModule_MipsDevice_noMatchingAbiSplit_throws() throws Exception { ZipPath apkLx86 = ZipPath.create("splits/apkL-x86.apk"); BuildApksResult tableOfContentsProto = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariant( variantSdkTargeting(sdkVersionFrom(21)), @@ -760,6 +809,9 @@ public void oneModule_extractedToTemporaryDirectory() throws Exception { ZipPath apkOne = ZipPath.create("apk_one.apk"); BuildApksResult tableOfContentsProto = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariantForSingleSplitApk( variantSdkTargeting(sdkVersionFrom(21)), @@ -797,6 +849,9 @@ public void twoModules_Ldevice_matchesLSplitsForSpecifiedModules() throws Except ZipPath apkLOther = ZipPath.create("apkL-other.apk"); BuildApksResult tableOfContentsProto = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariant( variantSdkTargeting( @@ -811,12 +866,12 @@ public void twoModules_Ldevice_matchesLSplitsForSpecifiedModules() throws Except createMasterApkDescription(ApkTargeting.getDefaultInstance(), apkLBase)), createSplitApkSet( "feature", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of(), createMasterApkDescription(ApkTargeting.getDefaultInstance(), apkLFeature)), createSplitApkSet( "other", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of(), createMasterApkDescription(ApkTargeting.getDefaultInstance(), apkLOther)))) .build(); @@ -849,6 +904,9 @@ public void moduleWithDependency_extractDependency() throws Exception { ZipPath apkFeature3 = ZipPath.create("feature3-master.apk"); BuildApksResult tableOfContentsProto = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariant( variantSdkTargeting( @@ -858,17 +916,17 @@ public void moduleWithDependency_extractDependency() throws Exception { createMasterApkDescription(ApkTargeting.getDefaultInstance(), apkBase)), createSplitApkSet( /* moduleName= */ "feature1", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of(), createMasterApkDescription(ApkTargeting.getDefaultInstance(), apkFeature1)), createSplitApkSet( /* moduleName= */ "feature2", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of("feature1"), createMasterApkDescription(ApkTargeting.getDefaultInstance(), apkFeature2)), createSplitApkSet( /* moduleName= */ "feature3", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of("feature2"), createMasterApkDescription( ApkTargeting.getDefaultInstance(), apkFeature3)))) @@ -906,6 +964,9 @@ public void diamondModuleDependenciesGraph() throws Exception { ZipPath apkFeature4 = ZipPath.create("feature4-master.apk"); BuildApksResult tableOfContentsProto = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariant( variantSdkTargeting( @@ -915,22 +976,22 @@ public void diamondModuleDependenciesGraph() throws Exception { createMasterApkDescription(ApkTargeting.getDefaultInstance(), apkBase)), createSplitApkSet( /* moduleName= */ "feature1", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of(), createMasterApkDescription(ApkTargeting.getDefaultInstance(), apkFeature1)), createSplitApkSet( /* moduleName= */ "feature2", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of("feature1"), createMasterApkDescription(ApkTargeting.getDefaultInstance(), apkFeature2)), createSplitApkSet( /* moduleName= */ "feature3", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of("feature1"), createMasterApkDescription(ApkTargeting.getDefaultInstance(), apkFeature3)), createSplitApkSet( /* moduleName= */ "feature4", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of("feature2", "feature3"), createMasterApkDescription( ApkTargeting.getDefaultInstance(), apkFeature4)))) @@ -968,6 +1029,9 @@ public void installTimeModule_alwaysExtracted() throws Exception { ZipPath apkFeature2 = ZipPath.create("feature2-master.apk"); BuildApksResult tableOfContentsProto = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariant( variantSdkTargeting( @@ -977,12 +1041,12 @@ public void installTimeModule_alwaysExtracted() throws Exception { createMasterApkDescription(ApkTargeting.getDefaultInstance(), apkBase)), createSplitApkSet( /* moduleName= */ "feature1", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of(), createMasterApkDescription(ApkTargeting.getDefaultInstance(), apkFeature1)), createSplitApkSet( /* moduleName= */ "feature2", - /* onDemand= */ false, + DeliveryType.INSTALL_TIME, /* moduleDependencies= */ ImmutableList.of(), createMasterApkDescription( ApkTargeting.getDefaultInstance(), apkFeature2)))) @@ -1021,6 +1085,9 @@ public void extractInstant_withBaseOnly() throws Exception { Path apkLBase = ZipPath.create("apkL-base.apk"); BuildApksResult tableOfContentsProto = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariant( variantSdkTargeting( @@ -1055,6 +1122,9 @@ public void extractInstant_withNoInstantModules() throws Exception { ZipPath apkLOther = ZipPath.create("apkL-other.apk"); BuildApksResult tableOfContentsProto = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariant( variantSdkTargeting( @@ -1097,6 +1167,9 @@ public void extractInstant_withNoInstantModules() throws Exception { public void extractApks_aboveMaxSdk_throws() throws Exception { BuildApksResult tableOfContentsProto = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariant( variantSdkTargeting( @@ -1134,6 +1207,9 @@ public void extractInstant_withModulesFlag() throws Exception { Path apkLOther = ZipPath.create("apkL-other.apk"); BuildApksResult tableOfContentsProto = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariant( variantSdkTargeting( @@ -1176,6 +1252,9 @@ public void extractInstant_withBaseAndSingleInstantModule() throws Exception { ZipPath apkOther = ZipPath.create("apkL-other.apk"); BuildApksResult tableOfContentsProto = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariant( variantSdkTargeting( @@ -1218,6 +1297,9 @@ public void extractInstant_withMultipleInstantModule() throws Exception { Path apkInstant2 = ZipPath.create("apkL-instant2.apk"); BuildApksResult tableOfContentsProto = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariant( variantSdkTargeting( @@ -1259,7 +1341,7 @@ public void testExtractFromDirectoryNoTableOfContents_throws() throws Exception () -> ExtractApksCommand.builder() .setApksArchivePath(tmpDir) - .setDeviceSpec(DeviceSpec.getDefaultInstance()) + .setDeviceSpec(deviceWithSdk(21)) .build() .execute()); @@ -1278,6 +1360,8 @@ private Path createApks(BuildApksResult buildApksResult, boolean apksInDirectory private static BuildApksResult minimalApkSet() { return BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder().setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariant( VariantTargeting.getDefaultInstance(), diff --git a/src/test/java/com/android/tools/build/bundletool/commands/GetSizeCommandTest.java b/src/test/java/com/android/tools/build/bundletool/commands/GetSizeCommandTest.java index 5159d08e..da1d6cab 100755 --- a/src/test/java/com/android/tools/build/bundletool/commands/GetSizeCommandTest.java +++ b/src/test/java/com/android/tools/build/bundletool/commands/GetSizeCommandTest.java @@ -55,7 +55,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.android.bundle.Commands.BuildApksResult; +import com.android.bundle.Commands.DeliveryType; import com.android.bundle.Commands.Variant; +import com.android.bundle.Config.Bundletool; import com.android.bundle.Devices.DeviceSpec; import com.android.bundle.Targeting.ApkTargeting; import com.android.bundle.Targeting.SdkVersion; @@ -72,6 +74,7 @@ import com.android.tools.build.bundletool.model.ZipPath; import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.model.exceptions.ValidationException; +import com.android.tools.build.bundletool.model.version.BundleToolVersion; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; @@ -432,7 +435,13 @@ public void getSizeTotalInternal_singleSplitVariant() throws Exception { EntryOption.UNCOMPRESSED); // APK stored uncompressed in the APKs zip. archiveBuilder.addFileWithContent(ZipPath.create("base-x86_64.apk"), new byte[10000]); archiveBuilder.addFileWithProtoContent( - ZipPath.create("toc.pb"), BuildApksResult.newBuilder().addVariant(lVariant).build()); + ZipPath.create("toc.pb"), + BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) + .addVariant(lVariant) + .build()); Path apksArchiveFile = archiveBuilder.writeTo(tmpDir.resolve("bundle.apks")); ConfigurationSizes configurationSizes = @@ -482,6 +491,9 @@ public void getSizeTotalInternal_multipleStandaloneVariant() throws Exception { archiveBuilder.addFileWithProtoContent( ZipPath.create("toc.pb"), BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant(preLLdpiVariant) .addVariant(preLMdpiVariant) .build()); @@ -532,7 +544,13 @@ public void getSizeTotalInternal_withNoDimensionsAndDeviceSpec() throws Exceptio archiveBuilder.addFileWithContent(ZipPath.create("preL.apk"), new byte[10000]); archiveBuilder.addFileWithProtoContent( ZipPath.create("toc.pb"), - BuildApksResult.newBuilder().addVariant(lVariant).addVariant(preLVariant).build()); + BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) + .addVariant(lVariant) + .addVariant(preLVariant) + .build()); Path apksArchiveFile = archiveBuilder.writeTo(tmpDir.resolve("bundle.apks")); ConfigurationSizes configurationSizes = @@ -586,7 +604,12 @@ public void getSizeTotalInternal_withDimensionsAndDeviceSpec() throws Exception /* isMasterSplit= */ false))); BuildApksResult tableOfContentsProto = - BuildApksResult.newBuilder().addVariant(lVariant).build(); + BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) + .addVariant(lVariant) + .build(); Path apksArchiveFile = createApksArchiveFile(tableOfContentsProto, tmpDir.resolve("bundle.apks")); @@ -667,7 +690,13 @@ public void getSizeTotalInternal_multipleDimensions() throws Exception { ZipPath.create("preL.apk")); BuildApksResult tableOfContentsProto = - BuildApksResult.newBuilder().addVariant(lVariant).addVariant(preLVariant).build(); + BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) + .addVariant(lVariant) + .addVariant(preLVariant) + .build(); Path apksArchiveFile = createApksArchiveFile(tableOfContentsProto, tmpDir.resolve("bundle.apks")); @@ -758,7 +787,13 @@ public void getSizeTotal_noDimensions() throws Exception { ApkTargeting.getDefaultInstance(), ZipPath.create("preL.apk")); BuildApksResult tableOfContentsProto = - BuildApksResult.newBuilder().addVariant(lVariant).addVariant(preLVariant).build(); + BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) + .addVariant(lVariant) + .addVariant(preLVariant) + .build(); Path apksArchiveFile = createApksArchiveFile(tableOfContentsProto, tmpDir.resolve("bundle.apks")); @@ -790,19 +825,19 @@ public void getSizeTotal_withSelectModules() throws Exception { ApkTargeting.getDefaultInstance(), ZipPath.create("base-master.apk"))), createSplitApkSet( /* moduleName= */ "feature1", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of(), createMasterApkDescription( ApkTargeting.getDefaultInstance(), ZipPath.create("base-feature1.apk"))), createSplitApkSet( /* moduleName= */ "feature2", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of("feature3"), createMasterApkDescription( ApkTargeting.getDefaultInstance(), ZipPath.create("base-feature2.apk"))), createSplitApkSet( /* moduleName= */ "feature3", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of(), createMasterApkDescription( ApkTargeting.getDefaultInstance(), ZipPath.create("base-feature3.apk")))); @@ -815,7 +850,13 @@ public void getSizeTotal_withSelectModules() throws Exception { ZipPath.create("preL.apk")); BuildApksResult tableOfContentsProto = - BuildApksResult.newBuilder().addVariant(lVariant).addVariant(preLVariant).build(); + BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) + .addVariant(lVariant) + .addVariant(preLVariant) + .build(); Path apksArchiveFile = createApksArchiveFile(tableOfContentsProto, tmpDir.resolve("bundle.apks")); @@ -867,6 +908,9 @@ public void getSizeTotal_withInstant() throws Exception { BuildApksResult tableOfContentsProto = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant(lInstantVariant) .addVariant(lSplitVariant) .addVariant(preLVariant) @@ -911,7 +955,12 @@ public void getSizeTotal_multipleDimensions() throws Exception { /* isMasterSplit= */ false))); BuildApksResult tableOfContentsProto = - BuildApksResult.newBuilder().addVariant(lVariant).build(); + BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) + .addVariant(lVariant) + .build(); Path apksArchiveFile = createApksArchiveFile(tableOfContentsProto, tmpDir.resolve("bundle.apks")); @@ -972,7 +1021,13 @@ public void getSizeTotal_withDimensionsAndDeviceSpec() throws Exception { ZipPath.create("preL.apk")); BuildApksResult tableOfContentsProto = - BuildApksResult.newBuilder().addVariant(lVariant).addVariant(preLVariant).build(); + BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) + .addVariant(lVariant) + .addVariant(preLVariant) + .build(); Path apksArchiveFile = createApksArchiveFile(tableOfContentsProto, tmpDir.resolve("bundle.apks")); diff --git a/src/test/java/com/android/tools/build/bundletool/commands/InstallApksCommandTest.java b/src/test/java/com/android/tools/build/bundletool/commands/InstallApksCommandTest.java index 2c943c10..5496b9c7 100755 --- a/src/test/java/com/android/tools/build/bundletool/commands/InstallApksCommandTest.java +++ b/src/test/java/com/android/tools/build/bundletool/commands/InstallApksCommandTest.java @@ -40,6 +40,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.android.bundle.Commands.BuildApksResult; +import com.android.bundle.Commands.DeliveryType; +import com.android.bundle.Config.Bundletool; import com.android.bundle.Devices.DeviceSpec; import com.android.bundle.Targeting.Abi.AbiAlias; import com.android.bundle.Targeting.ApkTargeting; @@ -52,6 +54,7 @@ import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.model.exceptions.InstallationException; import com.android.tools.build.bundletool.model.utils.SystemEnvironmentProvider; +import com.android.tools.build.bundletool.model.version.BundleToolVersion; import com.android.tools.build.bundletool.testing.FakeAdbServer; import com.android.tools.build.bundletool.testing.FakeDevice; import com.android.tools.build.bundletool.testing.FakeSystemEnvironmentProvider; @@ -401,6 +404,9 @@ public void deviceAbiIncompatible_throws() throws Exception { ZipPath apkLx86 = ZipPath.create("splits/apkL-x86.apk"); BuildApksResult tableOfContentsProto = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariant( variantSdkTargeting(sdkVersionFrom(21)), @@ -517,6 +523,9 @@ public void installsOnlySpecifiedModules( @FromDataPoints("apksInDirectory") boolean apksInDirectory) throws Exception { BuildApksResult tableOfContent = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariant( VariantTargeting.getDefaultInstance(), @@ -562,6 +571,9 @@ public void moduleDependencies_installDependency( @FromDataPoints("apksInDirectory") boolean apksInDirectory) throws Exception { BuildApksResult tableOfContent = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariant( VariantTargeting.getDefaultInstance(), @@ -571,21 +583,21 @@ public void moduleDependencies_installDependency( ApkTargeting.getDefaultInstance(), ZipPath.create("base-master.apk"))), createSplitApkSet( /* moduleName= */ "feature1", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of(), createMasterApkDescription( ApkTargeting.getDefaultInstance(), ZipPath.create("feature1-master.apk"))), createSplitApkSet( /* moduleName= */ "feature2", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of("feature1"), createMasterApkDescription( ApkTargeting.getDefaultInstance(), ZipPath.create("feature2-master.apk"))), createSplitApkSet( /* moduleName= */ "feature3", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of("feature2"), createMasterApkDescription( ApkTargeting.getDefaultInstance(), @@ -618,6 +630,9 @@ public void moduleDependencies_diamondGraph( @FromDataPoints("apksInDirectory") boolean apksInDirectory) throws Exception { BuildApksResult tableOfContent = BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariant( VariantTargeting.getDefaultInstance(), @@ -627,28 +642,28 @@ public void moduleDependencies_diamondGraph( ApkTargeting.getDefaultInstance(), ZipPath.create("base-master.apk"))), createSplitApkSet( /* moduleName= */ "feature1", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of(), createMasterApkDescription( ApkTargeting.getDefaultInstance(), ZipPath.create("feature1-master.apk"))), createSplitApkSet( /* moduleName= */ "feature2", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of("feature1"), createMasterApkDescription( ApkTargeting.getDefaultInstance(), ZipPath.create("feature2-master.apk"))), createSplitApkSet( /* moduleName= */ "feature3", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of("feature1"), createMasterApkDescription( ApkTargeting.getDefaultInstance(), ZipPath.create("feature3-master.apk"))), createSplitApkSet( /* moduleName= */ "feature4", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of("feature2", "feature3"), createMasterApkDescription( ApkTargeting.getDefaultInstance(), @@ -694,6 +709,8 @@ private static AdbServer fakeServerOneDevice(DeviceSpec deviceSpec) { /** Creates a table of content matching L+ devices. */ private static BuildApksResult createLPlusTableOfContent(ZipPath apkPath) { return BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder().setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariantForSingleSplitApk( variantSdkTargeting(sdkVersionFrom(21)), @@ -705,6 +722,8 @@ private static BuildApksResult createLPlusTableOfContent(ZipPath apkPath) { /** Creates a table of content matching all devices to a given apkPath. */ private static BuildApksResult createSimpleTableOfContent(ZipPath apkPath) { return BuildApksResult.newBuilder() + .setBundletool( + Bundletool.newBuilder().setVersion(BundleToolVersion.getCurrentVersion().toString())) .addVariant( createVariantForSingleSplitApk( VariantTargeting.getDefaultInstance(), ApkTargeting.getDefaultInstance(), apkPath)) diff --git a/src/test/java/com/android/tools/build/bundletool/device/ApkMatcherTest.java b/src/test/java/com/android/tools/build/bundletool/device/ApkMatcherTest.java index 15237805..93a3f2c7 100755 --- a/src/test/java/com/android/tools/build/bundletool/device/ApkMatcherTest.java +++ b/src/test/java/com/android/tools/build/bundletool/device/ApkMatcherTest.java @@ -64,7 +64,9 @@ import com.android.bundle.Commands.ApkSet; import com.android.bundle.Commands.BuildApksResult; +import com.android.bundle.Commands.DeliveryType; import com.android.bundle.Commands.Variant; +import com.android.bundle.Config.Bundletool; import com.android.bundle.Devices.DeviceSpec; import com.android.bundle.Targeting.Abi.AbiAlias; import com.android.bundle.Targeting.ApkTargeting; @@ -79,6 +81,7 @@ import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.android.tools.build.bundletool.model.utils.Versions; +import com.android.tools.build.bundletool.model.version.BundleToolVersion; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import java.util.Arrays; @@ -505,6 +508,9 @@ public void apexVariantMatch_matchesRightVariant() { BuildApksResult.newBuilder() .addVariant(multiAbiTargetingApexVariant(x86Targeting, x86Apk)) .addVariant(multiAbiTargetingApexVariant(x64X86Targeting, x64X86Apk)) + .setBundletool( + Bundletool.newBuilder() + .setVersion(BundleToolVersion.getCurrentVersion().toString())) .build(); assertThat(new ApkMatcher(abis("x86")).getMatchingApks(buildApksResult)) @@ -874,12 +880,12 @@ public void apkMatch_withModuleNameFiltering_splitApks_moduleWithDependency() { splitApkDescription(ApkTargeting.getDefaultInstance(), baseApk)), splitApkSet( /* moduleName= */ "feature1", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of(), splitApkDescription(ApkTargeting.getDefaultInstance(), feature1Apk)), splitApkSet( /* moduleName= */ "feature2", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of("feature1"), splitApkDescription(ApkTargeting.getDefaultInstance(), feature2Apk)))); @@ -925,22 +931,22 @@ public void apkMatch_withModuleNameFiltering_splitApks_diamondModuleDependencies splitApkDescription(ApkTargeting.getDefaultInstance(), baseApk)), splitApkSet( /* moduleName= */ "feature1", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of(), splitApkDescription(ApkTargeting.getDefaultInstance(), feature1Apk)), splitApkSet( /* moduleName= */ "feature2", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of("feature1"), splitApkDescription(ApkTargeting.getDefaultInstance(), feature2Apk)), splitApkSet( /* moduleName= */ "feature3", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of("feature1"), splitApkDescription(ApkTargeting.getDefaultInstance(), feature3Apk)), splitApkSet( /* moduleName= */ "feature4", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of("feature2", "feature3"), splitApkDescription(ApkTargeting.getDefaultInstance(), feature4Apk)))); @@ -968,7 +974,8 @@ public void apkMatch_withModuleTypeFiltering_splitApks_installTimeModules() { DeviceSpec device = deviceWithSdk(21); ZipPath baseApk = ZipPath.create("master-base.apk"); ZipPath onDemandFeatureApk = ZipPath.create("master-feature1.apk"); - ZipPath feature2Apk = ZipPath.create("master-feature2.apk"); + ZipPath installTimeFeatureApk = ZipPath.create("master-feature2.apk"); + ZipPath fastFollowFeatureApk = ZipPath.create("master-feature3.apk"); BuildApksResult buildApksResult = buildApksResult( createVariant( @@ -978,39 +985,86 @@ public void apkMatch_withModuleTypeFiltering_splitApks_installTimeModules() { splitApkDescription(ApkTargeting.getDefaultInstance(), baseApk)), splitApkSet( /* moduleName= */ "onDemandFeature", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of(), splitApkDescription(ApkTargeting.getDefaultInstance(), onDemandFeatureApk)), splitApkSet( - /* moduleName= */ "feature2", - /* onDemand= */ false, + /* moduleName= */ "installTimeFeature", + DeliveryType.INSTALL_TIME, /* moduleDependencies= */ ImmutableList.of(), - splitApkDescription(ApkTargeting.getDefaultInstance(), feature2Apk)))); + splitApkDescription(ApkTargeting.getDefaultInstance(), installTimeFeatureApk)), + splitApkSet( + /* moduleName= */ "fastFollowFeature", + /* deliveryType= */ DeliveryType.FAST_FOLLOW, + /* moduleDependencies= */ ImmutableList.of(), + splitApkDescription(ApkTargeting.getDefaultInstance(), fastFollowFeatureApk)))); // By default only install-time module are matched. Optional> allModules = Optional.empty(); assertThat( new ApkMatcher(device, allModules, NOT_MATCH_INSTANT).getMatchingApks(buildApksResult)) - .containsExactly(baseApk, feature2Apk); + .containsExactly(baseApk, installTimeFeatureApk); Optional> baseModuleOnly = Optional.of(ImmutableSet.of("base")); assertThat( new ApkMatcher(device, baseModuleOnly, NOT_MATCH_INSTANT) .getMatchingApks(buildApksResult)) - .containsExactly(baseApk, feature2Apk); + .containsExactly(baseApk, installTimeFeatureApk); - Optional> feature2ModuleOnly = Optional.of(ImmutableSet.of("feature2")); + Optional> installTimeModuleOnly = + Optional.of(ImmutableSet.of("installTimeFeature")); assertThat( - new ApkMatcher(device, feature2ModuleOnly, NOT_MATCH_INSTANT) + new ApkMatcher(device, installTimeModuleOnly, NOT_MATCH_INSTANT) .getMatchingApks(buildApksResult)) - .containsExactly(baseApk, feature2Apk); + .containsExactly(baseApk, installTimeFeatureApk); Optional> onDemandModuleOnly = Optional.of(ImmutableSet.of("onDemandFeature")); assertThat( new ApkMatcher(device, onDemandModuleOnly, NOT_MATCH_INSTANT) .getMatchingApks(buildApksResult)) - .containsExactly(baseApk, onDemandFeatureApk, feature2Apk); + .containsExactly(baseApk, onDemandFeatureApk, installTimeFeatureApk); + + Optional> fastFollowModuleOnly = + Optional.of(ImmutableSet.of("fastFollowFeature")); + assertThat( + new ApkMatcher(device, fastFollowModuleOnly, NOT_MATCH_INSTANT) + .getMatchingApks(buildApksResult)) + .containsExactly(baseApk, fastFollowFeatureApk, installTimeFeatureApk); + } + + @Test + public void apkMatch_withModuleTypeFiltering_splitApks_installTimeModules_deprecatedOnDemand() { + DeviceSpec device = deviceWithSdk(21); + ZipPath baseApk = ZipPath.create("master-base.apk"); + ZipPath onDemandFeatureApk = ZipPath.create("master-feature1.apk"); + ApkSet onDemandFeature = + splitApkSet( + /* moduleName= */ "onDemandFeature", + DeliveryType.ON_DEMAND, + /* moduleDependencies= */ ImmutableList.of(), + splitApkDescription(ApkTargeting.getDefaultInstance(), onDemandFeatureApk)); + ApkSet onDemandFeatureWithDeprecatedField = + onDemandFeature.toBuilder() + .setModuleMetadata( + onDemandFeature.getModuleMetadata().toBuilder().setOnDemandDeprecated(true)) + .build(); + + BuildApksResult buildApksResult = + buildApksResult( + /* bundletoolVersion= */ "0.10.0", + createVariant( + VariantTargeting.getDefaultInstance(), + splitApkSet( + /* moduleName= */ "base", + splitApkDescription(ApkTargeting.getDefaultInstance(), baseApk)), + onDemandFeatureWithDeprecatedField)); + + // By default only install-time module are matched. + Optional> allModules = Optional.empty(); + assertThat( + new ApkMatcher(device, allModules, NOT_MATCH_INSTANT).getMatchingApks(buildApksResult)) + .containsExactly(baseApk); } @Test @@ -1193,7 +1247,14 @@ public void matchesModuleSplit_incompatibleDeviceThrows() { } private static BuildApksResult buildApksResult(Variant... variants) { - return BuildApksResult.newBuilder().addAllVariant(Arrays.asList(variants)).build(); + return buildApksResult(BundleToolVersion.getCurrentVersion().toString(), variants); + } + + private static BuildApksResult buildApksResult(String bundletoolVersion, Variant... variants) { + return BuildApksResult.newBuilder() + .addAllVariant(Arrays.asList(variants)) + .setBundletool(Bundletool.newBuilder().setVersion(bundletoolVersion)) + .build(); } private static Variant splitApkVariant( diff --git a/src/test/java/com/android/tools/build/bundletool/device/DdmlibDeviceTest.java b/src/test/java/com/android/tools/build/bundletool/device/DdmlibDeviceTest.java index 2628634a..83bbae2e 100755 --- a/src/test/java/com/android/tools/build/bundletool/device/DdmlibDeviceTest.java +++ b/src/test/java/com/android/tools/build/bundletool/device/DdmlibDeviceTest.java @@ -20,7 +20,6 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Matchers.eq; -import static org.mockito.Matchers.isNull; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -61,8 +60,8 @@ public void doesNotAllowDowngrade() throws Exception { ddmlibDevice.installApks( ImmutableList.of(APK_PATH), InstallOptions.builder().setAllowDowngrade(false).build()); - // -d should *not* be passed as extra arg. - verify(mockDevice).installPackage(eq(APK_PATH.toString()), anyBoolean(), (String) isNull()); + // "-d" should *not* be passed as extra arg. + verify(mockDevice).installPackage(eq(APK_PATH.toString()), anyBoolean() /*, no extra args */); } @Test diff --git a/src/test/java/com/android/tools/build/bundletool/device/VariantTotalSizeAggregatorTest.java b/src/test/java/com/android/tools/build/bundletool/device/VariantTotalSizeAggregatorTest.java index c357afcb..53d1cf69 100755 --- a/src/test/java/com/android/tools/build/bundletool/device/VariantTotalSizeAggregatorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/device/VariantTotalSizeAggregatorTest.java @@ -55,6 +55,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; +import com.android.bundle.Commands.DeliveryType; import com.android.bundle.Commands.Variant; import com.android.bundle.Targeting.Abi.AbiAlias; import com.android.bundle.Targeting.ApkTargeting; @@ -64,6 +65,7 @@ import com.android.tools.build.bundletool.model.SizeConfiguration; import com.android.tools.build.bundletool.model.ZipPath; import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException; +import com.android.tools.build.bundletool.model.version.BundleToolVersion; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -90,7 +92,10 @@ public void splitVariant_singleModule_SingleTargeting() { ApkTargeting.getDefaultInstance(), ZipPath.create("base-master.apk")))); ConfigurationSizes configurationSizes = new VariantTotalSizeAggregator( - ImmutableMap.of("base-master.apk", 10L), lVariant, getSizeCommand.build()) + ImmutableMap.of("base-master.apk", 10L), + BundleToolVersion.getCurrentVersion(), + lVariant, + getSizeCommand.build()) .getSize(); assertThat(configurationSizes.getMaxSizeConfigurationMap()) .containsExactly(SizeConfiguration.getDefaultInstance(), 10L); @@ -118,6 +123,7 @@ public void splitVariant_singleModule_multipleTargeting() { ConfigurationSizes configurationSizes = new VariantTotalSizeAggregator( ImmutableMap.of("base-master.apk", 10L, "base-x86.apk", 4L, "base-x86_64.apk", 6L), + BundleToolVersion.getCurrentVersion(), lVariant, getSizeCommand.build()) .getSize(); @@ -168,6 +174,7 @@ public void splitVariant_singleModule_multipleTargeting_withDimensions() { .put("base-xxhdpi.apk", 3L) .put("base-en.apk", 1L) .build(), + BundleToolVersion.getCurrentVersion(), lVariant, getSizeCommand.setDimensions(ImmutableSet.of(ABI, SCREEN_DENSITY)).build()) .getSize(); @@ -230,6 +237,7 @@ public void splitVariant_singleModule_multipleTargeting_withDimensionsAndDeviceS .put("base-mdpi.apk", 6L) .put("base-ldpi.apk", 3L) .build(), + BundleToolVersion.getCurrentVersion(), lVariant, getSizeCommand .setDimensions(ImmutableSet.of(LANGUAGE)) @@ -289,6 +297,7 @@ public void splitVariant_singleModule_multipleTargeting_withDifferentDimensionsA .put("base-x86.apk", 4L) .put("base-x86_64.apk", 5L) .build(), + BundleToolVersion.getCurrentVersion(), lVariant, getSizeCommand .setDimensions(ImmutableSet.of(SCREEN_DENSITY, SDK)) @@ -356,6 +365,7 @@ public void splitVariant_singleModule_multipleTargeting_withAllDeviceSpecsAndDim .put("base-mips.apk", 4L) .put("base-mips64.apk", 5L) .build(), + BundleToolVersion.getCurrentVersion(), lVariant, getSizeCommand .setDimensions(ImmutableSet.of(SCREEN_DENSITY, SDK, LANGUAGE, ABI)) @@ -409,6 +419,7 @@ public void splitVariant_singleModule_multipleTargeting_withIncompatibleAbiDevic .put("apkL.apk", 10L) .put("apkL-x86.apk", 6L) .build(), + BundleToolVersion.getCurrentVersion(), lVariant, getSizeCommand .setDeviceSpec(mergeSpecs(abis("arm64-v8a"), locales("en"), density(XHDPI))) @@ -433,13 +444,13 @@ public void splitVariant_multipleModules_selectModules() { ApkTargeting.getDefaultInstance(), ZipPath.create("base-master.apk"))), createSplitApkSet( /* moduleName= */ "feature", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of(), createMasterApkDescription( ApkTargeting.getDefaultInstance(), ZipPath.create("feature-master.apk"))), createSplitApkSet( /* moduleName= */ "feature1", - /* onDemand= */ true, + DeliveryType.ON_DEMAND, /* moduleDependencies= */ ImmutableList.of(), createMasterApkDescription( ApkTargeting.getDefaultInstance(), ZipPath.create("feature1-master.apk"))), @@ -456,6 +467,7 @@ public void splitVariant_multipleModules_selectModules() { .put("feature1-master.apk", 6L) .put("feature2-master.apk", 4L) .build(), + BundleToolVersion.getCurrentVersion(), lVariant, getSizeCommand.setModules(ImmutableSet.of("base", "feature1")).build()) .getSize(); @@ -494,6 +506,7 @@ public void instantVariant_multipleModules_withInstantVariant() { .put("feature-master.apk", 15L) .put("feature1-master.apk", 6L) .build(), + BundleToolVersion.getCurrentVersion(), lVariant, getSizeCommand.setInstant(true).build()) .getSize(); @@ -555,6 +568,7 @@ public void splitVariant_multipleModules_multipleTargeting_withDeviceSpec() { .put("feature-xhdpi.apk", 6L) .put("feature-xxhdpi.apk", 5L) .build(), + BundleToolVersion.getCurrentVersion(), lVariant, getSizeCommand.setDeviceSpec(mergeSpecs(density(XHDPI), abis("mips"))).build()) .getSize(); @@ -579,7 +593,10 @@ public void getSize_standaloneVariant_withoutDimensionsAndDeviceSpec() { ConfigurationSizes configurationSizes = new VariantTotalSizeAggregator( - ImmutableMap.of("sample.apk", 10L), variant, getSizeCommand.build()) + ImmutableMap.of("sample.apk", 10L), + BundleToolVersion.getCurrentVersion(), + variant, + getSizeCommand.build()) .getSize(); assertThat(configurationSizes.getMinSizeConfigurationMap()) .isEqualTo(configurationSizes.getMaxSizeConfigurationMap()); @@ -600,6 +617,7 @@ public void getSize_standaloneVariant_withModules() { ConfigurationSizes configurationSizes = new VariantTotalSizeAggregator( ImmutableMap.of("sample.apk", 10L), + BundleToolVersion.getCurrentVersion(), variant, getSizeCommand.setModules(ImmutableSet.of("base")).build()) .getSize(); @@ -622,6 +640,7 @@ public void getSize_standaloneVariant_withDimensionsAndWithoutDeviceSpec() { ConfigurationSizes configurationSizes = new VariantTotalSizeAggregator( ImmutableMap.of("sample.apk", 20L), + BundleToolVersion.getCurrentVersion(), variant, getSizeCommand .setDimensions(ImmutableSet.of(ABI, SCREEN_DENSITY, LANGUAGE, SDK)) @@ -655,6 +674,7 @@ public void getSize_standaloneVariant_withDeviceSpecAndWithoutDimensions() { ConfigurationSizes configurationSizes = new VariantTotalSizeAggregator( ImmutableMap.of("sample.apk", 15L), + BundleToolVersion.getCurrentVersion(), variant, getSizeCommand.setDeviceSpec(mergeSpecs(locales("jp"), abis("mips"))).build()) .getSize(); @@ -679,6 +699,7 @@ public void getSize_standaloneVariants_withAllDeviceSpecAndDimensions() { ConfigurationSizes configurationSizes = new VariantTotalSizeAggregator( ImmutableMap.of("sample.apk", 11L), + BundleToolVersion.getCurrentVersion(), variant, getSizeCommand .setDimensions(ImmutableSet.of(ABI, SCREEN_DENSITY, LANGUAGE, SDK)) diff --git a/src/test/java/com/android/tools/build/bundletool/model/AndroidManifestTest.java b/src/test/java/com/android/tools/build/bundletool/model/AndroidManifestTest.java index c56d503b..e021c720 100755 --- a/src/test/java/com/android/tools/build/bundletool/model/AndroidManifestTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/AndroidManifestTest.java @@ -20,6 +20,7 @@ import static com.android.tools.build.bundletool.model.AndroidManifest.DEVELOPMENT_SDK_VERSION; import static com.android.tools.build.bundletool.model.AndroidManifest.HAS_CODE_RESOURCE_ID; import static com.android.tools.build.bundletool.model.AndroidManifest.IS_FEATURE_SPLIT_RESOURCE_ID; +import static com.android.tools.build.bundletool.model.AndroidManifest.MODULE_TYPE_ASSET_VALUE; import static com.android.tools.build.bundletool.model.AndroidManifest.MODULE_TYPE_FEATURE_VALUE; import static com.android.tools.build.bundletool.model.AndroidManifest.NAME_RESOURCE_ID; import static com.android.tools.build.bundletool.model.AndroidManifest.RESOURCE_RESOURCE_ID; @@ -29,6 +30,7 @@ import static com.android.tools.build.bundletool.model.BundleModule.ModuleType.FEATURE_MODULE; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifestForAssetModule; +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withFastFollowDelivery; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withFusingAttribute; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withInstallTimeDelivery; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withInstantInstallTimeDelivery; @@ -474,6 +476,20 @@ public void deliveryTypeAndOnDemandAttribute_deliveryElement_onDemand() { assertThat(manifest.isDeliveryTypeDeclared()).isTrue(); } + @Test + public void deliveryTypeAndOnDemandAttribute_deliveryElement_fastFollow() { + AndroidManifest manifest = + AndroidManifest.create( + androidManifest( + "com.test.app", + withTypeAttribute(MODULE_TYPE_ASSET_VALUE), + withFastFollowDelivery())); + + assertThat(manifest.getManifestDeliveryElement()).isPresent(); + assertThat(manifest.getOnDemandAttribute()).isEmpty(); + assertThat(manifest.isDeliveryTypeDeclared()).isTrue(); + } + @Test public void deliveryTypeAndOnDemandAttribute_deliveryElement_conditions() { AndroidManifest manifest = diff --git a/src/test/java/com/android/tools/build/bundletool/model/ManifestDeliveryElementTest.java b/src/test/java/com/android/tools/build/bundletool/model/ManifestDeliveryElementTest.java index e8bc3fcb..a86c2f1d 100755 --- a/src/test/java/com/android/tools/build/bundletool/model/ManifestDeliveryElementTest.java +++ b/src/test/java/com/android/tools/build/bundletool/model/ManifestDeliveryElementTest.java @@ -18,6 +18,7 @@ import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withEmptyDeliveryElement; +import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withFastFollowDelivery; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withFeatureCondition; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withFeatureConditionHexVersion; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withFusingAttribute; @@ -55,11 +56,13 @@ public class ManifestDeliveryElementTest { public void emptyDeliveryElement_notWellFormed() { Optional deliveryElement = ManifestDeliveryElement.fromManifestRootNode( - androidManifest("com.test.app", withEmptyDeliveryElement())); + androidManifest("com.test.app", withEmptyDeliveryElement()), + /* isFastFollowAllowed= */ false); assertThat(deliveryElement).isPresent(); assertThat(deliveryElement.get().hasInstallTimeElement()).isFalse(); assertThat(deliveryElement.get().hasOnDemandElement()).isFalse(); + assertThat(deliveryElement.get().hasFastFollowElement()).isFalse(); assertThat(deliveryElement.get().hasModuleConditions()).isFalse(); assertThat(deliveryElement.get().isWellFormed()).isFalse(); } @@ -68,11 +71,13 @@ public void emptyDeliveryElement_notWellFormed() { public void installTimeDeliveryOnly() { Optional deliveryElement = ManifestDeliveryElement.fromManifestRootNode( - androidManifest("com.test.app", withInstallTimeDelivery())); + androidManifest("com.test.app", withInstallTimeDelivery()), + /* isFastFollowAllowed= */ false); assertThat(deliveryElement).isPresent(); assertThat(deliveryElement.get().hasInstallTimeElement()).isTrue(); assertThat(deliveryElement.get().hasOnDemandElement()).isFalse(); + assertThat(deliveryElement.get().hasFastFollowElement()).isFalse(); assertThat(deliveryElement.get().hasModuleConditions()).isFalse(); assertThat(deliveryElement.get().isWellFormed()).isTrue(); } @@ -81,11 +86,28 @@ public void installTimeDeliveryOnly() { public void onDemandDeliveryOnly() { Optional deliveryElement = ManifestDeliveryElement.fromManifestRootNode( - androidManifest("com.test.app", withOnDemandDelivery())); + androidManifest("com.test.app", withOnDemandDelivery()), + /* isFastFollowAllowed= */ false); assertThat(deliveryElement).isPresent(); assertThat(deliveryElement.get().hasInstallTimeElement()).isFalse(); assertThat(deliveryElement.get().hasOnDemandElement()).isTrue(); + assertThat(deliveryElement.get().hasFastFollowElement()).isFalse(); + assertThat(deliveryElement.get().hasModuleConditions()).isFalse(); + assertThat(deliveryElement.get().isWellFormed()).isTrue(); + } + + @Test + public void fastFollowDeliveryOnly_fastFollowAllowed() { + Optional deliveryElement = + ManifestDeliveryElement.fromManifestRootNode( + androidManifest("com.test.app", withFastFollowDelivery()), + /* isFastFollowAllowed= */ true); + + assertThat(deliveryElement).isPresent(); + assertThat(deliveryElement.get().hasInstallTimeElement()).isFalse(); + assertThat(deliveryElement.get().hasOnDemandElement()).isFalse(); + assertThat(deliveryElement.get().hasFastFollowElement()).isTrue(); assertThat(deliveryElement.get().hasModuleConditions()).isFalse(); assertThat(deliveryElement.get().isWellFormed()).isTrue(); } @@ -94,11 +116,32 @@ public void onDemandDeliveryOnly() { public void onDemandAndInstallTimeDelivery() { Optional deliveryElement = ManifestDeliveryElement.fromManifestRootNode( - androidManifest("com.test.app", withInstallTimeDelivery(), withOnDemandDelivery())); + androidManifest("com.test.app", withInstallTimeDelivery(), withOnDemandDelivery()), + /* isFastFollowAllowed= */ false); assertThat(deliveryElement).isPresent(); assertThat(deliveryElement.get().hasInstallTimeElement()).isTrue(); assertThat(deliveryElement.get().hasOnDemandElement()).isTrue(); + assertThat(deliveryElement.get().hasFastFollowElement()).isFalse(); + assertThat(deliveryElement.get().hasModuleConditions()).isFalse(); + assertThat(deliveryElement.get().isWellFormed()).isTrue(); + } + + @Test + public void onDemandAndInstallTimeAndFastFollowDelivery_fastFollowAllowed() { + Optional deliveryElement = + ManifestDeliveryElement.fromManifestRootNode( + androidManifest( + "com.test.app", + withInstallTimeDelivery(), + withOnDemandDelivery(), + withFastFollowDelivery()), + /* isFastFollowAllowed= */ true); + + assertThat(deliveryElement).isPresent(); + assertThat(deliveryElement.get().hasInstallTimeElement()).isTrue(); + assertThat(deliveryElement.get().hasOnDemandElement()).isTrue(); + assertThat(deliveryElement.get().hasFastFollowElement()).isTrue(); assertThat(deliveryElement.get().hasModuleConditions()).isFalse(); assertThat(deliveryElement.get().isWellFormed()).isTrue(); } @@ -107,11 +150,13 @@ public void onDemandAndInstallTimeDelivery() { public void instantOnDemandDelivery() { Optional deliveryElement = ManifestDeliveryElement.instantFromManifestRootNode( - androidManifest("com.test.app", withInstantOnDemandDelivery())); + androidManifest("com.test.app", withInstantOnDemandDelivery()), + /* isFastFollowAllowed= */ false); assertThat(deliveryElement).isPresent(); assertThat(deliveryElement.get().hasInstallTimeElement()).isFalse(); assertThat(deliveryElement.get().hasOnDemandElement()).isTrue(); + assertThat(deliveryElement.get().hasFastFollowElement()).isFalse(); assertThat(deliveryElement.get().hasModuleConditions()).isFalse(); assertThat(deliveryElement.get().isWellFormed()).isTrue(); } @@ -120,11 +165,13 @@ public void instantOnDemandDelivery() { public void instantInstallTimeDelivery() { Optional deliveryElement = ManifestDeliveryElement.instantFromManifestRootNode( - androidManifest("com.test.app", withInstantInstallTimeDelivery())); + androidManifest("com.test.app", withInstantInstallTimeDelivery()), + /* isFastFollowAllowed= */ false); assertThat(deliveryElement).isPresent(); assertThat(deliveryElement.get().hasInstallTimeElement()).isTrue(); assertThat(deliveryElement.get().hasOnDemandElement()).isFalse(); + assertThat(deliveryElement.get().hasFastFollowElement()).isFalse(); assertThat(deliveryElement.get().hasModuleConditions()).isFalse(); assertThat(deliveryElement.get().isWellFormed()).isTrue(); } @@ -136,7 +183,8 @@ public void getModuleConditions_returnsAllConditions() { androidManifest( "com.test.app", withFeatureCondition("android.hardware.camera.ar"), - withMinSdkCondition(24))); + withMinSdkCondition(24)), + /* isFastFollowAllowed= */ false); assertThat(deliveryElement).isPresent(); @@ -158,7 +206,8 @@ public void getDeviceFeatureConditions_returnsAllConditions() { "com.test.app", withFeatureCondition("android.hardware.camera.ar"), withFeatureCondition("android.software.vr.mode"), - withMinSdkVersion(24))); + withMinSdkVersion(24)), + /* isFastFollowAllowed= */ false); assertThat(deliveryElement).isPresent(); @@ -176,7 +225,8 @@ public void moduleConditions_deviceFeatureVersions() { androidManifest( "com.test.app", withFeatureConditionHexVersion("android.software.opengl", 0x30000), - withFeatureCondition("android.hardware.vr.headtracking", 1))); + withFeatureCondition("android.hardware.vr.headtracking", 1)), + /* isFastFollowAllowed= */ false); assertThat(deliveryElement).isPresent(); @@ -191,8 +241,8 @@ public void moduleConditions_deviceFeatureVersions() { public void moduleConditions_unsupportedCondition_throws() throws Exception { Optional manifestDeliveryElement = ManifestDeliveryElement.fromManifestRootNode( - androidManifest( - "com.test.app", withFusingAttribute(false), withUnsupportedCondition())); + androidManifest("com.test.app", withFusingAttribute(false), withUnsupportedCondition()), + /* isFastFollowAllowed= */ false); assertThat(manifestDeliveryElement).isPresent(); @@ -216,7 +266,7 @@ public void moduleConditions_missingNameOfFeature_throws() throws Exception { Optional manifestDeliveryElement = ManifestDeliveryElement.fromManifestRootNode( - createAndroidManifestWithConditions(badCondition)); + createAndroidManifestWithConditions(badCondition), /* isFastFollowAllowed= */ false); assertThat(manifestDeliveryElement).isPresent(); @@ -239,7 +289,7 @@ public void moduleConditions_missingMinSdkValue_throws() { Optional manifestDeliveryElement = ManifestDeliveryElement.fromManifestRootNode( - createAndroidManifestWithConditions(badCondition)); + createAndroidManifestWithConditions(badCondition), /* isFastFollowAllowed= */ false); assertThat(manifestDeliveryElement).isPresent(); @@ -255,7 +305,8 @@ public void moduleConditions_missingMinSdkValue_throws() { public void getModuleConditions_multipleMinSdkCondition_throws() { Optional element = ManifestDeliveryElement.fromManifestRootNode( - androidManifest("com.test.app", withMinSdkCondition(24), withMinSdkCondition(28))); + androidManifest("com.test.app", withMinSdkCondition(24), withMinSdkCondition(28)), + /* isFastFollowAllowed= */ false); assertThat(element).isPresent(); ValidationException exception = @@ -279,7 +330,9 @@ public void moduleConditions_typoInElement_throws() { ValidationException exception = assertThrows( ValidationException.class, - () -> ManifestDeliveryElement.fromManifestRootNode(nodeWithTypo)); + () -> + ManifestDeliveryElement.fromManifestRootNode( + nodeWithTypo, /* isFastFollowAllowed= */ false)); assertThat(exception) .hasMessageThat() @@ -300,16 +353,67 @@ public void deliveryElement_typoInChildElement_throws() { ValidationException exception = assertThrows( ValidationException.class, - () -> ManifestDeliveryElement.fromManifestRootNode(nodeWithTypo)); + () -> + ManifestDeliveryElement.fromManifestRootNode( + nodeWithTypo, /* isFastFollowAllowed= */ false)); assertThat(exception) .hasMessageThat() .contains( - "Expected element to contain only or " + "Expected element to contain only , " + " elements but found: 'instal-time' with namespace URI: " + "'http://schemas.android.com/apk/distribution'"); } + @Test + public void deliveryElement_typoInChildElement_throws_fastFollowEnabled() { + XmlNode nodeWithTypo = + createAndroidManifestWithDeliveryElement( + XmlProtoElementBuilder.create(DISTRIBUTION_NAMESPACE_URI, "delivery") + .addChildElement( + XmlProtoElementBuilder.create(DISTRIBUTION_NAMESPACE_URI, "instal-time"))); + + ValidationException exception = + assertThrows( + ValidationException.class, + () -> + ManifestDeliveryElement.fromManifestRootNode( + nodeWithTypo, /* isFastFollowAllowed= */ true)); + + assertThat(exception) + .hasMessageThat() + .contains( + "Expected element to contain only , " + + ", elements but found: 'instal-time' " + + "with namespace URI: 'http://schemas.android.com/apk/distribution'"); + } + + @Test + public void fastFollowElement_childElement_throws() { + XmlNode nodeWithTypo = + createAndroidManifestWithDeliveryElement( + XmlProtoElementBuilder.create(DISTRIBUTION_NAMESPACE_URI, "delivery") + .addChildElement( + XmlProtoElementBuilder.create(DISTRIBUTION_NAMESPACE_URI, "fast-follow") + .addChildElement( + XmlProtoElementBuilder.create( + DISTRIBUTION_NAMESPACE_URI, "conditions")))); + + ValidationException exception = + assertThrows( + ValidationException.class, + () -> + ManifestDeliveryElement.fromManifestRootNode( + nodeWithTypo, /* isFastFollowAllowed= */ true)); + + assertThat(exception) + .hasMessageThat() + .contains( + "Expected element to have no child elements but found: " + + "'conditions' with namespace URI: " + + "'http://schemas.android.com/apk/distribution'"); + } + @Test public void onDemandElement_childElement_throws() { XmlNode nodeWithTypo = @@ -324,7 +428,9 @@ public void onDemandElement_childElement_throws() { ValidationException exception = assertThrows( ValidationException.class, - () -> ManifestDeliveryElement.fromManifestRootNode(nodeWithTypo)); + () -> + ManifestDeliveryElement.fromManifestRootNode( + nodeWithTypo, /* isFastFollowAllowed= */ false)); assertThat(exception) .hasMessageThat() @@ -344,12 +450,14 @@ public void onDemandElement_missingNamespace_throws() { ValidationException exception = assertThrows( ValidationException.class, - () -> ManifestDeliveryElement.fromManifestRootNode(nodeWithTypo)); + () -> + ManifestDeliveryElement.fromManifestRootNode( + nodeWithTypo, /* isFastFollowAllowed= */ false)); assertThat(exception) .hasMessageThat() .contains( - "Expected element to contain only or " + "Expected element to contain only , " + " elements but found: 'on-demand' with namespace not provided"); } @@ -363,12 +471,14 @@ public void installTimeElement_missingNamespace_throws() { ValidationException exception = assertThrows( ValidationException.class, - () -> ManifestDeliveryElement.fromManifestRootNode(nodeWithTypo)); + () -> + ManifestDeliveryElement.fromManifestRootNode( + nodeWithTypo, /* isFastFollowAllowed= */ false)); assertThat(exception) .hasMessageThat() .contains( - "Expected element to contain only or " + "Expected element to contain only , " + " elements but found: 'install-time' with namespace not " + "provided"); } @@ -385,7 +495,9 @@ public void conditionsElement_missingNamespace_throws() { ValidationException exception = assertThrows( ValidationException.class, - () -> ManifestDeliveryElement.fromManifestRootNode(nodeWithTypo)); + () -> + ManifestDeliveryElement.fromManifestRootNode( + nodeWithTypo, /* isFastFollowAllowed= */ false)); assertThat(exception) .hasMessageThat() @@ -414,7 +526,8 @@ public void minSdkCondition_missingNamespace_throws() { assertThrows( ValidationException.class, () -> - ManifestDeliveryElement.fromManifestRootNode(nodeWithTypo) + ManifestDeliveryElement.fromManifestRootNode( + nodeWithTypo, /* isFastFollowAllowed= */ false) .get() .getModuleConditions()); @@ -443,7 +556,8 @@ public void deviceFeatureCondition_missingNamespace_throws() { assertThrows( ValidationException.class, () -> - ManifestDeliveryElement.fromManifestRootNode(nodeWithTypo) + ManifestDeliveryElement.fromManifestRootNode( + nodeWithTypo, /* isFastFollowAllowed= */ false) .get() .getModuleConditions()); @@ -462,7 +576,7 @@ public void userCountriesCondition_parsesOk() { .addChildElement(createCountryCodeEntry("GB")) .build()); Optional deliveryElement = - ManifestDeliveryElement.fromManifestRootNode(manifest); + ManifestDeliveryElement.fromManifestRootNode(manifest, /* isFastFollowAllowed= */ false); assertThat(deliveryElement).isPresent(); Optional userCountriesCondition = @@ -485,7 +599,7 @@ public void userCountriesCondition_parsesExclusionOk() { .addChildElement(createCountryCodeEntry("SN")) .build()); Optional deliveryElement = - ManifestDeliveryElement.fromManifestRootNode(manifest); + ManifestDeliveryElement.fromManifestRootNode(manifest, /* isFastFollowAllowed= */ false); assertThat(deliveryElement).isPresent(); Optional userCountriesCondition = @@ -508,7 +622,7 @@ public void userCountriesCondition_badCountryElementName_throws() { .setValueAsString("DE"))) .build()); Optional deliveryElement = - ManifestDeliveryElement.fromManifestRootNode(manifest); + ManifestDeliveryElement.fromManifestRootNode(manifest, /* isFastFollowAllowed= */ false); assertThat(deliveryElement).isPresent(); ValidationException exception = @@ -533,7 +647,7 @@ public void userCountriesCondition_missingCodeAttribute_throws() { .setValueAsString("DE"))) .build()); Optional deliveryElement = - ManifestDeliveryElement.fromManifestRootNode(manifest); + ManifestDeliveryElement.fromManifestRootNode(manifest, /* isFastFollowAllowed= */ false); assertThat(deliveryElement).isPresent(); ValidationException exception = @@ -553,7 +667,8 @@ public void getModuleConditions_multipleUserCountriesConditions_throws() { androidManifest( "com.test.app", withUserCountriesCondition(ImmutableList.of("en", "us")), - withUserCountriesCondition(ImmutableList.of("sg"), /* exclude= */ true))); + withUserCountriesCondition(ImmutableList.of("sg"), /* exclude= */ true)), + /* isFastFollowAllowed= */ false); assertThat(element).isPresent(); ValidationException exception = diff --git a/src/test/java/com/android/tools/build/bundletool/optimizations/ApkOptimizationsTest.java b/src/test/java/com/android/tools/build/bundletool/optimizations/ApkOptimizationsTest.java index 85ebbcae..cd8b0662 100755 --- a/src/test/java/com/android/tools/build/bundletool/optimizations/ApkOptimizationsTest.java +++ b/src/test/java/com/android/tools/build/bundletool/optimizations/ApkOptimizationsTest.java @@ -18,6 +18,7 @@ import static com.android.tools.build.bundletool.model.OptimizationDimension.ABI; import static com.android.tools.build.bundletool.model.OptimizationDimension.LANGUAGE; import static com.android.tools.build.bundletool.model.OptimizationDimension.SCREEN_DENSITY; +import static com.android.tools.build.bundletool.model.OptimizationDimension.TEXTURE_COMPRESSION_FORMAT; import static com.google.common.truth.Truth.assertThat; import com.android.tools.build.bundletool.model.version.Version; @@ -52,4 +53,18 @@ public void getDefaultOptimizations_0_2_0_onlySplitsByAbiDensityAndLanguage() { .setUncompressNativeLibraries(true) .build()); } + + @Test + public void + getDefaultOptimizations_0_10_2_onlySplitsByAbiDensityTextureLanguageAndUncompressNativeLibs() { + ApkOptimizations defaultOptimizations = + ApkOptimizations.getDefaultOptimizationsForVersion(Version.of("0.10.2")); + assertThat(defaultOptimizations) + .isEqualTo( + ApkOptimizations.builder() + .setSplitDimensions( + ImmutableSet.of(ABI, SCREEN_DENSITY, TEXTURE_COMPRESSION_FORMAT, LANGUAGE)) + .setUncompressNativeLibraries(true) + .build()); + } } diff --git a/src/test/java/com/android/tools/build/bundletool/splitters/BundleSharderTest.java b/src/test/java/com/android/tools/build/bundletool/splitters/BundleSharderTest.java index b5dc5d8d..aa9ed7bd 100755 --- a/src/test/java/com/android/tools/build/bundletool/splitters/BundleSharderTest.java +++ b/src/test/java/com/android/tools/build/bundletool/splitters/BundleSharderTest.java @@ -93,6 +93,7 @@ import com.android.bundle.Targeting.VariantTargeting; import com.android.tools.build.bundletool.model.BundleMetadata; import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.BundleModuleName; import com.android.tools.build.bundletool.model.ModuleSplit; import com.android.tools.build.bundletool.model.ModuleSplit.SplitType; import com.android.tools.build.bundletool.model.OptimizationDimension; @@ -160,6 +161,10 @@ public class BundleSharderTest { DensityAlias.XXXHDPI, Sets.difference(ALL_DENSITIES, ImmutableSet.of(DensityAlias.XXXHDPI))); + private static final BundleModuleName BASE_MODULE_NAME = BundleModuleName.create("base"); + private static final BundleModuleName FEATURE_MODULE_NAME = BundleModuleName.create("feature"); + private static final BundleModuleName VR_MODULE_NAME = BundleModuleName.create("vr"); + private BundleSharder bundleSharder; private Path tmpDir; @@ -319,7 +324,8 @@ public void shardByAbi_havingNoNativeTargeting_producesOneApk( if (deviceSpec.isPresent()) { ShardedSystemSplits shardedSystemSplits = bundleSharder.shardForSystemApps( - ImmutableList.of(bundleModule), + /* modules= */ ImmutableList.of(bundleModule), + /* modulesToFuse= */ ImmutableSet.of(BASE_MODULE_NAME), ImmutableSet.of(OptimizationDimension.ABI), DEFAULT_METADATA); assertThat(shardedSystemSplits.getAdditionalSplits()).isEmpty(); @@ -387,7 +393,8 @@ public void shardByAbi_havingSingleAbi_producesOneApk( if (deviceSpec.isPresent()) { ShardedSystemSplits shardedSystemSplits = bundleSharder.shardForSystemApps( - ImmutableList.of(bundleModule), + /* modules= */ ImmutableList.of(bundleModule), + /* modulesToFuse= */ ImmutableSet.of(BASE_MODULE_NAME), ImmutableSet.of(OptimizationDimension.ABI), DEFAULT_METADATA); assertThat(shardedSystemSplits.getAdditionalSplits()).isEmpty(); @@ -539,7 +546,8 @@ public void shardByAbi_havingManyAbisForSystemShards_producesSingleApk() throws ShardedSystemSplits shards = bundleSharder.shardForSystemApps( - ImmutableList.of(bundleModule), + /* modules= */ ImmutableList.of(bundleModule), + /* modulesToFuse= */ ImmutableSet.of(BASE_MODULE_NAME), ImmutableSet.of(OptimizationDimension.ABI), DEFAULT_METADATA); @@ -612,7 +620,8 @@ public void shardByAbi_havingManyModulesWithLanguagesForSystemShards() throws Ex ShardedSystemSplits shards = bundleSharder.shardForSystemApps( - ImmutableList.of(baseModule, vrModule), + /* modules= */ ImmutableList.of(baseModule, vrModule), + /* modulesToFuse= */ ImmutableSet.of(BASE_MODULE_NAME, VR_MODULE_NAME), ImmutableSet.of( OptimizationDimension.ABI, OptimizationDimension.LANGUAGE, @@ -953,7 +962,8 @@ public void shardByAbiAndDensity_havingNoAbiAndNoResources_producesOneApk( if (deviceSpec.isPresent()) { ShardedSystemSplits shardedSystemSplits = bundleSharder.shardForSystemApps( - ImmutableList.of(bundleModule), + /* modules= */ ImmutableList.of(bundleModule), + /* modulesToFuse= */ ImmutableSet.of(BASE_MODULE_NAME), ImmutableSet.of(OptimizationDimension.ABI, OptimizationDimension.SCREEN_DENSITY), DEFAULT_METADATA); assertThat(shardedSystemSplits.getAdditionalSplits()).isEmpty(); @@ -1095,7 +1105,8 @@ public void shardByAbiAndDensity_havingOneAbiAndSomeDensityResource_producesMany ShardedSystemSplits shards = bundleSharder.shardForSystemApps( - ImmutableList.of(bundleModule), + /* modules= */ ImmutableList.of(bundleModule), + /* modulesToFuse= */ ImmutableSet.of(BASE_MODULE_NAME), ImmutableSet.of(OptimizationDimension.ABI, OptimizationDimension.SCREEN_DENSITY), DEFAULT_METADATA); @@ -1168,7 +1179,8 @@ public void shardByAbiAndDensity_havingOneAbiAndOneDensityMultipleLanguageResour ShardedSystemSplits shards = bundleSharder.shardForSystemApps( - ImmutableList.of(bundleModule), + /* modules= */ ImmutableList.of(bundleModule), + /* modulesToFuse= */ ImmutableSet.of(BASE_MODULE_NAME), ImmutableSet.of( OptimizationDimension.ABI, OptimizationDimension.SCREEN_DENSITY, @@ -1261,7 +1273,8 @@ public void shardByAbiAndDensity_havingOneAbiAndOneDensityMultipleLanguageResour ShardedSystemSplits shards = bundleSharder.shardForSystemApps( - ImmutableList.of(bundleModule), + /* modules= */ ImmutableList.of(bundleModule), + /* modulesToFuse= */ ImmutableSet.of(BASE_MODULE_NAME), ImmutableSet.of( OptimizationDimension.ABI, OptimizationDimension.SCREEN_DENSITY, @@ -1525,7 +1538,10 @@ public void manyModulesShardByNoDimension_producesFatApk( if (deviceSpec.isPresent()) { ShardedSystemSplits shardedSystemSplits = bundleSharder.shardForSystemApps( - ImmutableList.of(baseModule, featureModule), ImmutableSet.of(), DEFAULT_METADATA); + /* modules= */ ImmutableList.of(baseModule, featureModule), + /* modulesToFuse= */ ImmutableSet.of(BASE_MODULE_NAME, FEATURE_MODULE_NAME), + ImmutableSet.of(), + DEFAULT_METADATA); assertThat(shardedSystemSplits.getAdditionalSplits()).isEmpty(); shards = ImmutableList.of(shardedSystemSplits.getSystemImageSplit()); @@ -1728,7 +1744,8 @@ public void manyModulesShardByDensity_havingOnlyOneDensityResource_producesSingl if (deviceSpec.isPresent()) { ShardedSystemSplits shardedSystemSplits = bundleSharder.shardForSystemApps( - ImmutableList.of(baseModule, featureModule), + /* modules= */ ImmutableList.of(baseModule, featureModule), + /* modulesToFuse= */ ImmutableSet.of(BASE_MODULE_NAME, FEATURE_MODULE_NAME), ImmutableSet.of(OptimizationDimension.SCREEN_DENSITY), DEFAULT_METADATA); assertThat(shardedSystemSplits.getAdditionalSplits()).isEmpty(); @@ -1970,7 +1987,8 @@ public void manyModulesShardByAbiAndDensity_havingManyAbisAndSomeResource_produc ShardedSystemSplits shards = bundleSharder.shardForSystemApps( - ImmutableList.of(baseModule, featureModule), + /* modules= */ ImmutableList.of(baseModule, featureModule), + /* modulesToFuse= */ ImmutableSet.of(BASE_MODULE_NAME, FEATURE_MODULE_NAME), ImmutableSet.of(OptimizationDimension.ABI, OptimizationDimension.SCREEN_DENSITY), DEFAULT_METADATA); diff --git a/src/test/java/com/android/tools/build/bundletool/splitters/DexCompressionSplitterTest.java b/src/test/java/com/android/tools/build/bundletool/splitters/DexCompressionSplitterTest.java index bb253d50..843da1d9 100755 --- a/src/test/java/com/android/tools/build/bundletool/splitters/DexCompressionSplitterTest.java +++ b/src/test/java/com/android/tools/build/bundletool/splitters/DexCompressionSplitterTest.java @@ -17,6 +17,7 @@ package com.android.tools.build.bundletool.splitters; import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_P_API_VERSION; +import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_Q_API_VERSION; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; import static com.android.tools.build.bundletool.testing.TargetingUtils.variantSdkTargeting; import static com.android.tools.build.bundletool.testing.TestUtils.extractPaths; @@ -37,19 +38,19 @@ public class DexCompressionSplitterTest { @Test - public void dexCompressionSplitter_withP_withDexFiles() throws Exception { + public void dexCompressionSplitter_withQ_withDexFiles() throws Exception { DexCompressionSplitter dexCompressionSplitter = new DexCompressionSplitter(); ImmutableCollection splits = dexCompressionSplitter.split( ModuleSplit.forDex( - createModuleWithDexFile(), variantSdkTargeting(ANDROID_P_API_VERSION))); + createModuleWithDexFile(), variantSdkTargeting(ANDROID_Q_API_VERSION))); assertThat(splits).hasSize(1); ModuleSplit moduleSplit = Iterables.getOnlyElement(splits); assertThat(moduleSplit.getVariantTargeting()) - .isEqualTo(variantSdkTargeting(ANDROID_P_API_VERSION)); + .isEqualTo(variantSdkTargeting(ANDROID_Q_API_VERSION)); assertThat(extractPaths(moduleSplit.getEntries())).containsExactly("dex/classes.dex"); assertThat(moduleSplit.isMasterSplit()).isTrue(); @@ -58,7 +59,7 @@ public void dexCompressionSplitter_withP_withDexFiles() throws Exception { } @Test - public void dexCompressionSplitter_withP_noDexFiles() throws Exception { + public void dexCompressionSplitter_withQ_noDexFiles() throws Exception { DexCompressionSplitter dexCompressionSplitter = new DexCompressionSplitter(); ModuleSplit moduleSplit = ModuleSplit.forModule( @@ -67,7 +68,7 @@ public void dexCompressionSplitter_withP_noDexFiles() throws Exception { .addFile("assets/leftover.txt") .setManifest(androidManifest("com.test.app")) .build(), - variantSdkTargeting(ANDROID_P_API_VERSION)); + variantSdkTargeting(ANDROID_Q_API_VERSION)); ImmutableCollection splits = dexCompressionSplitter.split(moduleSplit); @@ -76,7 +77,7 @@ public void dexCompressionSplitter_withP_noDexFiles() throws Exception { } @Test - public void dexCompressionSplitter_withP_otherEntriesCompressionUnchanged() throws Exception { + public void dexCompressionSplitter_withQ_otherEntriesCompressionUnchanged() throws Exception { DexCompressionSplitter dexCompressionSplitter = new DexCompressionSplitter(); BundleModule bundleModule = createModuleBuilderWithDexFile() @@ -86,14 +87,14 @@ public void dexCompressionSplitter_withP_otherEntriesCompressionUnchanged() thro ImmutableCollection splits = dexCompressionSplitter.split( - ModuleSplit.forModule(bundleModule, variantSdkTargeting(ANDROID_P_API_VERSION))); + ModuleSplit.forModule(bundleModule, variantSdkTargeting(ANDROID_Q_API_VERSION))); assertThat(splits).hasSize(1); ModuleSplit moduleSplit = Iterables.getOnlyElement(splits); assertThat(moduleSplit.getVariantTargeting()) - .isEqualTo(variantSdkTargeting(ANDROID_P_API_VERSION)); + .isEqualTo(variantSdkTargeting(ANDROID_Q_API_VERSION)); assertThat(extractPaths(moduleSplit.getEntries())) .containsExactly("lib/x86_64/libsome.so", "assets/leftover.txt", "dex/classes.dex"); @@ -107,17 +108,19 @@ public void dexCompressionSplitter_withP_otherEntriesCompressionUnchanged() thro } @Test - public void dexCompressionSplitter_preP_withDexFiles() throws Exception { + public void dexCompressionSplitter_preQ_withDexFiles() throws Exception { DexCompressionSplitter dexCompressionSplitter = new DexCompressionSplitter(); ImmutableCollection splits = dexCompressionSplitter.split( - ModuleSplit.forDex(createModuleWithDexFile(), variantSdkTargeting(27))); + ModuleSplit.forDex( + createModuleWithDexFile(), variantSdkTargeting(ANDROID_P_API_VERSION))); assertThat(splits).hasSize(1); ModuleSplit moduleSplit = Iterables.getOnlyElement(splits); - assertThat(moduleSplit.getVariantTargeting()).isEqualTo(variantSdkTargeting(27)); + assertThat(moduleSplit.getVariantTargeting()) + .isEqualTo(variantSdkTargeting(ANDROID_P_API_VERSION)); assertThat(extractPaths(moduleSplit.getEntries())).containsExactly("dex/classes.dex"); assertThat(isCompressed(moduleSplit, "dex/classes.dex")).isTrue(); diff --git a/src/test/java/com/android/tools/build/bundletool/splitters/DexCompressionVariantGeneratorTest.java b/src/test/java/com/android/tools/build/bundletool/splitters/DexCompressionVariantGeneratorTest.java index e8174838..7904ad7f 100755 --- a/src/test/java/com/android/tools/build/bundletool/splitters/DexCompressionVariantGeneratorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/splitters/DexCompressionVariantGeneratorTest.java @@ -16,7 +16,7 @@ package com.android.tools.build.bundletool.splitters; -import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_P_API_VERSION; +import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_Q_API_VERSION; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; import static com.android.tools.build.bundletool.testing.TargetingUtils.variantMinSdkTargeting; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -35,7 +35,7 @@ @RunWith(JUnit4.class) public class DexCompressionVariantGeneratorTest { @Test - public void variantsGeneration_withDexFile_generatesPVariant() throws Exception { + public void variantsGeneration_withDexFile_generatesQVariant() throws Exception { DexCompressionVariantGenerator dexCompressionVariantGenerator = new DexCompressionVariantGenerator( ApkGenerationConfiguration.builder().setEnableDexCompressionSplitter(true).build()); @@ -43,7 +43,7 @@ public void variantsGeneration_withDexFile_generatesPVariant() throws Exception dexCompressionVariantGenerator .generate(createModuleWithDexFile()) .collect(toImmutableList()); - assertThat(splits).containsExactly(variantMinSdkTargeting(ANDROID_P_API_VERSION)); + assertThat(splits).containsExactly(variantMinSdkTargeting(ANDROID_Q_API_VERSION)); } @Test diff --git a/src/test/java/com/android/tools/build/bundletool/splitters/ModuleSplitterTest.java b/src/test/java/com/android/tools/build/bundletool/splitters/ModuleSplitterTest.java index 58a5cf07..384a08d5 100755 --- a/src/test/java/com/android/tools/build/bundletool/splitters/ModuleSplitterTest.java +++ b/src/test/java/com/android/tools/build/bundletool/splitters/ModuleSplitterTest.java @@ -29,7 +29,7 @@ import static com.android.tools.build.bundletool.model.OptimizationDimension.SCREEN_DENSITY; import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_L_API_VERSION; import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_M_API_VERSION; -import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_P_API_VERSION; +import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_Q_API_VERSION; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withInstant; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withMainActivity; @@ -888,7 +888,7 @@ public void nativeSplits_pPlusTargeting_withDexCompressionSplitter() throws Exce testModule, BUNDLETOOL_VERSION, ApkGenerationConfiguration.builder().setEnableDexCompressionSplitter(true).build(), - variantMinSdkTargeting(ANDROID_P_API_VERSION), + variantMinSdkTargeting(ANDROID_Q_API_VERSION), ImmutableSet.of("testModule")); List splits = moduleSplitter.splitModule(); @@ -896,7 +896,7 @@ public void nativeSplits_pPlusTargeting_withDexCompressionSplitter() throws Exce ModuleSplit moduleSplit = Iterables.getOnlyElement(splits); assertThat(moduleSplit.getSplitType()).isEqualTo(SplitType.SPLIT); assertThat(moduleSplit.getVariantTargeting()) - .isEqualTo(variantMinSdkTargeting(ANDROID_P_API_VERSION)); + .isEqualTo(variantMinSdkTargeting(ANDROID_Q_API_VERSION)); assertThat(moduleSplit.findEntry("dex/classes.dex").get().shouldCompress()).isFalse(); } diff --git a/src/test/java/com/android/tools/build/bundletool/splitters/ShardedApksGeneratorTest.java b/src/test/java/com/android/tools/build/bundletool/splitters/ShardedApksGeneratorTest.java index c1c080af..1bfae54c 100755 --- a/src/test/java/com/android/tools/build/bundletool/splitters/ShardedApksGeneratorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/splitters/ShardedApksGeneratorTest.java @@ -55,6 +55,7 @@ import com.android.bundle.Targeting.ScreenDensity.DensityAlias; import com.android.tools.build.bundletool.model.BundleMetadata; import com.android.tools.build.bundletool.model.BundleModule; +import com.android.tools.build.bundletool.model.BundleModuleName; import com.android.tools.build.bundletool.model.ModuleSplit; import com.android.tools.build.bundletool.model.ModuleSplit.SplitType; import com.android.tools.build.bundletool.model.version.BundleToolVersion; @@ -87,6 +88,8 @@ public class ShardedApksGeneratorTest { ApkOptimizations.getDefaultOptimizationsForVersion(BUNDLETOOL_VERSION); private static final DeviceSpec DEVICE_SPEC = mergeSpecs(sdkVersion(28), abis("x86"), density(DensityAlias.MDPI), locales("en-US")); + private static final BundleModuleName BASE_MODULE_NAME = BundleModuleName.create("base"); + private static final BundleModuleName VR_MODULE_NAME = BundleModuleName.create("vr"); @Rule public final TemporaryFolder tmp = new TemporaryFolder(); private Path tmpDir; @@ -111,13 +114,18 @@ public void simpleMultipleModules( .addFile("assets/leftover.txt") .setManifest(androidManifest("com.test.app")) .build(), - new BundleModuleBuilder("test") + new BundleModuleBuilder("vr") .addFile("assets/test.txt") .setManifest(androidManifestForFeature("com.test.app")) .build()); ImmutableList moduleSplits = - generateModuleSplits(bundleModule, standaloneSplitType, /* generate64BitShards= */ true); + standaloneSplitType.equals(SplitType.STANDALONE) + ? generateModuleSplitsForStandalone(bundleModule, /* generate64BitShards= */ true) + : generateModuleSplitsForSystem( + bundleModule, + /* generate64BitShards= */ true, + ImmutableSet.of(BASE_MODULE_NAME, VR_MODULE_NAME)); assertThat(moduleSplits).hasSize(1); ModuleSplit moduleSplit = moduleSplits.get(0); @@ -159,12 +167,16 @@ public void singleModule_withNativeLibsAndDensity( .setManifest(androidManifest("com.test.app")) .build()); - ImmutableList moduleSplits = - generateModuleSplits(bundleModule, standaloneSplitType, /* generate64BitShards= */ true); + ImmutableList moduleSplits; if (standaloneSplitType.equals(SplitType.SYSTEM)) { + moduleSplits = + generateModuleSplitsForSystem( + bundleModule, /* generate64BitShards= */ true, ImmutableSet.of(BASE_MODULE_NAME)); assertThat(moduleSplits).hasSize(1); // x86, mdpi split } else { + moduleSplits = + generateModuleSplitsForStandalone(bundleModule, /* generate64BitShards= */ true); assertThat(moduleSplits).hasSize(14); // 7 (density), 2 (abi) splits } assertThat(moduleSplits.stream().map(ModuleSplit::getSplitType).collect(toImmutableSet())) @@ -203,12 +215,16 @@ public void singleModule_withNativeLibsAndDensity_64bitNativeLibsDisabled( .setManifest(androidManifest("com.test.app")) .build()); - ImmutableList moduleSplits = - generateModuleSplits(bundleModule, standaloneSplitType, /* generate64BitShards= */ false); + ImmutableList moduleSplits; if (standaloneSplitType.equals(SplitType.SYSTEM)) { + moduleSplits = + generateModuleSplitsForSystem( + bundleModule, /* generate64BitShards= */ false, ImmutableSet.of(BASE_MODULE_NAME)); assertThat(moduleSplits).hasSize(1); // x86, mdpi split } else { + moduleSplits = + generateModuleSplitsForStandalone(bundleModule, /* generate64BitShards= */ false); assertThat(moduleSplits).hasSize(7); // 7 (density), 1 (abi) split } // Verify that the only ABI is x86. @@ -267,7 +283,8 @@ public void singleModule_withNativeLibsAndDensityWithDeviceSpec_64bitNativeLibsD ImmutableList moduleSplits = new ShardedApksGenerator(tmpDir, BUNDLETOOL_VERSION, /* generate64BitShards= */ false) .generateSystemSplits( - bundleModule, + /* modules= */ bundleModule, + /* modulesToFuse= */ ImmutableSet.of(BASE_MODULE_NAME), DEFAULT_METADATA, DEFAULT_APK_OPTIMIZATIONS, Optional.of( @@ -324,7 +341,8 @@ public void singleModule_withNativeLibsAndLanguagesWithDeviceSpec() throws Excep ImmutableList moduleSplits = new ShardedApksGenerator(tmpDir, BUNDLETOOL_VERSION, /* generate64BitShards= */ true) .generateSystemSplits( - bundleModule, + /* modules= */ bundleModule, + /* modulesToFuse= */ ImmutableSet.of(BASE_MODULE_NAME), DEFAULT_METADATA, DEFAULT_APK_OPTIMIZATIONS, Optional.of( @@ -409,7 +427,8 @@ public void multipleModule_withLanguages() throws Exception { ImmutableList moduleSplits = new ShardedApksGenerator(tmpDir, BUNDLETOOL_VERSION, /* generate64BitShards= */ true) .generateSystemSplits( - bundleModule, + /* modules= */ bundleModule, + /* modulesToFuse= */ ImmutableSet.of(BASE_MODULE_NAME, VR_MODULE_NAME), DEFAULT_METADATA, DEFAULT_APK_OPTIMIZATIONS, Optional.of( @@ -456,20 +475,25 @@ public void multipleModule_withLanguages() throws Exception { .containsExactly("assets/vr/languages#lang_it/image.jpg"); } - private ImmutableList generateModuleSplits( - ImmutableList bundleModule, - SplitType standaloneSplitType, - boolean generate64BitShards) { - if (standaloneSplitType.equals(SplitType.STANDALONE)) { + private ImmutableList generateModuleSplitsForStandalone( + ImmutableList bundleModule, boolean generate64BitShards) { return new ShardedApksGenerator(tmpDir, BUNDLETOOL_VERSION, generate64BitShards) .generateSplits(bundleModule, DEFAULT_METADATA, DEFAULT_APK_OPTIMIZATIONS); - } else { - return new ShardedApksGenerator(tmpDir, BUNDLETOOL_VERSION, generate64BitShards) - .generateSystemSplits( - bundleModule, DEFAULT_METADATA, DEFAULT_APK_OPTIMIZATIONS, Optional.of(DEVICE_SPEC)); - } } + private ImmutableList generateModuleSplitsForSystem( + ImmutableList bundleModule, + boolean generate64BitShards, + ImmutableSet moduleToFuse) { + return new ShardedApksGenerator(tmpDir, BUNDLETOOL_VERSION, generate64BitShards) + .generateSystemSplits( + /* modules= */ bundleModule, + /* modulesToFuse= */ moduleToFuse, + DEFAULT_METADATA, + DEFAULT_APK_OPTIMIZATIONS, + Optional.of(DEVICE_SPEC)); + } + private static ImmutableSet getEntriesPaths(ModuleSplit moduleSplit) { return moduleSplit.getEntries().stream() .map(moduleEntry -> moduleEntry.getPath().toString()) diff --git a/src/test/java/com/android/tools/build/bundletool/splitters/SplitApksGeneratorTest.java b/src/test/java/com/android/tools/build/bundletool/splitters/SplitApksGeneratorTest.java index 91eeac7e..7065f8ee 100755 --- a/src/test/java/com/android/tools/build/bundletool/splitters/SplitApksGeneratorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/splitters/SplitApksGeneratorTest.java @@ -18,7 +18,7 @@ import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_L_API_VERSION; import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_M_API_VERSION; -import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_P_API_VERSION; +import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_Q_API_VERSION; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; import static com.android.tools.build.bundletool.testing.TargetingUtils.lPlusVariantTargeting; import static com.android.tools.build.bundletool.testing.TargetingUtils.nativeDirectoryTargeting; @@ -183,15 +183,15 @@ public void multipleModules_withOnlyBaseModuleWithNativeLibrariesOtherModuleWith variantMinSdkTargeting( /* minSdkVersion= */ ANDROID_L_API_VERSION, /* alternativeSdkVersions= */ ANDROID_M_API_VERSION, - ANDROID_P_API_VERSION); + ANDROID_Q_API_VERSION); VariantTargeting mVariantTargeting = variantMinSdkTargeting( /* minSdkVersion= */ ANDROID_M_API_VERSION, /* alternativeSdkVersions= */ ANDROID_L_API_VERSION, - ANDROID_P_API_VERSION); - VariantTargeting pVariantTargeting = + ANDROID_Q_API_VERSION); + VariantTargeting qVariantTargeting = variantMinSdkTargeting( - /* minSdkVersion= */ ANDROID_P_API_VERSION, + /* minSdkVersion= */ ANDROID_Q_API_VERSION, /* alternativeSdkVersions= */ ANDROID_L_API_VERSION, ANDROID_M_API_VERSION); @@ -199,7 +199,7 @@ public void multipleModules_withOnlyBaseModuleWithNativeLibrariesOtherModuleWith assertThat(moduleSplits).hasSize(6); assertThat( moduleSplits.stream().map(ModuleSplit::getVariantTargeting).collect(toImmutableSet())) - .containsExactly(lVariantTargeting, mVariantTargeting, pVariantTargeting); + .containsExactly(lVariantTargeting, mVariantTargeting, qVariantTargeting); ModuleSplit baseLModule = getModuleSplit(moduleSplits, lVariantTargeting, "base"); assertThat(baseLModule.getSplitType()).isEqualTo(SplitType.SPLIT); @@ -225,13 +225,13 @@ public void multipleModules_withOnlyBaseModuleWithNativeLibrariesOtherModuleWith .containsExactly("assets/test.txt", "dex/classes.dex"); assertThat(isCompressed(testMModule, "dex/classes.dex")).isTrue(); - ModuleSplit basePModule = getModuleSplit(moduleSplits, pVariantTargeting, "base"); + ModuleSplit basePModule = getModuleSplit(moduleSplits, qVariantTargeting, "base"); assertThat(basePModule.getSplitType()).isEqualTo(SplitType.SPLIT); assertThat(extractPaths(basePModule.getEntries())) .containsExactly("assets/leftover.txt", "lib/x86_64/libsome.so"); assertThat(isCompressed(basePModule, "lib/x86_64/libsome.so")).isFalse(); - ModuleSplit testPModule = getModuleSplit(moduleSplits, pVariantTargeting, "test"); + ModuleSplit testPModule = getModuleSplit(moduleSplits, qVariantTargeting, "test"); assertThat(testPModule.getSplitType()).isEqualTo(SplitType.SPLIT); assertThat(extractPaths(testPModule.getEntries())) .containsExactly("assets/test.txt", "dex/classes.dex"); diff --git a/src/test/java/com/android/tools/build/bundletool/splitters/VariantGeneratorTest.java b/src/test/java/com/android/tools/build/bundletool/splitters/VariantGeneratorTest.java index 0e37aaf9..ecfcdca8 100755 --- a/src/test/java/com/android/tools/build/bundletool/splitters/VariantGeneratorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/splitters/VariantGeneratorTest.java @@ -17,7 +17,7 @@ package com.android.tools.build.bundletool.splitters; import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_M_API_VERSION; -import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_P_API_VERSION; +import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_Q_API_VERSION; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withMaxSdkVersion; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withMinSdkVersion; @@ -93,7 +93,7 @@ public void variantsWithNativeLibsDexFiles_withDexOptimization() throws Exceptio ImmutableCollection splits = variantGenerator.generateVariants(); assertThat(splits) - .containsExactly(lPlusVariantTargeting(), variantMinSdkTargeting(ANDROID_P_API_VERSION)); + .containsExactly(lPlusVariantTargeting(), variantMinSdkTargeting(ANDROID_Q_API_VERSION)); } @Test @@ -112,7 +112,7 @@ public void variantsWithNativeLibsDexFiles_withNativeLibsAndDexOptimization() th .containsExactly( lPlusVariantTargeting(), variantMinSdkTargeting(ANDROID_M_API_VERSION), - variantMinSdkTargeting(ANDROID_P_API_VERSION)); + variantMinSdkTargeting(ANDROID_Q_API_VERSION)); } @Test @@ -131,7 +131,7 @@ public void variantsWithAllOptimizations_minSdkAffectsLPlusVariant() throws Exce .containsExactly( variantMinSdkTargeting(22), variantMinSdkTargeting(ANDROID_M_API_VERSION), - variantMinSdkTargeting(ANDROID_P_API_VERSION)); + variantMinSdkTargeting(ANDROID_Q_API_VERSION)); } @Test @@ -149,14 +149,14 @@ public void variantsWithAllOptimizations_minSdkRemovesLPlusVariant() throws Exce assertThat(splits) .containsExactly( variantMinSdkTargeting(ANDROID_M_API_VERSION), - variantMinSdkTargeting(ANDROID_P_API_VERSION)); + variantMinSdkTargeting(ANDROID_Q_API_VERSION)); } @Test public void variantsWithAllOptimizations_maxSdkRemovesDexVariant() throws Exception { VariantGenerator variantGenerator = new VariantGenerator( - createSingleLibraryDexModuleMaxSdk(ANDROID_P_API_VERSION - 1), + createSingleLibraryDexModuleMaxSdk(ANDROID_Q_API_VERSION - 1), ApkGenerationConfiguration.builder() .setEnableDexCompressionSplitter(true) .setEnableNativeLibraryCompressionSplitter(true) diff --git a/src/test/java/com/android/tools/build/bundletool/testing/ApkSetUtils.java b/src/test/java/com/android/tools/build/bundletool/testing/ApkSetUtils.java index 9e39fceb..bdfada7d 100755 --- a/src/test/java/com/android/tools/build/bundletool/testing/ApkSetUtils.java +++ b/src/test/java/com/android/tools/build/bundletool/testing/ApkSetUtils.java @@ -18,7 +18,10 @@ import com.android.bundle.Commands.ApkDescription; import com.android.bundle.Commands.ApkSet; import com.android.bundle.Commands.BuildApksResult; +import com.android.bundle.Commands.DeliveryType; import com.android.bundle.Commands.ModuleMetadata; +import com.android.tools.build.bundletool.model.version.BundleToolVersion; +import com.android.tools.build.bundletool.model.version.Version; import com.google.common.collect.ImmutableList; import com.google.common.io.ByteStreams; import java.io.File; @@ -57,22 +60,25 @@ public static File extractFromApkSetFile(ZipFile apkSetFile, String path, Path o public static ApkSet splitApkSet(String moduleName, ApkDescription... apkDescriptions) { return splitApkSet( moduleName, - /* onDemand= */ false, + DeliveryType.INSTALL_TIME, /* moduleDependencies= */ ImmutableList.of(), apkDescriptions); } public static ApkSet splitApkSet( String moduleName, - boolean onDemand, + DeliveryType deliveryType, ImmutableList moduleDependencies, ApkDescription... apkDescriptions) { + ModuleMetadata.Builder moduleMetadata = + ModuleMetadata.newBuilder().setName(moduleName).addAllDependencies(moduleDependencies); + if (BundleToolVersion.getCurrentVersion().isNewerThan(Version.of("0.10.1"))) { + moduleMetadata.setDeliveryType(deliveryType); + } else { + moduleMetadata.setOnDemandDeprecated(deliveryType != DeliveryType.INSTALL_TIME); + } return ApkSet.newBuilder() - .setModuleMetadata( - ModuleMetadata.newBuilder() - .setName(moduleName) - .setOnDemand(onDemand) - .addAllDependencies(moduleDependencies)) + .setModuleMetadata(moduleMetadata) .addAllApkDescription(Arrays.asList(apkDescriptions)) .build(); } diff --git a/src/test/java/com/android/tools/build/bundletool/testing/ApksArchiveHelpers.java b/src/test/java/com/android/tools/build/bundletool/testing/ApksArchiveHelpers.java index 9bac00d5..d7343270 100755 --- a/src/test/java/com/android/tools/build/bundletool/testing/ApksArchiveHelpers.java +++ b/src/test/java/com/android/tools/build/bundletool/testing/ApksArchiveHelpers.java @@ -27,6 +27,7 @@ import com.android.bundle.Commands.ApkDescription; import com.android.bundle.Commands.ApkSet; import com.android.bundle.Commands.BuildApksResult; +import com.android.bundle.Commands.DeliveryType; import com.android.bundle.Commands.ModuleMetadata; import com.android.bundle.Commands.SplitApkMetadata; import com.android.bundle.Commands.StandaloneApkMetadata; @@ -39,6 +40,8 @@ import com.android.bundle.Targeting.VariantTargeting; import com.android.tools.build.bundletool.io.ZipBuilder; import com.android.tools.build.bundletool.model.ZipPath; +import com.android.tools.build.bundletool.model.version.BundleToolVersion; +import com.android.tools.build.bundletool.model.version.Version; import com.google.common.collect.ImmutableList; import java.nio.file.Files; import java.nio.file.Path; @@ -100,7 +103,10 @@ public static Variant standaloneVariant( return createVariant( variantTargeting, ApkSet.newBuilder() - .setModuleMetadata(ModuleMetadata.newBuilder().setName("base")) + .setModuleMetadata( + ModuleMetadata.newBuilder() + .setName("base") + .setDeliveryType(DeliveryType.INSTALL_TIME)) .addApkDescription( ApkDescription.newBuilder() .setTargeting(apkTargeting) @@ -117,7 +123,10 @@ public static Variant apexVariant( return createVariant( variantTargeting, ApkSet.newBuilder() - .setModuleMetadata(ModuleMetadata.newBuilder().setName("base")) + .setModuleMetadata( + ModuleMetadata.newBuilder() + .setName("base") + .setDeliveryType(DeliveryType.INSTALL_TIME)) .addApkDescription( ApkDescription.newBuilder() .setTargeting(apkTargeting) @@ -140,22 +149,25 @@ public static Variant multiAbiTargetingApexVariant(MultiAbiTargeting targeting, public static ApkSet createSplitApkSet(String moduleName, ApkDescription... apkDescription) { return createSplitApkSet( moduleName, - /* onDemand= */ false, + DeliveryType.INSTALL_TIME, /* moduleDependencies= */ ImmutableList.of(), apkDescription); } public static ApkSet createSplitApkSet( String moduleName, - boolean onDemand, + DeliveryType deliveryType, ImmutableList moduleDependencies, ApkDescription... apkDescription) { + ModuleMetadata.Builder moduleMetadata = + ModuleMetadata.newBuilder().setName(moduleName).addAllDependencies(moduleDependencies); + if (BundleToolVersion.getCurrentVersion().isNewerThan(Version.of("0.10.1"))) { + moduleMetadata.setDeliveryType(deliveryType); + } else { + moduleMetadata.setOnDemandDeprecated(deliveryType != DeliveryType.INSTALL_TIME); + } return ApkSet.newBuilder() - .setModuleMetadata( - ModuleMetadata.newBuilder() - .setName(moduleName) - .setOnDemand(onDemand) - .addAllDependencies(moduleDependencies)) + .setModuleMetadata(moduleMetadata) .addAllApkDescription(Arrays.asList(apkDescription)) .build(); } @@ -167,7 +179,7 @@ public static ApkSet createConditionalApkSet( ModuleMetadata.newBuilder() .setName(moduleName) .setTargeting(moduleTargeting) - .setOnDemand(false)) + .setDeliveryType(DeliveryType.INSTALL_TIME)) .addAllApkDescription(Arrays.asList(apkDescriptions)) .build(); } @@ -210,7 +222,11 @@ public static ApkDescription instantApkDescription(ApkTargeting apkTargeting, Zi public static ApkSet createInstantApkSet( String moduleName, ApkTargeting apkTargeting, Path apkPath) { return ApkSet.newBuilder() - .setModuleMetadata(ModuleMetadata.newBuilder().setName(moduleName).setIsInstant(true)) + .setModuleMetadata( + ModuleMetadata.newBuilder() + .setName(moduleName) + .setIsInstant(true) + .setDeliveryType(DeliveryType.INSTALL_TIME)) .addApkDescription( ApkDescription.newBuilder() .setPath(apkPath.toString()) @@ -222,7 +238,8 @@ public static ApkSet createInstantApkSet( public static ApkSet createStandaloneApkSet(ApkTargeting apkTargeting, Path apkPath) { // Note: Standalone APK is represented as a module named "base". return ApkSet.newBuilder() - .setModuleMetadata(ModuleMetadata.newBuilder().setName("base")) + .setModuleMetadata( + ModuleMetadata.newBuilder().setName("base").setDeliveryType(DeliveryType.INSTALL_TIME)) .addApkDescription( ApkDescription.newBuilder() .setPath(apkPath.toString()) @@ -235,7 +252,8 @@ public static ApkSet createStandaloneApkSet(ApkTargeting apkTargeting, Path apkP public static ApkSet createSystemApkSet(ApkTargeting apkTargeting, Path apkPath) { // Note: System APK is represented as a module named "base". return ApkSet.newBuilder() - .setModuleMetadata(ModuleMetadata.newBuilder().setName("base")) + .setModuleMetadata( + ModuleMetadata.newBuilder().setName("base").setDeliveryType(DeliveryType.INSTALL_TIME)) .addApkDescription( ApkDescription.newBuilder() .setPath(apkPath.toString()) diff --git a/src/test/java/com/android/tools/build/bundletool/testing/ManifestProtoUtils.java b/src/test/java/com/android/tools/build/bundletool/testing/ManifestProtoUtils.java index d959c608..5e20d477 100755 --- a/src/test/java/com/android/tools/build/bundletool/testing/ManifestProtoUtils.java +++ b/src/test/java/com/android/tools/build/bundletool/testing/ManifestProtoUtils.java @@ -61,6 +61,7 @@ import com.android.aapt.Resources.XmlElement; import com.android.aapt.Resources.XmlNamespace; import com.android.aapt.Resources.XmlNode; +import com.android.bundle.Commands.DeliveryType; import com.android.tools.build.bundletool.model.AndroidManifest; import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoAttributeBuilder; import com.android.tools.build.bundletool.model.utils.xmlproto.XmlProtoElementBuilder; @@ -420,6 +421,27 @@ public static ManifestMutator withOnDemandDelivery() { .getOrCreateChildElement(DISTRIBUTION_NAMESPACE_URI, "on-demand"); } + public static ManifestMutator withFastFollowDelivery() { + return manifestElement -> + manifestElement + .getOrCreateChildElement(DISTRIBUTION_NAMESPACE_URI, "module") + .getOrCreateChildElement(DISTRIBUTION_NAMESPACE_URI, "delivery") + .getOrCreateChildElement(DISTRIBUTION_NAMESPACE_URI, "fast-follow"); + } + + public static ManifestMutator withDelivery(DeliveryType deliveryType) { + switch (deliveryType) { + case INSTALL_TIME: + return withInstallTimeDelivery(); + case ON_DEMAND: + return withOnDemandDelivery(); + case FAST_FOLLOW: + return withFastFollowDelivery(); + default: + return withEmptyDeliveryElement(); + } + } + /** Adds the instant attribute under the dist:module tag. */ public static ManifestMutator withInstant(boolean value) { return manifestElement -> diff --git a/src/test/java/com/android/tools/build/bundletool/validation/ResourceTableValidatorTest.java b/src/test/java/com/android/tools/build/bundletool/validation/ResourceTableValidatorTest.java index 3a2e7d43..6cefd4d3 100755 --- a/src/test/java/com/android/tools/build/bundletool/validation/ResourceTableValidatorTest.java +++ b/src/test/java/com/android/tools/build/bundletool/validation/ResourceTableValidatorTest.java @@ -17,6 +17,11 @@ package com.android.tools.build.bundletool.validation; import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest; +import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.USER_PACKAGE_OFFSET; +import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.entry; +import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.pkg; +import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.resourceTable; +import static com.android.tools.build.bundletool.testing.ResourcesTableFactory.type; import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -25,6 +30,7 @@ import com.android.tools.build.bundletool.model.exceptions.ValidationException; import com.android.tools.build.bundletool.testing.BundleModuleBuilder; import com.android.tools.build.bundletool.testing.ResourceTableBuilder; +import com.google.common.collect.ImmutableList; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -138,4 +144,114 @@ public void validateModule_fileOutsideRes_throws() throws Exception { .hasMessageThat() .contains("references file 'assets/icon.png' outside of the 'res/' directory"); } + + @Test + public void duplicateResourceId_sameModule_throws() throws Exception { + BundleModule module = + new BundleModuleBuilder("module") + .setResourceTable( + resourceTable( + pkg( + USER_PACKAGE_OFFSET, + "com.test.app", + type(0x01, "drawable", entry(0x0002, "logo"), entry(0x0002, "logo2"))))) + .setManifest(androidManifest("com.test.app")) + .build(); + + ValidationException exception = + assertThrows( + ValidationException.class, + () -> new ResourceTableValidator().checkResourceIdsAreUnique(ImmutableList.of(module))); + + assertThat(exception).hasMessageThat().contains("Duplicate resource"); + } + + @Test + public void duplicateResourceId_differentModule_throws() throws Exception { + BundleModule firstModule = + new BundleModuleBuilder("firstModule") + .setResourceTable( + resourceTable( + pkg( + USER_PACKAGE_OFFSET, + "com.test.app", + type(0x01, "drawable", entry(0x0002, "logo"))))) + .setManifest(androidManifest("com.test.app")) + .build(); + BundleModule secondModule = + new BundleModuleBuilder("secondModule") + .setResourceTable( + resourceTable( + pkg( + USER_PACKAGE_OFFSET, + "com.test.app", + type(0x01, "drawable", entry(0x0002, "logo"))))) + .setManifest(androidManifest("com.test.app")) + .build(); + + ValidationException exception = + assertThrows( + ValidationException.class, + () -> + new ResourceTableValidator() + .checkResourceIdsAreUnique(ImmutableList.of(firstModule, secondModule))); + + assertThat(exception).hasMessageThat().contains("Duplicate resource"); + } + + @Test + public void noResourceTable_doesNotThrow() throws Exception { + BundleModule module = + new BundleModuleBuilder("module") + .setResourceTable(resourceTable()) + .setManifest(androidManifest("com.test.app")) + .build(); + + new ResourceTableValidator().checkResourceIdsAreUnique(ImmutableList.of(module)); + } + + @Test + public void nonDuplicateResources_sameModule_doesNotThrow() throws Exception { + BundleModule module = + new BundleModuleBuilder("module") + .setResourceTable( + resourceTable( + pkg( + USER_PACKAGE_OFFSET, + "com.test.app", + type(0x01, "drawable", entry(0x0002, "logo"), entry(0x0003, "logo2"))))) + .setManifest(androidManifest("com.test.app")) + .build(); + + new ResourceTableValidator().checkResourceIdsAreUnique(ImmutableList.of(module)); + } + + @Test + public void nonDuplicateResources_differentModule_doesNotThrow() throws Exception { + BundleModule firstModule = + new BundleModuleBuilder("firstModule") + .setResourceTable( + resourceTable( + pkg( + USER_PACKAGE_OFFSET, + "com.test.app", + type(0x01, "drawable", entry(0x0002, "logo"))))) + .setManifest(androidManifest("com.test.app")) + .build(); + BundleModule secondModule = + new BundleModuleBuilder("secondModule") + .setResourceTable( + resourceTable( + pkg( + USER_PACKAGE_OFFSET, + "com.test.app", + type(0x01, "drawable", entry(0x0003, "logo"))))) + .setManifest(androidManifest("com.test.app")) + .build(); + + new ResourceTableValidator() + .checkResourceIdsAreUnique(ImmutableList.of(firstModule, secondModule)); + } } + +